From 84e98833ca26f92888a7258ae9fe0b9d6884c018 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Mon, 5 Nov 2012 19:22:50 +1100 Subject: [PATCH] Update to v091r11 release. byuu says: This release refines HSU1 support as a bidirectional protocol, nests SFC manifests as "release/cartridge" and "release/information" (but release/ is not guaranteed to be finalized just yet), removes the database integration, and adds support for ananke. ananke represents inevitability. It's a library that, when installed, higan can use to load files from the command-line, and also from a new File -> Load Game menu option. I need to change the build rules a bit for it to work on Windows (need to make phoenix a DLL, basically), but it works now on Linux. Right now, it only takes *.sfc file names, looks them up in the included database, converts them to game folders, and returns the game folder path for higan to load. The idea is to continue expanding it to support everything we can that I don't want in the higan core: - load *.sfc, *.smc, *.swc, *.fig files - remove SNES copier headers - split apart merged firmware files - pull in external firmware files (eg dsp1b.rom - these are staying merged, just as SPC7110 prg+dat are merged) - load *.zip and *.7z archives - prompt for selection on multi-file archives - generate manifest files based on heuristics - apply BPS patches The "Load" menu option has been renamed to "Library", to represent games in your library. I'm going to add some sort of suffix to indicate unverified games, and use a different folder icon for those (eg manifests built on heuristics rather than from the database.) So basically, to future end users: File -> Load Game will be how they play games. Library -> (specific system) can be thought of as an infinitely-sized recent games list. purify will likely become a simple stub that invokes ananke's functions. No reason to duplicate all that code. --- .gitignore | 2 + ananke/Makefile | 37 + ananke/ananke.cpp | 85 + ananke/database/Super Famicom.bml | 1700 +++++++++++++++++ ananke/nall/Makefile | 118 ++ ananke/nall/algorithm.hpp | 17 + ananke/nall/any.hpp | 73 + ananke/nall/atoi.hpp | 103 + ananke/nall/base64.hpp | 118 ++ ananke/nall/beat/archive.hpp | 84 + ananke/nall/beat/base.hpp | 92 + .../nall/bps => ananke/nall/beat}/delta.hpp | 4 +- .../nall/bps => ananke/nall/beat}/linear.hpp | 4 +- .../bps => ananke/nall/beat}/metadata.hpp | 4 +- .../nall/bps => ananke/nall/beat}/multi.hpp | 10 +- .../nall/bps => ananke/nall/beat}/patch.hpp | 4 +- ananke/nall/bit.hpp | 82 + ananke/nall/bmp.hpp | 101 + ananke/nall/compositor.hpp | 152 ++ ananke/nall/config.hpp | 126 ++ ananke/nall/crc16.hpp | 25 + ananke/nall/crc32.hpp | 66 + ananke/nall/directory.hpp | 198 ++ ananke/nall/dl.hpp | 115 ++ ananke/nall/dsp.hpp | 13 + ananke/nall/dsp/buffer.hpp | 51 + ananke/nall/dsp/core.hpp | 167 ++ ananke/nall/dsp/resample/average.hpp | 72 + ananke/nall/dsp/resample/cosine.hpp | 44 + ananke/nall/dsp/resample/cubic.hpp | 50 + ananke/nall/dsp/resample/hermite.hpp | 62 + ananke/nall/dsp/resample/lib/sinc.hpp | 600 ++++++ ananke/nall/dsp/resample/linear.hpp | 43 + ananke/nall/dsp/resample/nearest.hpp | 43 + ananke/nall/dsp/resample/sinc.hpp | 54 + ananke/nall/dsp/settings.hpp | 50 + ananke/nall/emulation/famicom.hpp | 182 ++ ananke/nall/emulation/game-boy-advance.hpp | 68 + ananke/nall/emulation/game-boy.hpp | 122 ++ ananke/nall/emulation/satellaview.hpp | 26 + ananke/nall/emulation/sufami-turbo.hpp | 33 + ananke/nall/emulation/super-famicom-usart.hpp | 103 + ananke/nall/emulation/super-famicom.hpp | 803 ++++++++ ananke/nall/endian.hpp | 42 + ananke/nall/file.hpp | 335 ++++ ananke/nall/filemap.hpp | 213 +++ ananke/nall/function.hpp | 60 + ananke/nall/gzip.hpp | 85 + ananke/nall/http.hpp | 176 ++ ananke/nall/image.hpp | 539 ++++++ ananke/nall/inflate.hpp | 358 ++++ ananke/nall/input.hpp | 386 ++++ ananke/nall/interpolation.hpp | 59 + ananke/nall/intrinsics.hpp | 63 + ananke/nall/invoke.hpp | 52 + ananke/nall/ips.hpp | 100 + ananke/nall/lzss.hpp | 165 ++ ananke/nall/map.hpp | 117 ++ ananke/nall/mosaic.hpp | 10 + ananke/nall/mosaic/bitstream.hpp | 55 + ananke/nall/mosaic/context.hpp | 224 +++ ananke/nall/mosaic/parser.hpp | 126 ++ ananke/nall/nall.hpp | 58 + ananke/nall/platform.hpp | 86 + ananke/nall/png.hpp | 337 ++++ ananke/nall/priority-queue.hpp | 109 ++ ananke/nall/property.hpp | 91 + ananke/nall/public-cast.hpp | 32 + ananke/nall/random.hpp | 28 + ananke/nall/serial.hpp | 110 ++ ananke/nall/serializer.hpp | 146 ++ ananke/nall/set.hpp | 158 ++ ananke/nall/sha256.hpp | 145 ++ ananke/nall/sort.hpp | 77 + ananke/nall/stdint.hpp | 42 + ananke/nall/stream.hpp | 26 + ananke/nall/stream/auto.hpp | 25 + ananke/nall/stream/file.hpp | 42 + ananke/nall/stream/gzip.hpp | 34 + ananke/nall/stream/http.hpp | 49 + ananke/nall/stream/memory.hpp | 47 + ananke/nall/stream/mmap.hpp | 42 + ananke/nall/stream/stream.hpp | 92 + ananke/nall/stream/vector.hpp | 39 + ananke/nall/stream/zip.hpp | 38 + ananke/nall/string.hpp | 53 + ananke/nall/string/base.hpp | 219 +++ ananke/nall/string/bsv.hpp | 76 + ananke/nall/string/cast.hpp | 185 ++ ananke/nall/string/compare.hpp | 69 + ananke/nall/string/convert.hpp | 64 + ananke/nall/string/core.hpp | 200 ++ ananke/nall/string/cstring.hpp | 21 + ananke/nall/string/datetime.hpp | 31 + ananke/nall/string/filename.hpp | 74 + ananke/nall/string/markup/bml.hpp | 147 ++ ananke/nall/string/markup/document.hpp | 14 + ananke/nall/string/markup/node.hpp | 146 ++ ananke/nall/string/markup/xml.hpp | 218 +++ ananke/nall/string/math-fixed-point.hpp | 166 ++ ananke/nall/string/math-floating-point.hpp | 157 ++ ananke/nall/string/platform.hpp | 75 + ananke/nall/string/replace.hpp | 51 + ananke/nall/string/split.hpp | 36 + ananke/nall/string/static.hpp | 13 + ananke/nall/string/strm.hpp | 45 + ananke/nall/string/strpos.hpp | 33 + ananke/nall/string/trim.hpp | 55 + ananke/nall/string/utf8.hpp | 43 + ananke/nall/string/utility.hpp | 283 +++ ananke/nall/string/variadic.hpp | 11 + ananke/nall/string/wildcard.hpp | 78 + ananke/nall/string/wrapper.hpp | 43 + ananke/nall/traits.hpp | 33 + ananke/nall/udl.hpp | 28 + ananke/nall/unzip.hpp | 126 ++ ananke/nall/ups.hpp | 223 +++ ananke/nall/utility.hpp | 32 + ananke/nall/varint.hpp | 168 ++ ananke/nall/vector.hpp | 206 ++ ananke/nall/windows/detour.hpp | 192 ++ ananke/nall/windows/guid.hpp | 30 + ananke/nall/windows/launcher.hpp | 94 + ananke/nall/windows/registry.hpp | 120 ++ ananke/nall/windows/utf8.hpp | 87 + ananke/nall/xorg/guard.hpp | 29 + ananke/nall/xorg/xorg.hpp | 12 + ananke/nall/zip.hpp | 95 + ananke/phoenix/Makefile | 21 + ananke/phoenix/core/core.cpp | 1334 +++++++++++++ ananke/phoenix/core/core.hpp | 613 ++++++ ananke/phoenix/core/keyboard.hpp | 45 + ananke/phoenix/core/layout/fixed-layout.cpp | 80 + ananke/phoenix/core/layout/fixed-layout.hpp | 27 + .../phoenix/core/layout/horizontal-layout.cpp | 142 ++ .../phoenix/core/layout/horizontal-layout.hpp | 31 + .../phoenix/core/layout/vertical-layout.cpp | 142 ++ .../phoenix/core/layout/vertical-layout.hpp | 31 + ananke/phoenix/core/state.hpp | 278 +++ ananke/phoenix/gtk/action/action.cpp | 27 + ananke/phoenix/gtk/action/check-item.cpp | 33 + ananke/phoenix/gtk/action/item.cpp | 31 + ananke/phoenix/gtk/action/menu.cpp | 51 + ananke/phoenix/gtk/action/radio-item.cpp | 48 + ananke/phoenix/gtk/action/separator.cpp | 12 + ananke/phoenix/gtk/desktop.cpp | 36 + ananke/phoenix/gtk/dialog-window.cpp | 69 + ananke/phoenix/gtk/font.cpp | 58 + ananke/phoenix/gtk/keyboard.cpp | 142 ++ ananke/phoenix/gtk/message-window.cpp | 61 + ananke/phoenix/gtk/mouse.cpp | 20 + ananke/phoenix/gtk/platform.cpp | 87 + ananke/phoenix/gtk/platform.hpp | 501 +++++ ananke/phoenix/gtk/settings.cpp | 25 + ananke/phoenix/gtk/timer.cpp | 24 + ananke/phoenix/gtk/utility.cpp | 200 ++ ananke/phoenix/gtk/widget/button.cpp | 53 + ananke/phoenix/gtk/widget/canvas.cpp | 70 + ananke/phoenix/gtk/widget/check-box.cpp | 40 + ananke/phoenix/gtk/widget/combo-box.cpp | 73 + ananke/phoenix/gtk/widget/hex-edit.cpp | 264 +++ .../gtk/widget/horizontal-scroll-bar.cpp | 42 + .../phoenix/gtk/widget/horizontal-slider.cpp | 41 + ananke/phoenix/gtk/widget/label.cpp | 24 + ananke/phoenix/gtk/widget/line-edit.cpp | 45 + ananke/phoenix/gtk/widget/list-view.cpp | 217 +++ ananke/phoenix/gtk/widget/progress-bar.cpp | 23 + ananke/phoenix/gtk/widget/radio-box.cpp | 50 + ananke/phoenix/gtk/widget/text-edit.cpp | 66 + .../gtk/widget/vertical-scroll-bar.cpp | 42 + ananke/phoenix/gtk/widget/vertical-slider.cpp | 41 + ananke/phoenix/gtk/widget/viewport.cpp | 58 + ananke/phoenix/gtk/widget/widget.cpp | 47 + ananke/phoenix/gtk/window.cpp | 352 ++++ ananke/phoenix/phoenix.cpp | 52 + ananke/phoenix/phoenix.hpp | 19 + ananke/phoenix/qt/action/action.cpp | 49 + ananke/phoenix/qt/action/check-item.cpp | 27 + ananke/phoenix/qt/action/item.cpp | 21 + ananke/phoenix/qt/action/menu.cpp | 51 + ananke/phoenix/qt/action/radio-item.cpp | 41 + ananke/phoenix/qt/action/separator.cpp | 9 + ananke/phoenix/qt/desktop.cpp | 9 + ananke/phoenix/qt/dialog-window.cpp | 57 + ananke/phoenix/qt/font.cpp | 40 + ananke/phoenix/qt/keyboard.cpp | 142 ++ ananke/phoenix/qt/message-window.cpp | 47 + ananke/phoenix/qt/mouse.cpp | 14 + ananke/phoenix/qt/platform.cpp | 91 + ananke/phoenix/qt/platform.moc | 1105 +++++++++++ ananke/phoenix/qt/platform.moc.hpp | 633 ++++++ ananke/phoenix/qt/settings.cpp | 24 + ananke/phoenix/qt/timer.cpp | 25 + ananke/phoenix/qt/utility.cpp | 190 ++ ananke/phoenix/qt/widget/button.cpp | 52 + ananke/phoenix/qt/widget/canvas.cpp | 73 + ananke/phoenix/qt/widget/check-box.cpp | 42 + ananke/phoenix/qt/widget/combo-box.cpp | 68 + ananke/phoenix/qt/widget/hex-edit.cpp | 191 ++ .../qt/widget/horizontal-scroll-bar.cpp | 43 + .../phoenix/qt/widget/horizontal-slider.cpp | 43 + ananke/phoenix/qt/widget/label.cpp | 25 + ananke/phoenix/qt/widget/line-edit.cpp | 45 + ananke/phoenix/qt/widget/list-view.cpp | 164 ++ ananke/phoenix/qt/widget/progress-bar.cpp | 26 + ananke/phoenix/qt/widget/radio-box.cpp | 64 + ananke/phoenix/qt/widget/text-edit.cpp | 50 + .../phoenix/qt/widget/vertical-scroll-bar.cpp | 43 + ananke/phoenix/qt/widget/vertical-slider.cpp | 43 + ananke/phoenix/qt/widget/viewport.cpp | 51 + ananke/phoenix/qt/widget/widget.cpp | 52 + ananke/phoenix/qt/window.cpp | 288 +++ ananke/phoenix/reference/action/action.cpp | 8 + .../phoenix/reference/action/check-item.cpp | 15 + ananke/phoenix/reference/action/item.cpp | 11 + ananke/phoenix/reference/action/menu.cpp | 17 + .../phoenix/reference/action/radio-item.cpp | 18 + ananke/phoenix/reference/action/separator.cpp | 5 + ananke/phoenix/reference/desktop.cpp | 8 + ananke/phoenix/reference/dialog-window.cpp | 11 + ananke/phoenix/reference/font.cpp | 3 + ananke/phoenix/reference/keyboard.cpp | 10 + ananke/phoenix/reference/message-window.cpp | 15 + ananke/phoenix/reference/mouse.cpp | 7 + ananke/phoenix/reference/platform.cpp | 52 + ananke/phoenix/reference/platform.hpp | 383 ++++ ananke/phoenix/reference/timer.cpp | 8 + ananke/phoenix/reference/widget/button.cpp | 8 + ananke/phoenix/reference/widget/canvas.cpp | 8 + ananke/phoenix/reference/widget/check-box.cpp | 12 + ananke/phoenix/reference/widget/combo-box.cpp | 21 + ananke/phoenix/reference/widget/hex-edit.cpp | 17 + .../widget/horizontal-scroll-bar.cpp | 12 + .../reference/widget/horizontal-slider.cpp | 12 + ananke/phoenix/reference/widget/label.cpp | 5 + ananke/phoenix/reference/widget/line-edit.cpp | 11 + ananke/phoenix/reference/widget/list-view.cpp | 49 + .../phoenix/reference/widget/progress-bar.cpp | 5 + ananke/phoenix/reference/widget/radio-box.cpp | 15 + ananke/phoenix/reference/widget/text-edit.cpp | 17 + .../reference/widget/vertical-scroll-bar.cpp | 12 + .../reference/widget/vertical-slider.cpp | 12 + ananke/phoenix/reference/widget/viewport.cpp | 6 + ananke/phoenix/reference/widget/widget.cpp | 25 + ananke/phoenix/reference/window.cpp | 84 + ananke/phoenix/sync.sh | 9 + ananke/phoenix/windows/action/action.cpp | 12 + ananke/phoenix/windows/action/check-item.cpp | 18 + ananke/phoenix/windows/action/item.cpp | 29 + ananke/phoenix/windows/action/menu.cpp | 109 ++ ananke/phoenix/windows/action/radio-item.cpp | 26 + ananke/phoenix/windows/action/separator.cpp | 6 + ananke/phoenix/windows/desktop.cpp | 9 + ananke/phoenix/windows/dialog-window.cpp | 98 + ananke/phoenix/windows/font.cpp | 44 + ananke/phoenix/windows/keyboard.cpp | 137 ++ ananke/phoenix/windows/message-window.cpp | 41 + ananke/phoenix/windows/mouse.cpp | 14 + ananke/phoenix/windows/object.cpp | 13 + ananke/phoenix/windows/phoenix.Manifest | 14 + ananke/phoenix/windows/phoenix.rc | 1 + ananke/phoenix/windows/platform.cpp | 479 +++++ ananke/phoenix/windows/platform.hpp | 483 +++++ ananke/phoenix/windows/settings.cpp | 1 + ananke/phoenix/windows/timer.cpp | 31 + ananke/phoenix/windows/utility.cpp | 150 ++ ananke/phoenix/windows/widget/button.cpp | 90 + ananke/phoenix/windows/widget/canvas.cpp | 92 + ananke/phoenix/windows/widget/check-box.cpp | 39 + ananke/phoenix/windows/widget/combo-box.cpp | 70 + ananke/phoenix/windows/widget/hex-edit.cpp | 136 ++ .../windows/widget/horizontal-scroll-bar.cpp | 38 + .../windows/widget/horizontal-slider.cpp | 39 + ananke/phoenix/windows/widget/label.cpp | 64 + ananke/phoenix/windows/widget/line-edit.cpp | 45 + ananke/phoenix/windows/widget/list-view.cpp | 243 +++ .../phoenix/windows/widget/progress-bar.cpp | 25 + ananke/phoenix/windows/widget/radio-box.cpp | 43 + ananke/phoenix/windows/widget/text-edit.cpp | 58 + .../windows/widget/vertical-scroll-bar.cpp | 38 + .../windows/widget/vertical-slider.cpp | 39 + ananke/phoenix/windows/widget/viewport.cpp | 57 + ananke/phoenix/windows/widget/widget.cpp | 66 + ananke/phoenix/windows/window.cpp | 229 +++ higan/Makefile | 17 +- higan/emulator/emulator.hpp | 2 +- higan/emulator/interface.hpp | 4 +- higan/nall/beat/archive.hpp | 84 + higan/nall/beat/base.hpp | 92 + higan/nall/beat/delta.hpp | 214 +++ higan/nall/beat/linear.hpp | 152 ++ higan/nall/beat/metadata.hpp | 121 ++ higan/nall/beat/multi.hpp | 242 +++ higan/nall/beat/patch.hpp | 219 +++ higan/sfc/Makefile | 24 +- higan/sfc/cartridge/cartridge.cpp | 23 +- higan/sfc/cartridge/cartridge.hpp | 2 +- higan/sfc/cartridge/markup.cpp | 55 +- higan/sfc/chip/armdsp/armdsp.hpp | 2 + higan/sfc/chip/armdsp/serialization.cpp | 9 + higan/sfc/chip/hitachidsp/hitachidsp.hpp | 1 + higan/sfc/chip/hitachidsp/serialization.cpp | 12 + higan/sfc/chip/hsu1/hsu1.cpp | 110 +- higan/sfc/chip/hsu1/hsu1.hpp | 7 +- higan/sfc/chip/hsu1/serialization.cpp | 17 +- higan/sfc/chip/necdsp/necdsp.hpp | 1 + higan/sfc/chip/necdsp/serialization.cpp | 21 + .../configuration/configuration.cpp | 7 +- .../configuration/configuration.hpp | 8 +- higan/target-ethos/ethos.cpp | 29 +- higan/target-ethos/ethos.hpp | 2 +- higan/target-ethos/general/browser.cpp | 7 +- higan/target-ethos/general/presentation.cpp | 15 +- higan/target-ethos/general/presentation.hpp | 2 + higan/target-ethos/interface/interface.cpp | 16 +- higan/target-ethos/interface/interface.hpp | 2 +- higan/target-ethos/settings/scores.cpp | 32 - higan/target-ethos/settings/scores.hpp | 15 - higan/target-ethos/settings/server.cpp | 32 + higan/target-ethos/settings/server.hpp | 15 + higan/target-ethos/settings/settings.cpp | 10 +- higan/target-ethos/settings/settings.hpp | 2 +- higan/target-ethos/utility/database.cpp | 48 - higan/target-ethos/utility/utility.cpp | 44 +- higan/target-ethos/utility/utility.hpp | 2 +- 325 files changed, 30213 insertions(+), 274 deletions(-) create mode 100644 ananke/Makefile create mode 100644 ananke/ananke.cpp create mode 100644 ananke/database/Super Famicom.bml create mode 100644 ananke/nall/Makefile create mode 100644 ananke/nall/algorithm.hpp create mode 100644 ananke/nall/any.hpp create mode 100644 ananke/nall/atoi.hpp create mode 100644 ananke/nall/base64.hpp create mode 100644 ananke/nall/beat/archive.hpp create mode 100644 ananke/nall/beat/base.hpp rename {higan/nall/bps => ananke/nall/beat}/delta.hpp (99%) mode change 100755 => 100644 rename {higan/nall/bps => ananke/nall/beat}/linear.hpp (98%) mode change 100755 => 100644 rename {higan/nall/bps => ananke/nall/beat}/metadata.hpp (97%) mode change 100755 => 100644 rename {higan/nall/bps => ananke/nall/beat}/multi.hpp (97%) mode change 100755 => 100644 rename {higan/nall/bps => ananke/nall/beat}/patch.hpp (99%) mode change 100755 => 100644 create mode 100644 ananke/nall/bit.hpp create mode 100644 ananke/nall/bmp.hpp create mode 100644 ananke/nall/compositor.hpp create mode 100644 ananke/nall/config.hpp create mode 100644 ananke/nall/crc16.hpp create mode 100644 ananke/nall/crc32.hpp create mode 100644 ananke/nall/directory.hpp create mode 100644 ananke/nall/dl.hpp create mode 100644 ananke/nall/dsp.hpp create mode 100644 ananke/nall/dsp/buffer.hpp create mode 100644 ananke/nall/dsp/core.hpp create mode 100644 ananke/nall/dsp/resample/average.hpp create mode 100644 ananke/nall/dsp/resample/cosine.hpp create mode 100644 ananke/nall/dsp/resample/cubic.hpp create mode 100644 ananke/nall/dsp/resample/hermite.hpp create mode 100644 ananke/nall/dsp/resample/lib/sinc.hpp create mode 100644 ananke/nall/dsp/resample/linear.hpp create mode 100644 ananke/nall/dsp/resample/nearest.hpp create mode 100644 ananke/nall/dsp/resample/sinc.hpp create mode 100644 ananke/nall/dsp/settings.hpp create mode 100644 ananke/nall/emulation/famicom.hpp create mode 100644 ananke/nall/emulation/game-boy-advance.hpp create mode 100644 ananke/nall/emulation/game-boy.hpp create mode 100644 ananke/nall/emulation/satellaview.hpp create mode 100644 ananke/nall/emulation/sufami-turbo.hpp create mode 100644 ananke/nall/emulation/super-famicom-usart.hpp create mode 100644 ananke/nall/emulation/super-famicom.hpp create mode 100644 ananke/nall/endian.hpp create mode 100644 ananke/nall/file.hpp create mode 100644 ananke/nall/filemap.hpp create mode 100644 ananke/nall/function.hpp create mode 100644 ananke/nall/gzip.hpp create mode 100644 ananke/nall/http.hpp create mode 100644 ananke/nall/image.hpp create mode 100644 ananke/nall/inflate.hpp create mode 100644 ananke/nall/input.hpp create mode 100644 ananke/nall/interpolation.hpp create mode 100644 ananke/nall/intrinsics.hpp create mode 100644 ananke/nall/invoke.hpp create mode 100644 ananke/nall/ips.hpp create mode 100644 ananke/nall/lzss.hpp create mode 100644 ananke/nall/map.hpp create mode 100644 ananke/nall/mosaic.hpp create mode 100644 ananke/nall/mosaic/bitstream.hpp create mode 100644 ananke/nall/mosaic/context.hpp create mode 100644 ananke/nall/mosaic/parser.hpp create mode 100644 ananke/nall/nall.hpp create mode 100644 ananke/nall/platform.hpp create mode 100644 ananke/nall/png.hpp create mode 100644 ananke/nall/priority-queue.hpp create mode 100644 ananke/nall/property.hpp create mode 100644 ananke/nall/public-cast.hpp create mode 100644 ananke/nall/random.hpp create mode 100644 ananke/nall/serial.hpp create mode 100644 ananke/nall/serializer.hpp create mode 100644 ananke/nall/set.hpp create mode 100644 ananke/nall/sha256.hpp create mode 100644 ananke/nall/sort.hpp create mode 100644 ananke/nall/stdint.hpp create mode 100644 ananke/nall/stream.hpp create mode 100644 ananke/nall/stream/auto.hpp create mode 100644 ananke/nall/stream/file.hpp create mode 100644 ananke/nall/stream/gzip.hpp create mode 100644 ananke/nall/stream/http.hpp create mode 100644 ananke/nall/stream/memory.hpp create mode 100644 ananke/nall/stream/mmap.hpp create mode 100644 ananke/nall/stream/stream.hpp create mode 100644 ananke/nall/stream/vector.hpp create mode 100644 ananke/nall/stream/zip.hpp create mode 100644 ananke/nall/string.hpp create mode 100644 ananke/nall/string/base.hpp create mode 100644 ananke/nall/string/bsv.hpp create mode 100644 ananke/nall/string/cast.hpp create mode 100644 ananke/nall/string/compare.hpp create mode 100644 ananke/nall/string/convert.hpp create mode 100644 ananke/nall/string/core.hpp create mode 100644 ananke/nall/string/cstring.hpp create mode 100644 ananke/nall/string/datetime.hpp create mode 100644 ananke/nall/string/filename.hpp create mode 100644 ananke/nall/string/markup/bml.hpp create mode 100644 ananke/nall/string/markup/document.hpp create mode 100644 ananke/nall/string/markup/node.hpp create mode 100644 ananke/nall/string/markup/xml.hpp create mode 100644 ananke/nall/string/math-fixed-point.hpp create mode 100644 ananke/nall/string/math-floating-point.hpp create mode 100644 ananke/nall/string/platform.hpp create mode 100644 ananke/nall/string/replace.hpp create mode 100644 ananke/nall/string/split.hpp create mode 100644 ananke/nall/string/static.hpp create mode 100644 ananke/nall/string/strm.hpp create mode 100644 ananke/nall/string/strpos.hpp create mode 100644 ananke/nall/string/trim.hpp create mode 100644 ananke/nall/string/utf8.hpp create mode 100644 ananke/nall/string/utility.hpp create mode 100644 ananke/nall/string/variadic.hpp create mode 100644 ananke/nall/string/wildcard.hpp create mode 100644 ananke/nall/string/wrapper.hpp create mode 100644 ananke/nall/traits.hpp create mode 100644 ananke/nall/udl.hpp create mode 100644 ananke/nall/unzip.hpp create mode 100644 ananke/nall/ups.hpp create mode 100644 ananke/nall/utility.hpp create mode 100644 ananke/nall/varint.hpp create mode 100644 ananke/nall/vector.hpp create mode 100644 ananke/nall/windows/detour.hpp create mode 100644 ananke/nall/windows/guid.hpp create mode 100644 ananke/nall/windows/launcher.hpp create mode 100644 ananke/nall/windows/registry.hpp create mode 100644 ananke/nall/windows/utf8.hpp create mode 100644 ananke/nall/xorg/guard.hpp create mode 100644 ananke/nall/xorg/xorg.hpp create mode 100644 ananke/nall/zip.hpp create mode 100644 ananke/phoenix/Makefile create mode 100644 ananke/phoenix/core/core.cpp create mode 100644 ananke/phoenix/core/core.hpp create mode 100644 ananke/phoenix/core/keyboard.hpp create mode 100644 ananke/phoenix/core/layout/fixed-layout.cpp create mode 100644 ananke/phoenix/core/layout/fixed-layout.hpp create mode 100644 ananke/phoenix/core/layout/horizontal-layout.cpp create mode 100644 ananke/phoenix/core/layout/horizontal-layout.hpp create mode 100644 ananke/phoenix/core/layout/vertical-layout.cpp create mode 100644 ananke/phoenix/core/layout/vertical-layout.hpp create mode 100644 ananke/phoenix/core/state.hpp create mode 100644 ananke/phoenix/gtk/action/action.cpp create mode 100644 ananke/phoenix/gtk/action/check-item.cpp create mode 100644 ananke/phoenix/gtk/action/item.cpp create mode 100644 ananke/phoenix/gtk/action/menu.cpp create mode 100644 ananke/phoenix/gtk/action/radio-item.cpp create mode 100644 ananke/phoenix/gtk/action/separator.cpp create mode 100644 ananke/phoenix/gtk/desktop.cpp create mode 100644 ananke/phoenix/gtk/dialog-window.cpp create mode 100644 ananke/phoenix/gtk/font.cpp create mode 100644 ananke/phoenix/gtk/keyboard.cpp create mode 100644 ananke/phoenix/gtk/message-window.cpp create mode 100644 ananke/phoenix/gtk/mouse.cpp create mode 100644 ananke/phoenix/gtk/platform.cpp create mode 100644 ananke/phoenix/gtk/platform.hpp create mode 100644 ananke/phoenix/gtk/settings.cpp create mode 100644 ananke/phoenix/gtk/timer.cpp create mode 100644 ananke/phoenix/gtk/utility.cpp create mode 100644 ananke/phoenix/gtk/widget/button.cpp create mode 100644 ananke/phoenix/gtk/widget/canvas.cpp create mode 100644 ananke/phoenix/gtk/widget/check-box.cpp create mode 100644 ananke/phoenix/gtk/widget/combo-box.cpp create mode 100644 ananke/phoenix/gtk/widget/hex-edit.cpp create mode 100644 ananke/phoenix/gtk/widget/horizontal-scroll-bar.cpp create mode 100644 ananke/phoenix/gtk/widget/horizontal-slider.cpp create mode 100644 ananke/phoenix/gtk/widget/label.cpp create mode 100644 ananke/phoenix/gtk/widget/line-edit.cpp create mode 100644 ananke/phoenix/gtk/widget/list-view.cpp create mode 100644 ananke/phoenix/gtk/widget/progress-bar.cpp create mode 100644 ananke/phoenix/gtk/widget/radio-box.cpp create mode 100644 ananke/phoenix/gtk/widget/text-edit.cpp create mode 100644 ananke/phoenix/gtk/widget/vertical-scroll-bar.cpp create mode 100644 ananke/phoenix/gtk/widget/vertical-slider.cpp create mode 100644 ananke/phoenix/gtk/widget/viewport.cpp create mode 100644 ananke/phoenix/gtk/widget/widget.cpp create mode 100644 ananke/phoenix/gtk/window.cpp create mode 100644 ananke/phoenix/phoenix.cpp create mode 100644 ananke/phoenix/phoenix.hpp create mode 100644 ananke/phoenix/qt/action/action.cpp create mode 100644 ananke/phoenix/qt/action/check-item.cpp create mode 100644 ananke/phoenix/qt/action/item.cpp create mode 100644 ananke/phoenix/qt/action/menu.cpp create mode 100644 ananke/phoenix/qt/action/radio-item.cpp create mode 100644 ananke/phoenix/qt/action/separator.cpp create mode 100644 ananke/phoenix/qt/desktop.cpp create mode 100644 ananke/phoenix/qt/dialog-window.cpp create mode 100644 ananke/phoenix/qt/font.cpp create mode 100644 ananke/phoenix/qt/keyboard.cpp create mode 100644 ananke/phoenix/qt/message-window.cpp create mode 100644 ananke/phoenix/qt/mouse.cpp create mode 100644 ananke/phoenix/qt/platform.cpp create mode 100644 ananke/phoenix/qt/platform.moc create mode 100644 ananke/phoenix/qt/platform.moc.hpp create mode 100644 ananke/phoenix/qt/settings.cpp create mode 100644 ananke/phoenix/qt/timer.cpp create mode 100644 ananke/phoenix/qt/utility.cpp create mode 100644 ananke/phoenix/qt/widget/button.cpp create mode 100644 ananke/phoenix/qt/widget/canvas.cpp create mode 100644 ananke/phoenix/qt/widget/check-box.cpp create mode 100644 ananke/phoenix/qt/widget/combo-box.cpp create mode 100644 ananke/phoenix/qt/widget/hex-edit.cpp create mode 100644 ananke/phoenix/qt/widget/horizontal-scroll-bar.cpp create mode 100644 ananke/phoenix/qt/widget/horizontal-slider.cpp create mode 100644 ananke/phoenix/qt/widget/label.cpp create mode 100644 ananke/phoenix/qt/widget/line-edit.cpp create mode 100644 ananke/phoenix/qt/widget/list-view.cpp create mode 100644 ananke/phoenix/qt/widget/progress-bar.cpp create mode 100644 ananke/phoenix/qt/widget/radio-box.cpp create mode 100644 ananke/phoenix/qt/widget/text-edit.cpp create mode 100644 ananke/phoenix/qt/widget/vertical-scroll-bar.cpp create mode 100644 ananke/phoenix/qt/widget/vertical-slider.cpp create mode 100644 ananke/phoenix/qt/widget/viewport.cpp create mode 100644 ananke/phoenix/qt/widget/widget.cpp create mode 100644 ananke/phoenix/qt/window.cpp create mode 100644 ananke/phoenix/reference/action/action.cpp create mode 100644 ananke/phoenix/reference/action/check-item.cpp create mode 100644 ananke/phoenix/reference/action/item.cpp create mode 100644 ananke/phoenix/reference/action/menu.cpp create mode 100644 ananke/phoenix/reference/action/radio-item.cpp create mode 100644 ananke/phoenix/reference/action/separator.cpp create mode 100644 ananke/phoenix/reference/desktop.cpp create mode 100644 ananke/phoenix/reference/dialog-window.cpp create mode 100644 ananke/phoenix/reference/font.cpp create mode 100644 ananke/phoenix/reference/keyboard.cpp create mode 100644 ananke/phoenix/reference/message-window.cpp create mode 100644 ananke/phoenix/reference/mouse.cpp create mode 100644 ananke/phoenix/reference/platform.cpp create mode 100644 ananke/phoenix/reference/platform.hpp create mode 100644 ananke/phoenix/reference/timer.cpp create mode 100644 ananke/phoenix/reference/widget/button.cpp create mode 100644 ananke/phoenix/reference/widget/canvas.cpp create mode 100644 ananke/phoenix/reference/widget/check-box.cpp create mode 100644 ananke/phoenix/reference/widget/combo-box.cpp create mode 100644 ananke/phoenix/reference/widget/hex-edit.cpp create mode 100644 ananke/phoenix/reference/widget/horizontal-scroll-bar.cpp create mode 100644 ananke/phoenix/reference/widget/horizontal-slider.cpp create mode 100644 ananke/phoenix/reference/widget/label.cpp create mode 100644 ananke/phoenix/reference/widget/line-edit.cpp create mode 100644 ananke/phoenix/reference/widget/list-view.cpp create mode 100644 ananke/phoenix/reference/widget/progress-bar.cpp create mode 100644 ananke/phoenix/reference/widget/radio-box.cpp create mode 100644 ananke/phoenix/reference/widget/text-edit.cpp create mode 100644 ananke/phoenix/reference/widget/vertical-scroll-bar.cpp create mode 100644 ananke/phoenix/reference/widget/vertical-slider.cpp create mode 100644 ananke/phoenix/reference/widget/viewport.cpp create mode 100644 ananke/phoenix/reference/widget/widget.cpp create mode 100644 ananke/phoenix/reference/window.cpp create mode 100644 ananke/phoenix/sync.sh create mode 100644 ananke/phoenix/windows/action/action.cpp create mode 100644 ananke/phoenix/windows/action/check-item.cpp create mode 100644 ananke/phoenix/windows/action/item.cpp create mode 100644 ananke/phoenix/windows/action/menu.cpp create mode 100644 ananke/phoenix/windows/action/radio-item.cpp create mode 100644 ananke/phoenix/windows/action/separator.cpp create mode 100644 ananke/phoenix/windows/desktop.cpp create mode 100644 ananke/phoenix/windows/dialog-window.cpp create mode 100644 ananke/phoenix/windows/font.cpp create mode 100644 ananke/phoenix/windows/keyboard.cpp create mode 100644 ananke/phoenix/windows/message-window.cpp create mode 100644 ananke/phoenix/windows/mouse.cpp create mode 100644 ananke/phoenix/windows/object.cpp create mode 100644 ananke/phoenix/windows/phoenix.Manifest create mode 100644 ananke/phoenix/windows/phoenix.rc create mode 100644 ananke/phoenix/windows/platform.cpp create mode 100644 ananke/phoenix/windows/platform.hpp create mode 100644 ananke/phoenix/windows/settings.cpp create mode 100644 ananke/phoenix/windows/timer.cpp create mode 100644 ananke/phoenix/windows/utility.cpp create mode 100644 ananke/phoenix/windows/widget/button.cpp create mode 100644 ananke/phoenix/windows/widget/canvas.cpp create mode 100644 ananke/phoenix/windows/widget/check-box.cpp create mode 100644 ananke/phoenix/windows/widget/combo-box.cpp create mode 100644 ananke/phoenix/windows/widget/hex-edit.cpp create mode 100644 ananke/phoenix/windows/widget/horizontal-scroll-bar.cpp create mode 100644 ananke/phoenix/windows/widget/horizontal-slider.cpp create mode 100644 ananke/phoenix/windows/widget/label.cpp create mode 100644 ananke/phoenix/windows/widget/line-edit.cpp create mode 100644 ananke/phoenix/windows/widget/list-view.cpp create mode 100644 ananke/phoenix/windows/widget/progress-bar.cpp create mode 100644 ananke/phoenix/windows/widget/radio-box.cpp create mode 100644 ananke/phoenix/windows/widget/text-edit.cpp create mode 100644 ananke/phoenix/windows/widget/vertical-scroll-bar.cpp create mode 100644 ananke/phoenix/windows/widget/vertical-slider.cpp create mode 100644 ananke/phoenix/windows/widget/viewport.cpp create mode 100644 ananke/phoenix/windows/widget/widget.cpp create mode 100644 ananke/phoenix/windows/window.cpp create mode 100644 higan/nall/beat/archive.hpp create mode 100644 higan/nall/beat/base.hpp create mode 100644 higan/nall/beat/delta.hpp create mode 100644 higan/nall/beat/linear.hpp create mode 100644 higan/nall/beat/metadata.hpp create mode 100644 higan/nall/beat/multi.hpp create mode 100644 higan/nall/beat/patch.hpp delete mode 100644 higan/target-ethos/settings/scores.cpp delete mode 100644 higan/target-ethos/settings/scores.hpp create mode 100644 higan/target-ethos/settings/server.cpp create mode 100644 higan/target-ethos/settings/server.hpp delete mode 100644 higan/target-ethos/utility/database.cpp diff --git a/.gitignore b/.gitignore index c4026543..841da7a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ purify/*.o purify/purify purify/analyze-gba +ananke/ananke.o +ananke/libananke.so diff --git a/ananke/Makefile b/ananke/Makefile new file mode 100644 index 00000000..b223ee2b --- /dev/null +++ b/ananke/Makefile @@ -0,0 +1,37 @@ +include nall/Makefile +include phoenix/Makefile + +path := /usr/local/lib +flags := -std=gnu++11 -I. -O3 -fomit-frame-pointer + +all: + $(cpp) $(flags) -fPIC -o ananke.o -c ananke.cpp + $(cpp) $(flags) -shared -Wl,-soname,libananke.so.1 -o libananke.so ananke.o + +clean: + -@$(call delete,*.o) + -@$(call delete,*.so) + +install: uninstall + if [ ! -d ~/.config/ananke ]; then mkdir ~/.config/ananke; fi + if [ -d ~/.config/ananke/database ]; then rm -r ~/.config/ananke/database; fi + cp -r database ~/.config/ananke/database + sudo cp libananke.so $(path)/libananke.so.1 + sudo ln -s $(path)/libananke.so.1 $(path)/libananke.so + +uninstall: + if [ -f $(path)/libananke.so ]; then sudo rm $(path)/libananke.so; fi + if [ -f $(path)/libananke.so.1 ]; then sudo rm $(path)/libananke.so.1; fi + +sync: +ifeq ($(shell id -un),byuu) + if [ -d ./nall ]; then rm -r ./nall; fi + if [ -d ./phoenix ]; then rm -r ./phoenix; fi + cp -r ../nall ./nall + cp -r ../phoenix ./phoenix + rm -r nall/test + rm -r phoenix/nall + rm -r phoenix/test +endif + +force: diff --git a/ananke/ananke.cpp b/ananke/ananke.cpp new file mode 100644 index 00000000..8fefdd22 --- /dev/null +++ b/ananke/ananke.cpp @@ -0,0 +1,85 @@ +#include +using namespace nall; + +#include +using namespace phoenix; + +struct Ananke { + struct Configuration : configuration { + string path; + + Configuration() { + append(path = userpath(), "Path"); + directory::create({configpath(), "ananke/"}); + load({configpath(), "ananke/settings.cfg"}); + } + + ~Configuration() { + save({configpath(), "ananke/settings.cfg"}); + } + } config; + + string createSuperFamicomDatabase(const string &filename, Markup::Node &document, const string &manifest) { + string pathname = { + userpath(), "Emulation/Super Famicom/", + document["release/information/name"].text(), + " (", document["release/information/region"].text(), ")", + " (", document["release/information/revision"].text(), ")", + ".sfc/" + }; + directory::create(pathname); + + file::write({pathname, "manifest.bml"}, (const uint8_t*)(const char*)manifest, manifest.length()); + auto buffer = file::read(filename); + unsigned offset = 0; + + for(auto &node : document["release/information/configuration"]) { + if(node.name != "rom") continue; + string name = node["name"].text(); + unsigned size = node["size"].decimal(); + if(file::exists({pathname, name}) == false) { + file::write({pathname, name}, buffer.data() + offset, size); + } + offset += size; + } + + return pathname; + } + + string openSuperFamicom(const string &filename) { + string sha256 = file::sha256(filename); + + string databaseText = string::read({configpath(), "ananke/database/Super Famicom.bml"}).rtrim("\n"); + lstring databaseItem = databaseText.split("\n\n"); + + for(auto &item : databaseItem) { + item.append("\n"); + auto document = Markup::Document(item); + + if(document["release/information/sha256"].text() == sha256) { + return createSuperFamicomDatabase(filename, document, item); + } + } + + return ""; + } + + string open(string filename = "") { + if(filename.empty()) filename = DialogWindow::fileOpen(Window::none(), config.path); + if(filename.empty()) return ""; + config.path = dir(filename); + + if(filename.endswith(".sfc")) return openSuperFamicom(filename); + return ""; + } +}; + +extern "C" string ananke_browse(const string &filename) { + Ananke ananke; + return ananke.open(); +} + +extern "C" string ananke_open(const string &filename) { + Ananke ananke; + return ananke.open(filename); +} diff --git a/ananke/database/Super Famicom.bml b/ananke/database/Super Famicom.bml new file mode 100644 index 00000000..50804e6e --- /dev/null +++ b/ananke/database/Super Famicom.bml @@ -0,0 +1,1700 @@ +database revision=2012-11-03 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: ActRaiser + name: ActRaiser + region: NA + revision: 1.0 + board: SHVC-1A3B-11 + sha256: b8055844825653210d252d29a2229f9a3e7e512004e83940620173c57d8723f0 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Addams Family + name: Addams Family, The + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: e645310d2406ace85523ed91070ee7ff6aa245217267dacb158ae9fc75109692 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: American Gladiators + name: American Gladiators + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: dc9cefb4dd50dce2e9d626ac71d4a06306516cba4bc784c90e4a30fe4e7ff4ef + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Arcana + name: Arcana + region: NA + revision: 1.0 + board: SHVC-1A3B-13 + sha256: aac9d6f2b8408e4bbdc83ebbba755428caf8021fefbfac7220fb4c772abd9944 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x200000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Art of Fighting + name: Art of Fighting + region: NA + revision: 1.0 + board: SHVC-1J0N-01 + sha256: 55e57c5e012583ff0fafd1aee16b3f8269ee2b34fe10f10b93ba0dde72f2b78d + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1K0N revision=01 + rom name=program.rom size=0x100000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + necdsp model=uPD7725 frequency=8000000 + rom id=program name=dsp1b.program.rom size=0x1800 + rom id=data name=dsp1b.data.rom size=0x800 + ram size=0x200 + map id=dr address=00-1f,80-9f:6000-6fff + map id=sr address=00-1f,80-9f:7000-7fff + information + title: Ballz 3D + name: Ballz 3D + region: NA + revision: 1.0 + board: SHVC-1K0N-01 + sha256: a3213e40d7abbc25a5b909642433103b06d8f90500c930bf64093ade0329da78 + configuration + rom name=program.rom size=0x100000 + rom name=dsp1b.program.rom size=0x1800 + rom name=dsp1b.data.rom size=0x800 + ram name=dsp1b.data.ram size=0x200 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bass Masters Classic + name: Bass Masters Classic + region: NA + revision: 1.0 + board: SHVC-2A0N-20 + sha256: c670e16c076f6d92ba581b78769b604e79ad567c04c647dac557f45a48c3448f + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bass Masters Classic: Pro Edition + name: Bass Masters Classic - Pro Edition + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: db1ac03cc8b7daaa812da239029bcf999b30b2afe1c03d51f7ae849a796617ea + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A3M revision=10,20,21 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: Bassin's Black Bass + name: Bassin's Black Bass + region: NA + revision: 1.0 + board: SHVC-1A3M-21 + sha256: e2be173c77bd1957787be36d13334f655e14d32dad99cacb0fd5e5fc65d96fa1 + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Battletoads in Battlemaniacs + name: Battletoads in Battlemaniacs + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: b0dbd4d7e5689c32234e80b0c5362ef67c425ab72d6ddb49d1cb1133ef630ef7 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bazooka Blitzkrieg + name: Bazooka Blitzkrieg + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: 0b0f991da7ce4b3c2974d6adf62191ec554db9793c5def14cdfb62b7ae28cc98 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bonkers + name: Bonkers + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 854d2492d1cb059749bb0904ca5f92a5eeec09167abf84f7cca4023b1819e4f0 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bust-A-Move + name: Bust-A-Move + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: d6f6c30732dae8d00cd83628c3156acbdf26f99df701f779522e21de74dae5fe + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: California Games II + name: California Games II + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 19f4a1f08aa55ff3cc8ff7ae60ffe03f0e436e8d8901455f1311f2276497a492 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Capcom's MVP Football + name: Capcom's MVP Football + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 9590110a990e90f525d5c8d70fc2a3da10879378003173b6761afb8bf042ee0d + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x200000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Captain Commando + name: Captain Commando + region: NA + revision: 1.0 + board: SHVC-1J0N-20 + sha256: d9b7f9356be0780f0037093a86ef8450f15e569cbd3680073d1cd345dfadb709 + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x100000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Claymates + name: Claymates + region: NA + revision: 1.0 + board: SHVC-1J0N-10 + sha256: 6d9cd7f0cda3c0a82ed3f6a92cbbba4fe8365438e0a7867ad1cae2044d1738eb + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Doomsday Warrior + name: Doomsday Warrior + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: bb915b286b33842e39e9022366169233a4041515c7ecc60ac428420b28e48dd5 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=BJ0N revision=01,20 + rom name=program.rom size=0x300000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Double Dragon V: The Shadow Falls + name: Double Dragon V - The Shadow Falls + region: NA + revision: 1.0 + board: SHVC-BJ0N-01 + sha256: b32aa9cbf8f6baacc84f6418fa6fbc33b9ce71458fecc91275aafdbf6f743a99 + configuration + rom name=program.rom size=0x300000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x200000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Dragon: The Bruce Lee Story + name: Dragon - The Bruce Lee Story + region: NA + revision: 1.0 + board: SHVC-1J0N-20 + sha256: d98d7da1e7636e067563e2e480d7dfbc013b7e9bdf1329fd61c5cacac0293e1d + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Dragon's Lair + name: Dragon's Lair + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: 49a1f9f5e6084b3daa1b13ab5a37ebe8bd3cf20e1c7429fbf722298092893e81 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Drakkhen + name: Drakkhen + region: NA + revision: 1.0 + board: SHVC-1A3B-11 + sha256: 74910ce01d19d25cb97a243a51f21c3d522f02fb116682f60440da3292bb14f7 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x100000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Elite Soccer + name: Elite Soccer + region: NA + revision: 1.0 + board: SHVC-1J0N-10 + sha256: b296054effb1948039635044bc905fdf8ff53e7a73038fd5d8436a913ea5ad8a + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: ESPN Baseball Tonight + name: ESPN Baseball Tonight + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: c702c3860e9dffdaa1917d013ecbcefdd2c47f89726992f7f810d879772dcc4d + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A3M revision=10,20,21 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: ESPN Speed World + name: ESPN Speed World + region: NA + revision: 1.0 + board: SHVC-1A3M-21 + sha256: 7b2fdab972e393395f5379d7fb13e40464db152f6acf58b2d2a6a18f81cefecb + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A1M revision=11 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x800 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=70-7f,f0-ff:0000-7fff + information + title: Exertainment Mountain Bike Rally + name: Exertainment Mountain Bike Rally + region: NA + revision: 1.0 + board: SHVC-1A1M-11 + sha256: f2455253db316b6ccd0c5c686d1f1e2d94cd5e37534e70a3a07a409120d58df6 + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x800 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Family Dog + name: Family Dog + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 2891f1eab285133364ecc379a5c9e1d0026d60f425f1a458d149014f386cfa50 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Fatal Fury + name: Fatal Fury + region: NA + revision: 1.0 + board: SHVC-2A0N-01 + sha256: c92f389d25870aada3002775838ec9f69a988120c0238af885fd08d46bd94930 + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Final Fight + name: Final Fight + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: 60cca2592d0756b8c4c7a0a254fafa5ac47aa752521fd1f77dcbf4b6ee1bee90 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x140000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Final Fight 2 + name: Final Fight 2 + region: NA + revision: 1.0 + board: SHVC-2A0N-01 + sha256: 744d1a16c3f99970283597ae8f43b7c3621c5f995c4566ef24b8d70b0692007b + configuration + rom name=program.rom size=0x140000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: First Samurai + name: First Samurai + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: 4c1354337efa788169387458fa6bdbcf4be0c98369920af2bd876ad98d29070f + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Flintstones: The Movie + name: Flintstones - The Movie, The + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: ff09d72d6241b331dc429d1cf27c04c26546f23312a22c7a14e6a4bf41ed1069 + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Flintstones: The Treasure of Sierra Madrock + name: Flintstones - The Treasure of Sierra Madrock, The + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 3d5bbc06e7e9797d937c803610c40b262c14c3f0a39e8048dd6b0b052a040fc1 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Hal's Hole in One Golf + name: Hal's Hole in One Golf + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: de1cf1512e48473b03adb87b7504f394e8b330b346bac24044f833d83609799a + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Home Improvement + name: Home Improvement + region: NA + revision: 1.0 + board: SHVC-2A0N-11 + sha256: 48a3ac52e2c9128abc2dc60e07817a51898e8a93be0d51d6f541a8635263a089 + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Incredible Crash Dummies + name: Incredible Crash Dummies, The + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 0415adfe80977485f84cdffaaa79f18c91c004c7dba202b4cf9a94eb11adeada + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Inindo: Way of the Ninja + name: Inindo - Way of the Ninja + region: NA + revision: 1.0 + board: SHVC-1A3B-13 + sha256: 5e5cac64fdcf09a52a9c0ab1ebc8bd309dcb1256faf1405284443569b5284fe5 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Kawasaki Superbike Challenge + name: Kawasaki Superbike Challenge + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: fec6dd097e378e795dd164be658b10b1c60672b2351a7f4a7722d1fe5736410e + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=BJ0N revision=01,20 + rom name=program.rom size=0x280000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: King Arthur & The Knights of Justice + name: King Arthur & The Knights of Justice + region: NA + revision: 1.0 + board: SHVC-BJ0N-20 + sha256: 1b58d0aed4510811c73d267244a7e915aa0b334c86e68f3fa5883f5cb534e4d7 + configuration + rom name=program.rom size=0x280000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: King Arthur's World + name: King Arthur's World + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: aca9eb59d6783e2cae3787c69053888fea98f5dfe4c8af8b5a6360e0afb3b5d7 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1L3B revision=11 + sa1 + rom name=program.rom size=0x400000 + ram id=bitmap name=save.ram size=0x2000 + ram id=internal size=0x800 + map id=io address=00-3f,80-bf:2200-23ff + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=c0-ff:0000-ffff + map id=bwram address=00-3f,80-bf:6000-7fff + map id=bwram address=40-4f:0000-ffff + map id=iram address=00-3f,80-bf:3000-37ff + information + title: Kirby Super Star + name: Kirby Super Star + region: NA + revision: 1.0 + board: SHVC-1L3B-11 + sha256: 4e095fbbdec4a16b075d7140385ff68b259870ca9e3357f076dfff7f3d1c4a62 + configuration + rom name=program.rom size=0x400000 + ram name=save.ram size=0x2000 + ram size=0x800 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: La Legende de Zelda: La Triforce des Dieux + name: Legende de Zelda - La Triforce des Dieux, La + region: QC + revision: 1.0 + board: SHVC-1A3B-13 + sha256: dd499445275fca6692c0487a8bd70a23f6c8e78e766df0e3c58fbbe53f8adb06 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1K0N revision=01 + rom name=program.rom size=0x80000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + necdsp model=uPD7725 frequency=8000000 + rom id=program name=dsp1.program.rom size=0x1800 + rom id=data name=dsp1.data.rom size=0x800 + ram size=0x200 + map id=dr address=00-1f,80-9f:6000-6fff + map id=sr address=00-1f,80-9f:7000-7fff + information + title: Lock On + name: Lock On + region: NA + revision: 1.0 + board: SHVC-1K0N-01 + sha256: 0e2ba574ff73587f211c8f818d444631584832e9240548f003171c11b898ad62 + configuration + rom name=program.rom size=0x80000 + rom name=dsp1.program.rom size=0x1800 + rom name=dsp1.data.rom size=0x800 + ram name=dsp1.data.ram size=0x200 + +release + cartridge region=NTSC + board type=1A3M revision=10,20,21 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: Lufia & The Fortress of Doom + name: Lufia 1 - The Fortress of Doom + region: NA + revision: 1.0 + board: SHVC-1A3M-20 + sha256: 73731a5a7932965de02a9e98055dcf88b4d17b8f710a6ecfde3e36a1f248773b + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x100000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Madden NFL '94 + name: Madden NFL '94 + region: NA + revision: 1.0 + board: SHVC-1J0N-01 + sha256: 7e77e196db47e87a5b297e60f0dfa7ce41df8d2d1fdd9152e06628d0b0e586af + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A5M revision=20 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x8000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: Mario Paint + name: Mario Paint + region: NA + revision: 1.0 + board: SHVC-1A5M-20 + sha256: e842cac1a4301be196f1e137fbd1a16866d5c913f24dbca313f4dd8bd7472f45 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x8000 + +release + cartridge region=NTSC + board type=1A1B revision=05,06 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x800 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: MechWarrior + name: MechWarrior + region: NA + revision: 1.0 + board: SHVC-1A1B-06 + sha256: 2a08704748f5ef6488348c4099729feca600412d331bda3756e51efd8b94e113 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x800 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Metal Marines + name: Metal Marines + region: NA + revision: 1.0 + board: SHVC-2A0N-01 + sha256: 0a9609a505dd1555006b16f53d961b3ce50c518aa1597a77dcd46e55ecc716ff + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Metal Warriors + name: Metal Warriors + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: 0d7f875877fe856066cfb39b4ecdbbe7d48393a75770720876c94419f809bb1c + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=2A3M revision=20 + rom name=program.rom size=0x180000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: NBA Live '96 + name: NBA Live '96 + region: NA + revision: 1.0 + board: SHVC-2A3M-20 + sha256: 2d6fc4214245684a8f8f9bb553de637b7c660919ec775bfe3baaf74060c9157e + configuration + rom name=program.rom size=0x180000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: NHLPA Hockey '93 + name: NHLPA Hockey '93 + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 55f3432a130085c112d65aa6443c41eb7a8aeec59aad2c2b4b2ac536b604b449 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Nigel Mansell's World Championship Racing + name: Nigel Mansell's World Championship Racing + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: ce9c819d6496e58901b39d9b04558a89e09ccc3aac33690b8d02bb0406682a57 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Ninja Gaiden Trilogy + name: Ninja Gaiden Trilogy + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: fccc96af24a2463b1c53253e1c5c8ef63641355fae53c0fb410427f29743262b + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1L3B revision=11 + sa1 + rom name=program.rom size=0x100000 + ram id=bitmap name=save.ram size=0x2000 + ram id=internal size=0x800 + map id=io address=00-3f,80-bf:2200-23ff + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=c0-ff:0000-ffff + map id=bwram address=00-3f,80-bf:6000-7fff + map id=bwram address=40-4f:0000-ffff + map id=iram address=00-3f,80-bf:3000-37ff + information + title: PGA European Tour + name: PGA European Tour + region: NA + revision: 1.0 + board: SHVC-1L3B-11 + sha256: 5abfb974ca0e56aabb3f4126817d14a546c57a5a5c6042d31196063d80996698 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + ram size=0x800 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Pirates of Dark Water + name: Pirates of Dark Water, The + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 447dfa710e69479159e9d407474fbf5f67d3a3330ab0c7627afd123ded3fdb3a + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Power Piggs of the Dark Age + name: Power Piggs of the Dark Age + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: 0288ec049723cd0c7f1148cdc1aef0b6922b8a756affe373c99d5690e0dfceaa + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Radical Rex + name: Radical Rex + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 1869c0faf93bf21b7ff965f1925fad4b2924a64b1e48f4837221eebdf741226c + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Ren & Stimpy Show: Buckeroo$! + name: Ren & Stimpy Show - Buckeroo$!, The + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: d322ce440076be0e3678277596acee8089098f4397b35ac8b3df88be5ce5e02f + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=BJ0N revision=01,20 + rom name=program.rom size=0x400000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Rise of the Robots + name: Rise of the Robots + region: NA + revision: 1.0 + board: SHVC-BJ0N-01 + sha256: 38be8013bbe07b2020ba30031fb0a2c77bad8a3eb61fac8217adfe82d6c402af + configuration + rom name=program.rom size=0x400000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Rival Turf! + name: Rival Turf! + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: 3f59cc687d22cd1b23cc33ae6e4518234c9da813c01f79f4c43716e12d32a12d + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: RoboCop versus The Terminator + name: RoboCop versus The Terminator + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: a2115e7576dec06e0de613efb89de861815a78ef72e78a3784be09fb7541928f + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Rocketeer + name: Rocketeer, The + region: NA + revision: 1.0 + board: SHVC-1A0N-02 + sha256: b072fd9b08042e3262446fdf418a41848251072a32bd7f8335cc03543c4ae6c8 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Rocko's Modern Life: Spunky's Dangerous Day + name: Rocko's Modern Life - Spunky's Dangerous Day + region: NA + revision: 1.0 + board: SHVC-2A0N-11 + sha256: cde8876b99c337ff932322506ceef05519e5882b853c54bb8c650d9500cd5957 + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=2J5M revision=01 + rom name=program.rom size=0x180000 + ram name=save.ram size=0x8000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + map id=ram address=10-1f,90-9f:6000-7fff mask=0xe000 + map id=ram address=30-3f,b0-bf:6000-7fff mask=0xe000 + information + title: Romance of the Three Kingdoms III: Dragon of Destiny + name: Romance of the Three Kingdoms III - Dragon of Destiny + region: NA + revision: 1.0 + board: SHVC-2J5M-01 + sha256: 88fa0a78ca98c6386e086c7fa9e81a2625bdecc55115dde6c6f4770b0670aa40 + configuration + rom name=program.rom size=0x180000 + ram name=save.ram size=0x8000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: R-Type III: The Third Lightning + name: R-Type III - The Third Lightning + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 4d6c7d6d2693d8d43bafaff7582f9a94885362dadd9ee4012bbbdce1ba10c30e + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=YA0N revision=01 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Run Saber + name: Run Saber + region: NA + revision: 1.0 + board: SHVC-YA0N-01 + sha256: 4158e3e8890a52f0b12dc9ad5a29276058a247ff41e9f1d22897ebde1eb11269 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=BJ0N revision=01,20 + rom name=program.rom size=0x400000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Samurai Shodown + name: Samurai Shodown + region: NA + revision: 1.0 + board: SHVC-BJ0N-01 + sha256: 5db804171fca42486485ed85e4afe45b29e6d01304bdf75d520bfc42429739e3 + configuration + rom name=program.rom size=0x400000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: SeaQuest DSV + name: SeaQuest DSV + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: a4ab8cfad2f236675b1c0124f8484688e149f38e8628a3b38e9ec14d491ec07e + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: SmartBall + name: SmartBall + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: cbca00fa5dfd6c72db2f21d010255657c33f7ac48de2554262035ead11bdf314 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2J0N revision=11 + rom name=program.rom size=0x180000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Sonic Blast Man II + name: Sonic Blast Man II + region: NA + revision: 1.0 + board: SHVC-2J0N-11 + sha256: efe78f6fc68ddd0f6ef0ad9e0223d9417c14fcadece987dc8f50423fd6723b27 + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Soul Blazer + name: Soul Blazer + region: NA + revision: 1.0 + board: SHVC-1A3B-13 + sha256: 8438da09de8ce9aded3bb08644543f7b60fb60cffc68ce2d67d6a0643f2ecfc2 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Space Ace + name: Space Ace + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 85887dfa92374048fb20809c00eabea428992024cf875d287d0431b9767cc7cb + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Spider-Man: The Animated Series + name: Spider-Man - The Animated Series + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: f05d777e3de69aab18d336cac0af07f794f8d00090d085f86cebaed3679cabad + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Sports Illustrated: Championship Football & Baseball + name: Sports Illustrated - Championship Football & Baseball + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 254d17a82a32d8bd231ca3a87d356b65e65cb0229902a69a57c21a4c99bbba1f + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Steel Talons + name: Steel Talons + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 91f938b4989215b1cd39635797f23b59b9d7b6d36e583f9eb69d022abe537bfc + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Sterling Sharpe: End 2 End + name: Sterling Sharpe - End 2 End + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: e3b07a59f969ced91c4579bb459f2c747a6c3f12c45ae4776483ef4830f5f00f + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1N0N revision=01 + sdd1 + rom name=program.rom size=0x400000 + map id=io address=00-3f,80-bf:4800-4807 + map id=rom address=00-3f,80-bf:8000-ffff mask=0x8000 + map id=rom address=c0-ff:0000-ffff + information + title: Street Fighter Alpha 2 + name: Street Fighter Alpha 2 + region: NA + revision: 1.0 + board: SHVC-1N0N-01 + sha256: 910a29f834199c63c22beddc749baba746da9922196a553255deade59f4fc127 + configuration + rom name=program.rom size=0x400000 + +release + cartridge region=NTSC + board type=3J0N revision=01 + rom name=program.rom size=0x280000 + map id=rom address=00-2f,80-af:8000-ffff + map id=rom address=40-6f,c0-ef:0000-ffff + information + title: Street Fighter II Turbo: Hyper Fighting + name: Street Fighter II Turbo - Hyper Fighting + region: NA + revision: 1.0 + board: SHVC-3J0N-01 + sha256: 3e487f8ba48c0b5e31744e3281d6bce375089db6075c8eb3d9a929376b817381 + configuration + rom name=program.rom size=0x280000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x80000 + ram name=save.ram size=0x200 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Super Baseball Simulator 1.000 + name: Super Baseball Simulator 1.000 + region: NA + revision: 1.0 + board: SHVC-1A3B-12 + sha256: 1622371a5a4001fff9690323e89b7a8d449cdc3cae6dcd1249f0c7dc8c651d33 + configuration + rom name=program.rom size=0x80000 + ram name=save.ram size=0x200 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x80000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Super Bomberman + name: Super Bomberman + region: NA + revision: 1.0 + board: SHVC-1J0N-10 + sha256: 4efab3f49cbe91ec77b6cba747ddfedfdc0b080c755a8b6ba51234f0676c000f + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Caesers Palace + name: Super Caesers Palace + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: d42f8c7969b4c434f9ca04ce0080d897877a5e71c2926d309ef5dae93ba25548 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Goal! 2 + name: Super Goal! 2 + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: d4caa6683ee1b6c70c10fd0555ade33dadcc6551d94e791586e64fd683d8f3a8 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1K1B revision=01 + rom name=program.rom size=0x80000 + ram name=save.ram size=0x800 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + map id=ram address=20-3f,a0-bf:6000-7fff + necdsp model=uPD7725 frequency=8000000 + rom id=program name=dsp1.program.rom size=0x1800 + rom id=data name=dsp1.data.rom size=0x800 + ram size=0x200 + map id=dr address=00-1f,80-9f:6000-6fff + map id=sr address=00-1f,80-9f:7000-7fff + information + title: Super Mario Kart + name: Super Mario Kart + region: NA + revision: 1.0 + board: SHVC-1K1B-01 + sha256: 89ad4ba02a2518ca792cf96b61b36613f86baac92344c9c10d7fab5433bebc16 + configuration + rom name=program.rom size=0x80000 + ram name=save.ram size=0x800 + rom name=dsp1.program.rom size=0x1800 + rom name=dsp1.data.rom size=0x800 + ram name=dsp1.data.ram size=0x200 + +release + cartridge region=NTSC + board type=1CB5B revision=20 + superfx revision=3 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x8000 + map id=io address=00-3f,80-bf:3000-32ff + map id=rom address=00-3f:8000-ffff mask=0x8000 + map id=rom address=40-5f:0000-ffff + map id=ram address=00-3f,80-bf:6000-7fff size=0x2000 + map id=ram address=70-71:0000-ffff + information + title: Super Mario World 2: Yoshi's Island + name: Super Mario World 2 - Yoshi's Island + region: NA + revision: 1.1 + board: SHVC-1CB5B-20 + sha256: bd763c1a56365c244be92e6cffefd318780a2a19eda7d5baf1c6d5bd6c1b3e06 + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x8000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Off Road + name: Super Off Road + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: 24b687f95bb9737d223738f13c00aeaa7d697fa9e2fd50597b81d0cfa2160daa + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super R-Type + name: Super R-Type + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: 05c7f6461209020785fba33007e1830820aa44ada4b1a6f991d936bf2335b15b + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Slam Dunk + name: Super Slam Dunk + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: e987ceb53cc1407b1ba612b13a7b5391db6c41ea14552c165ae62ad42eeaa0ae + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Slap Shot + name: Super Slap Shot + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: c39ebf88af138e87ca5d963ed71a8b7defba3e2bc271d72186d00056e8225721 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Turrican 2 + name: Super Turrican 2 + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: 96da3512a1aa05a40f1e5d61c48932b0d55d9f136d6418b848153a9fecab06de + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Tecmo Super NBA Basketball + name: Tecmo Super NBA Basketball + region: NA + revision: 1.0 + board: SHVC-1A3B-13 + sha256: 14bce564f976d1431127259edbcb23c52c79361fed9814d307d627c4650e800e + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Time Slip + name: Time Slip + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 8e82f98d2e62bc1e5fcf2386c2b5ca54998398220efcedd67858aaaa92289a42 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A1M revision=11 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x800 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=70-7f,f0-ff:0000-7fff + information + title: Tin Star + name: Tin Star + region: NA + revision: 1.0 + board: SHVC-1A1M-11 + sha256: 0503cd93b4d211a825acd47ff3813668b4ce68890c8be2fbfe5ac2b46882dfcf + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x800 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Tommy Moe's Winter Extreme: Skiing and Snowboarding + name: Tommy Moe's Winter Extreme - Skiing and Snowboarding + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: d99008d2181986dc7f65228696d940b5d31be8471f166d1ab9b1c14f1503bcfb + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A3M revision=10,20,21 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: True Golf: Wicked 18 + name: True Golf - Wicked 18 + region: NA + revision: 1.0 + board: SHVC-1A3M-10 + sha256: dd96a8f4f9c8988301ff710a4c70ebe3bf7914901f3547abe1d5f0dd5c0b921b + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Ultimate Fighter + name: Ultimate Fighter + region: NA + revision: 1.0 + board: SHVC-2A0N-11 + sha256: 78bf82963cded9162e25035053c8b1a9f760748ff0beacc230d005204992737d + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=2A3M revision=20 + rom name=program.rom size=0x180000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: Wayne Gretzky and the NHLPA All-Stars + name: Wayne Gretzky and the NHLPA All-Stars + region: NA + revision: 1.0 + board: SHVC-2A3M-20 + sha256: dd73690dd3165a16580e191c92a497102758f312c759353f685e371755c663a8 + configuration + rom name=program.rom size=0x180000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Wings 2: Aces High + name: Wings 2 - Aces High + region: NA + revision: 1.0 + board: SHVC-1A0N-02 + sha256: c3bcd5c716f96e6359ebcfd85c3e9b07b46c5124bf4010d89ceef5b6f2f868f6 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A1B revision=05,06 + rom name=program.rom size=0x80000 + ram name=save.ram size=0x800 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: World League Soccer + name: World League Soccer + region: NA + revision: 1.0 + board: SHVC-1A1B-05 + sha256: d4d9f1b41dad7e7a126a9adbe8d86c4b339e120c866156796de1cb0c9a214189 + configuration + rom name=program.rom size=0x80000 + ram name=save.ram size=0x800 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Zero: The Kamikaze Squirrel + name: Zero - The Kamikaze Squirrel + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 7d414b7f5941f1eddc35259a22accbbbd7b47c517dfcf8bad86c4dcfa9e50b1e + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Zool + name: Zool + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 25414de02c6805ca62574cfb39c23bf292b3d8c4ff33eb8f212ccdbcd61c5ae3 + configuration + rom name=program.rom size=0x100000 + diff --git a/ananke/nall/Makefile b/ananke/nall/Makefile new file mode 100644 index 00000000..bbc4b029 --- /dev/null +++ b/ananke/nall/Makefile @@ -0,0 +1,118 @@ +# 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 Windows,$(uname)),) + platform := win + delete = del $(subst /,\,$1) + else ifneq ($(findstring CYGWIN,$(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.7 + else + compiler := gcc-4.7 + endif +endif + +c := $(compiler) -std=gnu99 +cpp := $(subst cc,++,$(compiler)) -std=gnu++0x + +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/ananke/nall/algorithm.hpp b/ananke/nall/algorithm.hpp new file mode 100644 index 00000000..037f0bb7 --- /dev/null +++ b/ananke/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/ananke/nall/any.hpp b/ananke/nall/any.hpp new file mode 100644 index 00000000..7661a2a4 --- /dev/null +++ b/ananke/nall/any.hpp @@ -0,0 +1,73 @@ +#ifndef NALL_ANY_HPP +#define NALL_ANY_HPP + +#include +#include + +namespace nall { + struct any { + 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 type_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(nullptr) {} + ~any() { if(container) delete container; } + template any(const T& value_) : container(nullptr) { 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 nullptr; + return &static_cast*>(value->container)->value; + } + + template const T* any_cast(const any *value) { + if(!value || value->type() != typeid(T)) return nullptr; + return &static_cast*>(value->container)->value; + } +} + +#endif diff --git a/ananke/nall/atoi.hpp b/ananke/nall/atoi.hpp new file mode 100644 index 00000000..26756c79 --- /dev/null +++ b/ananke/nall/atoi.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_ATOI_HPP +#define NALL_ATOI_HPP + +#include + +namespace nall { + +//note: this header is intended to form the base for user-defined literals; +//once they are supported by GCC. eg: +//unsigned operator "" b(const char *s) { return binary(s); } +//-> signed data = 1001b; +//(0b1001 is nicer, but is not part of the C++ standard) + +constexpr inline uintmax_t binary_(const char *s, uintmax_t sum = 0) { + return ( + *s == '0' || *s == '1' ? binary_(s + 1, (sum << 1) | *s - '0') : + sum + ); +} + +constexpr inline uintmax_t octal_(const char *s, uintmax_t sum = 0) { + return ( + *s >= '0' && *s <= '7' ? octal_(s + 1, (sum << 3) | *s - '0') : + sum + ); +} + +constexpr inline uintmax_t decimal_(const char *s, uintmax_t sum = 0) { + return ( + *s >= '0' && *s <= '9' ? decimal_(s + 1, (sum * 10) + *s - '0') : + sum + ); +} + +constexpr inline uintmax_t hex_(const char *s, uintmax_t sum = 0) { + return ( + *s >= 'A' && *s <= 'F' ? hex_(s + 1, (sum << 4) | *s - 'A' + 10) : + *s >= 'a' && *s <= 'f' ? hex_(s + 1, (sum << 4) | *s - 'a' + 10) : + *s >= '0' && *s <= '9' ? hex_(s + 1, (sum << 4) | *s - '0') : + sum + ); +} + +// + +constexpr inline uintmax_t binary(const char *s) { + return ( + *s == '0' && *(s + 1) == 'B' ? binary_(s + 2) : + *s == '0' && *(s + 1) == 'b' ? binary_(s + 2) : + *s == '%' ? binary_(s + 1) : + binary_(s) + ); +} + +constexpr inline uintmax_t octal(const char *s) { + return ( + octal_(s) + ); +} + +constexpr inline intmax_t integer(const char *s) { + return ( + *s == '+' ? +decimal_(s + 1) : + *s == '-' ? -decimal_(s + 1) : + decimal_(s) + ); +} + +constexpr inline uintmax_t decimal(const char *s) { + return ( + decimal_(s) + ); +} + +constexpr inline uintmax_t hex(const char *s) { + return ( + *s == '0' && *(s + 1) == 'X' ? hex_(s + 2) : + *s == '0' && *(s + 1) == 'x' ? hex_(s + 2) : + *s == '$' ? hex_(s + 1) : + hex_(s) + ); +} + +constexpr inline intmax_t numeral(const char *s) { + return ( + *s == '0' && *(s + 1) == 'X' ? hex_(s + 2) : + *s == '0' && *(s + 1) == 'x' ? hex_(s + 2) : + *s == '0' && *(s + 1) == 'B' ? binary_(s + 2) : + *s == '0' && *(s + 1) == 'b' ? binary_(s + 2) : + *s == '0' ? octal_(s + 1) : + *s == '+' ? +decimal_(s + 1) : + *s == '-' ? -decimal_(s + 1) : + decimal_(s) + ); +} + +inline double fp(const char *s) { + return atof(s); +} + +} + +#endif diff --git a/ananke/nall/base64.hpp b/ananke/nall/base64.hpp new file mode 100644 index 00000000..daf3fa60 --- /dev/null +++ b/ananke/nall/base64.hpp @@ -0,0 +1,118 @@ +#ifndef NALL_BASE64_HPP +#define NALL_BASE64_HPP + +#include +#include + +namespace nall { + struct base64 { + static bool encode(char *&output, const uint8_t* input, unsigned inlength) { + output = new char[inlength * 8 / 6 + 8](); + + 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 string encode(const string &data) { + char *buffer = nullptr; + encode(buffer, (const uint8_t*)(const char*)data, data.length()); + string result = buffer; + delete[] buffer; + return result; + } + + static bool decode(uint8_t *&output, unsigned &outlength, const char *input) { + unsigned inlength = strlen(input), infix = 0; + output = new uint8_t[inlength + 1](); + + 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; + } + + static string decode(const string &data) { + uint8_t *buffer = nullptr; + unsigned size = 0; + decode(buffer, size, (const char*)data); + string result = (const char*)buffer; + delete[] buffer; + return result; + } + + private: + static char enc(uint8_t n) { + //base64 for URL encodings (URL = -_, MIME = +/) + 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/ananke/nall/beat/archive.hpp b/ananke/nall/beat/archive.hpp new file mode 100644 index 00000000..ef7294cf --- /dev/null +++ b/ananke/nall/beat/archive.hpp @@ -0,0 +1,84 @@ +#ifndef NALL_BEAT_ARCHIVE_HPP +#define NALL_BEAT_ARCHIVE_HPP + +#include + +namespace nall { + +struct beatArchive : beatBase { + bool create(const string &beatname, string pathname, const string &metadata = "") { + if(fp.open(beatname, file::mode::write) == false) return false; + if(pathname.endswith("/") == false) pathname.append("/"); + + checksum = ~0; + writeString("BPA1"); + writeNumber(metadata.length()); + writeString(metadata); + + lstring list; + ls(list, pathname, pathname); + for(auto &name : list) { + if(name.endswith("/")) { + name.rtrim<1>("/"); + writeNumber(0 | ((name.length() - 1) << 1)); + writeString(name); + } else { + file stream; + if(stream.open({pathname, name}, file::mode::read) == false) return false; + writeNumber(1 | ((name.length() - 1) << 1)); + writeString(name); + unsigned size = stream.size(); + writeNumber(size); + uint32_t checksum = ~0; + while(size--) { + uint8_t data = stream.read(); + write(data); + checksum = crc32_adjust(checksum, data); + } + writeChecksum(~checksum); + } + } + + writeChecksum(~checksum); + fp.close(); + return true; + } + + bool unpack(const string &beatname, string pathname) { + if(fp.open(beatname, file::mode::read) == false) return false; + if(pathname.endswith("/") == false) pathname.append("/"); + + checksum = ~0; + if(readString(4) != "BPA1") return false; + unsigned length = readNumber(); + while(length--) read(); + + directory::create(pathname); + while(fp.offset() < fp.size() - 4) { + unsigned data = readNumber(); + string name = readString((data >> 1) + 1); + if(name.position("\\") || name.position("../")) return false; //block path exploits + + if((data & 1) == 0) { + directory::create({pathname, name}); + } else { + file stream; + if(stream.open({pathname, name}, file::mode::write) == false) return false; + unsigned size = readNumber(); + uint32_t checksum = ~0; + while(size--) { + uint8_t data = read(); + stream.write(data); + checksum = crc32_adjust(checksum, data); + } + if(readChecksum(~checksum) == false) return false; + } + } + + return readChecksum(~checksum); + } +}; + +} + +#endif diff --git a/ananke/nall/beat/base.hpp b/ananke/nall/beat/base.hpp new file mode 100644 index 00000000..8e0001be --- /dev/null +++ b/ananke/nall/beat/base.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_BEAT_BASE_HPP +#define NALL_BEAT_BASE_HPP + +namespace nall { + +struct beatBase { +protected: + file fp; + uint32_t checksum; + + void ls(lstring &list, const string &path, const string &basepath) { + lstring paths = directory::folders(path); + for(auto &pathname : paths) { + list.append(string{path, pathname}.ltrim<1>(basepath)); + ls(list, {path, pathname}, basepath); + } + + lstring files = directory::files(path); + for(auto &filename : files) { + list.append(string{path, filename}.ltrim<1>(basepath)); + } + } + + void write(uint8_t data) { + fp.write(data); + checksum = crc32_adjust(checksum, data); + } + + void writeNumber(uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) return write(0x80 | x); + write(x); + data--; + } + } + + void writeString(const string &text) { + unsigned length = text.length(); + for(unsigned n = 0; n < length; n++) write(text[n]); + } + + void writeChecksum(uint32_t checksum) { + write(checksum >> 0); + write(checksum >> 8); + write(checksum >> 16); + write(checksum >> 24); + } + + uint8_t read() { + uint8_t data = fp.read(); + checksum = crc32_adjust(checksum, data); + return data; + } + + uint64_t readNumber() { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + } + + string readString(unsigned length) { + string text; + text.reserve(length + 1); + for(unsigned n = 0; n < length; n++) { + text[n] = fp.read(); + checksum = crc32_adjust(checksum, text[n]); + } + text[length] = 0; + return text; + } + + bool readChecksum(uint32_t source) { + uint32_t checksum = 0; + checksum |= read() << 0; + checksum |= read() << 8; + checksum |= read() << 16; + checksum |= read() << 24; + return checksum == source; + } +}; + +} + +#endif diff --git a/higan/nall/bps/delta.hpp b/ananke/nall/beat/delta.hpp old mode 100755 new mode 100644 similarity index 99% rename from higan/nall/bps/delta.hpp rename to ananke/nall/beat/delta.hpp index 6cee56a3..ce120537 --- a/higan/nall/bps/delta.hpp +++ b/ananke/nall/beat/delta.hpp @@ -1,5 +1,5 @@ -#ifndef NALL_BPS_DELTA_HPP -#define NALL_BPS_DELTA_HPP +#ifndef NALL_BEAT_DELTA_HPP +#define NALL_BEAT_DELTA_HPP #include #include diff --git a/higan/nall/bps/linear.hpp b/ananke/nall/beat/linear.hpp old mode 100755 new mode 100644 similarity index 98% rename from higan/nall/bps/linear.hpp rename to ananke/nall/beat/linear.hpp index df840283..078aae34 --- a/higan/nall/bps/linear.hpp +++ b/ananke/nall/beat/linear.hpp @@ -1,5 +1,5 @@ -#ifndef NALL_BPS_LINEAR_HPP -#define NALL_BPS_LINEAR_HPP +#ifndef NALL_BEAT_LINEAR_HPP +#define NALL_BEAT_LINEAR_HPP #include #include diff --git a/higan/nall/bps/metadata.hpp b/ananke/nall/beat/metadata.hpp old mode 100755 new mode 100644 similarity index 97% rename from higan/nall/bps/metadata.hpp rename to ananke/nall/beat/metadata.hpp index 46759e6f..58e6ab0a --- a/higan/nall/bps/metadata.hpp +++ b/ananke/nall/beat/metadata.hpp @@ -1,5 +1,5 @@ -#ifndef NALL_BPS_METADATA_HPP -#define NALL_BPS_METADATA_HPP +#ifndef NALL_BEAT_METADATA_HPP +#define NALL_BEAT_METADATA_HPP #include #include diff --git a/higan/nall/bps/multi.hpp b/ananke/nall/beat/multi.hpp old mode 100755 new mode 100644 similarity index 97% rename from higan/nall/bps/multi.hpp rename to ananke/nall/beat/multi.hpp index e3989ba8..cddf5d6b --- a/higan/nall/bps/multi.hpp +++ b/ananke/nall/beat/multi.hpp @@ -1,9 +1,9 @@ -#ifndef NALL_BPS_MULTI_HPP -#define NALL_BPS_MULTI_HPP +#ifndef NALL_BEAT_MULTI_HPP +#define NALL_BEAT_MULTI_HPP -#include -#include -#include +#include +#include +#include namespace nall { diff --git a/higan/nall/bps/patch.hpp b/ananke/nall/beat/patch.hpp old mode 100755 new mode 100644 similarity index 99% rename from higan/nall/bps/patch.hpp rename to ananke/nall/beat/patch.hpp index 85c4dcae..8c6de75b --- a/higan/nall/bps/patch.hpp +++ b/ananke/nall/beat/patch.hpp @@ -1,5 +1,5 @@ -#ifndef NALL_BPS_PATCH_HPP -#define NALL_BPS_PATCH_HPP +#ifndef NALL_BEAT_PATCH_HPP +#define NALL_BEAT_PATCH_HPP #include #include diff --git a/ananke/nall/bit.hpp b/ananke/nall/bit.hpp new file mode 100644 index 00000000..63403eb0 --- /dev/null +++ b/ananke/nall/bit.hpp @@ -0,0 +1,82 @@ +#ifndef NALL_BIT_HPP +#define NALL_BIT_HPP + +#include + +namespace nall { + template + inline uintmax_t uclamp(const uintmax_t x) { + enum : uintmax_t { b = 1ull << (bits - 1), y = b * 2 - 1 }; + return y + ((x - y) & -(x < y)); //min(x, y); + } + + template + inline uintmax_t uclip(const uintmax_t x) { + enum : uintmax_t { b = 1ull << (bits - 1), m = b * 2 - 1 }; + return (x & m); + } + + template + inline intmax_t sclamp(const intmax_t x) { + enum : intmax_t { b = 1ull << (bits - 1), m = b - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; + } + + template + inline intmax_t sclip(const intmax_t x) { + enum : uintmax_t { b = 1ull << (bits - 1), m = b * 2 - 1 }; + return ((x & m) ^ b) - b; + } + + namespace bit { + constexpr inline uintmax_t mask(const char *s, uintmax_t sum = 0) { + return ( + *s == '0' || *s == '1' ? mask(s + 1, (sum << 1) | 1) : + *s == ' ' || *s == '_' ? mask(s + 1, sum) : + *s ? mask(s + 1, sum << 1) : + sum + ); + } + + constexpr inline uintmax_t test(const char *s, uintmax_t sum = 0) { + return ( + *s == '0' || *s == '1' ? test(s + 1, (sum << 1) | (*s - '0')) : + *s == ' ' || *s == '_' ? test(s + 1, sum) : + *s ? test(s + 1, sum << 1) : + sum + ); + } + + //lowest(0b1110) == 0b0010 + constexpr inline uintmax_t lowest(const uintmax_t x) { + return x & -x; + } + + //clear_lowest(0b1110) == 0b1100 + constexpr inline uintmax_t clear_lowest(const uintmax_t x) { + return x & (x - 1); + } + + //set_lowest(0b0101) == 0b0111 + constexpr inline uintmax_t set_lowest(const uintmax_t x) { + return x | (x + 1); + } + + //count number of bits set in a byte + inline unsigned count(uintmax_t x) { + unsigned count = 0; + do count += x & 1; while(x >>= 1); + return count; + } + + //round up to next highest single bit: + //round(15) == 16, round(16) == 16, round(17) == 32 + inline uintmax_t round(uintmax_t x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } + } +} + +#endif diff --git a/ananke/nall/bmp.hpp b/ananke/nall/bmp.hpp new file mode 100644 index 00000000..33cdf4dc --- /dev/null +++ b/ananke/nall/bmp.hpp @@ -0,0 +1,101 @@ +#ifndef NALL_BMP_HPP +#define NALL_BMP_HPP + +#include + +//BMP reader / writer +//author: byuu +//note: only 24-bit RGB and 32-bit ARGB uncompressed images supported + +namespace nall { + +struct bmp { + inline static bool read(const string &filename, uint32_t *&data, unsigned &width, unsigned &height); + inline static bool write(const string &filename, const uint32_t *data, unsigned width, unsigned height, unsigned pitch, bool alpha = false); +}; + +bool bmp::read(const string &filename, uint32_t *&data, unsigned &width, unsigned &height) { + file fp; + if(fp.open(filename, file::mode::read) == false) return false; + if(fp.size() < 0x36) return false; + + if(fp.readm(2) != 0x424d) return false; + fp.seek(0x000a); + unsigned offset = fp.readl(4); + unsigned dibsize = fp.readl(4); + if(dibsize != 40) return false; + signed headerWidth = fp.readl(4); + if(headerWidth < 0) return false; + signed headerHeight = fp.readl(4); + fp.readl(2); + unsigned bitsPerPixel = fp.readl(2); + if(bitsPerPixel != 24 && bitsPerPixel != 32) return false; + unsigned compression = fp.readl(4); + if(compression != 0) return false; + fp.seek(offset); + + bool noFlip = headerHeight < 0; + width = headerWidth, height = abs(headerHeight); + data = new uint32_t[width * height]; + + unsigned bytesPerPixel = bitsPerPixel / 8; + unsigned alignedWidth = width * bytesPerPixel; + unsigned paddingLength = 0; + while(alignedWidth % 4) alignedWidth++, paddingLength++; + + for(unsigned y = 0; y < height; y++) { + uint32_t *p = noFlip ? data + y * width : data + (height - 1 - y) * width; + for(unsigned x = 0; x < width; x++, p++) { + *p = fp.readl(bytesPerPixel); + if(bytesPerPixel == 3) *p |= 255 << 24; + } + if(paddingLength) fp.readl(paddingLength); + } + + fp.close(); + return true; +} + +bool bmp::write(const string &filename, const uint32_t *data, unsigned width, unsigned height, unsigned pitch, bool alpha) { + file fp; + if(fp.open(filename, file::mode::write) == false) return false; + + unsigned bitsPerPixel = alpha ? 32 : 24; + unsigned bytesPerPixel = bitsPerPixel / 8; + unsigned alignedWidth = width * bytesPerPixel; + unsigned paddingLength = 0; + unsigned imageSize = alignedWidth * height; + unsigned fileSize = 0x36 + imageSize; + while(alignedWidth % 4) alignedWidth++, paddingLength++; + + fp.writem(0x424d, 2); //signature + fp.writel(fileSize, 4); //file size + fp.writel(0, 2); //reserved + fp.writel(0, 2); //reserved + fp.writel(0x36, 4); //offset + + fp.writel(40, 4); //DIB size + fp.writel(width, 4); //width + fp.writel(-height, 4); //height + fp.writel(1, 2); //color planes + fp.writel(bitsPerPixel, 2); //bits per pixel + fp.writel(0, 4); //compression method (BI_RGB) + fp.writel(imageSize, 4); //image data size + fp.writel(3780, 4); //horizontal resolution + fp.writel(3780, 4); //vertical resolution + fp.writel(0, 4); //palette size + fp.writel(0, 4); //important color count + + for(unsigned y = 0; y < height; y++) { + const uint32_t *p = (const uint32_t*)((const uint8_t*)data + y * pitch); + for(unsigned x = 0; x < width; x++) fp.writel(*p++, bytesPerPixel); + if(paddingLength) fp.writel(0, paddingLength); + } + + fp.close(); + return true; +} + +} + +#endif diff --git a/ananke/nall/compositor.hpp b/ananke/nall/compositor.hpp new file mode 100644 index 00000000..6b9245f6 --- /dev/null +++ b/ananke/nall/compositor.hpp @@ -0,0 +1,152 @@ +#ifndef NALL_COMPOSITOR_HPP +#define NALL_COMPOSITOR_HPP + +#include + +namespace nall { + +struct compositor { + inline static bool enabled(); + inline static bool enable(bool status); + + #if defined(PLATFORM_X) + enum class Compositor : unsigned { Unknown, Metacity, Xfwm4 }; + inline static Compositor detect(); + + inline static bool enabled_metacity(); + inline static bool enable_metacity(bool status); + + inline static bool enabled_xfwm4(); + inline static bool enable_xfwm4(bool status); + #endif +}; + +#if defined(PLATFORM_X) + +//Metacity + +bool compositor::enabled_metacity() { + FILE *fp = popen("gconftool-2 --get /apps/metacity/general/compositing_manager", "r"); + if(fp == 0) return false; + + char buffer[512]; + if(fgets(buffer, sizeof buffer, fp) == 0) return false; + + if(!memcmp(buffer, "true", 4)) return true; + return false; +} + +bool compositor::enable_metacity(bool status) { + FILE *fp; + if(status) { + fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager true", "r"); + } else { + fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager false", "r"); + } + if(fp == 0) return false; + pclose(fp); + return true; +} + +//Xfwm4 + +bool compositor::enabled_xfwm4() { + FILE *fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing'", "r"); + if(fp == 0) return false; + + char buffer[512]; + if(fgets(buffer, sizeof buffer, fp) == 0) return false; + + if(!memcmp(buffer, "true", 4)) return true; + return false; +} + +bool compositor::enable_xfwm4(bool status) { + FILE *fp; + if(status) { + fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing' -t 'bool' -s 'true'", "r"); + } else { + fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing' -t 'bool' -s 'false'", "r"); + } + if(fp == 0) return false; + pclose(fp); + return true; +} + +//General + +compositor::Compositor compositor::detect() { + Compositor result = Compositor::Unknown; + + FILE *fp; + char buffer[512]; + + fp = popen("pidof metacity", "r"); + if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Metacity; + pclose(fp); + + fp = popen("pidof xfwm4", "r"); + if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Xfwm4; + pclose(fp); + + return result; +} + +bool compositor::enabled() { + switch(detect()) { + case Compositor::Metacity: return enabled_metacity(); + case Compositor::Xfwm4: return enabled_xfwm4(); + default: return false; + } +} + +bool compositor::enable(bool status) { + switch(detect()) { + case Compositor::Metacity: return enable_metacity(status); + case Compositor::Xfwm4: return enable_xfwm4(status); + default: return false; + } +} + +#elif defined(PLATFORM_WINDOWS) + +bool compositor::enabled() { + HMODULE module = GetModuleHandleW(L"dwmapi"); + if(module == 0) module = LoadLibraryW(L"dwmapi"); + if(module == 0) return false; + + auto pDwmIsCompositionEnabled = (HRESULT (WINAPI*)(BOOL*))GetProcAddress(module, "DwmIsCompositionEnabled"); + if(pDwmIsCompositionEnabled == 0) return false; + + BOOL result; + if(pDwmIsCompositionEnabled(&result) != S_OK) return false; + return result; +} + +bool compositor::enable(bool status) { + HMODULE module = GetModuleHandleW(L"dwmapi"); + if(module == 0) module = LoadLibraryW(L"dwmapi"); + if(module == 0) return false; + + auto pDwmEnableComposition = (HRESULT (WINAPI*)(UINT))GetProcAddress(module, "DwmEnableComposition"); + if(pDwmEnableComposition == 0) return false; + + if(pDwmEnableComposition(status) != S_OK) return false; + return true; +} + +#else + +bool compositor::enabled() { + return false; +} + +bool compositor::enable(bool) { + return false; +} + +#endif + +} + +#endif diff --git a/ananke/nall/config.hpp b/ananke/nall/config.hpp new file mode 100644 index 00000000..94be7dc1 --- /dev/null +++ b/ananke/nall/config.hpp @@ -0,0 +1,126 @@ +#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; + + inline string get() const { + switch(type) { + case boolean_t: return { *(bool*)data }; + case signed_t: return { *(signed*)data }; + case unsigned_t: return { *(unsigned*)data }; + case double_t: return { *(double*)data }; + case string_t: return { "\"", *(string*)data, "\"" }; + } + return "???"; + } + + inline 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; + } + } + }; + vector list; + + template + inline void append(T &data, const char *name, const char *desc = "") { + item_t item = { (uintptr_t)&data, name, desc }; + if(configuration_traits::is_boolean::value) item.type = boolean_t; + else if(configuration_traits::is_signed::value) item.type = signed_t; + else if(configuration_traits::is_unsigned::value) item.type = unsigned_t; + else if(configuration_traits::is_double::value) item.type = double_t; + else if(configuration_traits::is_string::value) item.type = string_t; + else item.type = unknown_t; + list.append(item); + } + + //deprecated + template + inline void attach(T &data, const char *name, const char *desc = "") { + append(data, name, desc); + } + + inline virtual bool load(const string &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; + } + } + + inline virtual bool save(const string &filename) const { + file fp; + if(fp.open(filename, file::mode::write)) { + for(unsigned i = 0; i < list.size(); i++) { + string output; + output.append(list[i].name, " = ", list[i].get()); + if(list[i].desc != "") output.append(" # ", list[i].desc); + output.append("\r\n"); + fp.print(output); + } + + fp.close(); + return true; + } else { + return false; + } + } + }; +} + +#endif diff --git a/ananke/nall/crc16.hpp b/ananke/nall/crc16.hpp new file mode 100644 index 00000000..cd6e72fd --- /dev/null +++ b/ananke/nall/crc16.hpp @@ -0,0 +1,25 @@ +#ifndef NALL_CRC16_HPP +#define NALL_CRC16_HPP + +#include + +namespace nall { + inline uint16_t crc16_adjust(uint16_t crc16, uint8_t data) { + for(unsigned n = 0; n < 8; n++) { + if((crc16 & 1) ^ (data & 1)) crc16 = (crc16 >> 1) ^ 0x8408; + else crc16 >>= 1; + data >>= 1; + } + return crc16; + } + + inline uint16_t crc16_calculate(const uint8_t *data, unsigned length) { + uint16_t crc16 = ~0; + for(unsigned n = 0; n < length; n++) { + crc16 = crc16_adjust(crc16, data[n]); + } + return ~crc16; + } +} + +#endif diff --git a/ananke/nall/crc32.hpp b/ananke/nall/crc32.hpp new file mode 100644 index 00000000..ad36fbf6 --- /dev/null +++ b/ananke/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/ananke/nall/directory.hpp b/ananke/nall/directory.hpp new file mode 100644 index 00000000..36db7532 --- /dev/null +++ b/ananke/nall/directory.hpp @@ -0,0 +1,198 @@ +#ifndef NALL_DIRECTORY_HPP +#define NALL_DIRECTORY_HPP + +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include +#else + #include + #include + #include +#endif + +namespace nall { + +struct directory { + static bool create(const string &pathname, unsigned permissions = 0755); //recursive + static bool remove(const string &pathname); //recursive + 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(PLATFORM_WINDOWS) + inline bool directory::create(const string &pathname, unsigned permissions) { + string path; + lstring list = string{pathname}.transform("\\", "/").rtrim<1>("/").split("/"); + bool result = true; + for(auto &part : list) { + path.append(part, "/"); + result &= (_wmkdir(utf16_t(path)) == 0); + } + return result; + } + + inline bool directory::remove(const string &pathname) { + lstring list = directory::contents(pathname); + for(auto &name : list) { + if(name.endswith("/")) directory::remove({pathname, name}); + else file::remove({pathname, name}); + } + return _wrmdir(utf16_t(pathname)) == 0; + } + + inline bool directory::exists(const string &pathname) { + string name = pathname; + name.trim<1>("\""); + DWORD result = GetFileAttributes(utf16_t(name)); + 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 = (const char*)utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + } + while(FindNextFile(handle, &data) != false) { + if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) { + if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + string name = (const char*)utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + } + } + FindClose(handle); + } + if(list.size() > 0) list.sort(); + for(auto &name : list) name.append("/"); //must append after sorting + 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 = (const char*)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 = (const char*)utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + } + FindClose(handle); + } + if(list.size() > 0) list.sort(); + 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); + for(auto &file : files) folders.append(file); + return folders; + } +#else + inline bool directory::create(const string &pathname, unsigned permissions) { + string path; + lstring list = string{pathname}.rtrim<1>("/").split("/"); + bool result = true; + for(auto &part : list) { + path.append(part, "/"); + result &= (mkdir(path, permissions) == 0); + } + return result; + } + + inline bool directory::remove(const string &pathname) { + lstring list = directory::contents(pathname); + for(auto &name : list) { + if(name.endswith("/")) directory::remove({pathname, name}); + else file::remove({pathname, name}); + } + return rmdir(pathname) == 0; + } + + 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(ep->d_name); + } + } + closedir(dp); + } + if(list.size() > 0) list.sort(); + for(auto &name : list) name.append("/"); //must append after sorting + 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) list.sort(); + 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); + for(auto &file : files) folders.append(file); + return folders; + } +#endif + +} + +#endif diff --git a/ananke/nall/dl.hpp b/ananke/nall/dl.hpp new file mode 100644 index 00000000..3bd7d4d2 --- /dev/null +++ b/ananke/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_WINDOWS) + #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_WINDOWS) + 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/ananke/nall/dsp.hpp b/ananke/nall/dsp.hpp new file mode 100644 index 00000000..a2400ec7 --- /dev/null +++ b/ananke/nall/dsp.hpp @@ -0,0 +1,13 @@ +#ifndef NALL_DSP_HPP +#define NALL_DSP_HPP + +#include +#ifdef __SSE__ + #include +#endif + +#define NALL_DSP_INTERNAL_HPP +#include +#undef NALL_DSP_INTERNAL_HPP + +#endif diff --git a/ananke/nall/dsp/buffer.hpp b/ananke/nall/dsp/buffer.hpp new file mode 100644 index 00000000..4386d0e9 --- /dev/null +++ b/ananke/nall/dsp/buffer.hpp @@ -0,0 +1,51 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct Buffer { + double **sample; + uint16_t rdoffset; + uint16_t wroffset; + unsigned channels; + + void setChannels(unsigned channels) { + for(unsigned c = 0; c < this->channels; c++) { + if(sample[c]) delete[] sample[c]; + } + if(sample) delete[] sample; + + this->channels = channels; + if(channels == 0) return; + + sample = new double*[channels]; + for(unsigned c = 0; c < channels; c++) { + sample[c] = new double[65536](); + } + } + + inline double& read(unsigned channel, signed offset = 0) { + return sample[channel][(uint16_t)(rdoffset + offset)]; + } + + inline double& write(unsigned channel, signed offset = 0) { + return sample[channel][(uint16_t)(wroffset + offset)]; + } + + inline void clear() { + for(unsigned c = 0; c < channels; c++) { + for(unsigned n = 0; n < 65536; n++) { + sample[c][n] = 0; + } + } + rdoffset = 0; + wroffset = 0; + } + + Buffer() { + channels = 0; + } + + ~Buffer() { + setChannels(0); + } +}; + +#endif diff --git a/ananke/nall/dsp/core.hpp b/ananke/nall/dsp/core.hpp new file mode 100644 index 00000000..a5b967b1 --- /dev/null +++ b/ananke/nall/dsp/core.hpp @@ -0,0 +1,167 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +#include +#include + +namespace nall { + +//precision: can be float, double or long double +#define real float + +struct DSP; + +struct Resampler { + DSP &dsp; + real frequency; + + virtual void setFrequency() = 0; + virtual void clear() = 0; + virtual void sample() = 0; + Resampler(DSP &dsp) : dsp(dsp) {} +}; + +struct DSP { + enum class ResampleEngine : unsigned { + Nearest, + Linear, + Cosine, + Cubic, + Hermite, + Average, + Sinc, + }; + + inline void setChannels(unsigned channels); + inline void setPrecision(unsigned precision); + inline void setFrequency(real frequency); //inputFrequency + inline void setVolume(real volume); + inline void setBalance(real balance); + + inline void setResampler(ResampleEngine resamplingEngine); + inline void setResamplerFrequency(real frequency); //outputFrequency + + inline void sample(signed channel[]); + inline bool pending(); + inline void read(signed channel[]); + + inline void clear(); + inline DSP(); + inline ~DSP(); + +protected: + friend class ResampleNearest; + friend class ResampleLinear; + friend class ResampleCosine; + friend class ResampleCubic; + friend class ResampleAverage; + friend class ResampleHermite; + friend class ResampleSinc; + + struct Settings { + unsigned channels; + unsigned precision; + real frequency; + real volume; + real balance; + + //internal + real intensity; + real intensityInverse; + } settings; + + Resampler *resampler; + inline void write(real channel[]); + + #include "buffer.hpp" + Buffer buffer; + Buffer output; + + inline void adjustVolume(); + inline void adjustBalance(); + inline signed clamp(const unsigned bits, const signed x); +}; + +#include "resample/nearest.hpp" +#include "resample/linear.hpp" +#include "resample/cosine.hpp" +#include "resample/cubic.hpp" +#include "resample/hermite.hpp" +#include "resample/average.hpp" +#include "resample/sinc.hpp" +#include "settings.hpp" + +void DSP::sample(signed channel[]) { + for(unsigned c = 0; c < settings.channels; c++) { + buffer.write(c) = (real)channel[c] * settings.intensityInverse; + } + buffer.wroffset++; + resampler->sample(); +} + +bool DSP::pending() { + return output.rdoffset != output.wroffset; +} + +void DSP::read(signed channel[]) { + adjustVolume(); + adjustBalance(); + + for(unsigned c = 0; c < settings.channels; c++) { + channel[c] = clamp(settings.precision, output.read(c) * settings.intensity); + } + output.rdoffset++; +} + +void DSP::write(real channel[]) { + for(unsigned c = 0; c < settings.channels; c++) { + output.write(c) = channel[c]; + } + output.wroffset++; +} + +void DSP::adjustVolume() { + for(unsigned c = 0; c < settings.channels; c++) { + output.read(c) *= settings.volume; + } +} + +void DSP::adjustBalance() { + if(settings.channels != 2) return; //TODO: support > 2 channels + if(settings.balance < 0.0) output.read(1) *= 1.0 + settings.balance; + if(settings.balance > 0.0) output.read(0) *= 1.0 - settings.balance; +} + +signed DSP::clamp(const unsigned bits, const signed x) { + const signed b = 1U << (bits - 1); + const signed m = (1U << (bits - 1)) - 1; + return (x > m) ? m : (x < -b) ? -b : x; +} + +void DSP::clear() { + buffer.clear(); + output.clear(); + resampler->clear(); +} + +DSP::DSP() { + setResampler(ResampleEngine::Hermite); + setResamplerFrequency(44100.0); + + setChannels(2); + setPrecision(16); + setFrequency(44100.0); + setVolume(1.0); + setBalance(0.0); + + clear(); +} + +DSP::~DSP() { + if(resampler) delete resampler; +} + +#undef real + +} + +#endif diff --git a/ananke/nall/dsp/resample/average.hpp b/ananke/nall/dsp/resample/average.hpp new file mode 100644 index 00000000..867b13bf --- /dev/null +++ b/ananke/nall/dsp/resample/average.hpp @@ -0,0 +1,72 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleAverage : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + inline void sampleLinear(); + ResampleAverage(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleAverage::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleAverage::clear() { + fraction = 0.0; +} + +void ResampleAverage::sample() { + //can only average if input frequency >= output frequency + if(step < 1.0) return sampleLinear(); + + fraction += 1.0; + + real scalar = 1.0; + if(fraction > step) scalar = 1.0 - (fraction - step); + + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) += dsp.buffer.read(c) * scalar; + } + + if(fraction >= step) { + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) /= step; + } + dsp.output.wroffset++; + + fraction -= step; + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) = dsp.buffer.read(c) * fraction; + } + } + + dsp.buffer.rdoffset++; +} + +void ResampleAverage::sampleLinear() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + + channel[n] = a * (1.0 - mu) + b * mu; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/cosine.hpp b/ananke/nall/dsp/resample/cosine.hpp new file mode 100644 index 00000000..3363d5f6 --- /dev/null +++ b/ananke/nall/dsp/resample/cosine.hpp @@ -0,0 +1,44 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleCosine : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleCosine(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleCosine::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleCosine::clear() { + fraction = 0.0; +} + +void ResampleCosine::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + mu = (1.0 - cos(mu * 3.14159265)) / 2.0; + + channel[n] = a * (1.0 - mu) + b * mu; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/cubic.hpp b/ananke/nall/dsp/resample/cubic.hpp new file mode 100644 index 00000000..bc4cc955 --- /dev/null +++ b/ananke/nall/dsp/resample/cubic.hpp @@ -0,0 +1,50 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleCubic : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleCubic(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleCubic::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleCubic::clear() { + fraction = 0.0; +} + +void ResampleCubic::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -3); + real b = dsp.buffer.read(n, -2); + real c = dsp.buffer.read(n, -1); + real d = dsp.buffer.read(n, -0); + + real mu = fraction; + + real A = d - c - a + b; + real B = a - b - A; + real C = c - a; + real D = b; + + channel[n] = A * (mu * 3) + B * (mu * 2) + C * mu + D; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/hermite.hpp b/ananke/nall/dsp/resample/hermite.hpp new file mode 100644 index 00000000..0cc9ba0e --- /dev/null +++ b/ananke/nall/dsp/resample/hermite.hpp @@ -0,0 +1,62 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleHermite : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleHermite(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleHermite::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleHermite::clear() { + fraction = 0.0; +} + +void ResampleHermite::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -3); + real b = dsp.buffer.read(n, -2); + real c = dsp.buffer.read(n, -1); + real d = dsp.buffer.read(n, -0); + + const real tension = 0.0; //-1 = low, 0 = normal, +1 = high + const real bias = 0.0; //-1 = left, 0 = even, +1 = right + + real mu1, mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu1 = fraction; + mu2 = mu1 * mu1; + mu3 = mu2 * mu1; + + m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0; + m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0; + m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0; + m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0; + + a0 = +2 * mu3 - 3 * mu2 + 1; + a1 = mu3 - 2 * mu2 + mu1; + a2 = mu3 - mu2; + a3 = -2 * mu3 + 3 * mu2; + + channel[n] = (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/lib/sinc.hpp b/ananke/nall/dsp/resample/lib/sinc.hpp new file mode 100644 index 00000000..3e953679 --- /dev/null +++ b/ananke/nall/dsp/resample/lib/sinc.hpp @@ -0,0 +1,600 @@ +// If these types are changed to anything other than "float", you should comment out the SSE detection directives below +// so that the SSE code is not used. + +typedef float resample_coeff_t; // note: sizeof(resample_coeff_t) must be == to a power of 2, and not larger than 16 +typedef float resample_samp_t; + + +// ...but don't comment this single RESAMPLE_SSEREGPARM define out when disabling SSE. +#define RESAMPLE_SSEREGPARM + +#if defined(__SSE__) + #define SINCRESAMPLE_USE_SSE 1 + #ifndef __x86_64__ + #undef RESAMPLE_SSEREGPARM + #define RESAMPLE_SSEREGPARM __attribute__((sseregparm)) + #endif +#else + // TODO: altivec here +#endif + +namespace ResampleUtility +{ + inline void kaiser_window(double* io, int count, double beta); + inline void gen_sinc(double* out, int size, double cutoff, double kaiser); + inline void gen_sinc_os(double* out, int size, double cutoff, double kaiser); + inline void normalize(double* io, int size, double gain = 1.0); + + inline void* make_aligned(void* ptr, unsigned boundary); // boundary must be a power of 2 +} + +class SincResampleHR +{ + private: + + inline void Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d); + + inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM; + inline resample_samp_t read(void) RESAMPLE_SSEREGPARM; + inline bool output_avail(void); + + private: + + inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count); + + unsigned ratio; + unsigned num_convolutions; + + resample_coeff_t *coeffs; + std::vector coeffs_mem; + + // second half of ringbuffer should be copy of first half. + resample_samp_t *rb; + std::vector rb_mem; + + signed rb_readpos; + signed rb_writepos; + signed rb_in; + signed rb_eff_size; + + friend class SincResample; +}; + +class SincResample +{ + public: + + enum + { + QUALITY_LOW = 0, + QUALITY_MEDIUM = 2, + QUALITY_HIGH = 4 + }; + + inline SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality = QUALITY_HIGH); + + inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM; + inline resample_samp_t read(void) RESAMPLE_SSEREGPARM; + inline bool output_avail(void); + + private: + + inline void Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min); + + inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count) RESAMPLE_SSEREGPARM; + + unsigned num_convolutions; + unsigned num_phases; + + unsigned step_int; + double step_fract; + + double input_pos_fract; + + + std::vector coeffs; // Pointers into coeff_mem. + std::vector coeff_mem; + + + std::vector rb; // second half should be copy of first half. + signed rb_readpos; + signed rb_writepos; + signed rb_in; + + bool hr_used; + SincResampleHR hr; +}; + + +// +// Code: +// +//#include "resample.hpp" + +#if 0 +namespace bit +{ + inline unsigned round(unsigned x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } +} +#endif + +void SincResampleHR::Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d) +{ + const unsigned align_boundary = 16; + std::vector coeffs_tmp; + double cutoff; // 1.0 = f/2 + + ratio = ratio_arg; + + //num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) + 1) &~ 1; // round up to be even + num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) | 1); + + cutoff = (1.0 / ratio) - (d / num_convolutions); + +//printf("%d %d %.20f\n", ratio, num_convolutions, cutoff); + assert(num_convolutions > ratio); + + + // Generate windowed sinc of POWER + coeffs_tmp.resize(num_convolutions); + //ResampleUtility::gen_sinc(&coeffs_tmp[0], num_convolutions, cutoff, beta); + ResampleUtility::gen_sinc_os(&coeffs_tmp[0], num_convolutions, cutoff, beta); + ResampleUtility::normalize(&coeffs_tmp[0], num_convolutions); + + // Copy from coeffs_tmp to coeffs~ + // We multiply many coefficients at a time in the mac loop, so make sure the last few that don't really + // exist are allocated, zero'd mem. + + coeffs_mem.resize(((num_convolutions + 7) &~ 7) * sizeof(resample_coeff_t) + (align_boundary - 1)); + coeffs = (resample_coeff_t *)ResampleUtility::make_aligned(&coeffs_mem[0], align_boundary); + + + for(unsigned i = 0; i < num_convolutions; i++) + coeffs[i] = coeffs_tmp[i]; + + rb_eff_size = nall::bit::round(num_convolutions * 2) >> 1; + rb_readpos = 0; + rb_writepos = 0; + rb_in = 0; + + rb_mem.resize(rb_eff_size * 2 * sizeof(resample_samp_t) + (align_boundary - 1)); + rb = (resample_samp_t *)ResampleUtility::make_aligned(&rb_mem[0], align_boundary); +} + + +inline bool SincResampleHR::output_avail(void) +{ + return(rb_in >= (signed)num_convolutions); +} + +inline void SincResampleHR::write(resample_samp_t sample) +{ + assert(!output_avail()); + + rb[rb_writepos] = sample; + rb[rb_writepos + rb_eff_size] = sample; + rb_writepos = (rb_writepos + 1) & (rb_eff_size - 1); + rb_in++; +} + +resample_samp_t SincResampleHR::mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count) +{ +#if SINCRESAMPLE_USE_SSE + __m128 accum_veca[2] = { _mm_set1_ps(0), _mm_set1_ps(0) }; + + resample_samp_t accum; + + for(unsigned c = 0; c < count; c += 8) + { + for(unsigned i = 0; i < 2; i++) + { + __m128 co[2]; + __m128 w[2]; + + co[i] = _mm_load_ps(&coeff[c + i * 4]); + w[i] = _mm_load_ps(&wave[c + i * 4]); + + w[i] = _mm_mul_ps(w[i], co[i]); + + accum_veca[i] = _mm_add_ps(w[i], accum_veca[i]); + } + } + + __m128 accum_vec = _mm_add_ps(accum_veca[0], accum_veca[1]); //_mm_add_ps(_mm_add_ps(accum_veca[0], accum_veca[1]), _mm_add_ps(accum_veca[2], accum_veca[3])); + + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6))); + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6))); + + _mm_store_ss(&accum, accum_vec); + + return accum; +#else + resample_samp_t accum[4] = { 0, 0, 0, 0 }; + + for(unsigned c = 0; c < count; c+= 4) + { + accum[0] += wave[c + 0] * coeff[c + 0]; + accum[1] += wave[c + 1] * coeff[c + 1]; + accum[2] += wave[c + 2] * coeff[c + 2]; + accum[3] += wave[c + 3] * coeff[c + 3]; + } + + return (accum[0] + accum[1]) + (accum[2] + accum[3]); // don't mess with parentheses(assuming compiler doesn't already, which it may... + +#endif +} + + +resample_samp_t SincResampleHR::read(void) +{ + assert(output_avail()); + resample_samp_t ret; + + ret = mac(&rb[rb_readpos], &coeffs[0], num_convolutions); + + rb_readpos = (rb_readpos + ratio) & (rb_eff_size - 1); + rb_in -= ratio; + + return ret; +} + + +SincResample::SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality) +{ + const struct + { + double beta; + double d; + unsigned pn_nume; + unsigned phases_min; + } qtab[5] = + { + { 5.658, 3.62, 4096, 4 }, + { 6.764, 4.32, 8192, 4 }, + { 7.865, 5.0, 16384, 8 }, + { 8.960, 5.7, 32768, 16 }, + { 10.056, 6.4, 65536, 32 } + }; + + // Sanity checks + assert(ceil(input_rate) > 0); + assert(ceil(output_rate) > 0); + assert(ceil(input_rate / output_rate) <= 1024); + assert(ceil(output_rate / input_rate) <= 1024); + + // The simplistic number-of-phases calculation code doesn't work well enough for when desired_bandwidth is close to 1.0 and when + // upsampling. + assert(desired_bandwidth >= 0.25 && desired_bandwidth < 0.96); + assert(quality >= 0 && quality <= 4); + + hr_used = false; + +#if 1 + // Round down to the nearest multiple of 4(so wave buffer remains aligned) + // It also adjusts the effective intermediate sampling rate up slightly, so that the upper frequencies below f/2 + // aren't overly attenuated so much. In the future, we might want to do an FFT or something to choose the intermediate rate more accurately + // to virtually eliminate over-attenuation. + unsigned ioratio_rd = (unsigned)floor(input_rate / (output_rate * (1.0 + (1.0 - desired_bandwidth) / 2) )) & ~3; + + if(ioratio_rd >= 8) + { + hr.Init(ioratio_rd, desired_bandwidth, qtab[quality].beta, qtab[quality].d); //10.056, 6.4); + hr_used = true; + + input_rate /= ioratio_rd; + } +#endif + + Init(input_rate, output_rate, desired_bandwidth, qtab[quality].beta, qtab[quality].d, qtab[quality].pn_nume, qtab[quality].phases_min); +} + +void SincResample::Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min) +{ + const unsigned max_mult_atatime = 8; // multiply "granularity". must be power of 2. + const unsigned max_mult_minus1 = (max_mult_atatime - 1); + const unsigned conv_alignment_bytes = 16; // must be power of 2 + const double input_to_output_ratio = input_rate / output_rate; + const double output_to_input_ratio = output_rate / input_rate; + double cutoff; // 1.0 = input_rate / 2 + std::vector coeff_init_buffer; + + // Round up num_convolutions to be even. + if(output_rate > input_rate) + num_convolutions = ((unsigned)ceil(d / (1.0 - desired_bandwidth)) + 1) & ~1; + else + num_convolutions = ((unsigned)ceil(d / (output_to_input_ratio * (1.0 - desired_bandwidth))) + 1) & ~1; + + if(output_rate > input_rate) // Upsampling + cutoff = desired_bandwidth; + else // Downsampling + cutoff = output_to_input_ratio * desired_bandwidth; + + // Round up to be even. + num_phases = (std::max(pn_nume / num_convolutions, phases_min) + 1) &~1; + + // Adjust cutoff to account for the multiple phases. + cutoff = cutoff / num_phases; + + assert((num_convolutions & 1) == 0); + assert((num_phases & 1) == 0); + +// fprintf(stderr, "num_convolutions=%u, num_phases=%u, total expected coeff byte size=%lu\n", num_convolutions, num_phases, +// (long)((num_phases + 2) * ((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * sizeof(float) + conv_alignment_bytes)); + + coeff_init_buffer.resize(num_phases * num_convolutions); + + coeffs.resize(num_phases + 1 + 1); + + coeff_mem.resize((num_phases + 1 + 1) * ((num_convolutions + max_mult_minus1) &~ max_mult_minus1) * sizeof(resample_coeff_t) + conv_alignment_bytes); + + // Assign aligned pointers into coeff_mem + { + resample_coeff_t *base_ptr = (resample_coeff_t *)ResampleUtility::make_aligned(&coeff_mem[0], conv_alignment_bytes); + + for(unsigned phase = 0; phase < (num_phases + 1 + 1); phase++) + { + coeffs[phase] = base_ptr + (((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * phase); + } + } + + ResampleUtility::gen_sinc(&coeff_init_buffer[0], num_phases * num_convolutions, cutoff, beta); + ResampleUtility::normalize(&coeff_init_buffer[0], num_phases * num_convolutions, num_phases); + + // Reorder coefficients to allow for more efficient convolution. + for(int phase = -1; phase < ((int)num_phases + 1); phase++) + { + for(int conv = 0; conv < (int)num_convolutions; conv++) + { + double coeff; + + if(phase == -1 && conv == 0) + coeff = 0; + else if(phase == (int)num_phases && conv == ((int)num_convolutions - 1)) + coeff = 0; + else + coeff = coeff_init_buffer[conv * num_phases + phase]; + + coeffs[phase + 1][conv] = coeff; + } + } + + // Free a bit of mem + coeff_init_buffer.resize(0); + + step_int = floor(input_to_output_ratio); + step_fract = input_to_output_ratio - step_int; + + input_pos_fract = 0; + + // Do NOT use rb.size() later in the code, since it'll include the padding. + // We should only need one "max_mult_minus1" here, not two, since it won't matter if it over-reads(due to doing "max_mult_atatime" multiplications at a time + // rather than just 1, in which case this over-read wouldn't happen), from the first half into the duplicated half, + // since those corresponding coefficients will be zero anyway; this is just to handle the case of reading off the end of the duplicated half to + // prevent illegal memory accesses. + rb.resize(num_convolutions * 2 + max_mult_minus1); + + rb_readpos = 0; + rb_writepos = 0; + rb_in = 0; +} + +resample_samp_t SincResample::mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count) +{ + resample_samp_t accum = 0; +#if SINCRESAMPLE_USE_SSE + __m128 accum_vec_a[2] = { _mm_set1_ps(0), _mm_set1_ps(0) }; + __m128 accum_vec_b[2] = { _mm_set1_ps(0), _mm_set1_ps(0) }; + + for(unsigned c = 0; c < count; c += 8) //8) //4) + { + __m128 coeff_a[2]; + __m128 coeff_b[2]; + __m128 w[2]; + __m128 result_a[2], result_b[2]; + + for(unsigned i = 0; i < 2; i++) + { + coeff_a[i] = _mm_load_ps(&coeffs_a[c + (i * 4)]); + coeff_b[i] = _mm_load_ps(&coeffs_b[c + (i * 4)]); + w[i] = _mm_loadu_ps(&wave[c + (i * 4)]); + + result_a[i] = _mm_mul_ps(coeff_a[i], w[i]); + result_b[i] = _mm_mul_ps(coeff_b[i], w[i]); + + accum_vec_a[i] = _mm_add_ps(result_a[i], accum_vec_a[i]); + accum_vec_b[i] = _mm_add_ps(result_b[i], accum_vec_b[i]); + } + } + + __m128 accum_vec, av_a, av_b; + __m128 mult_a_vec = _mm_set1_ps(1.0 - ffract); + __m128 mult_b_vec = _mm_set1_ps(ffract); + + av_a = _mm_mul_ps(mult_a_vec, /*accum_vec_a[0]);*/ _mm_add_ps(accum_vec_a[0], accum_vec_a[1])); + av_b = _mm_mul_ps(mult_b_vec, /*accum_vec_b[0]);*/ _mm_add_ps(accum_vec_b[0], accum_vec_b[1])); + + accum_vec = _mm_add_ps(av_a, av_b); + + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6))); + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6))); + + _mm_store_ss(&accum, accum_vec); +#else + resample_coeff_t mult_a = 1.0 - ffract; + resample_coeff_t mult_b = ffract; + + for(unsigned c = 0; c < count; c += 4) + { + accum += wave[c + 0] * (coeffs_a[c + 0] * mult_a + coeffs_b[c + 0] * mult_b); + accum += wave[c + 1] * (coeffs_a[c + 1] * mult_a + coeffs_b[c + 1] * mult_b); + accum += wave[c + 2] * (coeffs_a[c + 2] * mult_a + coeffs_b[c + 2] * mult_b); + accum += wave[c + 3] * (coeffs_a[c + 3] * mult_a + coeffs_b[c + 3] * mult_b); + } +#endif + + return accum; +} + +inline bool SincResample::output_avail(void) +{ + return(rb_in >= (int)num_convolutions); +} + +resample_samp_t SincResample::read(void) +{ + assert(output_avail()); + double phase = input_pos_fract * num_phases - 0.5; + signed phase_int = (signed)floor(phase); + double phase_fract = phase - phase_int; + unsigned phase_a = num_phases - 1 - phase_int; + unsigned phase_b = phase_a - 1; + resample_samp_t ret; + + ret = mac(&rb[rb_readpos], &coeffs[phase_a + 1][0], &coeffs[phase_b + 1][0], phase_fract, num_convolutions); + + unsigned int_increment = step_int; + + input_pos_fract += step_fract; + int_increment += floor(input_pos_fract); + input_pos_fract -= floor(input_pos_fract); + + rb_readpos = (rb_readpos + int_increment) % num_convolutions; + rb_in -= int_increment; + + return ret; +} + +inline void SincResample::write(resample_samp_t sample) +{ + assert(!output_avail()); + + if(hr_used) + { + hr.write(sample); + + if(hr.output_avail()) + { + sample = hr.read(); + } + else + { + return; + } + } + + rb[rb_writepos + 0 * num_convolutions] = sample; + rb[rb_writepos + 1 * num_convolutions] = sample; + rb_writepos = (rb_writepos + 1) % num_convolutions; + rb_in++; +} + +void ResampleUtility::kaiser_window( double* io, int count, double beta) +{ + int const accuracy = 24; //16; //12; + + double* end = io + count; + + double beta2 = beta * beta * (double) -0.25; + double to_fract = beta2 / ((double) count * count); + double i = 0; + double rescale = 0; // Doesn't need an initializer, to shut up gcc + + for ( ; io < end; ++io, i += 1 ) + { + double x = i * i * to_fract - beta2; + double u = x; + double k = x + 1; + + double n = 2; + do + { + u *= x / (n * n); + n += 1; + k += u; + } + while ( k <= u * (1 << accuracy) ); + + if ( !i ) + rescale = 1 / k; // otherwise values get large + + *io *= k * rescale; + } +} + +void ResampleUtility::gen_sinc(double* out, int size, double cutoff, double kaiser) +{ + assert( size % 2 == 0 ); // size must be even + + int const half_size = size / 2; + double* const mid = &out [half_size]; + + // Generate right half of sinc + for ( int i = 0; i < half_size; i++ ) + { + double angle = (i * 2 + 1) * (M_PI / 2); + mid [i] = sin( angle * cutoff ) / angle; + } + + kaiser_window( mid, half_size, kaiser ); + + // Mirror for left half + for ( int i = 0; i < half_size; i++ ) + out [i] = mid [half_size - 1 - i]; +} + +void ResampleUtility::gen_sinc_os(double* out, int size, double cutoff, double kaiser) +{ + assert( size % 2 == 1); // size must be odd + + for(int i = 0; i < size; i++) + { + if(i == (size / 2)) + out[i] = 2 * M_PI * (cutoff / 2); //0.078478; //1.0; //sin(2 * M_PI * (cutoff / 2) * (i - size / 2)) / (i - (size / 2)); + else + out[i] = sin(2 * M_PI * (cutoff / 2) * (i - size / 2)) / (i - (size / 2)); + +// out[i] *= 0.3635819 - 0.4891775 * cos(2 * M_PI * i / (size - 1)) + 0.1365995 * cos(4 * M_PI * i / (size - 1)) - 0.0106411 * cos(6 * M_PI * i / (size - 1)); +//0.42 - 0.5 * cos(2 * M_PI * i / (size - 1)) + 0.08 * cos(4 * M_PI * i / (size - 1)); + +// printf("%d %f\n", i, out[i]); + } + + kaiser_window(&out[size / 2], size / 2 + 1, kaiser); + + // Mirror for left half + for ( int i = 0; i < size / 2; i++ ) + out [i] = out [size - 1 - i]; + +} + +void ResampleUtility::normalize(double* io, int size, double gain) +{ + double sum = 0; + for ( int i = 0; i < size; i++ ) + sum += io [i]; + + double scale = gain / sum; + for ( int i = 0; i < size; i++ ) + io [i] *= scale; +} + +void* ResampleUtility::make_aligned(void* ptr, unsigned boundary) +{ + unsigned char* null_ptr = (unsigned char *)NULL; + unsigned char* uc_ptr = (unsigned char *)ptr; + + uc_ptr += (boundary - ((uc_ptr - null_ptr) & (boundary - 1))) & (boundary - 1); + + //while((uc_ptr - null_ptr) & (boundary - 1)) + // uc_ptr++; + + //printf("%16llx %16llx\n", (unsigned long long)ptr, (unsigned long long)uc_ptr); + + assert((uc_ptr - (unsigned char *)ptr) < boundary && (uc_ptr >= (unsigned char *)ptr)); + + return uc_ptr; +} diff --git a/ananke/nall/dsp/resample/linear.hpp b/ananke/nall/dsp/resample/linear.hpp new file mode 100644 index 00000000..3c2dc9e6 --- /dev/null +++ b/ananke/nall/dsp/resample/linear.hpp @@ -0,0 +1,43 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleLinear : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleLinear(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleLinear::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleLinear::clear() { + fraction = 0.0; +} + +void ResampleLinear::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + + channel[n] = a * (1.0 - mu) + b * mu; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/nearest.hpp b/ananke/nall/dsp/resample/nearest.hpp new file mode 100644 index 00000000..14b401eb --- /dev/null +++ b/ananke/nall/dsp/resample/nearest.hpp @@ -0,0 +1,43 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleNearest : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleNearest(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleNearest::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleNearest::clear() { + fraction = 0.0; +} + +void ResampleNearest::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + + channel[n] = mu < 0.5 ? a : b; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/sinc.hpp b/ananke/nall/dsp/resample/sinc.hpp new file mode 100644 index 00000000..a77a1eeb --- /dev/null +++ b/ananke/nall/dsp/resample/sinc.hpp @@ -0,0 +1,54 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +#include "lib/sinc.hpp" + +struct ResampleSinc : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + inline ResampleSinc(DSP &dsp); + +private: + inline void remakeSinc(); + SincResample *sinc_resampler[8]; +}; + +void ResampleSinc::setFrequency() { + remakeSinc(); +} + +void ResampleSinc::clear() { + remakeSinc(); +} + +void ResampleSinc::sample() { + for(unsigned c = 0; c < dsp.settings.channels; c++) { + sinc_resampler[c]->write(dsp.buffer.read(c)); + } + + if(sinc_resampler[0]->output_avail()) { + do { + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) = sinc_resampler[c]->read(); + } + dsp.output.wroffset++; + } while(sinc_resampler[0]->output_avail()); + } + + dsp.buffer.rdoffset++; +} + +ResampleSinc::ResampleSinc(DSP &dsp) : Resampler(dsp) { + for(unsigned n = 0; n < 8; n++) sinc_resampler[n] = 0; +} + +void ResampleSinc::remakeSinc() { + assert(dsp.settings.channels < 8); + + for(unsigned c = 0; c < dsp.settings.channels; c++) { + if(sinc_resampler[c]) delete sinc_resampler[c]; + sinc_resampler[c] = new SincResample(dsp.settings.frequency, frequency, 0.85, SincResample::QUALITY_HIGH); + } +} + +#endif diff --git a/ananke/nall/dsp/settings.hpp b/ananke/nall/dsp/settings.hpp new file mode 100644 index 00000000..3a8f24c6 --- /dev/null +++ b/ananke/nall/dsp/settings.hpp @@ -0,0 +1,50 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +void DSP::setChannels(unsigned channels) { + assert(channels > 0); + buffer.setChannels(channels); + output.setChannels(channels); + settings.channels = channels; +} + +void DSP::setPrecision(unsigned precision) { + settings.precision = precision; + settings.intensity = 1 << (settings.precision - 1); + settings.intensityInverse = 1.0 / settings.intensity; +} + +void DSP::setFrequency(real frequency) { + settings.frequency = frequency; + resampler->setFrequency(); +} + +void DSP::setVolume(real volume) { + settings.volume = volume; +} + +void DSP::setBalance(real balance) { + settings.balance = balance; +} + +void DSP::setResampler(ResampleEngine engine) { + if(resampler) delete resampler; + + switch(engine) { + case ResampleEngine::Nearest: resampler = new ResampleNearest(*this); return; + case ResampleEngine::Linear: resampler = new ResampleLinear (*this); return; + case ResampleEngine::Cosine: resampler = new ResampleCosine (*this); return; + case ResampleEngine::Cubic: resampler = new ResampleCubic (*this); return; + case ResampleEngine::Hermite: resampler = new ResampleHermite(*this); return; + case ResampleEngine::Average: resampler = new ResampleAverage(*this); return; + case ResampleEngine::Sinc: resampler = new ResampleSinc (*this); return; + } + + throw; +} + +void DSP::setResamplerFrequency(real frequency) { + resampler->frequency = frequency; + resampler->setFrequency(); +} + +#endif diff --git a/ananke/nall/emulation/famicom.hpp b/ananke/nall/emulation/famicom.hpp new file mode 100644 index 00000000..84275d69 --- /dev/null +++ b/ananke/nall/emulation/famicom.hpp @@ -0,0 +1,182 @@ +#ifndef NALL_EMULATION_FAMICOM_HPP +#define NALL_EMULATION_FAMICOM_HPP + +#include +#include + +namespace nall { + +struct FamicomCartridge { + string markup; + inline FamicomCartridge(const uint8_t *data, unsigned size); + +//private: + unsigned mapper; + unsigned mirror; + unsigned prgrom; + unsigned prgram; + unsigned chrrom; + unsigned chrram; +}; + +FamicomCartridge::FamicomCartridge(const uint8_t *data, unsigned size) { + markup = ""; + if(size < 16) return; + if(data[0] != 'N') return; + if(data[1] != 'E') return; + if(data[2] != 'S') return; + if(data[3] != 26) return; + + markup.append("\n"); + + mapper = ((data[7] >> 4) << 4) | (data[6] >> 4); + mirror = ((data[6] & 0x08) >> 2) | (data[6] & 0x01); + prgrom = data[4] * 0x4000; + chrrom = data[5] * 0x2000; + prgram = 0u; + chrram = chrrom == 0u ? 8192u : 0u; + + markup.append("\n"); + + switch(mapper) { + default: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 1: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 2: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 3: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 4: + //MMC3 + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + //MMC6 + //markup.append(" \n"); + //markup.append(" \n"); + //prgram = 1024; + break; + + case 5: + markup.append(" \n"); + markup.append(" \n"); + prgram = 65536; + break; + + case 7: + markup.append(" \n"); + break; + + case 9: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 10: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 16: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 21: + case 23: + case 25: + //VRC4 + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 22: + //VRC2 + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + break; + + case 24: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 26: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 34: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 66: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 69: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 73: + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 75: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 85: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + } + + markup.append(" \n"); + if(prgrom) markup.append(" \n"); + if(prgram) markup.append(" \n"); + markup.append(" \n"); + + markup.append(" \n"); + if(chrrom) markup.append(" \n"); + if(chrram) markup.append(" \n"); + markup.append(" \n"); + + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/game-boy-advance.hpp b/ananke/nall/emulation/game-boy-advance.hpp new file mode 100644 index 00000000..e16d0949 --- /dev/null +++ b/ananke/nall/emulation/game-boy-advance.hpp @@ -0,0 +1,68 @@ +#ifndef NALL_EMULATION_GAME_BOY_ADVANCE_HPP +#define NALL_EMULATION_GAME_BOY_ADVANCE_HPP + +#include +#include +#include + +namespace nall { + +struct GameBoyAdvanceCartridge { + string markup; + string identifiers; + inline GameBoyAdvanceCartridge(const uint8_t *data, unsigned size); +}; + +GameBoyAdvanceCartridge::GameBoyAdvanceCartridge(const uint8_t *data, unsigned size) { + struct Identifier { + string name; + unsigned size; + }; + vector idlist; + idlist.append({"SRAM_V", 6}); + idlist.append({"SRAM_F_V", 8}); + idlist.append({"EEPROM_V", 8}); + idlist.append({"FLASH_V", 7}); + idlist.append({"FLASH512_V", 10}); + idlist.append({"FLASH1M_V", 9}); + + lstring list; + for(auto &id : idlist) { + for(signed n = 0; n < size - 16; n++) { + if(!memcmp(data + n, (const char*)id.name, id.size)) { + const char *p = (const char*)data + n + id.size; + if(p[0] >= '0' && p[0] <= '9' + && p[1] >= '0' && p[1] <= '9' + && p[2] >= '0' && p[2] <= '9' + ) { + char text[16]; + memcpy(text, data + n, id.size + 3); + text[id.size + 3] = 0; + list.appendonce(text); + } + } + } + } + identifiers = list.concatenate(","); + + markup = ""; + + markup.append("\n"); + markup.append("\n"); + markup.append(" \n"); + if(0); + else if(identifiers.beginswith("SRAM_V" )) markup.append(" \n"); + else if(identifiers.beginswith("SRAM_F_V" )) markup.append(" \n"); + else if(identifiers.beginswith("EEPROM_V" )) markup.append(" \n"); + else if(identifiers.beginswith("FLASH_V" )) markup.append(" \n"); + else if(identifiers.beginswith("FLASH512_V")) markup.append(" \n"); + else if(identifiers.beginswith("FLASH1M_V" )) markup.append(" \n"); + if(identifiers.empty() == false) markup.append(" \n"); + + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/game-boy.hpp b/ananke/nall/emulation/game-boy.hpp new file mode 100644 index 00000000..ef1f3da9 --- /dev/null +++ b/ananke/nall/emulation/game-boy.hpp @@ -0,0 +1,122 @@ +#ifndef NALL_EMULATION_GAME_BOY_HPP +#define NALL_EMULATION_GAME_BOY_HPP + +#include +#include + +namespace nall { + +struct GameBoyCartridge { + string markup; + inline GameBoyCartridge(uint8_t *data, unsigned size); + +//private: + struct Information { + string mapper; + bool ram; + bool battery; + bool rtc; + bool rumble; + + unsigned romsize; + unsigned ramsize; + + bool cgb; + bool cgbonly; + } info; +}; + +GameBoyCartridge::GameBoyCartridge(uint8_t *romdata, unsigned romsize) { + markup = ""; + if(romsize < 0x4000) return; + + info.mapper = "unknown"; + info.ram = false; + info.battery = false; + info.rtc = false; + info.rumble = false; + + info.romsize = 0; + info.ramsize = 0; + + unsigned base = romsize - 0x8000; + if(romdata[base + 0x0104] == 0xce && romdata[base + 0x0105] == 0xed + && romdata[base + 0x0106] == 0x66 && romdata[base + 0x0107] == 0x66 + && romdata[base + 0x0108] == 0xcc && romdata[base + 0x0109] == 0x0d + && romdata[base + 0x0147] >= 0x0b && romdata[base + 0x0147] <= 0x0d + ) { + //MMM01 stores header at bottom of image + //flip this around for consistency with all other mappers + uint8_t header[0x8000]; + memcpy(header, romdata + base, 0x8000); + memmove(romdata + 0x8000, romdata, romsize - 0x8000); + memcpy(romdata, header, 0x8000); + } + + info.cgb = (romdata[0x0143] & 0x80) == 0x80; + info.cgbonly = (romdata[0x0143] & 0xc0) == 0xc0; + + switch(romdata[0x0147]) { + case 0x00: info.mapper = "none"; break; + case 0x01: info.mapper = "MBC1"; break; + case 0x02: info.mapper = "MBC1"; info.ram = true; break; + case 0x03: info.mapper = "MBC1"; info.ram = true; info.battery = true; break; + case 0x05: info.mapper = "MBC2"; info.ram = true; break; + case 0x06: info.mapper = "MBC2"; info.ram = true; info.battery = true; break; + case 0x08: info.mapper = "none"; info.ram = true; break; + case 0x09: info.mapper = "MBC0"; info.ram = true; info.battery = true; break; + case 0x0b: info.mapper = "MMM01"; break; + case 0x0c: info.mapper = "MMM01"; info.ram = true; break; + case 0x0d: info.mapper = "MMM01"; info.ram = true; info.battery = true; break; + case 0x0f: info.mapper = "MBC3"; info.rtc = true; info.battery = true; break; + case 0x10: info.mapper = "MBC3"; info.rtc = true; info.ram = true; info.battery = true; break; + case 0x11: info.mapper = "MBC3"; break; + case 0x12: info.mapper = "MBC3"; info.ram = true; break; + case 0x13: info.mapper = "MBC3"; info.ram = true; info.battery = true; break; + case 0x19: info.mapper = "MBC5"; break; + case 0x1a: info.mapper = "MBC5"; info.ram = true; break; + case 0x1b: info.mapper = "MBC5"; info.ram = true; info.battery = true; break; + case 0x1c: info.mapper = "MBC5"; info.rumble = true; break; + case 0x1d: info.mapper = "MBC5"; info.rumble = true; info.ram = true; break; + case 0x1e: info.mapper = "MBC5"; info.rumble = true; info.ram = true; info.battery = true; break; + case 0xfc: break; //Pocket Camera + case 0xfd: break; //Bandai TAMA5 + case 0xfe: info.mapper = "HuC3"; break; + case 0xff: info.mapper = "HuC1"; info.ram = true; info.battery = true; break; + } + + 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; + 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; + } + + 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; + case 0x03: info.ramsize = 32 * 1024; break; + } + + if(info.mapper == "MBC2") info.ramsize = 512; //512 x 4-bit + + markup = "\n"; + markup.append("\n"); + markup.append(" \n"); + markup.append(" \n"); + if(info.ramsize > 0) markup.append(" \n"); + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/satellaview.hpp b/ananke/nall/emulation/satellaview.hpp new file mode 100644 index 00000000..ddae080c --- /dev/null +++ b/ananke/nall/emulation/satellaview.hpp @@ -0,0 +1,26 @@ +#ifndef NALL_EMULATION_SATELLAVIEW_HPP +#define NALL_EMULATION_SATELLAVIEW_HPP + +#include +#include + +namespace nall { + +struct SatellaviewCartridge { + string markup; + inline SatellaviewCartridge(const uint8_t *data, unsigned size); +}; + +SatellaviewCartridge::SatellaviewCartridge(const uint8_t *data, unsigned size) { + markup = ""; + + markup.append("\n"); + markup.append("\n"); + markup.append(" \n"); + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/sufami-turbo.hpp b/ananke/nall/emulation/sufami-turbo.hpp new file mode 100644 index 00000000..0256360c --- /dev/null +++ b/ananke/nall/emulation/sufami-turbo.hpp @@ -0,0 +1,33 @@ +#ifndef NALL_EMULATION_SUFAMI_TURBO_HPP +#define NALL_EMULATION_SUFAMI_TURBO_HPP + +#include +#include + +namespace nall { + +struct SufamiTurboCartridge { + string markup; + inline SufamiTurboCartridge(const uint8_t *data, unsigned size); +}; + +SufamiTurboCartridge::SufamiTurboCartridge(const uint8_t *data, unsigned size) { + markup = ""; + + if(size < 0x20000) return; //too small to be a valid game? + if(memcmp(data, "BANDAI SFC-ADX", 14)) return; //missing required header? + unsigned romsize = data[0x36] * 0x20000; //128KB + unsigned ramsize = data[0x37] * 0x800; //2KB + bool linkable = data[0x35] != 0x00; //TODO: unconfirmed + + markup.append("\n"); + markup.append("\n"); + markup.append(" \n"); + markup.append(" \n"); + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/super-famicom-usart.hpp b/ananke/nall/emulation/super-famicom-usart.hpp new file mode 100644 index 00000000..68dea605 --- /dev/null +++ b/ananke/nall/emulation/super-famicom-usart.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_EMULATION_SUPER_FAMICOM_USART_HPP +#define NALL_EMULATION_SUPER_FAMICOM_USART_HPP + +#include +#include +#include +#include + +#include +#include +#include + +#define usartproc dllexport + +static nall::function usart_quit; +static nall::function usart_usleep; +static nall::function usart_readable; +static nall::function usart_read; +static nall::function usart_writable; +static nall::function usart_write; + +extern "C" usartproc void usart_init( + nall::function quit, + nall::function usleep, + nall::function readable, + nall::function read, + nall::function writable, + nall::function write +) { + usart_quit = quit; + usart_usleep = usleep; + usart_readable = readable; + usart_read = read; + usart_writable = writable; + usart_write = write; +} + +extern "C" usartproc void usart_main(int, char**); + +// + +static nall::serial usart; +static bool usart_is_virtual = true; +static bool usart_sigint = false; + +static bool usart_virtual() { + return usart_is_virtual; +} + +// + +static bool usarthw_quit() { + return usart_sigint; +} + +static void usarthw_usleep(unsigned milliseconds) { + usleep(milliseconds); +} + +static bool usarthw_readable() { + return usart.readable(); +} + +static uint8_t usarthw_read() { + while(true) { + uint8_t buffer[1]; + signed length = usart.read((uint8_t*)&buffer, 1); + if(length > 0) return buffer[0]; + } +} + +static bool usarthw_writable() { + return usart.writable(); +} + +static void usarthw_write(uint8_t data) { + uint8_t buffer[1] = { data }; + usart.write((uint8_t*)&buffer, 1); +} + +static void sigint(int) { + signal(SIGINT, SIG_DFL); + usart_sigint = true; +} + +int main(int argc, char **argv) { + setpriority(PRIO_PROCESS, 0, -20); //requires superuser privileges; otherwise priority = +0 + signal(SIGINT, sigint); + + if(usart.open("/dev/ttyACM0", 57600, true) == false) { + printf("error: unable to open USART hardware device\n"); + return 0; + } + + usart_is_virtual = false; + usart_init(usarthw_quit, usarthw_usleep, usarthw_readable, usarthw_read, usarthw_writable, usarthw_write); + usart_main(argc, argv); + usart.close(); + + return 0; +} + +#endif diff --git a/ananke/nall/emulation/super-famicom.hpp b/ananke/nall/emulation/super-famicom.hpp new file mode 100644 index 00000000..3115d7ea --- /dev/null +++ b/ananke/nall/emulation/super-famicom.hpp @@ -0,0 +1,803 @@ +#ifndef NALL_EMULATION_SUPER_FAMICOM_HPP +#define NALL_EMULATION_SUPER_FAMICOM_HPP + +#include +#include + +namespace nall { + +struct SuperFamicomCartridge { + string markup; + inline SuperFamicomCartridge(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); + + 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; +}; + +SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size) { + //skip copier header + if((size & 0x7fff) == 512) data += 512, size -= 512; + + markup = ""; + if(size < 0x8000) return; + + read_header(data, size); + + markup = ""; + if(type == TypeGameBoy) return; + if(type == TypeBsx) return; + if(type == TypeSufamiTurbo) return; + + markup.append("\n"); + + const char *range = (rom_size > 0x200000) || (ram_size > 32 * 1024) ? "0000-7fff" : "0000-ffff"; + markup.append("\n"); + + if(type == TypeSuperGameBoy1Bios || type == TypeSuperGameBoy2Bios) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(has_cx4) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(has_spc7110) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(has_sdd1) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(mapper == LoROM) { + markup.append( + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + } + + else if(mapper == HiROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + } + + else if(mapper == ExLoROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + } + + else if(mapper == ExHiROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + } + + else if(mapper == SuperFXROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + markup.append( + " \n" + ); + } + + else if(mapper == SA1ROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + markup.append( + " \n" + ); + } + + else if(mapper == BSCLoROM) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(mapper == BSCHiROM) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(mapper == BSXROM) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(mapper == STROM) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_spc7110rtc) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_srtc) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_obc1) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_dsp1) { + //91e87d11e1c30d172556bed2211cce2efa94ba595f58c5d264809ef4d363a97b dsp1.rom + markup.append( + " \n" + " \n" + ); + if(dsp1_mapper == DSP1LoROM1MB) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + if(dsp1_mapper == DSP1LoROM2MB) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + if(dsp1_mapper == DSP1HiROM) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + markup.append( + " \n" + ); + } + + if(has_dsp2) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_dsp3) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_dsp4) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_st010) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_st011) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_st018) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + markup.append("\n"); + markup.transform("'", "\""); +} + +void SuperFamicomCartridge::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 SuperFamicomCartridge::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 SuperFamicomCartridge::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; +} + +} + +#endif diff --git a/ananke/nall/endian.hpp b/ananke/nall/endian.hpp new file mode 100644 index 00000000..1f834b5b --- /dev/null +++ b/ananke/nall/endian.hpp @@ -0,0 +1,42 @@ +#ifndef NALL_ENDIAN_HPP +#define NALL_ENDIAN_HPP + +#include + +#if defined(ENDIAN_LSB) + //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 +#elif defined(ENDIAN_MSB) + //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 +#else + #error "Unknown endian. Please specify in nall/intrinsics.hpp" +#endif + +#endif diff --git a/ananke/nall/file.hpp b/ananke/nall/file.hpp new file mode 100644 index 00000000..e9d9a2ff --- /dev/null +++ b/ananke/nall/file.hpp @@ -0,0 +1,335 @@ +#ifndef NALL_FILE_HPP +#define NALL_FILE_HPP + +#include +#include +#include +#include +#include +#include + +namespace nall { + inline FILE* fopen_utf8(const string &utf8_filename, const char *mode) { + #if !defined(_WIN32) + return fopen(utf8_filename, mode); + #else + return _wfopen(utf16_t(utf8_filename), utf16_t(mode)); + #endif + } + + struct file { + enum class mode : unsigned { read, write, modify, append, readwrite = modify, writeread = append }; + enum class index : unsigned { absolute, relative }; + enum class time : unsigned { create, modify, access }; + + static bool copy(const string &sourcename, const string &targetname) { + file rd, wr; + if(rd.open(sourcename, mode::read) == false) return false; + if(wr.open(targetname, mode::write) == false) return false; + for(unsigned n = 0; n < rd.size(); n++) wr.write(rd.read()); + return true; + } + + static bool move(const string &sourcename, const string &targetname) { + #if !defined(_WIN32) + return rename(sourcename, targetname) == 0; + #else + return _wrename(utf16_t(sourcename), utf16_t(targetname)) == 0; + #endif + } + + static bool remove(const string &filename) { + return unlink(filename) == 0; + } + + static bool truncate(const string &filename, unsigned size) { + #if !defined(_WIN32) + return truncate(filename, size) == 0; + #else + bool result = false; + FILE *fp = fopen(filename, "rb+"); + if(fp) { + result = _chsize(fileno(fp), size) == 0; + fclose(fp); + } + return result; + #endif + } + + static vector read(const string &filename) { + vector memory; + file fp; + if(fp.open(filename, mode::read)) { + memory.resize(fp.size()); + fp.read(memory.data(), memory.size()); + } + return memory; + } + + static bool read(const string &filename, uint8_t *data, unsigned size) { + file fp; + if(fp.open(filename, mode::read) == false) return false; + fp.read(data, size); + fp.close(); + return true; + } + + static bool write(const string &filename, const vector &buffer) { + file fp; + if(fp.open(filename, mode::write) == false) return false; + fp.write(buffer.data(), buffer.size()); + fp.close(); + return true; + } + + static bool write(const string &filename, const uint8_t *data, unsigned size) { + file fp; + if(fp.open(filename, mode::write) == false) return false; + fp.write(data, size); + fp.close(); + return true; + } + + static string sha256(const string &filename) { + auto buffer = read(filename); + return nall::sha256(buffer.data(), buffer.size()); + } + + 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; + } + + unsigned offset() const { + if(!fp) return 0; //file not open + return file_offset; + } + + unsigned size() const { + if(!fp) return 0; //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 string &filename) { + #if !defined(_WIN32) + struct stat64 data; + return stat64(filename, &data) == 0; + #else + struct __stat64 data; + return _wstat64(utf16_t(filename), &data) == 0; + #endif + } + + static uintmax_t size(const string &filename) { + #if !defined(_WIN32) + struct stat64 data; + stat64(filename, &data); + #else + struct __stat64 data; + _wstat64(utf16_t(filename), &data); + #endif + return S_ISREG(data.st_mode) ? data.st_size : 0u; + } + + static time_t timestamp(const string &filename, file::time mode = file::time::create) { + #if !defined(_WIN32) + struct stat64 data; + stat64(filename, &data); + #else + struct __stat64 data; + _wstat64(utf16_t(filename), &data); + #endif + switch(mode) { default: + case file::time::create: return data.st_ctime; + case file::time::modify: return data.st_mtime; + case file::time::access: return data.st_atime; + } + } + + bool open() const { + return fp; + } + + bool open(const string &filename, mode mode_) { + if(fp) return false; + + switch(file_mode = mode_) { + #if !defined(_WIN32) + case mode::read: fp = fopen(filename, "rb" ); break; + case mode::write: fp = fopen(filename, "wb+"); break; //need read permission for buffering + case mode::readwrite: fp = fopen(filename, "rb+"); break; + case mode::writeread: fp = fopen(filename, "wb+"); break; + #else + case mode::read: fp = _wfopen(utf16_t(filename), L"rb" ); break; + case mode::write: fp = _wfopen(utf16_t(filename), L"wb+"); break; + case mode::readwrite: fp = _wfopen(utf16_t(filename), L"rb+"); break; + case mode::writeread: fp = _wfopen(utf16_t(filename), 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; //invalidate buffer + 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/ananke/nall/filemap.hpp b/ananke/nall/filemap.hpp new file mode 100644 index 00000000..f57d933c --- /dev/null +++ b/ananke/nall/filemap.hpp @@ -0,0 +1,213 @@ +#ifndef NALL_FILEMAP_HPP +#define NALL_FILEMAP_HPP + +#include +#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 open() const { return p_open(); } + 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_open() const { + return p_handle; + } + + bool p_open(const char *filename, mode mode_) { + if(file::exists(filename) && file::size(filename) == 0) { + p_handle = 0; + p_size = 0; + return true; + } + + 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_open() const { + return p_handle; + } + + bool p_open(const char *filename, mode mode_) { + if(file::exists(filename) && file::size(filename) == 0) { + p_handle = 0; + p_size = 0; + return true; + } + + 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/ananke/nall/function.hpp b/ananke/nall/function.hpp new file mode 100644 index 00000000..ca574b8c --- /dev/null +++ b/ananke/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 { + mutable 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 = nullptr; } } + + function& operator=(const function &source) { + if(this != &source) { + if(callback) { delete callback; callback = nullptr; } + if(source.callback) callback = source.callback->copy(); + } + return *this; + } + + function(const function &source) : callback(nullptr) { operator=(source); } + function() : callback(nullptr) {} + function(void *function) : callback(nullptr) { 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/ananke/nall/gzip.hpp b/ananke/nall/gzip.hpp new file mode 100644 index 00000000..a72a3faf --- /dev/null +++ b/ananke/nall/gzip.hpp @@ -0,0 +1,85 @@ +#ifndef NALL_GZIP_HPP +#define NALL_GZIP_HPP + +#include +#include + +namespace nall { + +struct gzip { + string filename; + uint8_t *data; + unsigned size; + + inline bool decompress(const string &filename); + inline bool decompress(const uint8_t *data, unsigned size); + + inline gzip(); + inline ~gzip(); +}; + +bool gzip::decompress(const string &filename) { + if(auto memory = file::read(filename)) { + return decompress(memory.data(), memory.size()); + } + return false; +} + +bool gzip::decompress(const uint8_t *data, unsigned size) { + if(size < 18) return false; + if(data[0] != 0x1f) return false; + if(data[1] != 0x8b) return false; + unsigned cm = data[2]; + unsigned flg = data[3]; + unsigned mtime = data[4]; + mtime |= data[5] << 8; + mtime |= data[6] << 16; + mtime |= data[7] << 24; + unsigned xfl = data[8]; + unsigned os = data[9]; + unsigned p = 10; + unsigned isize = data[size - 4]; + isize |= data[size - 3] << 8; + isize |= data[size - 2] << 16; + isize |= data[size - 1] << 24; + filename = ""; + + if(flg & 0x04) { //FEXTRA + unsigned xlen = data[p + 0]; + xlen |= data[p + 1] << 8; + p += 2 + xlen; + } + + if(flg & 0x08) { //FNAME + char buffer[PATH_MAX]; + for(unsigned n = 0; n < PATH_MAX; n++, p++) { + buffer[n] = data[p]; + if(data[p] == 0) break; + } + if(data[p++]) return false; + filename = buffer; + } + + if(flg & 0x10) { //FCOMMENT + while(data[p++]); + } + + if(flg & 0x02) { //FHCRC + p += 2; + } + + this->size = isize; + this->data = new uint8_t[this->size]; + return inflate(this->data, this->size, data + p, size - p - 8); +} + +gzip::gzip() : data(nullptr) { +} + +gzip::~gzip() { + if(data) delete[] data; +} + +} + +#endif diff --git a/ananke/nall/http.hpp b/ananke/nall/http.hpp new file mode 100644 index 00000000..48aeb097 --- /dev/null +++ b/ananke/nall/http.hpp @@ -0,0 +1,176 @@ +#ifndef NALL_HTTP_HPP +#define NALL_HTTP_HPP + +#if !defined(_WIN32) + #include + #include + #include + #include +#else + #include + #include + #include +#endif + +#include +#include + +namespace nall { + +struct http { + string hostname; + addrinfo *serverinfo; + int serversocket; + string header; + + inline void download(const string &path, uint8_t *&data, unsigned &size) { + data = 0; + size = 0; + + send({ + "GET ", path, " HTTP/1.1\r\n" + "Host: ", hostname, "\r\n" + "Connection: close\r\n" + "\r\n" + }); + + header = downloadHeader(); + downloadContent(data, size); + } + + inline bool connect(string host, unsigned port) { + hostname = host; + + addrinfo hints; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + int status = getaddrinfo(hostname, string(port), &hints, &serverinfo); + if(status != 0) return false; + + serversocket = socket(serverinfo->ai_family, serverinfo->ai_socktype, serverinfo->ai_protocol); + if(serversocket == -1) return false; + + int result = ::connect(serversocket, serverinfo->ai_addr, serverinfo->ai_addrlen); + if(result == -1) return false; + + return true; + } + + inline bool send(const string &data) { + return send((const uint8_t*)(const char*)data, data.length()); + } + + inline bool send(const uint8_t *data, unsigned size) { + while(size) { + int length = ::send(serversocket, (const char*)data, size, 0); + if(length == -1) return false; + data += length; + size -= length; + } + return true; + } + + inline string downloadHeader() { + string output; + do { + char buffer[2]; + int length = recv(serversocket, buffer, 1, 0); + if(length <= 0) return output; + buffer[1] = 0; + output.append(buffer); + } while(output.endswith("\r\n\r\n") == false); + return output; + } + + inline string downloadChunkLength() { + string output; + do { + char buffer[2]; + int length = recv(serversocket, buffer, 1, 0); + if(length <= 0) return output; + buffer[1] = 0; + output.append(buffer); + } while(output.endswith("\r\n") == false); + return output; + } + + inline void downloadContent(uint8_t *&data, unsigned &size) { + unsigned capacity = 0; + + if(header.iposition("\r\nTransfer-Encoding: chunked\r\n")) { + while(true) { + unsigned length = hex(downloadChunkLength()); + if(length == 0) break; + capacity += length; + data = (uint8_t*)realloc(data, capacity); + + char buffer[length]; + while(length) { + int packetlength = recv(serversocket, buffer, length, 0); + if(packetlength <= 0) break; + memcpy(data + size, buffer, packetlength); + size += packetlength; + length -= packetlength; + } + } + } else if(auto position = header.iposition("\r\nContent-Length: ")) { + unsigned length = decimal((const char*)header + position() + 18); + while(length) { + char buffer[256]; + int packetlength = recv(serversocket, buffer, min(256, length), 0); + if(packetlength <= 0) break; + capacity += packetlength; + data = (uint8_t*)realloc(data, capacity); + memcpy(data + size, buffer, packetlength); + size += packetlength; + length -= packetlength; + } + } else { + while(true) { + char buffer[256]; + int packetlength = recv(serversocket, buffer, 256, 0); + if(packetlength <= 0) break; + capacity += packetlength; + data = (uint8_t*)realloc(data, capacity); + memcpy(data + size, buffer, packetlength); + size += packetlength; + } + } + + data = (uint8_t*)realloc(data, capacity + 1); + data[capacity] = 0; + } + + inline void disconnect() { + close(serversocket); + freeaddrinfo(serverinfo); + serverinfo = 0; + serversocket = -1; + } + + #ifdef _WIN32 + inline int close(int sock) { + return closesocket(sock); + } + + inline http() { + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sock == INVALID_SOCKET && WSAGetLastError() == WSANOTINITIALISED) { + WSADATA wsaData; + if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + WSACleanup(); + return; + } + } else { + close(sock); + } + } + #endif +}; + +} + +#endif diff --git a/ananke/nall/image.hpp b/ananke/nall/image.hpp new file mode 100644 index 00000000..e334b6e0 --- /dev/null +++ b/ananke/nall/image.hpp @@ -0,0 +1,539 @@ +#ifndef NALL_IMAGE_HPP +#define NALL_IMAGE_HPP + +#include +#include +#include +#include +#include +#include + +namespace nall { + +struct image { + uint8_t *data; + unsigned width; + unsigned height; + unsigned pitch; + + bool endian; //0 = little, 1 = big + unsigned depth; + unsigned stride; + + struct Channel { + uint64_t mask; + unsigned depth; + unsigned shift; + + inline bool operator==(const Channel &source) { + return mask == source.mask && depth == source.depth && shift == source.shift; + } + + inline bool operator!=(const Channel &source) { + return !operator==(source); + } + } alpha, red, green, blue; + + typedef double (*interpolation)(double, double, double, double, double); + static inline unsigned bitDepth(uint64_t color); + static inline unsigned bitShift(uint64_t color); + static inline uint64_t normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth); + + inline bool operator==(const image &source); + inline bool operator!=(const image &source); + + inline image& operator=(const image &source); + inline image& operator=(image &&source); + inline image(const image &source); + inline image(image &&source); + inline image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline image(const string &filename); + inline image(const uint8_t *data, unsigned size); + inline image(); + inline ~image(); + + inline uint64_t read(const uint8_t *data) const; + inline void write(uint8_t *data, uint64_t value) const; + + inline void free(); + inline bool empty() const; + inline void allocate(unsigned width, unsigned height); + inline void clear(uint64_t color); + inline bool load(const string &filename); +//inline bool loadBMP(const uint8_t *data, unsigned size); + inline bool loadPNG(const uint8_t *data, unsigned size); + inline void scale(unsigned width, unsigned height, interpolation op); + inline void transform(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline void alphaBlend(uint64_t alphaColor); + +protected: + inline uint64_t interpolate(double mu, const uint64_t *s, interpolation op); + inline void scaleX(unsigned width, interpolation op); + inline void scaleY(unsigned height, interpolation op); + inline bool loadBMP(const string &filename); + inline bool loadPNG(const string &filename); +}; + +//static + +unsigned image::bitDepth(uint64_t color) { + unsigned depth = 0; + if(color) while((color & 1) == 0) color >>= 1; + while((color & 1) == 1) { color >>= 1; depth++; } + return depth; +} + +unsigned image::bitShift(uint64_t color) { + unsigned shift = 0; + if(color) while((color & 1) == 0) { color >>= 1; shift++; } + return shift; +} + +uint64_t image::normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth) { + while(sourceDepth < targetDepth) { + color = (color << sourceDepth) | color; + sourceDepth += sourceDepth; + } + if(targetDepth < sourceDepth) color >>= (sourceDepth - targetDepth); + return color; +} + +//public + +bool image::operator==(const image &source) { + if(width != source.width) return false; + if(height != source.height) return false; + if(pitch != source.pitch) return false; + + if(endian != source.endian) return false; + if(stride != source.stride) return false; + + if(alpha != source.alpha) return false; + if(red != source.red) return false; + if(green != source.green) return false; + if(blue != source.blue) return false; + + return memcmp(data, source.data, width * height * stride) == 0; +} + +bool image::operator!=(const image &source) { + return !operator==(source); +} + +image& image::operator=(const image &source) { + free(); + + width = source.width; + height = source.height; + pitch = source.pitch; + + endian = source.endian; + stride = source.stride; + + alpha = source.alpha; + red = source.red; + green = source.green; + blue = source.blue; + + data = new uint8_t[width * height * stride]; + memcpy(data, source.data, width * height * stride); + return *this; +} + +image& image::operator=(image &&source) { + free(); + + width = source.width; + height = source.height; + pitch = source.pitch; + + endian = source.endian; + stride = source.stride; + + alpha = source.alpha; + red = source.red; + green = source.green; + blue = source.blue; + + data = source.data; + source.data = nullptr; + return *this; +} + +image::image(const image &source) : data(nullptr) { + operator=(source); +} + +image::image(image &&source) : data(nullptr) { + operator=(std::forward(source)); +} + +image::image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = endian; + this->depth = depth; + this->stride = (depth / 8) + ((depth & 7) > 0); + + alpha.mask = alphaMask, red.mask = redMask, green.mask = greenMask, blue.mask = blueMask; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); +} + +image::image(const string &filename) : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = 0; + this->depth = 32; + this->stride = 4; + + alpha.mask = 255u << 24, red.mask = 255u << 16, green.mask = 255u << 8, blue.mask = 255u << 0; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); + + load(filename); +} + +image::image(const uint8_t *data, unsigned size) : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = 0; + this->depth = 32; + this->stride = 4; + + alpha.mask = 255u << 24, red.mask = 255u << 16, green.mask = 255u << 8, blue.mask = 255u << 0; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); + + loadPNG(data, size); +} + +image::image() : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = 0; + this->depth = 32; + this->stride = 4; + + alpha.mask = 255u << 24, red.mask = 255u << 16, green.mask = 255u << 8, blue.mask = 255u << 0; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); +} + +image::~image() { + free(); +} + +uint64_t image::read(const uint8_t *data) const { + uint64_t result = 0; + if(endian == 0) { + for(signed n = stride - 1; n >= 0; n--) result = (result << 8) | data[n]; + } else { + for(signed n = 0; n < stride; n++) result = (result << 8) | data[n]; + } + return result; +} + +void image::write(uint8_t *data, uint64_t value) const { + if(endian == 0) { + for(signed n = 0; n < stride; n++) { data[n] = value; value >>= 8; } + } else { + for(signed n = stride - 1; n >= 0; n--) { data[n] = value; value >>= 8; } + } +} + +void image::free() { + if(data) delete[] data; + data = nullptr; +} + +bool image::empty() const { + if(data == nullptr) return true; + if(width == 0 || height == 0) return true; + return false; +} + +void image::allocate(unsigned width, unsigned height) { + if(data != nullptr && this->width == width && this->height == height) return; + free(); + data = new uint8_t[width * height * stride](); + pitch = width * stride; + this->width = width; + this->height = height; +} + +void image::clear(uint64_t color) { + uint8_t *dp = data; + for(unsigned n = 0; n < width * height; n++) { + write(dp, color); + dp += stride; + } +} + +bool image::load(const string &filename) { + if(loadBMP(filename) == true) return true; + if(loadPNG(filename) == true) return true; + return false; +} + +void image::scale(unsigned outputWidth, unsigned outputHeight, interpolation op) { + if(width != outputWidth) scaleX(outputWidth, op); + if(height != outputHeight) scaleY(outputHeight, op); +} + +void image::transform(bool outputEndian, unsigned outputDepth, uint64_t outputAlphaMask, uint64_t outputRedMask, uint64_t outputGreenMask, uint64_t outputBlueMask) { + image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask); + output.allocate(width, height); + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = output.data + output.pitch * y; + uint8_t *sp = data + pitch * y; + for(unsigned x = 0; x < width; x++) { + uint64_t color = read(sp); + sp += stride; + + uint64_t a = (color & alpha.mask) >> alpha.shift; + uint64_t r = (color & red.mask) >> red.shift; + uint64_t g = (color & green.mask) >> green.shift; + uint64_t b = (color & blue.mask) >> blue.shift; + + a = normalize(a, alpha.depth, output.alpha.depth); + r = normalize(r, red.depth, output.red.depth); + g = normalize(g, green.depth, output.green.depth); + b = normalize(b, blue.depth, output.blue.depth); + + output.write(dp, (a << output.alpha.shift) | (r << output.red.shift) | (g << output.green.shift) | (b << output.blue.shift)); + dp += output.stride; + } + } + + operator=(std::move(output)); +} + +void image::alphaBlend(uint64_t alphaColor) { + uint64_t alphaR = (alphaColor & red.mask) >> red.shift; + uint64_t alphaG = (alphaColor & green.mask) >> green.shift; + uint64_t alphaB = (alphaColor & blue.mask) >> blue.shift; + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = data + pitch * y; + for(unsigned x = 0; x < width; x++) { + uint64_t color = read(dp); + + uint64_t colorA = (color & alpha.mask) >> alpha.shift; + uint64_t colorR = (color & red.mask) >> red.shift; + uint64_t colorG = (color & green.mask) >> green.shift; + uint64_t colorB = (color & blue.mask) >> blue.shift; + double alphaScale = (double)colorA / (double)((1 << alpha.depth) - 1); + + colorA = (1 << alpha.depth) - 1; + colorR = (colorR * alphaScale) + (alphaR * (1.0 - alphaScale)); + colorG = (colorG * alphaScale) + (alphaG * (1.0 - alphaScale)); + colorB = (colorB * alphaScale) + (alphaB * (1.0 - alphaScale)); + + write(dp, (colorA << alpha.shift) | (colorR << red.shift) | (colorG << green.shift) | (colorB << blue.shift)); + dp += stride; + } + } +} + +//protected + +uint64_t image::interpolate(double mu, const uint64_t *s, double (*op)(double, double, double, double, double)) { + uint64_t aa = (s[0] & alpha.mask) >> alpha.shift, ar = (s[0] & red.mask) >> red.shift, + ag = (s[0] & green.mask) >> green.shift, ab = (s[0] & blue.mask) >> blue.shift; + uint64_t ba = (s[1] & alpha.mask) >> alpha.shift, br = (s[1] & red.mask) >> red.shift, + bg = (s[1] & green.mask) >> green.shift, bb = (s[1] & blue.mask) >> blue.shift; + uint64_t ca = (s[2] & alpha.mask) >> alpha.shift, cr = (s[2] & red.mask) >> red.shift, + cg = (s[2] & green.mask) >> green.shift, cb = (s[2] & blue.mask) >> blue.shift; + uint64_t da = (s[3] & alpha.mask) >> alpha.shift, dr = (s[3] & red.mask) >> red.shift, + dg = (s[3] & green.mask) >> green.shift, db = (s[3] & blue.mask) >> blue.shift; + + int64_t A = op(mu, aa, ba, ca, da); + int64_t R = op(mu, ar, br, cr, dr); + int64_t G = op(mu, ag, bg, cg, dg); + int64_t B = op(mu, ab, bb, cb, db); + + A = max(0, min(A, (1 << alpha.depth) - 1)); + R = max(0, min(R, (1 << red.depth) - 1)); + G = max(0, min(G, (1 << green.depth) - 1)); + B = max(0, min(B, (1 << blue.depth) - 1)); + + return (A << alpha.shift) | (R << red.shift) | (G << green.shift) | (B << blue.shift); +} + +void image::scaleX(unsigned outputWidth, interpolation op) { + uint8_t *outputData = new uint8_t[outputWidth * height * stride]; + unsigned outputPitch = outputWidth * stride; + double step = (double)width / (double)outputWidth; + const uint8_t *terminal = data + pitch * height; + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = outputData + outputPitch * y; + uint8_t *sp = data + pitch * y; + + double fraction = 0.0; + uint64_t s[4] = { sp < terminal ? read(sp) : 0 }; //B,C (0,1) = center of kernel { 0, 0, 1, 2 } + s[1] = s[0]; + s[2] = sp + stride < terminal ? read(sp += stride) : s[1]; + s[3] = sp + stride < terminal ? read(sp += stride) : s[2]; + + for(unsigned x = 0; x < width; x++) { + while(fraction <= 1.0) { + if(dp >= outputData + outputPitch * height) break; + write(dp, interpolate(fraction, (const uint64_t*)&s, op)); + dp += stride; + fraction += step; + } + + s[0] = s[1]; s[1] = s[2]; s[2] = s[3]; + if(sp + stride < terminal) s[3] = read(sp += stride); + fraction -= 1.0; + } + } + + free(); + data = outputData; + width = outputWidth; + pitch = width * stride; +} + +void image::scaleY(unsigned outputHeight, interpolation op) { + uint8_t *outputData = new uint8_t[width * outputHeight * stride]; + double step = (double)height / (double)outputHeight; + const uint8_t *terminal = data + pitch * height; + + #pragma omp parallel for + for(unsigned x = 0; x < width; x++) { + uint8_t *dp = outputData + stride * x; + uint8_t *sp = data + stride * x; + + double fraction = 0.0; + uint64_t s[4] = { sp < terminal ? read(sp) : 0 }; + s[1] = s[0]; + s[2] = sp + pitch < terminal ? read(sp += pitch) : s[1]; + s[3] = sp + pitch < terminal ? read(sp += pitch) : s[2]; + + for(unsigned y = 0; y < height; y++) { + while(fraction <= 1.0) { + if(dp >= outputData + pitch * outputHeight) break; + write(dp, interpolate(fraction, (const uint64_t*)&s, op)); + dp += pitch; + fraction += step; + } + + s[0] = s[1]; s[1] = s[2]; s[2] = s[3]; + if(sp + pitch < terminal) s[3] = read(sp += pitch); + fraction -= 1.0; + } + } + + free(); + data = outputData; + height = outputHeight; +} + +bool image::loadBMP(const string &filename) { + uint32_t *outputData; + unsigned outputWidth, outputHeight; + if(bmp::read(filename, outputData, outputWidth, outputHeight) == false) return false; + + allocate(outputWidth, outputHeight); + const uint32_t *sp = outputData; + uint8_t *dp = data; + + for(unsigned y = 0; y < outputHeight; y++) { + for(unsigned x = 0; x < outputWidth; x++) { + uint32_t color = *sp++; + uint64_t a = normalize((uint8_t)(color >> 24), 8, alpha.depth); + uint64_t r = normalize((uint8_t)(color >> 16), 8, red.depth); + uint64_t g = normalize((uint8_t)(color >> 8), 8, green.depth); + uint64_t b = normalize((uint8_t)(color >> 0), 8, blue.depth); + write(dp, (a << alpha.shift) | (r << red.shift) | (g << green.shift) | (b << blue.shift)); + dp += stride; + } + } + + delete[] outputData; + return true; +} + +bool image::loadPNG(const uint8_t *pngData, unsigned pngSize) { + png source; + if(source.decode(pngData, pngSize) == false) return false; + + allocate(source.info.width, source.info.height); + const uint8_t *sp = source.data; + uint8_t *dp = data; + + auto decode = [&]() -> uint64_t { + uint64_t p, r, g, b, a; + + switch(source.info.colorType) { + case 0: //L + r = g = b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 2: //R,G,B + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 3: //P + p = source.readbits(sp); + r = source.info.palette[p][0]; + g = source.info.palette[p][1]; + b = source.info.palette[p][2]; + a = (1 << source.info.bitDepth) - 1; + break; + case 4: //L,A + r = g = b = source.readbits(sp); + a = source.readbits(sp); + break; + case 6: //R,G,B,A + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = source.readbits(sp); + break; + } + + a = normalize(a, source.info.bitDepth, alpha.depth); + r = normalize(r, source.info.bitDepth, red.depth); + g = normalize(g, source.info.bitDepth, green.depth); + b = normalize(b, source.info.bitDepth, blue.depth); + + return (a << alpha.shift) | (r << red.shift) | (g << green.shift) | (b << blue.shift); + }; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + write(dp, decode()); + dp += stride; + } + } + + return true; +} + +bool image::loadPNG(const string &filename) { + filemap map; + if(map.open(filename, filemap::mode::read) == false) return false; + return loadPNG(map.data(), map.size()); +} + +} + +#endif diff --git a/ananke/nall/inflate.hpp b/ananke/nall/inflate.hpp new file mode 100644 index 00000000..cbbf6d29 --- /dev/null +++ b/ananke/nall/inflate.hpp @@ -0,0 +1,358 @@ +#ifndef NALL_INFLATE_HPP +#define NALL_INFLATE_HPP + +#include + +namespace nall { + +namespace puff { + inline int puff( + unsigned char *dest, unsigned long *destlen, + unsigned char *source, unsigned long *sourcelen + ); +} + +inline bool inflate( + uint8_t *target, unsigned targetLength, + const uint8_t *source, unsigned sourceLength +) { + unsigned long tl = targetLength, sl = sourceLength; + int result = puff::puff((unsigned char*)target, &tl, (unsigned char*)source, &sl); + return result == 0; +} + +namespace puff { + +//zlib/contrib/puff.c +//version 2.1* +//author: Mark Adler +//license: zlib +//ported by: byuu + +//* I have corrected a bug in fixed(), where it was accessing uninitialized +// memory: calling construct() with lencode prior to initializing lencode.count + +enum { + MAXBITS = 15, + MAXLCODES = 286, + MAXDCODES = 30, + FIXLCODES = 288, + MAXCODES = MAXLCODES + MAXDCODES, +}; + +struct state { + unsigned char *out; + unsigned long outlen; + unsigned long outcnt; + + unsigned char *in; + unsigned long inlen; + unsigned long incnt; + int bitbuf; + int bitcnt; + + jmp_buf env; +}; + +struct huffman { + short *count; + short *symbol; +}; + +inline int bits(state *s, int need) { + long val; + + val = s->bitbuf; + while(s->bitcnt < need) { + if(s->incnt == s->inlen) longjmp(s->env, 1); + val |= (long)(s->in[s->incnt++]) << s->bitcnt; + s->bitcnt += 8; + } + + s->bitbuf = (int)(val >> need); + s->bitcnt -= need; + + return (int)(val & ((1L << need) - 1)); +} + +inline int stored(state *s) { + unsigned len; + + s->bitbuf = 0; + s->bitcnt = 0; + + if(s->incnt + 4 > s->inlen) return 2; + len = s->in[s->incnt++]; + len |= s->in[s->incnt++] << 8; + if(s->in[s->incnt++] != (~len & 0xff) || + s->in[s->incnt++] != ((~len >> 8) & 0xff) + ) return 2; + + if(s->incnt + len > s->inlen) return 2; + if(s->out != 0) { + if(s->outcnt + len > s->outlen) return 1; + while(len--) s->out[s->outcnt++] = s->in[s->incnt++]; + } else { + s->outcnt += len; + s->incnt += len; + } + + return 0; +} + +inline int decode(state *s, huffman *h) { + int len, code, first, count, index, bitbuf, left; + short *next; + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while(true) { + while(left--) { + code |= bitbuf & 1; + bitbuf >>= 1; + count = *next++; + if(code - count < first) { + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS + 1) - len; + if(left == 0) break; + if(s->incnt == s->inlen) longjmp(s->env, 1); + bitbuf = s->in[s->incnt++]; + if(left > 8) left = 8; + } + + return -10; +} + +inline int construct(huffman *h, short *length, int n) { + int symbol, len, left; + short offs[MAXBITS + 1]; + + for(len = 0; len <= MAXBITS; len++) h->count[len] = 0; + for(symbol = 0; symbol < n; symbol++) h->count[length[symbol]]++; + if(h->count[0] == n) return 0; + + left = 1; + for(len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= h->count[len]; + if(left < 0) return left; + } + + offs[1] = 0; + for(len = 1; len < MAXBITS; len++) offs[len + 1] = offs[len] + h->count[len]; + + for(symbol = 0; symbol < n; symbol++) { + if(length[symbol] != 0) h->symbol[offs[length[symbol]]++] = symbol; + } + + return left; +} + +inline int codes(state *s, huffman *lencode, huffman *distcode) { + int symbol, len; + unsigned dist; + static const short lens[29] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 + }; + static const short lext[29] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + }; + static const short dists[30] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + static const short dext[30] = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13 + }; + + do { + symbol = decode(s, lencode); + if(symbol < 0) return symbol; + if(symbol < 256) { + if(s->out != 0) { + if(s->outcnt == s->outlen) return 1; + s->out[s->outcnt] = symbol; + } + s->outcnt++; + } else if(symbol > 256) { + symbol -= 257; + if(symbol >= 29) return -10; + len = lens[symbol] + bits(s, lext[symbol]); + + symbol = decode(s, distcode); + if(symbol < 0) return symbol; + dist = dists[symbol] + bits(s, dext[symbol]); + #ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR + if(dist > s->outcnt) return -11; + #endif + + if(s->out != 0) { + if(s->outcnt + len > s->outlen) return 1; + while(len--) { + s->out[s->outcnt] = + #ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR + dist > s->outcnt ? 0 : + #endif + s->out[s->outcnt - dist]; + s->outcnt++; + } + } else { + s->outcnt += len; + } + } + } while(symbol != 256); + + return 0; +} + +inline int fixed(state *s) { + static int virgin = 1; + static short lencnt[MAXBITS + 1], lensym[FIXLCODES]; + static short distcnt[MAXBITS + 1], distsym[MAXDCODES]; + static huffman lencode, distcode; + + if(virgin) { + int symbol = 0; + short lengths[FIXLCODES]; + + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + for(; symbol < 144; symbol++) lengths[symbol] = 8; + for(; symbol < 256; symbol++) lengths[symbol] = 9; + for(; symbol < 280; symbol++) lengths[symbol] = 7; + for(; symbol < FIXLCODES; symbol++) lengths[symbol] = 8; + construct(&lencode, lengths, FIXLCODES); + + for(symbol = 0; symbol < MAXDCODES; symbol++) lengths[symbol] = 5; + construct(&distcode, lengths, MAXDCODES); + + virgin = 0; + } + + return codes(s, &lencode, &distcode); +} + +inline int dynamic(state *s) { + int nlen, ndist, ncode, index, err; + short lengths[MAXCODES]; + short lencnt[MAXBITS + 1], lensym[MAXLCODES]; + short distcnt[MAXBITS + 1], distsym[MAXDCODES]; + huffman lencode, distcode; + static const short order[19] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + nlen = bits(s, 5) + 257; + ndist = bits(s, 5) + 1; + ncode = bits(s, 4) + 4; + if(nlen > MAXLCODES || ndist > MAXDCODES) return -3; + + for(index = 0; index < ncode; index++) lengths[order[index]] = bits(s, 3); + for(; index < 19; index++) lengths[order[index]] = 0; + + err = construct(&lencode, lengths, 19); + if(err != 0) return -4; + + index = 0; + while(index < nlen + ndist) { + int symbol, len; + + symbol = decode(s, &lencode); + if(symbol < 16) { + lengths[index++] = symbol; + } else { + len = 0; + if(symbol == 16) { + if(index == 0) return -5; + len = lengths[index - 1]; + symbol = 3 + bits(s, 2); + } else if(symbol == 17) { + symbol = 3 + bits(s, 3); + } else { + symbol = 11 + bits(s, 7); + } + if(index + symbol > nlen + ndist) return -6; + while(symbol--) lengths[index++] = len; + } + } + + if(lengths[256] == 0) return -9; + + err = construct(&lencode, lengths, nlen); + if(err < 0 || (err > 0 && nlen - lencode.count[0] != 1)) return -7; + + err = construct(&distcode, lengths + nlen, ndist); + if(err < 0 || (err > 0 && ndist - distcode.count[0] != 1)) return -8; + + return codes(s, &lencode, &distcode); +} + +inline int puff( + unsigned char *dest, unsigned long *destlen, + unsigned char *source, unsigned long *sourcelen +) { + state s; + int last, type, err; + + s.out = dest; + s.outlen = *destlen; + s.outcnt = 0; + + s.in = source; + s.inlen = *sourcelen; + s.incnt = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + if(setjmp(s.env) != 0) { + err = 2; + } else { + do { + last = bits(&s, 1); + type = bits(&s, 2); + err = type == 0 ? stored(&s) + : type == 1 ? fixed(&s) + : type == 2 ? dynamic(&s) + : -1; + if(err != 0) break; + } while(!last); + } + + if(err <= 0) { + *destlen = s.outcnt; + *sourcelen = s.incnt; + } + + return err; +} + +} + +} + +#endif diff --git a/ananke/nall/input.hpp b/ananke/nall/input.hpp new file mode 100644 index 00000000..cd765393 --- /dev/null +++ b/ananke/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 { "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 { "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 { "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/ananke/nall/interpolation.hpp b/ananke/nall/interpolation.hpp new file mode 100644 index 00000000..afc7108b --- /dev/null +++ b/ananke/nall/interpolation.hpp @@ -0,0 +1,59 @@ +#ifndef NALL_INTERPOLATION_HPP +#define NALL_INTERPOLATION_HPP + +namespace nall { + +struct Interpolation { + static inline double Nearest(double mu, double a, double b, double c, double d) { + return (mu <= 0.5 ? b : c); + } + + static inline double Sublinear(double mu, double a, double b, double c, double d) { + mu = ((mu - 0.5) * 2.0) + 0.5; + if(mu < 0) mu = 0; + if(mu > 1) mu = 1; + return b * (1.0 - mu) + c * mu; + } + + static inline double Linear(double mu, double a, double b, double c, double d) { + return b * (1.0 - mu) + c * mu; + } + + static inline double Cosine(double mu, double a, double b, double c, double d) { + mu = (1.0 - cos(mu * 3.14159265)) / 2.0; + return b * (1.0 - mu) + c * mu; + } + + static inline double Cubic(double mu, double a, double b, double c, double d) { + double A = d - c - a + b; + double B = a - b - A; + double C = c - a; + double D = b; + return A * (mu * mu * mu) + B * (mu * mu) + C * mu + D; + } + + static inline double 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.0 + bias) * (1.0 - tension) / 2.0; + m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0; + m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0; + m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0; + + 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); + } +}; + +} + +#endif diff --git a/ananke/nall/intrinsics.hpp b/ananke/nall/intrinsics.hpp new file mode 100644 index 00000000..413ef593 --- /dev/null +++ b/ananke/nall/intrinsics.hpp @@ -0,0 +1,63 @@ +#ifndef NALL_INTRINSICS_HPP +#define NALL_INTRINSICS_HPP + +struct Intrinsics { + enum class Compiler : unsigned { GCC, VisualC, Unknown }; + enum class Platform : unsigned { X, OSX, Windows, Unknown }; + enum class Endian : unsigned { LSB, MSB, Unknown }; + + static inline Compiler compiler(); + static inline Platform platform(); + static inline Endian endian(); +}; + +/* Compiler detection */ + +#if defined(__GNUC__) + #define COMPILER_GCC + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::GCC; } +#elif defined(_MSC_VER) + #define COMPILER_VISUALC + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::VisualC; } +#else + #warning "unable to detect compiler" + #define COMPILER_UNKNOWN + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::Unknown; } +#endif + +/* Platform detection */ + +#if defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PLATFORM_X + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::X; } +#elif defined(__APPLE__) + #define PLATFORM_OSX + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::OSX; } +#elif defined(_WIN32) + #define PLATFORM_WINDOWS + #define PLATFORM_WIN + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::Windows; } +#else + #warning "unable to detect platform" + #define PLATFORM_UNKNOWN + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::Unknown; } +#endif + +/* Endian detection */ + +#if defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64) + #define ENDIAN_LSB + #define ARCH_LSB + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::LSB; } +#elif defined(__powerpc__) || defined(_M_PPC) || defined(__BIG_ENDIAN__) + #define ENDIAN_MSB + #define ARCH_MSB + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::MSB; } +#else + #warning "unable to detect endian" + #define ENDIAN_UNKNOWN + #define ARCH_UNKNOWN + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::Unknown; } +#endif + +#endif diff --git a/ananke/nall/invoke.hpp b/ananke/nall/invoke.hpp new file mode 100644 index 00000000..9dfb7d0b --- /dev/null +++ b/ananke/nall/invoke.hpp @@ -0,0 +1,52 @@ +#ifndef NALL_INVOKE_HPP +#define NALL_INVOKE_HPP + +//void invoke(const string &name, const string& args...); +//if a program is specified, it is executed with the arguments provided +//if a file is specified, the file is opened using the program associated with said file type +//if a folder is specified, the folder is opened using the associated file explorer +//if a URL is specified, the default web browser is opened and pointed at the URL requested +//path environment variable is always consulted +//execution is asynchronous (non-blocking); use system() for synchronous execution + +#include +#ifdef _WIN32 + #include +#endif + +namespace nall { + +#ifdef _WIN32 + +template +inline void invoke(const string &name, Args&&... args) { + lstring argl(std::forward(args)...); + for(auto &arg : argl) if(arg.position(" ")) arg = {"\"", arg, "\""}; + string arguments = argl.concatenate(" "); + ShellExecuteW(NULL, NULL, utf16_t(name), utf16_t(arguments), NULL, SW_SHOWNORMAL); +} + +#else + +template +inline void invoke(const string &name, Args&&... args) { + pid_t pid = fork(); + if(pid == 0) { + const char *argv[1 + sizeof...(args) + 1], **argp = argv; + lstring argl(std::forward(args)...); + *argp++ = (const char*)name; + for(auto &arg : argl) *argp++ = (const char*)arg; + *argp++ = nullptr; + + if(execvp(name, (char* const*)argv) < 0) { + execlp("xdg-open", "xdg-open", (const char*)name, nullptr); + } + exit(0); + } +} + +#endif + +} + +#endif diff --git a/ananke/nall/ips.hpp b/ananke/nall/ips.hpp new file mode 100644 index 00000000..473d74c5 --- /dev/null +++ b/ananke/nall/ips.hpp @@ -0,0 +1,100 @@ +#ifndef NALL_IPS_HPP +#define NALL_IPS_HPP + +#include +#include +#include + +namespace nall { + +struct ips { + inline bool apply(); + inline void source(const uint8_t *data, unsigned size); + inline void modify(const uint8_t *data, unsigned size); + inline ips(); + inline ~ips(); + + uint8_t *data; + unsigned size; + const uint8_t *sourceData; + unsigned sourceSize; + const uint8_t *modifyData; + unsigned modifySize; +}; + +bool ips::apply() { + if(modifySize < 8) return false; + if(modifyData[0] != 'P') return false; + if(modifyData[1] != 'A') return false; + if(modifyData[2] != 'T') return false; + if(modifyData[3] != 'C') return false; + if(modifyData[4] != 'H') return false; + + if(data) delete[] data; + data = new uint8_t[16 * 1024 * 1024 + 65536](); //maximum size of IPS patch + single-tag padding + size = sourceSize; + memcpy(data, sourceData, sourceSize); + unsigned offset = 5; + + while(true) { + unsigned address, length; + + if(offset > modifySize - 3) break; + address = modifyData[offset++] << 16; + address |= modifyData[offset++] << 8; + address |= modifyData[offset++] << 0; + + if(address == 0x454f46) { //EOF + if(offset == modifySize) return true; + if(offset == modifySize - 3) { + size = modifyData[offset++] << 16; + size |= modifyData[offset++] << 8; + size |= modifyData[offset++] << 0; + return true; + } + } + + if(offset > modifySize - 2) break; + length = modifyData[offset++] << 8; + length |= modifyData[offset++] << 0; + + if(length) { //Copy + if(offset > modifySize - length) break; + while(length--) data[address++] = modifyData[offset++]; + } else { //RLE + if(offset > modifySize - 3) break; + length = modifyData[offset++] << 8; + length |= modifyData[offset++] << 0; + if(length == 0) break; //illegal + while(length--) data[address++] = modifyData[offset]; + offset++; + } + + size = max(size, address); + } + + delete[] data; + data = nullptr; + return false; +} + +void ips::source(const uint8_t *data, unsigned size) { + sourceData = data, sourceSize = size; +} + +void ips::modify(const uint8_t *data, unsigned size) { + modifyData = data, modifySize = size; +} + +ips::ips() : data(nullptr), sourceData(nullptr), modifyData(nullptr) { +} + +ips::~ips() { + if(data) delete[] data; + if(sourceData) delete[] sourceData; + if(modifyData) delete[] modifyData; +} + +} + +#endif diff --git a/ananke/nall/lzss.hpp b/ananke/nall/lzss.hpp new file mode 100644 index 00000000..fb3e0ba6 --- /dev/null +++ b/ananke/nall/lzss.hpp @@ -0,0 +1,165 @@ +#ifndef NALL_LZSS_HPP +#define NALL_LZSS_HPP + +#include +#include +#include +#include + +namespace nall { + +//19:5 pulldown +//8:1 marker: d7-d0 +//length: { 4 - 35 }, offset: { 1 - 0x80000 } +//4-byte file size header +//little-endian encoding +struct lzss { + inline void source(const uint8_t *data, unsigned size); + inline bool source(const string &filename); + inline unsigned size() const; + inline bool compress(const string &filename); + inline bool decompress(uint8_t *targetData, unsigned targetSize); + inline bool decompress(const string &filename); + +protected: + struct Node { + unsigned offset; + Node *next; + inline Node() : offset(0), next(nullptr) {} + inline ~Node() { if(next) delete next; } + } *tree[65536]; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + +public: + inline lzss() : sourceData(nullptr), sourceSize(0) {} +}; + +void lzss::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +bool lzss::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + sourceData = sourceFile.data(); + sourceSize = sourceFile.size(); + return true; +} + +unsigned lzss::size() const { + unsigned size = 0; + if(sourceSize < 4) return size; + for(unsigned n = 0; n < 32; n += 8) size |= sourceData[n >> 3] << n; + return size; +} + +bool lzss::compress(const string &filename) { + file targetFile; + if(targetFile.open(filename, file::mode::write) == false) return false; + + for(unsigned n = 0; n < 32; n += 8) targetFile.write(sourceSize >> n); + for(unsigned n = 0; n < 65536; n++) tree[n] = 0; + + uint8_t buffer[25]; + unsigned sourceOffset = 0; + + while(sourceOffset < sourceSize) { + uint8_t mask = 0x00; + unsigned bufferOffset = 1; + + for(unsigned iteration = 0; iteration < 8; iteration++) { + if(sourceOffset >= sourceSize) break; + + uint16_t symbol = sourceData[sourceOffset + 0]; + if(sourceOffset < sourceSize - 1) symbol |= sourceData[sourceOffset + 1] << 8; + Node *node = tree[symbol]; + unsigned maxLength = 0, maxOffset = 0; + + while(node) { + if(node->offset < sourceOffset - 0x80000) { + //out-of-range: all subsequent nodes will also be, so free up their memory + if(node->next) { delete node->next; node->next = 0; } + break; + } + + unsigned length = 0, x = sourceOffset, y = node->offset; + while(length < 35 && x < sourceSize && sourceData[x++] == sourceData[y++]) length++; + if(length > maxLength) maxLength = length, maxOffset = node->offset; + if(length == 35) break; + + node = node->next; + } + + //attach current symbol to top of tree for subsequent searches + node = new Node; + node->offset = sourceOffset; + node->next = tree[symbol]; + tree[symbol] = node; + + if(maxLength < 4) { + buffer[bufferOffset++] = sourceData[sourceOffset++]; + } else { + unsigned output = ((maxLength - 4) << 19) | (sourceOffset - 1 - maxOffset); + for(unsigned n = 0; n < 24; n += 8) buffer[bufferOffset++] = output >> n; + mask |= 0x80 >> iteration; + sourceOffset += maxLength; + } + } + + buffer[0] = mask; + targetFile.write(buffer, bufferOffset); + } + + sourceFile.close(); + targetFile.close(); + return true; +} + +bool lzss::decompress(uint8_t *targetData, unsigned targetSize) { + if(targetSize < size()) return false; + + unsigned sourceOffset = 4, targetOffset = 0; + while(sourceOffset < sourceSize) { + uint8_t mask = sourceData[sourceOffset++]; + + for(unsigned iteration = 0; iteration < 8; iteration++) { + if(sourceOffset >= sourceSize) break; + + if((mask & (0x80 >> iteration)) == 0) { + targetData[targetOffset++] = sourceData[sourceOffset++]; + } else { + unsigned code = 0; + for(unsigned n = 0; n < 24; n += 8) code |= sourceData[sourceOffset++] << n; + unsigned length = (code >> 19) + 4; + unsigned offset = targetOffset - 1 - (code & 0x7ffff); + while(length--) targetData[targetOffset++] = targetData[offset++]; + } + } + } +} + +bool lzss::decompress(const string &filename) { + if(sourceSize < 4) return false; + unsigned targetSize = size(); + + file fp; + if(fp.open(filename, file::mode::write) == false) return false; + fp.truncate(targetSize); + fp.close(); + + filemap targetFile; + if(targetFile.open(filename, filemap::mode::readwrite) == false) return false; + uint8_t *targetData = targetFile.data(); + + bool result = decompress(targetData, targetSize); + sourceFile.close(); + targetFile.close(); + return result; +} + +} + +#endif diff --git a/ananke/nall/map.hpp b/ananke/nall/map.hpp new file mode 100644 index 00000000..938f0c2d --- /dev/null +++ b/ananke/nall/map.hpp @@ -0,0 +1,117 @@ +#ifndef NALL_MAP_HPP +#define NALL_MAP_HPP + +#include + +namespace nall { + +template +struct map { + struct pair { + LHS name; + RHS data; + }; + + inline void reset() { + list.reset(); + } + + inline unsigned size() const { + return list.size(); + } + + //O(log n) find + inline optional find(const LHS &name) const { + signed first = 0, last = size() - 1; + while(first <= last) { + signed middle = (first + last) / 2; + if(name < list[middle].name) last = middle - 1; //search lower half + else if(list[middle].name < name) first = middle + 1; //search upper half + else return { true, (unsigned)middle }; //match found + } + return { false, 0u }; + } + + //O(n) insert + O(log n) find + inline RHS& insert(const LHS &name, const RHS &data) { + if(auto position = find(name)) { + list[position()].data = data; + return list[position()].data; + } + signed offset = size(); + for(unsigned n = 0; n < size(); n++) { + if(name < list[n].name) { offset = n; break; } + } + list.insert(offset, { name, data }); + return list[offset].data; + } + + //O(log n) find + inline void modify(const LHS &name, const RHS &data) { + if(auto position = find(name)) list[position()].data = data; + } + + //O(n) remove + O(log n) find + inline void remove(const LHS &name) { + if(auto position = find(name)) list.remove(position()); + } + + //O(log n) find + inline RHS& operator[](const LHS &name) { + if(auto position = find(name)) return list[position()].data; + throw; + } + + inline const RHS& operator[](const LHS &name) const { + if(auto position = find(name)) return list[position()].data; + throw; + } + + inline RHS& operator()(const LHS &name) { + if(auto position = find(name)) return list[position()].data; + return insert(name, RHS()); + } + + inline const RHS& operator()(const LHS &name, const RHS &data) const { + if(auto position = find(name)) return list[position()].data; + return data; + } + + inline pair* begin() { return list.begin(); } + inline pair* end() { return list.end(); } + inline const pair* begin() const { return list.begin(); } + inline const pair* end() const { return list.end(); } + +protected: + vector list; +}; + +template +struct bidirectional_map { + const map &lhs; + const map &rhs; + + inline void reset() { + llist.reset(); + rlist.reset(); + } + + inline unsigned size() const { + return llist.size(); + } + + inline void insert(const LHS &ldata, const RHS &rdata) { + llist.insert(ldata, rdata); + rlist.insert(rdata, ldata); + } + + inline bidirectional_map() : lhs(llist), rhs(rlist) {} + +protected: + map llist; + map rlist; +}; + +} + +#endif diff --git a/ananke/nall/mosaic.hpp b/ananke/nall/mosaic.hpp new file mode 100644 index 00000000..16fd0bfd --- /dev/null +++ b/ananke/nall/mosaic.hpp @@ -0,0 +1,10 @@ +#ifndef NALL_MOSAIC_HPP +#define NALL_MOSAIC_HPP + +#define NALL_MOSAIC_INTERNAL_HPP +#include +#include +#include +#undef NALL_MOSAIC_INTERNAL_HPP + +#endif diff --git a/ananke/nall/mosaic/bitstream.hpp b/ananke/nall/mosaic/bitstream.hpp new file mode 100644 index 00000000..e2cb3bc5 --- /dev/null +++ b/ananke/nall/mosaic/bitstream.hpp @@ -0,0 +1,55 @@ +#ifdef NALL_MOSAIC_INTERNAL_HPP + +namespace nall { +namespace mosaic { + +struct bitstream { + filemap fp; + uint8_t *data; + unsigned size; + bool readonly; + bool endian; + + inline bool read(uint64_t addr) const { + if(data == nullptr || (addr >> 3) >= size) return 0; + unsigned mask = endian == 0 ? (0x01 << (addr & 7)) : (0x80 >> (addr & 7)); + return data[addr >> 3] & mask; + } + + inline void write(uint64_t addr, bool value) { + if(data == nullptr || readonly == true || (addr >> 3) >= size) return; + unsigned mask = endian == 0 ? (0x01 << (addr & 7)) : (0x80 >> (addr & 7)); + if(value == 0) data[addr >> 3] &= ~mask; + if(value == 1) data[addr >> 3] |= mask; + } + + inline bool open(const string &filename) { + readonly = false; + if(fp.open(filename, filemap::mode::readwrite) == false) { + readonly = true; + if(fp.open(filename, filemap::mode::read) == false) { + return false; + } + } + data = fp.data(); + size = fp.size(); + return true; + } + + inline void close() { + fp.close(); + data = nullptr; + } + + inline bitstream() : data(nullptr), endian(1) { + } + + inline ~bitstream() { + close(); + } +}; + +} +} + +#endif diff --git a/ananke/nall/mosaic/context.hpp b/ananke/nall/mosaic/context.hpp new file mode 100644 index 00000000..bc7a518a --- /dev/null +++ b/ananke/nall/mosaic/context.hpp @@ -0,0 +1,224 @@ +#ifdef NALL_MOSAIC_INTERNAL_HPP + +namespace nall { +namespace mosaic { + +struct context { + unsigned offset; + unsigned width; + unsigned height; + unsigned count; + + bool endian; //0 = lsb, 1 = msb + bool order; //0 = linear, 1 = planar + unsigned depth; //1 - 24bpp + + unsigned blockWidth; + unsigned blockHeight; + unsigned blockStride; + unsigned blockOffset; + vector block; + + unsigned tileWidth; + unsigned tileHeight; + unsigned tileStride; + unsigned tileOffset; + vector tile; + + unsigned mosaicWidth; + unsigned mosaicHeight; + unsigned mosaicStride; + unsigned mosaicOffset; + vector mosaic; + + unsigned paddingWidth; + unsigned paddingHeight; + unsigned paddingColor; + vector palette; + + inline unsigned objectWidth() const { return blockWidth * tileWidth * mosaicWidth + paddingWidth; } + inline unsigned objectHeight() const { return blockHeight * tileHeight * mosaicHeight + paddingHeight; } + inline unsigned objectSize() const { + unsigned size = blockStride * tileWidth * tileHeight * mosaicWidth * mosaicHeight + + blockOffset * tileHeight * mosaicWidth * mosaicHeight + + tileStride * mosaicWidth * mosaicHeight + + tileOffset * mosaicHeight; + return max(1u, size); + } + + inline unsigned eval(const string &expression) { + intmax_t result; + if(fixedpoint::eval(expression, result) == false) return 0u; + return result; + } + + inline void eval(vector &buffer, const string &expression_) { + string expression = expression_; + bool function = false; + for(auto &c : expression) { + if(c == '(') function = true; + if(c == ')') function = false; + if(c == ',' && function == true) c = ';'; + } + + lstring list = expression.split(","); + for(auto &item : list) { + item.trim(); + if(item.wildcard("f(?*) ?*")) { + item.ltrim<1>("f("); + lstring part = item.split<1>(") "); + lstring args = part[0].split<3>(";"); + for(auto &item : args) item.trim(); + + unsigned length = eval(args(0, "0")); + unsigned offset = eval(args(1, "0")); + unsigned stride = eval(args(2, "0")); + if(args.size() < 2) offset = buffer.size(); + if(args.size() < 3) stride = 1; + + for(unsigned n = 0; n < length; n++) { + string fn = part[1]; + fn.replace("n", decimal(n)); + fn.replace("o", decimal(offset)); + fn.replace("p", decimal(buffer.size())); + buffer.resize(offset + 1); + buffer[offset] = eval(fn); + offset += stride; + } + } else if(item.wildcard("base64*")) { + unsigned offset = 0; + item.ltrim<1>("base64"); + if(item.wildcard("(?*) *")) { + item.ltrim<1>("("); + lstring part = item.split<1>(") "); + offset = eval(part[0]); + item = part(1, ""); + } + item.trim(); + for(auto &c : item) { + if(c >= 'A' && c <= 'Z') buffer.append(offset + c - 'A' + 0); + if(c >= 'a' && c <= 'z') buffer.append(offset + c - 'a' + 26); + if(c >= '0' && c <= '9') buffer.append(offset + c - '0' + 52); + if(c == '-') buffer.append(offset + 62); + if(c == '_') buffer.append(offset + 63); + } + } else if(item.wildcard("file *")) { + item.ltrim<1>("file "); + item.trim(); + //... + } else if(item.empty() == false) { + buffer.append(eval(item)); + } + } + } + + inline void parse(const string &data) { + reset(); + + lstring lines = data.split("\n"); + for(auto &line : lines) { + lstring part = line.split<1>(":"); + if(part.size() != 2) continue; + part[0].trim(); + part[1].trim(); + + if(part[0] == "offset") offset = eval(part[1]); + if(part[0] == "width") width = eval(part[1]); + if(part[0] == "height") height = eval(part[1]); + if(part[0] == "count") count = eval(part[1]); + + if(part[0] == "endian") endian = eval(part[1]); + if(part[0] == "order") order = eval(part[1]); + if(part[0] == "depth") depth = eval(part[1]); + + if(part[0] == "blockWidth") blockWidth = eval(part[1]); + if(part[0] == "blockHeight") blockHeight = eval(part[1]); + if(part[0] == "blockStride") blockStride = eval(part[1]); + if(part[0] == "blockOffset") blockOffset = eval(part[1]); + if(part[0] == "block") eval(block, part[1]); + + if(part[0] == "tileWidth") tileWidth = eval(part[1]); + if(part[0] == "tileHeight") tileHeight = eval(part[1]); + if(part[0] == "tileStride") tileStride = eval(part[1]); + if(part[0] == "tileOffset") tileOffset = eval(part[1]); + if(part[0] == "tile") eval(tile, part[1]); + + if(part[0] == "mosaicWidth") mosaicWidth = eval(part[1]); + if(part[0] == "mosaicHeight") mosaicHeight = eval(part[1]); + if(part[0] == "mosaicStride") mosaicStride = eval(part[1]); + if(part[0] == "mosaicOffset") mosaicOffset = eval(part[1]); + if(part[0] == "mosaic") eval(mosaic, part[1]); + + if(part[0] == "paddingWidth") paddingWidth = eval(part[1]); + if(part[0] == "paddingHeight") paddingHeight = eval(part[1]); + if(part[0] == "paddingColor") paddingColor = eval(part[1]); + if(part[0] == "palette") eval(palette, part[1]); + } + + sanitize(); + } + + inline bool load(const string &filename) { + string filedata; + if(filedata.readfile(filename) == false) return false; + parse(filedata); + return true; + } + + inline void sanitize() { + if(depth < 1) depth = 1; + if(depth > 24) depth = 24; + + if(blockWidth < 1) blockWidth = 1; + if(blockHeight < 1) blockHeight = 1; + + if(tileWidth < 1) tileWidth = 1; + if(tileHeight < 1) tileHeight = 1; + + if(mosaicWidth < 1) mosaicWidth = 1; + if(mosaicHeight < 1) mosaicHeight = 1; + } + + inline void reset() { + offset = 0; + width = 0; + height = 0; + count = 0; + + endian = 1; + order = 0; + depth = 1; + + blockWidth = 1; + blockHeight = 1; + blockStride = 0; + blockOffset = 0; + block.reset(); + + tileWidth = 1; + tileHeight = 1; + tileStride = 0; + tileOffset = 0; + tile.reset(); + + mosaicWidth = 1; + mosaicHeight = 1; + mosaicStride = 0; + mosaicOffset = 0; + mosaic.reset(); + + paddingWidth = 0; + paddingHeight = 0; + paddingColor = 0x000000; + palette.reset(); + } + + inline context() { + reset(); + } +}; + +} +} + +#endif diff --git a/ananke/nall/mosaic/parser.hpp b/ananke/nall/mosaic/parser.hpp new file mode 100644 index 00000000..b2c0b8ef --- /dev/null +++ b/ananke/nall/mosaic/parser.hpp @@ -0,0 +1,126 @@ +#ifdef NALL_MOSAIC_INTERNAL_HPP + +namespace nall { +namespace mosaic { + +struct parser { + image canvas; + + //export from bitstream to canvas + inline void load(bitstream &stream, uint64_t offset, context &ctx, unsigned width, unsigned height) { + canvas.allocate(width, height); + canvas.clear(ctx.paddingColor); + parse(1, stream, offset, ctx, width, height); + } + + //import from canvas to bitstream + inline bool save(bitstream &stream, uint64_t offset, context &ctx) { + if(stream.readonly) return false; + parse(0, stream, offset, ctx, canvas.width, canvas.height); + return true; + } + + inline parser() : canvas(0, 32, 0u, 255u << 16, 255u << 8, 255u << 0) { + } + +private: + inline uint32_t read(unsigned x, unsigned y) const { + unsigned addr = y * canvas.width + x; + if(addr >= canvas.width * canvas.height) return 0u; + uint32_t *buffer = (uint32_t*)canvas.data; + return buffer[addr]; + } + + inline void write(unsigned x, unsigned y, uint32_t data) { + unsigned addr = y * canvas.width + x; + if(addr >= canvas.width * canvas.height) return; + uint32_t *buffer = (uint32_t*)canvas.data; + buffer[addr] = data; + } + + inline void parse(bool load, bitstream &stream, uint64_t offset, context &ctx, unsigned width, unsigned height) { + stream.endian = ctx.endian; + unsigned canvasWidth = width / (ctx.mosaicWidth * ctx.tileWidth * ctx.blockWidth + ctx.paddingWidth); + unsigned canvasHeight = height / (ctx.mosaicHeight * ctx.tileHeight * ctx.blockHeight + ctx.paddingHeight); + unsigned bitsPerBlock = ctx.depth * ctx.blockWidth * ctx.blockHeight; + + unsigned objectOffset = 0; + for(unsigned objectY = 0; objectY < canvasHeight; objectY++) { + for(unsigned objectX = 0; objectX < canvasWidth; objectX++) { + if(objectOffset >= ctx.count && ctx.count > 0) break; + unsigned objectIX = objectX * ctx.objectWidth(); + unsigned objectIY = objectY * ctx.objectHeight(); + objectOffset++; + + unsigned mosaicOffset = 0; + for(unsigned mosaicY = 0; mosaicY < ctx.mosaicHeight; mosaicY++) { + for(unsigned mosaicX = 0; mosaicX < ctx.mosaicWidth; mosaicX++) { + unsigned mosaicData = ctx.mosaic(mosaicOffset, mosaicOffset); + unsigned mosaicIX = (mosaicData % ctx.mosaicWidth) * (ctx.tileWidth * ctx.blockWidth); + unsigned mosaicIY = (mosaicData / ctx.mosaicWidth) * (ctx.tileHeight * ctx.blockHeight); + mosaicOffset++; + + unsigned tileOffset = 0; + for(unsigned tileY = 0; tileY < ctx.tileHeight; tileY++) { + for(unsigned tileX = 0; tileX < ctx.tileWidth; tileX++) { + unsigned tileData = ctx.tile(tileOffset, tileOffset); + unsigned tileIX = (tileData % ctx.tileWidth) * ctx.blockWidth; + unsigned tileIY = (tileData / ctx.tileWidth) * ctx.blockHeight; + tileOffset++; + + unsigned blockOffset = 0; + for(unsigned blockY = 0; blockY < ctx.blockHeight; blockY++) { + for(unsigned blockX = 0; blockX < ctx.blockWidth; blockX++) { + if(load) { + unsigned palette = 0; + for(unsigned n = 0; n < ctx.depth; n++) { + unsigned index = blockOffset++; + if(ctx.order == 1) index = (index % ctx.depth) * ctx.blockWidth * ctx.blockHeight + (index / ctx.depth); + palette |= stream.read(offset + ctx.block(index, index)) << n; + } + + write( + objectIX + mosaicIX + tileIX + blockX, + objectIY + mosaicIY + tileIY + blockY, + ctx.palette(palette, palette) + ); + } else /* save */ { + uint32_t palette = read( + objectIX + mosaicIX + tileIX + blockX, + objectIY + mosaicIY + tileIY + blockY + ); + + for(unsigned n = 0; n < ctx.depth; n++) { + unsigned index = blockOffset++; + if(ctx.order == 1) index = (index % ctx.depth) * ctx.blockWidth * ctx.blockHeight + (index / ctx.depth); + stream.write(offset + ctx.block(index, index), palette & 1); + palette >>= 1; + } + } + } //blockX + } //blockY + + offset += ctx.blockStride; + } //tileX + + offset += ctx.blockOffset; + } //tileY + + offset += ctx.tileStride; + } //mosaicX + + offset += ctx.tileOffset; + } //mosaicY + + offset += ctx.mosaicStride; + } //objectX + + offset += ctx.mosaicOffset; + } //objectY + } +}; + +} +} + +#endif diff --git a/ananke/nall/nall.hpp b/ananke/nall/nall.hpp new file mode 100644 index 00000000..06040fac --- /dev/null +++ b/ananke/nall/nall.hpp @@ -0,0 +1,58 @@ +#ifndef NALL_HPP +#define NALL_HPP + +//include the most common nall headers with one statement +//does not include the most obscure components with high cost and low usage + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include + #include +#endif + +#if defined(PLATFORM_X) + #include +#endif + +#endif diff --git a/ananke/nall/platform.hpp b/ananke/nall/platform.hpp new file mode 100644 index 00000000..a45a6723 --- /dev/null +++ b/ananke/nall/platform.hpp @@ -0,0 +1,86 @@ +#ifndef NALL_PLATFORM_HPP +#define NALL_PLATFORM_HPP + +#if defined(_WIN32) + //minimum version needed for _wstat64, etc + #undef __MSVCRT_VERSION__ + #define __MSVCRT_VERSION__ 0x0601 + #include +#endif + +//========================= +//standard platform headers +//========================= + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(_WIN32) + #include + #include + #include + #include + #undef interface + #define dllexport __declspec(dllexport) +#else + #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 putenv _putenv + #define vsnprintf _vsnprintf + inline void usleep(unsigned milliseconds) { Sleep(milliseconds / 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 + +#endif diff --git a/ananke/nall/png.hpp b/ananke/nall/png.hpp new file mode 100644 index 00000000..f5ebaab4 --- /dev/null +++ b/ananke/nall/png.hpp @@ -0,0 +1,337 @@ +#ifndef NALL_PNG_HPP +#define NALL_PNG_HPP + +//PNG image decoder +//author: byuu + +#include +#include + +namespace nall { + +struct png { + //colorType: + //0 = L + //2 = R,G,B + //3 = P + //4 = L,A + //6 = R,G,B,A + struct Info { + unsigned width; + unsigned height; + unsigned bitDepth; + unsigned colorType; + unsigned compressionMethod; + unsigned filterType; + unsigned interlaceMethod; + + unsigned bytesPerPixel; + unsigned pitch; + + uint8_t palette[256][3]; + } info; + + uint8_t *data; + unsigned size; + + inline bool decode(const string &filename); + inline bool decode(const uint8_t *sourceData, unsigned sourceSize); + inline unsigned readbits(const uint8_t *&data); + unsigned bitpos; + + inline png(); + inline ~png(); + +protected: + enum class FourCC : unsigned { + IHDR = 0x49484452, + PLTE = 0x504c5445, + IDAT = 0x49444154, + IEND = 0x49454e44, + }; + + inline unsigned interlace(unsigned pass, unsigned index); + inline unsigned inflateSize(); + inline bool deinterlace(const uint8_t *&inputData, unsigned pass); + inline bool filter(uint8_t *outputData, const uint8_t *inputData, unsigned width, unsigned height); + inline unsigned read(const uint8_t *data, unsigned length); +}; + +bool png::decode(const string &filename) { + if(auto memory = file::read(filename)) { + return decode(memory.data(), memory.size()); + } + return false; +} + +bool png::decode(const uint8_t *sourceData, unsigned sourceSize) { + if(sourceSize < 8) return false; + if(read(sourceData + 0, 4) != 0x89504e47) return false; + if(read(sourceData + 4, 4) != 0x0d0a1a0a) return false; + + uint8_t *compressedData = 0; + unsigned compressedSize = 0; + + unsigned offset = 8; + while(offset < sourceSize) { + unsigned length = read(sourceData + offset + 0, 4); + unsigned fourCC = read(sourceData + offset + 4, 4); + unsigned checksum = read(sourceData + offset + 8 + length, 4); + + if(fourCC == (unsigned)FourCC::IHDR) { + info.width = read(sourceData + offset + 8, 4); + info.height = read(sourceData + offset + 12, 4); + info.bitDepth = read(sourceData + offset + 16, 1); + info.colorType = read(sourceData + offset + 17, 1); + info.compressionMethod = read(sourceData + offset + 18, 1); + info.filterType = read(sourceData + offset + 19, 1); + info.interlaceMethod = read(sourceData + offset + 20, 1); + + if(info.bitDepth == 0 || info.bitDepth > 16) return false; + if(info.bitDepth & (info.bitDepth - 1)) return false; //not a power of two + if(info.compressionMethod != 0) return false; + if(info.filterType != 0) return false; + if(info.interlaceMethod != 0 && info.interlaceMethod != 1) return false; + + switch(info.colorType) { + case 0: info.bytesPerPixel = info.bitDepth * 1; break; //L + case 2: info.bytesPerPixel = info.bitDepth * 3; break; //R,G,B + case 3: info.bytesPerPixel = info.bitDepth * 1; break; //P + case 4: info.bytesPerPixel = info.bitDepth * 2; break; //L,A + case 6: info.bytesPerPixel = info.bitDepth * 4; break; //R,G,B,A + default: return false; + } + + if(info.colorType == 2 || info.colorType == 4 || info.colorType == 6) + if(info.bitDepth != 8 && info.bitDepth != 16) return false; + if(info.colorType == 3 && info.bitDepth == 16) return false; + + info.bytesPerPixel = (info.bytesPerPixel + 7) / 8; + info.pitch = (int)info.width * info.bytesPerPixel; + } + + if(fourCC == (unsigned)FourCC::PLTE) { + if(length % 3) return false; + for(unsigned n = 0, p = offset + 8; n < length / 3; n++) { + info.palette[n][0] = sourceData[p++]; + info.palette[n][1] = sourceData[p++]; + info.palette[n][2] = sourceData[p++]; + } + } + + if(fourCC == (unsigned)FourCC::IDAT) { + compressedData = (uint8_t*)realloc(compressedData, compressedSize + length); + memcpy(compressedData + compressedSize, sourceData + offset + 8, length); + compressedSize += length; + } + + if(fourCC == (unsigned)FourCC::IEND) { + break; + } + + offset += 4 + 4 + length + 4; + } + + unsigned interlacedSize = inflateSize(); + uint8_t *interlacedData = new uint8_t[interlacedSize]; + + bool result = inflate(interlacedData, interlacedSize, compressedData + 2, compressedSize - 6); + delete[] compressedData; + + if(result == false) { + delete[] interlacedData; + return false; + } + + size = info.width * info.height * info.bytesPerPixel; + data = new uint8_t[size]; + + if(info.interlaceMethod == 0) { + if(filter(data, interlacedData, info.width, info.height) == false) { + delete[] interlacedData; + delete[] data; + data = 0; + return false; + } + } else { + const uint8_t *passData = interlacedData; + for(unsigned pass = 0; pass < 7; pass++) { + if(deinterlace(passData, pass) == false) { + delete[] interlacedData; + delete[] data; + data = 0; + return false; + } + } + } + + delete[] interlacedData; + return true; +} + +unsigned png::interlace(unsigned pass, unsigned index) { + static const unsigned data[7][4] = { + //x-distance, y-distance, x-origin, y-origin + { 8, 8, 0, 0 }, + { 8, 8, 4, 0 }, + { 4, 8, 0, 4 }, + { 4, 4, 2, 0 }, + { 2, 4, 0, 2 }, + { 2, 2, 1, 0 }, + { 1, 2, 0, 1 }, + }; + return data[pass][index]; +} + +unsigned png::inflateSize() { + if(info.interlaceMethod == 0) { + return info.width * info.height * info.bytesPerPixel + info.height; + } + + unsigned size = 0; + for(unsigned pass = 0; pass < 7; pass++) { + unsigned xd = interlace(pass, 0), yd = interlace(pass, 1); + unsigned xo = interlace(pass, 2), yo = interlace(pass, 3); + unsigned width = (info.width + (xd - xo - 1)) / xd; + unsigned height = (info.height + (yd - yo - 1)) / yd; + if(width == 0 || height == 0) continue; + size += width * height * info.bytesPerPixel + height; + } + return size; +} + +bool png::deinterlace(const uint8_t *&inputData, unsigned pass) { + unsigned xd = interlace(pass, 0), yd = interlace(pass, 1); + unsigned xo = interlace(pass, 2), yo = interlace(pass, 3); + unsigned width = (info.width + (xd - xo - 1)) / xd; + unsigned height = (info.height + (yd - yo - 1)) / yd; + if(width == 0 || height == 0) return true; + + unsigned outputSize = width * height * info.bytesPerPixel; + uint8_t *outputData = new uint8_t[outputSize]; + bool result = filter(outputData, inputData, width, height); + + const uint8_t *rd = outputData; + for(unsigned y = yo; y < info.height; y += yd) { + uint8_t *wr = data + y * info.pitch; + for(unsigned x = xo; x < info.width; x += xd) { + for(unsigned b = 0; b < info.bytesPerPixel; b++) { + wr[x * info.bytesPerPixel + b] = *rd++; + } + } + } + + inputData += outputSize + height; + delete[] outputData; + return result; +} + +bool png::filter(uint8_t *outputData, const uint8_t *inputData, unsigned width, unsigned height) { + uint8_t *wr = outputData; + const uint8_t *rd = inputData; + int bpp = info.bytesPerPixel, pitch = width * bpp; + for(int y = 0; y < height; y++) { + uint8_t filter = *rd++; + + switch(filter) { + case 0x00: //None + for(int x = 0; x < pitch; x++) { + wr[x] = rd[x]; + } + break; + + case 0x01: //Subtract + for(int x = 0; x < pitch; x++) { + wr[x] = rd[x] + (x - bpp < 0 ? 0 : wr[x - bpp]); + } + break; + + case 0x02: //Above + for(int x = 0; x < pitch; x++) { + wr[x] = rd[x] + (y - 1 < 0 ? 0 : wr[x - pitch]); + } + break; + + case 0x03: //Average + for(int x = 0; x < pitch; x++) { + short a = x - bpp < 0 ? 0 : wr[x - bpp]; + short b = y - 1 < 0 ? 0 : wr[x - pitch]; + + wr[x] = rd[x] + (uint8_t)((a + b) / 2); + } + break; + + case 0x04: //Paeth + for(int x = 0; x < pitch; x++) { + short a = x - bpp < 0 ? 0 : wr[x - bpp]; + short b = y - 1 < 0 ? 0 : wr[x - pitch]; + short c = x - bpp < 0 || y - 1 < 0 ? 0 : wr[x - pitch - bpp]; + + short p = a + b - c; + short pa = p > a ? p - a : a - p; + short pb = p > b ? p - b : b - p; + short pc = p > c ? p - c : c - p; + + uint8_t paeth = (uint8_t)((pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c); + + wr[x] = rd[x] + paeth; + } + break; + + default: //Invalid + return false; + } + + rd += pitch; + wr += pitch; + } + + return true; +} + +unsigned png::read(const uint8_t *data, unsigned length) { + unsigned result = 0; + while(length--) result = (result << 8) | (*data++); + return result; +} + +unsigned png::readbits(const uint8_t *&data) { + unsigned result = 0; + switch(info.bitDepth) { + case 1: + result = (*data >> bitpos) & 1; + bitpos++; + if(bitpos == 8) { data++; bitpos = 0; } + break; + case 2: + result = (*data >> bitpos) & 3; + bitpos += 2; + if(bitpos == 8) { data++; bitpos = 0; } + break; + case 4: + result = (*data >> bitpos) & 15; + bitpos += 4; + if(bitpos == 8) { data++; bitpos = 0; } + break; + case 8: + result = *data++; + break; + case 16: + result = (data[0] << 8) | (data[1] << 0); + data += 2; + break; + } + return result; +} + +png::png() : data(nullptr) { + bitpos = 0; +} + +png::~png() { + if(data) delete[] data; +} + +} + +#endif diff --git a/ananke/nall/priority-queue.hpp b/ananke/nall/priority-queue.hpp new file mode 100644 index 00000000..1aedc6f1 --- /dev/null +++ b/ananke/nall/priority-queue.hpp @@ -0,0 +1,109 @@ +#ifndef NALL_PRIORITY_QUEUE_HPP +#define NALL_PRIORITY_QUEUE_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) append (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/ananke/nall/property.hpp b/ananke/nall/property.hpp new file mode 100644 index 00000000..665afcad --- /dev/null +++ b/ananke/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 of 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/ananke/nall/public-cast.hpp b/ananke/nall/public-cast.hpp new file mode 100644 index 00000000..331800e1 --- /dev/null +++ b/ananke/nall/public-cast.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_PUBLIC_CAST_HPP +#define NALL_PUBLIC_CAST_HPP + +//this is a proof-of-concept-*only* C++ access-privilege elevation exploit. +//this code is 100% legal C++, per C++98 section 14.7.2 paragraph 8: +//"access checking rules do not apply to names in explicit instantiations." +//usage example: + +//struct N { typedef void (Class::*)(); }; +//template class public_cast; +//(class.*public_cast::value); + +//Class::Reference may be public, protected or private +//Class::Reference may be a function, object or variable + +namespace nall { + template struct public_cast; + + template struct public_cast { + static typename T::type value; + }; + + template typename T::type public_cast::value; + + template struct public_cast { + static typename T::type value; + }; + + template typename T::type public_cast::value = public_cast::value = P; +} + +#endif diff --git a/ananke/nall/random.hpp b/ananke/nall/random.hpp new file mode 100644 index 00000000..409c4561 --- /dev/null +++ b/ananke/nall/random.hpp @@ -0,0 +1,28 @@ +#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_lfsr { + inline void seed(unsigned seed__) { + seed_ = seed__; + } + + inline unsigned operator()() { + return seed_ = (seed_ >> 1) ^ (((seed_ & 1) - 1) & 0xedb88320); + } + + random_lfsr() : seed_(0) { + } + + private: + unsigned seed_; + }; +} + +#endif diff --git a/ananke/nall/serial.hpp b/ananke/nall/serial.hpp new file mode 100644 index 00000000..da87ae50 --- /dev/null +++ b/ananke/nall/serial.hpp @@ -0,0 +1,110 @@ +#ifndef NALL_SERIAL_HPP +#define NALL_SERIAL_HPP + +#include +#include +#include +#include + +#include + +namespace nall { + struct serial { + bool readable() { + if(port_open == false) return false; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(port, &fdset); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + int result = select(FD_SETSIZE, &fdset, nullptr, nullptr, &timeout); + if(result < 1) return false; + return FD_ISSET(port, &fdset); + } + + //-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); + } + + bool writable() { + if(port_open == false) return false; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(port, &fdset); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + int result = select(FD_SETSIZE, nullptr, &fdset, nullptr, &timeout); + if(result < 1) return false; + return FD_ISSET(port, &fdset); + } + + //-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/ananke/nall/serializer.hpp b/ananke/nall/serializer.hpp new file mode 100644 index 00000000..fcb39456 --- /dev/null +++ b/ananke/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++] = (uintmax_t)value >> (n << 3); + } else if(imode == Load) { + value = 0; + for(unsigned n = 0; n < size; n++) value |= (uintmax_t)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/ananke/nall/set.hpp b/ananke/nall/set.hpp new file mode 100644 index 00000000..c6d3d06e --- /dev/null +++ b/ananke/nall/set.hpp @@ -0,0 +1,158 @@ +#ifndef NALL_SET_HPP +#define NALL_SET_HPP + +//set +//* unordered +//* intended for unique items +//* dynamic growth +//* reference-based variant + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + +template struct set; + +template struct set::value>::type> { + struct exception_out_of_bounds{}; + +protected: + T *pool; + unsigned poolsize, objectsize; + +public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } +}; + +//reference set +template struct set::value>::type> { + struct exception_out_of_bounds{}; + +protected: + typedef typename std::remove_reference::type T; + T **pool; + unsigned poolsize, objectsize; + +public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) free(pool); + pool = nullptr; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned size) { + if(size == poolsize) return; + pool = (T**)realloc(pool, sizeof(T*) * size); + poolsize = size; + objectsize = min(objectsize, size); + } + + void resize(unsigned size) { + if(size > poolsize) reserve(bit::round(size)); //amortize growth + objectsize = size; + } + + bool append(T& data) { + if(find(data)) return false; + unsigned offset = objectsize++; + if(offset >= poolsize) resize(offset + 1); + pool[offset] = &data; + return true; + } + + template + bool append(T& data, Args&&... args) { + bool result = append(data); + append(std::forward(args)...); + return result; + } + + bool remove(T& data) { + if(auto position = find(data)) { + for(signed i = position(); i < objectsize - 1; i++) pool[i] = pool[i + 1]; + resize(objectsize - 1); + return true; + } + return false; + } + + optional find(const T& data) { + for(unsigned n = 0; n < objectsize; n++) if(pool[n] == &data) return {true, n}; + return {false, 0u}; + } + + template set(Args&&... args) : pool(nullptr), poolsize(0), objectsize(0) { + construct(std::forward(args)...); + } + + ~set() { + reset(); + } + + set& operator=(const set &source) { + if(&source == this) return *this; + if(pool) free(pool); + objectsize = source.objectsize; + poolsize = source.poolsize; + pool = (T**)malloc(sizeof(T*) * poolsize); + memcpy(pool, source.pool, sizeof(T*) * objectsize); + return *this; + } + + set& operator=(const set &&source) { + if(&source == this) return *this; + if(pool) free(pool); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = nullptr; + source.reset(); + return *this; + } + + T& operator[](unsigned position) const { + if(position >= objectsize) throw exception_out_of_bounds(); + return *pool[position]; + } + + struct iterator { + bool operator!=(const iterator &source) const { return position != source.position; } + T& operator*() { return source.operator[](position); } + iterator& operator++() { position++; return *this; } + iterator(const set &source, unsigned position) : source(source), position(position) {} + private: + const set &source; + unsigned position; + }; + + iterator begin() { return iterator(*this, 0); } + iterator end() { return iterator(*this, objectsize); } + const iterator begin() const { return iterator(*this, 0); } + const iterator end() const { return iterator(*this, objectsize); } + +private: + void construct() {} + void construct(const set &source) { operator=(source); } + void construct(const set &&source) { operator=(std::move(source)); } + template void construct(T& data, Args&&... args) { + append(data); + construct(std::forward(args)...); + } +}; + +} + +#endif diff --git a/ananke/nall/sha256.hpp b/ananke/nall/sha256.hpp new file mode 100644 index 00000000..c63367a7 --- /dev/null +++ b/ananke/nall/sha256.hpp @@ -0,0 +1,145 @@ +#ifndef NALL_SHA256_HPP +#define NALL_SHA256_HPP + +//author: vladitx + +#include + +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; + }; + + inline 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; + } + + inline 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); + } + } + + inline 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); + } + + inline 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/ananke/nall/sort.hpp b/ananke/nall/sort.hpp new file mode 100644 index 00000000..36d91865 --- /dev/null +++ b/ananke/nall/sort.hpp @@ -0,0 +1,77 @@ +#ifndef NALL_SORT_HPP +#define NALL_SORT_HPP + +#include +#include + +//class: merge sort +//average: O(n log n) +//worst: O(n log n) +//memory: O(n) +//stack: O(log n) +//stable?: yes + +//note: merge sort was chosen over quick sort, because: +//* it is a stable sort +//* it lacks O(n^2) worst-case overhead + +#define NALL_SORT_INSERTION +//#define NALL_SORT_SELECTION + +namespace nall { + template + void sort(T list[], unsigned size, const Comparator &lessthan) { + if(size <= 1) return; //nothing to sort + + //use insertion sort to quickly sort smaller blocks + if(size < 64) { + #if defined(NALL_SORT_INSERTION) + for(signed i = 1, j; i < size; i++) { + T copy = std::move(list[i]); + for(j = i - 1; j >= 0; j--) { + if(lessthan(list[j], copy)) break; + list[j + 1] = std::move(list[j]); + } + list[j + 1] = std::move(copy); + } + #elif defined(NALL_SORT_SELECTION) + for(unsigned i = 0; i < size; i++) { + unsigned min = i; + for(unsigned j = i + 1; j < size; j++) { + if(lessthan(list[j], list[min])) min = j; + } + if(min != i) std::swap(list[i], list[min]); + } + #endif + return; + } + + //split list in half and recursively sort both + unsigned middle = size / 2; + sort(list, middle, lessthan); + sort(list + middle, size - middle, lessthan); + + //left and right are sorted here; perform merge sort + T *buffer = new T[size]; + unsigned offset = 0, left = 0, right = middle; + while(left < middle && right < size) { + if(lessthan(list[left], list[right])) { + buffer[offset++] = std::move(list[left++]); + } else { + buffer[offset++] = std::move(list[right++]); + } + } + while(left < middle) buffer[offset++] = std::move(list[left++]); + while(right < size) buffer[offset++] = std::move(list[right++]); + + for(unsigned i = 0; i < size; i++) list[i] = std::move(buffer[i]); + delete[] buffer; + } + + template + void sort(T list[], unsigned size) { + return sort(list, size, [](const T &l, const T &r) { return l < r; }); + } +} + +#endif diff --git a/ananke/nall/stdint.hpp b/ananke/nall/stdint.hpp new file mode 100644 index 00000000..c63f5912 --- /dev/null +++ b/ananke/nall/stdint.hpp @@ -0,0 +1,42 @@ +#ifndef NALL_STDINT_HPP +#define NALL_STDINT_HPP + +#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/ananke/nall/stream.hpp b/ananke/nall/stream.hpp new file mode 100644 index 00000000..586ccda7 --- /dev/null +++ b/ananke/nall/stream.hpp @@ -0,0 +1,26 @@ +#ifndef NALL_STREAM_HPP +#define NALL_STREAM_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define NALL_STREAM_INTERNAL_HPP +#include +#include +#include +#include +#include +#include +#include +#include +#undef NALL_STREAM_INTERNAL_HPP + +#endif diff --git a/ananke/nall/stream/auto.hpp b/ananke/nall/stream/auto.hpp new file mode 100644 index 00000000..d1b6e2ba --- /dev/null +++ b/ananke/nall/stream/auto.hpp @@ -0,0 +1,25 @@ +#ifndef NALL_STREAM_AUTO_HPP +#define NALL_STREAM_AUTO_HPP + +namespace nall { + +#define autostream(...) (*makestream(__VA_ARGS__)) + +inline std::unique_ptr makestream(const string &path) { + if(path.ibeginswith("http://")) return std::unique_ptr(new httpstream(path, 80)); + if(path.iendswith(".gz")) return std::unique_ptr(new gzipstream(filestream{path})); + if(path.iendswith(".zip")) return std::unique_ptr(new zipstream(filestream{path})); + return std::unique_ptr(new mmapstream(path)); +} + +inline std::unique_ptr makestream(uint8_t *data, unsigned size) { + return std::unique_ptr(new memorystream(data, size)); +} + +inline std::unique_ptr makestream(const uint8_t *data, unsigned size) { + return std::unique_ptr(new memorystream(data, size)); +} + +} + +#endif diff --git a/ananke/nall/stream/file.hpp b/ananke/nall/stream/file.hpp new file mode 100644 index 00000000..878418cf --- /dev/null +++ b/ananke/nall/stream/file.hpp @@ -0,0 +1,42 @@ +#ifndef NALL_STREAM_FILE_HPP +#define NALL_STREAM_FILE_HPP + +#include + +namespace nall { + +struct filestream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return pwritable; } + bool randomaccess() const { return false; } + + unsigned size() const { return pfile.size(); } + unsigned offset() const { return pfile.offset(); } + void seek(unsigned offset) const { pfile.seek(offset); } + + uint8_t read() const { return pfile.read(); } + void write(uint8_t data) const { pfile.write(data); } + + filestream(const string &filename) { + pfile.open(filename, file::mode::readwrite); + pwritable = pfile.open(); + if(!pwritable) pfile.open(filename, file::mode::read); + } + + filestream(const string &filename, file::mode mode) { + pfile.open(filename, mode); + pwritable = mode == file::mode::write || mode == file::mode::readwrite; + } + +private: + mutable file pfile; + bool pwritable; +}; + +} + +#endif diff --git a/ananke/nall/stream/gzip.hpp b/ananke/nall/stream/gzip.hpp new file mode 100644 index 00000000..0c270a72 --- /dev/null +++ b/ananke/nall/stream/gzip.hpp @@ -0,0 +1,34 @@ +#ifndef NALL_STREAM_GZIP_HPP +#define NALL_STREAM_GZIP_HPP + +#include + +namespace nall { + +struct gzipstream : memorystream { + using stream::read; + using stream::write; + + gzipstream(const stream &stream) { + unsigned size = stream.size(); + uint8_t *data = new uint8_t[size]; + stream.read(data, size); + + gzip archive; + bool result = archive.decompress(data, size); + delete[] data; + if(result == false) return; + + psize = archive.size; + pdata = new uint8_t[psize]; + memcpy(pdata, archive.data, psize); + } + + ~gzipstream() { + if(pdata) delete[] pdata; + } +}; + +} + +#endif diff --git a/ananke/nall/stream/http.hpp b/ananke/nall/stream/http.hpp new file mode 100644 index 00000000..2f9e45dd --- /dev/null +++ b/ananke/nall/stream/http.hpp @@ -0,0 +1,49 @@ +#ifndef NALL_STREAM_HTTP_HPP +#define NALL_STREAM_HTTP_HPP + +#include + +namespace nall { + +struct httpstream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return true; } + bool randomaccess() const { return true; } + + unsigned size() const { return psize; } + unsigned offset() const { return poffset; } + void seek(unsigned offset) const { poffset = offset; } + + uint8_t read() const { return pdata[poffset++]; } + void write(uint8_t data) const { pdata[poffset++] = data; } + + uint8_t read(unsigned offset) const { return pdata[offset]; } + void write(unsigned offset, uint8_t data) const { pdata[offset] = data; } + + httpstream(const string &url, unsigned port) : pdata(nullptr), psize(0), poffset(0) { + string uri = url; + uri.ltrim<1>("http://"); + lstring part = uri.split<1>("/"); + part[1] = { "/", part[1] }; + + http connection; + if(connection.connect(part[0], port) == false) return; + connection.download(part[1], pdata, psize); + } + + ~httpstream() { + if(pdata) delete[] pdata; + } + +private: + mutable uint8_t *pdata; + mutable unsigned psize, poffset; +}; + +} + +#endif diff --git a/ananke/nall/stream/memory.hpp b/ananke/nall/stream/memory.hpp new file mode 100644 index 00000000..cf49b3b2 --- /dev/null +++ b/ananke/nall/stream/memory.hpp @@ -0,0 +1,47 @@ +#ifndef NALL_STREAM_MEMORY_HPP +#define NALL_STREAM_MEMORY_HPP + +#include + +namespace nall { + +struct memorystream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return pwritable; } + bool randomaccess() const { return true; } + + uint8_t *data() const { return pdata; } + unsigned size() const { return psize; } + unsigned offset() const { return poffset; } + void seek(unsigned offset) const { poffset = offset; } + + uint8_t read() const { return pdata[poffset++]; } + void write(uint8_t data) const { pdata[poffset++] = data; } + + uint8_t read(unsigned offset) const { return pdata[offset]; } + void write(unsigned offset, uint8_t data) const { pdata[offset] = data; } + + memorystream() : pdata(nullptr), psize(0), poffset(0), pwritable(true) {} + + memorystream(uint8_t *data, unsigned size) { + pdata = data, psize = size, poffset = 0; + pwritable = true; + } + + memorystream(const uint8_t *data, unsigned size) { + pdata = (uint8_t*)data, psize = size, poffset = 0; + pwritable = false; + } + +protected: + mutable uint8_t *pdata; + mutable unsigned psize, poffset, pwritable; +}; + +} + +#endif diff --git a/ananke/nall/stream/mmap.hpp b/ananke/nall/stream/mmap.hpp new file mode 100644 index 00000000..ce30f810 --- /dev/null +++ b/ananke/nall/stream/mmap.hpp @@ -0,0 +1,42 @@ +#ifndef NALL_STREAM_MMAP_HPP +#define NALL_STREAM_MMAP_HPP + +#include + +namespace nall { + +struct mmapstream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return pwritable; } + bool randomaccess() const { return true; } + + unsigned size() const { return pmmap.size(); } + unsigned offset() const { return poffset; } + void seek(unsigned offset) const { poffset = offset; } + + uint8_t read() const { return pdata[poffset++]; } + void write(uint8_t data) const { pdata[poffset++] = data; } + + uint8_t read(unsigned offset) const { return pdata[offset]; } + void write(unsigned offset, uint8_t data) const { pdata[offset] = data; } + + mmapstream(const string &filename) { + pmmap.open(filename, filemap::mode::readwrite); + pwritable = pmmap.open(); + if(!pwritable) pmmap.open(filename, filemap::mode::read); + pdata = pmmap.data(), poffset = 0; + } + +private: + mutable filemap pmmap; + mutable uint8_t *pdata; + mutable unsigned pwritable, poffset; +}; + +} + +#endif diff --git a/ananke/nall/stream/stream.hpp b/ananke/nall/stream/stream.hpp new file mode 100644 index 00000000..9d937729 --- /dev/null +++ b/ananke/nall/stream/stream.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_STREAM_STREAM_HPP +#define NALL_STREAM_STREAM_HPP + +namespace nall { + +struct stream { + virtual bool seekable() const = 0; + virtual bool readable() const = 0; + virtual bool writable() const = 0; + virtual bool randomaccess() const = 0; + + virtual uint8_t* data() const { return nullptr; } + virtual unsigned size() const = 0; + virtual unsigned offset() const = 0; + virtual void seek(unsigned offset) const = 0; + + virtual uint8_t read() const = 0; + virtual void write(uint8_t data) const = 0; + + virtual uint8_t read(unsigned) const { return 0; } + virtual void write(unsigned, uint8_t) const {} + + operator bool() const { + return size(); + } + + bool empty() const { + return size() == 0; + } + + bool end() const { + return offset() >= size(); + } + + uintmax_t readl(unsigned length = 1) const { + uintmax_t data = 0, shift = 0; + while(length--) { data |= read() << shift; shift += 8; } + return data; + } + + uintmax_t readm(unsigned length = 1) const { + uintmax_t data = 0; + while(length--) data = (data << 8) | read(); + return data; + } + + void read(uint8_t *data, unsigned length) const { + while(length--) *data++ = read(); + } + + void writel(uintmax_t data, unsigned length = 1) const { + while(length--) { + write(data); + data >>= 8; + } + } + + void writem(uintmax_t data, unsigned length = 1) const { + uintmax_t shift = 8 * length; + while(length--) { + shift -= 8; + write(data >> shift); + } + } + + void write(const uint8_t *data, unsigned length) const { + while(length--) write(*data++); + } + + struct byte { + operator uint8_t() const { return s.read(offset); } + byte& operator=(uint8_t data) { s.write(offset, data); return *this; } + byte(const stream &s, unsigned offset) : s(s), offset(offset) {} + + private: + const stream &s; + const unsigned offset; + }; + + byte operator[](unsigned offset) const { + return byte(*this, offset); + } + + stream() {} + virtual ~stream() {} + stream(const stream&) = delete; + stream& operator=(const stream&) = delete; +}; + +} + +#endif diff --git a/ananke/nall/stream/vector.hpp b/ananke/nall/stream/vector.hpp new file mode 100644 index 00000000..59f36c02 --- /dev/null +++ b/ananke/nall/stream/vector.hpp @@ -0,0 +1,39 @@ +#ifndef NALL_STREAM_VECTOR_HPP +#define NALL_STREAM_VECTOR_HPP + +#include +#include + +namespace nall { + +struct vectorstream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return pwritable; } + bool randomaccess() const { return true; } + + uint8_t* data() const { return memory.data(); } + unsigned size() const { return memory.size(); } + unsigned offset() const { return poffset; } + void seek(unsigned offset) const { poffset = offset; } + + uint8_t read() const { return memory[poffset++]; } + void write(uint8_t data) const { memory[poffset++] = data; } + + uint8_t read(unsigned offset) const { return memory[offset]; } + void write(unsigned offset, uint8_t data) const { memory[offset] = data; } + + vectorstream(vector &memory) : memory(memory), poffset(0), pwritable(true) {} + vectorstream(const vector &memory) : memory((vector&)memory), poffset(0), pwritable(false) {} + +protected: + vector &memory; + mutable unsigned poffset, pwritable; +}; + +} + +#endif diff --git a/ananke/nall/stream/zip.hpp b/ananke/nall/stream/zip.hpp new file mode 100644 index 00000000..94aa3992 --- /dev/null +++ b/ananke/nall/stream/zip.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_STREAM_ZIP_HPP +#define NALL_STREAM_ZIP_HPP + +#include + +namespace nall { + +struct zipstream : memorystream { + using stream::read; + using stream::write; + + zipstream(const stream &stream, const string &filter = "*") { + unsigned size = stream.size(); + uint8_t *data = new uint8_t[size]; + stream.read(data, size); + + unzip archive; + if(archive.open(data, size) == false) return; + delete[] data; + + for(auto &file : archive.file) { + if(file.name.wildcard(filter)) { + auto buffer = archive.extract(file); + psize = buffer.size(); + pdata = buffer.move(); + return; + } + } + } + + ~zipstream() { + if(pdata) delete[] pdata; + } +}; + +} + +#endif diff --git a/ananke/nall/string.hpp b/ananke/nall/string.hpp new file mode 100644 index 00000000..82c1bbb1 --- /dev/null +++ b/ananke/nall/string.hpp @@ -0,0 +1,53 @@ +#ifndef NALL_STRING_HPP +#define NALL_STRING_HPP + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NALL_STRING_INTERNAL_HPP +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#undef NALL_STRING_INTERNAL_HPP + +#endif diff --git a/ananke/nall/string/base.hpp b/ananke/nall/string/base.hpp new file mode 100644 index 00000000..5866f877 --- /dev/null +++ b/ananke/nall/string/base.hpp @@ -0,0 +1,219 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + struct cstring; + struct string; + struct lstring; + template inline const char* to_string(T); + + struct cstring { + inline operator const char*() const; + inline unsigned length() const; + inline bool operator==(const char*) const; + inline bool operator!=(const char*) const; + inline optional position(const char *key) const; + inline optional iposition(const char *key) const; + inline cstring& operator=(const char *data); + inline cstring(const char *data); + inline cstring(); + + protected: + const char *data; + }; + + struct string { + inline static string read(const string &filename); + inline static string date(); + inline static string time(); + inline static string datetime(); + + inline void reserve(unsigned); + inline bool empty() const; + + template inline string& assign(Args&&... args); + template inline string& append(Args&&... args); + + inline bool readfile(const string&); + + template inline string& replace(const char*, const char*); + template inline string& ireplace(const char*, const char*); + template inline string& qreplace(const char*, const char*); + template inline string& iqreplace(const char*, const char*); + + inline unsigned length() const; + inline unsigned capacity() const; + + template inline lstring split(const char*) const; + template inline lstring isplit(const char*) const; + template inline lstring qsplit(const char*) const; + template inline lstring iqsplit(const char*) 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& qlower(); + inline string& qupper(); + 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 = " ", const char *rkey = 0); + inline string& strip(); + + inline optional position(const char *key) const; + inline optional iposition(const char *key) const; + inline optional qposition(const char *key) const; + inline optional iqposition(const char *key) const; + + 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(); + + inline char* begin() { return &data[0]; } + inline char* end() { return &data[length()]; } + inline const char* begin() const { return &data[0]; } + inline const char* end() const { return &data[length()]; } + + //internal functions + inline string& assign_(const char*); + inline string& append_(const char*); + + protected: + char *data; + unsigned size; + + template inline string& ureplace(const char*, const char*); + + #if defined(QSTRING_H) + public: + inline operator QString() const; + #endif + }; + + struct lstring : vector { + inline optional find(const char*) const; + inline string concatenate(const char*) const; + inline void append() {} + template inline void append(const string&, Args&&...); + + template inline lstring& split(const char*, const char*); + template inline lstring& isplit(const char*, const char*); + template inline lstring& qsplit(const char*, const char*); + template inline lstring& iqsplit(const char*, const char*); + + inline bool operator==(const lstring&) const; + inline bool operator!=(const lstring&) const; + + inline lstring& operator=(const lstring&); + inline lstring& operator=(lstring&); + inline lstring& operator=(lstring&&); + + template inline lstring(Args&&... args); + inline lstring(const lstring&); + inline lstring(lstring&); + inline lstring(lstring&&); + + protected: + template inline lstring& usplit(const char*, const char*); + }; + + //compare.hpp + inline char chrlower(char c); + inline char chrupper(char c); + inline int istrcmp(const char *str1, const char *str2); + inline bool strbegin(const char *str, const char *key); + inline bool istrbegin(const char *str, const char *key); + inline bool strend(const char *str, const char *key); + inline bool istrend(const char *str, const char *key); + + //convert.hpp + inline char* strlower(char *str); + inline char* strupper(char *str); + inline char* qstrlower(char *str); + inline char* qstrupper(char *str); + inline char* strtr(char *dest, const char *before, const char *after); + + //math.hpp + inline bool strint(const char *str, int &result); + inline bool strmath(const char *str, int &result); + + //platform.hpp + inline string activepath(); + inline string realpath(const string &name); + inline string userpath(); + inline string configpath(); + inline string temppath(); + + //strm.hpp + inline unsigned strmcpy(char *target, const char *source, unsigned length); + inline unsigned strmcat(char *target, const char *source, unsigned length); + inline bool strccpy(char *target, const char *source, unsigned length); + inline bool strccat(char *target, const char *source, unsigned length); + inline void strpcpy(char *&target, const char *source, unsigned &length); + + //strpos.hpp + inline optional strpos(const char *str, const char *key); + inline optional istrpos(const char *str, const char *key); + inline optional qstrpos(const char *str, const char *key); + inline optional iqstrpos(const char *str, const char *key); + template inline optional ustrpos(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 = " ", const char *rkey = 0); + inline char* strip(char *s); + + //utility.hpp + template alwaysinline bool chrequal(char x, char y); + template alwaysinline bool quoteskip(T *&p); + template alwaysinline bool quotecopy(char *&t, T *&p); + inline string substr(const char *src, unsigned start = 0, unsigned length = ~0u); + inline string sha256(const uint8_t *data, unsigned size); + + inline char* integer(char *result, intmax_t value); + inline char* decimal(char *result, uintmax_t value); + + template inline string integer(intmax_t value); + template inline string linteger(intmax_t value); + template inline string decimal(uintmax_t value); + template inline string ldecimal(uintmax_t value); + template inline string hex(uintmax_t value); + template inline string binary(uintmax_t value); + inline unsigned fp(char *str, long double value); + inline string fp(long double value); + + //variadic.hpp + template inline void print(Args&&... args); + + //wildcard.hpp + inline bool wildcard(const char *str, const char *pattern); + inline bool iwildcard(const char *str, const char *pattern); +}; + +#endif diff --git a/ananke/nall/string/bsv.hpp b/ananke/nall/string/bsv.hpp new file mode 100644 index 00000000..d9415d53 --- /dev/null +++ b/ananke/nall/string/bsv.hpp @@ -0,0 +1,76 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//BSV v1.0 parser +//revision 0.02 + +namespace nall { + +struct BSV { + static inline string 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; + } + + static inline string 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/ananke/nall/string/cast.hpp b/ananke/nall/string/cast.hpp new file mode 100644 index 00000000..7c7e276b --- /dev/null +++ b/ananke/nall/string/cast.hpp @@ -0,0 +1,185 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +//convert any (supported) type to a const char* without constructing a new nall::string +//this is used inside istring(...) to build nall::string values +template struct stringify; + +// base types + +template<> struct stringify { + bool value; + operator const char*() const { return value ? "true" : "false"; } + stringify(bool value) : value(value) {} +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(char value) { integer(data, value); } +}; + +// signed integers + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed char value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed short value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed int value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed long value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed long long value) { integer(data, value); } +}; + +template struct stringify> { + char data[256]; + operator const char*() const { return data; } + stringify(int_t value) { integer(data, value); } +}; + +// unsigned integers + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned char value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned short value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned int value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned long value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned long long value) { decimal(data, value); } +}; + +template struct stringify> { + char data[256]; + operator const char*() const { return data; } + stringify(uint_t value) { decimal(data, value); } +}; + +// floating-point + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(float value) { fp(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(double value) { fp(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(long double value) { fp(data, value); } +}; + +// strings + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(char *value) : value(value) {} +}; + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(const char *value) : value(value) {} +}; + +template<> struct stringify { + const string &value; + operator const char*() const { return value; } + stringify(const string &value) : value(value) {} +}; + +template<> struct stringify { + const string &value; + operator const char*() const { return value; } + stringify(const string &value) : value(value) {} +}; + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(const cstring &value) : value(value) {} +}; + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(const cstring &value) : value(value) {} +}; + +#if defined(QSTRING_H) + +template<> struct stringify { + const QString &value; + operator const char*() const { return value.toUtf8().constData(); } + stringify(const QString &value) : value(value) {} +}; + +template<> struct stringify { + const QString &value; + operator const char*() const { return value.toUtf8().constData(); } + stringify(const QString &value) : value(value) {} +}; + +string::operator QString() const { + return QString::fromUtf8(*this); +} + +#endif + +// + +template stringify make_string(T value) { + return stringify(std::forward(value)); +} + +} + +#endif diff --git a/ananke/nall/string/compare.hpp b/ananke/nall/string/compare.hpp new file mode 100644 index 00000000..941c8e67 --- /dev/null +++ b/ananke/nall/string/compare.hpp @@ -0,0 +1,69 @@ +#ifdef NALL_STRING_INTERNAL_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 istrcmp(const char *str1, const char *str2) { + while(*str1) { + if(chrlower(*str1) != chrlower(*str2)) break; + str1++, str2++; + } + return (int)chrlower(*str1) - (int)chrlower(*str2); +} + +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 istrbegin(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 istrend(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/ananke/nall/string/convert.hpp b/ananke/nall/string/convert.hpp new file mode 100644 index 00000000..f5a2a780 --- /dev/null +++ b/ananke/nall/string/convert.hpp @@ -0,0 +1,64 @@ +#ifdef NALL_STRING_INTERNAL_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* qstrlower(char *s) { + if(!s) return 0; + bool quoted = false; + while(*s) { + if(*s == '\"' || *s == '\'') quoted ^= 1; + if(quoted == false && *s >= 'A' && *s <= 'Z') *s += 0x20; + s++; + } +} + +char* qstrupper(char *s) { + if(!s) return 0; + bool quoted = false; + while(*s) { + if(*s == '\"' || *s == '\'') quoted ^= 1; + if(quoted == false && *s >= 'a' && *s <= 'z') *s -= 0x20; + s++; + } +} + +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; +} + +} + +#endif diff --git a/ananke/nall/string/core.hpp b/ananke/nall/string/core.hpp new file mode 100644 index 00000000..d5f75f3a --- /dev/null +++ b/ananke/nall/string/core.hpp @@ -0,0 +1,200 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +static void istring(string &output) { +} + +template +static void istring(string &output, const T &value, Args&&... args) { + output.append_(make_string(value)); + istring(output, std::forward(args)...); +} + +void string::reserve(unsigned size_) { + if(size_ > size) { + size = size_; + data = (char*)realloc(data, size + 1); + data[size] = 0; + } +} + +bool string::empty() const { + return !*data; +} + +template string& string::assign(Args&&... args) { + *data = 0; + istring(*this, std::forward(args)...); + return *this; +} + +template string& string::append(Args&&... args) { + istring(*this, std::forward(args)...); + return *this; +} + +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::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) { + if(&value == this) return *this; + assign(value); + return *this; +} + +string& string::operator=(string &&source) { + if(&source == this) return *this; + if(data) free(data); + size = source.size; + data = source.data; + source.data = nullptr; + source.size = 0; + return *this; +} + +template string::string(Args&&... args) { + size = 64; + data = (char*)malloc(size + 1); + *data = 0; + istring(*this, std::forward(args)...); +} + +string::string(const string &value) { + if(&value == this) return; + size = strlen(value); + data = strdup(value); +} + +string::string(string &&source) { + if(&source == this) return; + size = source.size; + data = source.data; + source.data = nullptr; +} + +string::~string() { + if(data) free(data); +} + +bool string::readfile(const string &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 }; +} + +string lstring::concatenate(const char *separator) const { + string output; + for(unsigned i = 0; i < size(); i++) { + output.append(operator[](i), i < size() - 1 ? separator : ""); + } + return output; +} + +template void lstring::append(const string &data, Args&&... args) { + vector::append(data); + append(std::forward(args)...); +} + +bool lstring::operator==(const lstring &source) const { + if(this == &source) return true; + if(size() != source.size()) return false; + for(unsigned n = 0; n < size(); n++) { + if(operator[](n) != source[n]) return false; + } + return true; +} + +bool lstring::operator!=(const lstring &source) const { + return !operator==(source); +} + +lstring& lstring::operator=(const lstring &source) { + vector::operator=(source); + return *this; +} + +lstring& lstring::operator=(lstring &source) { + vector::operator=(source); + return *this; +} + +lstring& lstring::operator=(lstring &&source) { + vector::operator=(std::move(source)); + return *this; +} + +template lstring::lstring(Args&&... args) { + append(std::forward(args)...); +} + +lstring::lstring(const lstring &source) { + vector::operator=(source); +} + +lstring::lstring(lstring &source) { + vector::operator=(source); +} + +lstring::lstring(lstring &&source) { + vector::operator=(std::move(source)); +} + +} + +#endif diff --git a/ananke/nall/string/cstring.hpp b/ananke/nall/string/cstring.hpp new file mode 100644 index 00000000..13b508ff --- /dev/null +++ b/ananke/nall/string/cstring.hpp @@ -0,0 +1,21 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//const string: +//bind a const char* pointer to an object that has various testing functionality; +//yet lacks the memory allocation and modification functionality of the string class + +namespace nall { + +cstring::operator const char*() const { return data; } +unsigned cstring::length() const { return strlen(data); } +bool cstring::operator==(const char *s) const { return !strcmp(data, s); } +bool cstring::operator!=(const char *s) const { return strcmp(data, s); } +optional cstring::position (const char *key) const { return strpos(data, key); } +optional cstring::iposition(const char *key) const { return istrpos(data, key); } +cstring& cstring::operator=(const char *data) { this->data = data; return *this; } +cstring::cstring(const char *data) : data(data) {} +cstring::cstring() : data("") {} + +} + +#endif diff --git a/ananke/nall/string/datetime.hpp b/ananke/nall/string/datetime.hpp new file mode 100644 index 00000000..5382fdfd --- /dev/null +++ b/ananke/nall/string/datetime.hpp @@ -0,0 +1,31 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string string::date() { + time_t timestamp = ::time(0); + tm *info = localtime(×tamp); + return { + decimal<4, '0'>(1900 + info->tm_year), "-", + decimal<2, '0'>(1 + info->tm_mon), "-", + decimal<2, '0'>(info->tm_mday) + }; +} + +string string::time() { + time_t timestamp = ::time(0); + tm *info = localtime(×tamp); + return { + decimal<2, '0'>(info->tm_hour), ":", + decimal<2, '0'>(info->tm_min), ":", + decimal<2, '0'>(info->tm_sec) + }; +} + +string string::datetime() { + return {string::date(), " ", string::time()}; +} + +} + +#endif diff --git a/ananke/nall/string/filename.hpp b/ananke/nall/string/filename.hpp new file mode 100644 index 00000000..19b5f117 --- /dev/null +++ b/ananke/nall/string/filename.hpp @@ -0,0 +1,74 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +// "/foo/bar.c" -> "/foo/" +// "/foo/" -> "/foo/" +// "bar.c" -> "./" +inline string dir(string name) { + for(signed i = name.length(); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + name[i + 1] = 0; + break; + } + if(i == 0) name = "./"; + } + return name; +} + +// "/foo/bar.c" -> "bar.c" +// "/foo/" -> "" +// "bar.c" -> "bar.c" +inline string notdir(string name) { + for(signed i = name.length(); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + return (const char*)name + i + 1; + } + } + return name; +} + +// "/foo/bar/baz" -> "/foo/bar/" +// "/foo/bar/" -> "/foo/" +// "/foo/bar" -> "/foo/" +inline string parentdir(string name) { + unsigned length = name.length(), paths = 0, prev, last; + for(unsigned i = 0; i < length; i++) { + if(name[i] == '/' || name[i] == '\\') { + paths++; + prev = last; + last = i; + } + } + if(last + 1 == length) last = prev; //if name ends in slash; use previous slash + if(paths > 1) name[last + 1] = 0; + return name; +} + +// "/foo/bar.c" -> "/foo/bar" +inline string basename(string name) { + for(signed i = name.length(); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') break; //file has no extension + if(name[i] == '.') { + name[i] = 0; + break; + } + } + return name; +} + +// "/foo/bar.c" -> "c" +// "/foo/bar" -> "" +inline string extension(string name) { + for(signed i = name.length(); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') return ""; //file has no extension + if(name[i] == '.') { + return (const char*)name + i + 1; + } + } + return name; +} + +} + +#endif diff --git a/ananke/nall/string/markup/bml.hpp b/ananke/nall/string/markup/bml.hpp new file mode 100644 index 00000000..338ca406 --- /dev/null +++ b/ananke/nall/string/markup/bml.hpp @@ -0,0 +1,147 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//BML v1.0 parser +//revision 0.02 + +namespace nall { +namespace BML { + +struct Node : Markup::Node { +protected: + //test to verify if a valid character for a node name + bool valid(char p) const { //A-Z, a-z, 0-9, -./ + return p - 'A' < 26u || p - 'a' < 26u || p - '0' < 10u || p - '-' < 3u; + } + + //determine indentation level, without incrementing pointer + unsigned readDepth(const char *p) { + unsigned depth = 0; + while(p[depth] == '\t' || p[depth] == ' ') depth++; + return depth; + } + + //determine indentation level + unsigned parseDepth(const char *&p) { + unsigned depth = readDepth(p); + p += depth; + return depth; + } + + //read name + void parseName(const char *&p) { + unsigned length = 0; + while(valid(p[length])) length++; + if(length == 0) throw "Invalid node name"; + name = substr(p, 0, length); + p += length; + } + + void parseData(const char *&p) { + if(*p == '=' && *(p + 1) == '\"') { + unsigned length = 2; + while(p[length] && p[length] != '\n' && p[length] != '\"') length++; + if(p[length] != '\"') throw "Unescaped value"; + data = substr(p, 2, length - 2); + p += length + 1; + } else if(*p == '=') { + unsigned length = 1; + while(p[length] && p[length] != '\n' && p[length] != '\"' && p[length] != ' ') length++; + if(p[length] == '\"') throw "Illegal character in value"; + data = substr(p, 1, length - 1); + p += length; + } else if(*p == ':') { + unsigned length = 1; + while(p[length] && p[length] != '\n') length++; + data = {substr(p, 1, length - 1), "\n"}; + p += length; + } + } + + //read all attributes for a node + void parseAttributes(const char *&p) { + while(*p && *p != '\n') { + if(*p != ' ') throw "Invalid node name"; + while(*p == ' ') p++; //skip excess spaces + + Node node; + node.attribute = true; + unsigned length = 0; + while(valid(p[length])) length++; + if(length == 0) throw "Invalid attribute name"; + node.name = substr(p, 0, length); + node.parseData(p += length); + children.append(node); + } + } + + //read a node and all of its children nodes + void parseNode(const char *&p) { + level = parseDepth(p); + parseName(p); + parseData(p); + parseAttributes(p); + if(*p++ != '\n') throw "Missing line feed"; + + while(*p) { + if(*p == '\n') { p++; continue; } + + unsigned depth = readDepth(p); + if(depth <= level) break; + + if(p[depth] == ':') { + p += depth; + unsigned length = 0; + while(p[length] && p[length] != '\n') length++; + data.append(substr(p, 1, length - 1), "\n"); + p += length; + continue; + } + + Node node; + node.parseNode(p); + children.append(node); + } + + data.rtrim<1>("\n"); + } + + //read top-level nodes + void parse(const char *p) { + while(*p) { + Node node; + node.parseNode(p); + if(node.level > 0) throw "Root nodes cannot be indented"; + children.append(node); + } + } + + friend class Document; +}; + +struct Document : Node { + string error; + + bool load(string document) { + name = "{root}", data = ""; + + try { + document.replace("\r", ""); + while(document.position("\n\n")) document.replace("\n\n", "\n"); + parse(document); + } catch(const char *perror) { + error = perror; + children.reset(); + return false; + } + return true; + } + + Document(const string &document = "") { + load(document); + } +}; + +} +} + +#endif diff --git a/ananke/nall/string/markup/document.hpp b/ananke/nall/string/markup/document.hpp new file mode 100644 index 00000000..34465a4f --- /dev/null +++ b/ananke/nall/string/markup/document.hpp @@ -0,0 +1,14 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { +namespace Markup { + +inline Node Document(const string &markup) { + if(markup.beginswith("<")) return XML::Document(markup); + return BML::Document(markup); +} + +} +} + +#endif diff --git a/ananke/nall/string/markup/node.hpp b/ananke/nall/string/markup/node.hpp new file mode 100644 index 00000000..77822373 --- /dev/null +++ b/ananke/nall/string/markup/node.hpp @@ -0,0 +1,146 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//note: specific markups inherit from Markup::Node +//vector will slice any data; so derived nodes must not contain data nor virtual functions +//vector would incur a large performance penalty and greatly increased complexity + +namespace nall { +namespace Markup { + +struct Node { + string name; + string data; + bool attribute; + + bool exists() const { + return !name.empty(); + } + + string text() const { + return string{data}.strip(); + } + + intmax_t integer() const { + return numeral(text()); + } + + uintmax_t decimal() const { + return numeral(text()); + } + + void reset() { + children.reset(); + } + + bool evaluate(const string &query) const { + if(query.empty()) return true; + lstring rules = string{query}.replace(" ", "").split(","); + + for(auto &rule : rules) { + enum class Comparator : unsigned { ID, EQ, NE, LT, LE, GT, GE }; + auto comparator = Comparator::ID; + if(rule.wildcard("*!=*")) comparator = Comparator::NE; + else if(rule.wildcard("*<=*")) comparator = Comparator::LE; + else if(rule.wildcard("*>=*")) comparator = Comparator::GE; + else if(rule.wildcard ("*=*")) comparator = Comparator::EQ; + else if(rule.wildcard ("*<*")) comparator = Comparator::LT; + else if(rule.wildcard ("*>*")) comparator = Comparator::GT; + + if(comparator == Comparator::ID) { + if(find(rule).size()) continue; + return false; + } + + lstring side; + switch(comparator) { + case Comparator::EQ: side = rule.split<1> ("="); break; + case Comparator::NE: side = rule.split<1>("!="); break; + case Comparator::LT: side = rule.split<1> ("<"); break; + case Comparator::LE: side = rule.split<1>("<="); break; + case Comparator::GT: side = rule.split<1> (">"); break; + case Comparator::GE: side = rule.split<1>(">="); break; + } + + string data = text(); + if(side(0).empty() == false) { + auto result = find(side(0)); + if(result.size() == 0) return false; + data = result(0).data; + } + + switch(comparator) { + case Comparator::EQ: if(data.wildcard(side(1)) == true) continue; break; + case Comparator::NE: if(data.wildcard(side(1)) == false) continue; break; + case Comparator::LT: if(numeral(data) < numeral(side(1))) continue; break; + case Comparator::LE: if(numeral(data) <= numeral(side(1))) continue; break; + case Comparator::GT: if(numeral(data) > numeral(side(1))) continue; break; + case Comparator::GE: if(numeral(data) >= numeral(side(1))) continue; break; + } + + return false; + } + + return true; + } + + vector find(const string &query) const { + vector result; + + lstring path = query.split("/"); + string name = path.take(0), rule; + unsigned lo = 0u, hi = ~0u; + + if(name.wildcard("*[*]")) { + lstring side = name.split<1>("["); + name = side(0); + side = side(1).rtrim<1>("]").split<1>("-"); + lo = side(0).empty() ? 0u : numeral(side(0)); + hi = side(1).empty() ? ~0u : numeral(side(1)); + } + + if(name.wildcard("*(*)")) { + lstring side = name.split<1>("("); + name = side(0); + rule = side(1).rtrim<1>(")"); + } + + unsigned position = 0; + for(auto &node : children) { + if(node.name.wildcard(name) == false) continue; + if(node.evaluate(rule) == false) continue; + + bool inrange = position >= lo && position <= hi; + position++; + if(inrange == false) continue; + + if(path.size() == 0) result.append(node); + else { + auto list = node.find(path.concatenate("/")); + for(auto &item : list) result.append(item); + } + } + + return result; + } + + Node operator[](const string &query) const { + auto result = find(query); + return result(0); + } + + Node* begin() { return children.begin(); } + Node* end() { return children.end(); } + const Node* begin() const { return children.begin(); } + const Node* end() const { return children.end(); } + + Node() : attribute(false), level(0) {} + +protected: + unsigned level; + vector children; +}; + +} +} + +#endif diff --git a/ananke/nall/string/markup/xml.hpp b/ananke/nall/string/markup/xml.hpp new file mode 100644 index 00000000..d3a3e15a --- /dev/null +++ b/ananke/nall/string/markup/xml.hpp @@ -0,0 +1,218 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//XML v1.0 subset parser +//revision 0.03 + +namespace nall { +namespace XML { + +struct Node : Markup::Node { +protected: + inline string escape() const { + string result = data; + result.replace("&", "&"); + result.replace("<", "<"); + result.replace(">", ">"); + if(attribute == false) return result; + result.replace("\'", "'"); + result.replace("\"", """); + return result; + } + + inline bool isName(char c) const { + if(c >= 'A' && c <= 'Z') return true; + if(c >= 'a' && c <= 'z') return true; + if(c >= '0' && c <= '9') return true; + if(c == '.' || c == '_') return true; + if(c == '?') return true; + return false; + } + + inline bool isWhitespace(char c) const { + if(c == ' ' || c == '\t') return true; + if(c == '\r' || c == '\n') return true; + return false; + } + + //copy part of string from source document into target string; decode markup while copying + inline void copy(string &target, const char *source, unsigned length) { + target.reserve(length + 1); + + #if defined(NALL_XML_LITERAL) + memcpy(target(), source, length); + target[length] = 0; + return; + #endif + + char *output = target(); + while(length) { + if(*source == '&') { + if(!memcmp(source, "<", 4)) { *output++ = '<'; source += 4; length -= 4; continue; } + if(!memcmp(source, ">", 4)) { *output++ = '>'; source += 4; length -= 4; continue; } + if(!memcmp(source, "&", 5)) { *output++ = '&'; source += 5; length -= 5; continue; } + if(!memcmp(source, "'", 6)) { *output++ = '\''; source += 6; length -= 6; continue; } + if(!memcmp(source, """, 6)) { *output++ = '\"'; source += 6; length -= 6; continue; } + } + + if(attribute == false && source[0] == '<' && source[1] == '!') { + //comment + if(!memcmp(source, "", 3)) source++, length--; + source += 3, length -= 3; + continue; + } + + //CDATA + if(!memcmp(source, "", 3)) *output++ = *source++, length--; + source += 3, length -= 3; + continue; + } + } + + *output++ = *source++, length--; + } + *output = 0; + } + + inline bool parseExpression(const char *&p) { + if(*(p + 1) != '!') return false; + + //comment + if(!memcmp(p, "", 3)) p++; + if(!*p) throw "unclosed comment"; + p += 3; + return true; + } + + //CDATA + if(!memcmp(p, "", 3)) p++; + if(!*p) throw "unclosed CDATA"; + p += 3; + return true; + } + + //DOCTYPE + if(!memcmp(p, "') counter--; + } while(counter); + return true; + } + + return false; + } + + //returns true if tag closes itself (); false if not () + inline bool parseHead(const char *&p) { + //parse name + const char *nameStart = ++p; //skip '<' + while(isName(*p)) p++; + const char *nameEnd = p; + copy(name, nameStart, nameEnd - nameStart); + if(name.empty()) throw "missing element name"; + + //parse attributes + while(*p) { + while(isWhitespace(*p)) p++; + if(!*p) throw "unclosed attribute"; + if(*p == '?' || *p == '/' || *p == '>') break; + + //parse attribute name + Node attribute; + attribute.attribute = true; + + const char *nameStart = p; + while(isName(*p)) p++; + const char *nameEnd = p; + copy(attribute.name, nameStart, nameEnd - nameStart); + if(attribute.name.empty()) throw "missing attribute name"; + + //parse attribute data + if(*p++ != '=') throw "missing attribute value"; + char terminal = *p++; + if(terminal != '\'' && terminal != '\"') throw "attribute value not quoted"; + const char *dataStart = p; + while(*p && *p != terminal) p++; + if(!*p) throw "missing attribute data terminal"; + const char *dataEnd = p++; //skip closing terminal + + copy(attribute.data, dataStart, dataEnd - dataStart); + children.append(attribute); + } + + //parse closure + if(*p == '?' && *(p + 1) == '>') { p += 2; return true; } + if(*p == '/' && *(p + 1) == '>') { p += 2; return true; } + if(*p == '>') { p += 1; return false; } + throw "invalid element tag"; + } + + //parse element and all of its child elements + inline void parseElement(const char *&p) { + Node node; + if(node.parseHead(p) == false) node.parse(p); + children.append(node); + } + + //return true if matches this node's name + inline bool parseClosureElement(const char *&p) { + if(p[0] != '<' || p[1] != '/') return false; + p += 2; + const char *nameStart = p; + while(*p && *p != '>') p++; + if(*p != '>') throw "unclosed closure element"; + const char *nameEnd = p++; + if(memcmp(name, nameStart, nameEnd - nameStart)) throw "closure element name mismatch"; + return true; + } + + //parse contents of an element + inline void parse(const char *&p) { + const char *dataStart = p, *dataEnd = p; + + while(*p) { + while(*p && *p != '<') p++; + if(!*p) break; + dataEnd = p; + if(parseClosureElement(p) == true) break; + if(parseExpression(p) == true) continue; + parseElement(p); + } + + copy(data, dataStart, dataEnd - dataStart); + } +}; + +struct Document : Node { + string error; + + inline bool load(const char *document) { + if(document == nullptr) return false; + reset(); + try { + parse(document); + } catch(const char *error) { + reset(); + this->error = error; + return false; + } + return true; + } + + inline Document() {} + inline Document(const char *document) { load(document); } +}; + +} +} + +#endif diff --git a/ananke/nall/string/math-fixed-point.hpp b/ananke/nall/string/math-fixed-point.hpp new file mode 100644 index 00000000..a61b23f3 --- /dev/null +++ b/ananke/nall/string/math-fixed-point.hpp @@ -0,0 +1,166 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace fixedpoint { + +static nall::function eval_fallback; + +static intmax_t eval_integer(const char *& s) { + if(!*s) throw "unrecognized integer"; + intmax_t 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 intmax_t eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized token"; + intmax_t 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 if(eval_fallback) value = eval_fallback(s); //optional user-defined syntax parsing + + 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 == '/') { intmax_t result = eval(++s, 13); if(result == 0) throw "division by zero"; value /= result; continue; } + if(x == '%') { intmax_t result = eval(++s, 13); if(result == 0) throw "division by zero"; value %= result; 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 == '?') { + intmax_t lhs = eval(++s, 2); + if(*s != ':') throw "mismatched ternary"; + intmax_t rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized token"; + } + + return value; +} + +static bool eval(const char *s, intmax_t &result) { + try { + result = eval(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +static intmax_t parse(const char *s) { + try { + intmax_t result = eval(s); + return result; + } catch(const char *) { + return 0; + } +} + +} + +#endif diff --git a/ananke/nall/string/math-floating-point.hpp b/ananke/nall/string/math-floating-point.hpp new file mode 100644 index 00000000..43a2f0f4 --- /dev/null +++ b/ananke/nall/string/math-floating-point.hpp @@ -0,0 +1,157 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace floatingpoint { + +static nall::function eval_fallback; + +static double eval_integer(const char *&s) { + if(!*s) throw "unrecognized integer"; + intmax_t value = 0, radix = 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' && y != '.') { + 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; } + if(*s == '.') { s++; break; } + return value; + } + //floating-point + while(true) { + if(*s >= '0' && *s <= '9') { radix = radix * 10 + (*s++ - '0'); continue; } + return atof(nall::string{ nall::decimal(value), ".", nall::decimal(radix) }); + } + } + + //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 double eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized token"; + double value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched group"; + } + + else if(x == '!') value = !eval(++s, 9); + else if(x == '+') value = +eval(++s, 9); + else if(x == '-') value = -eval(++s, 9); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else if(eval_fallback) value = eval_fallback(s); //optional user-defined syntax parsing + + else throw "unrecognized token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 9) break; + if(x == '*') { value *= eval(++s, 9); continue; } + if(x == '/') { double result = eval(++s, 9); if(result == 0.0) throw "division by zero"; value /= result; continue; } + + if(depth >= 8) break; + if(x == '+') { value += eval(++s, 8); continue; } + if(x == '-') { value -= eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 7); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 7); continue; } + if(x == '<') { value = value < eval(++s, 7); continue; } + if(x == '>') { value = value > eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 6); continue; } + 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 == '?') { + double lhs = eval(++s, 2); + if(*s != ':') throw "mismatched ternary"; + double rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized token"; + } + + return value; +} + +static bool eval(const char *s, double &result) { + try { + result = eval(s); + return true; + } catch(const char*e) { + result = 0; + return false; + } +} + +static double parse(const char *s) { + try { + double result = eval(s); + return result; + } catch(const char *) { + return 0; + } +} + +} + +#endif diff --git a/ananke/nall/string/platform.hpp b/ananke/nall/string/platform.hpp new file mode 100644 index 00000000..5c44a313 --- /dev/null +++ b/ananke/nall/string/platform.hpp @@ -0,0 +1,75 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string activepath() { + string result; + #ifdef _WIN32 + wchar_t path[PATH_MAX] = L""; + auto unused = _wgetcwd(path, PATH_MAX); + result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #else + char path[PATH_MAX] = ""; + auto unused = getcwd(path, PATH_MAX); + result = path; + #endif + if(result.empty()) result = "."; + if(result.endswith("/") == false) result.append("/"); + return result; +} + +string realpath(const string &name) { + string result; + #ifdef _WIN32 + wchar_t path[PATH_MAX] = L""; + if(_wfullpath(path, utf16_t(name), PATH_MAX)) result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #else + char path[PATH_MAX] = ""; + if(::realpath(name, path)) result = path; + #endif + if(result.empty()) result = {activepath(), name}; + return result; +} + +string userpath() { + string result; + #ifdef _WIN32 + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, path); + result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #else + char path[PATH_MAX] = ""; + struct passwd *userinfo = getpwuid(getuid()); + if(userinfo) strcpy(path, userinfo->pw_dir); + result = path; + #endif + if(result.empty()) result = "."; + if(result.endswith("/") == false) result.append("/"); + return result; +} + +string configpath() { + #ifdef _WIN32 + return userpath(); + #else + return {userpath(), ".config/"}; + #endif +} + +string temppath() { + #ifdef _WIN32 + wchar_t path[PATH_MAX] = L""; + GetTempPathW(PATH_MAX, path); +//path.transform("\\", "/"); + return (const char*)utf8_t(path); + #else + return "/tmp/"; + #endif +} + +} + +#endif diff --git a/ananke/nall/string/replace.hpp b/ananke/nall/string/replace.hpp new file mode 100644 index 00000000..2bd1412f --- /dev/null +++ b/ananke/nall/string/replace.hpp @@ -0,0 +1,51 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template +string& string::ureplace(const char *key, const char *token) { + if(!key || !*key) return *this; + enum : unsigned { limit = Limit ? Limit : ~0u }; + + const char *p = data; + unsigned counter = 0, keyLength = 0; + + while(*p) { + if(quoteskip(p)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) { counter++; p += n; keyLength = n; break; } + if(!chrequal(key[n], p[n])) { p++; break; } + } + } + if(counter == 0) return *this; + if(Limit) counter = min(counter, Limit); + + char *t = data, *base; + unsigned tokenLength = strlen(token); + if(tokenLength > keyLength) { + t = base = strdup(data); + reserve((unsigned)(p - data) + ((tokenLength - keyLength) * counter)); + } + char *o = data; + + while(*t && counter) { + if(quotecopy(o, t)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) { counter--; memcpy(o, token, tokenLength); t += keyLength; o += tokenLength; break; } + if(!chrequal(key[n], t[n])) { *o++ = *t++; break; } + } + } + do *o++ = *t; while(*t++); + if(tokenLength > keyLength) free(base); + + return *this; +} + +template string &string::replace(const char *key, const char *token) { return ureplace(key, token); } +template string &string::ireplace(const char *key, const char *token) { return ureplace(key, token); } +template string &string::qreplace(const char *key, const char *token) { return ureplace(key, token); } +template string &string::iqreplace(const char *key, const char *token) { return ureplace(key, token); } + +}; + +#endif diff --git a/ananke/nall/string/split.hpp b/ananke/nall/string/split.hpp new file mode 100644 index 00000000..bb12a91b --- /dev/null +++ b/ananke/nall/string/split.hpp @@ -0,0 +1,36 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template lstring& lstring::usplit(const char *key, const char *base) { + reset(); + if(!key || !*key) return *this; + + const char *p = base; + + while(*p) { + if(Limit) if(size() >= Limit) break; + if(quoteskip(p)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) { + append(substr(base, 0, p - base)); + p += n; + base = p; + break; + } + if(!chrequal(key[n], p[n])) { p++; break; } + } + } + + append(base); + return *this; +} + +template lstring& lstring::split(const char *key, const char *src) { return usplit(key, src); } +template lstring& lstring::isplit(const char *key, const char *src) { return usplit(key, src); } +template lstring& lstring::qsplit(const char *key, const char *src) { return usplit(key, src); } +template lstring& lstring::iqsplit(const char *key, const char *src) { return usplit(key, src); } + +}; + +#endif diff --git a/ananke/nall/string/static.hpp b/ananke/nall/string/static.hpp new file mode 100644 index 00000000..ca521cb0 --- /dev/null +++ b/ananke/nall/string/static.hpp @@ -0,0 +1,13 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string string::read(const string &filename) { + string data; + data.readfile(filename); + return data; +} + +} + +#endif diff --git a/ananke/nall/string/strm.hpp b/ananke/nall/string/strm.hpp new file mode 100644 index 00000000..21d05652 --- /dev/null +++ b/ananke/nall/string/strm.hpp @@ -0,0 +1,45 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +// +//strmcpy, strmcat created by byuu +// + +//return = strlen(target) +unsigned strmcpy(char *target, const char *source, unsigned length) { + const char *origin = target; + if(length) { + while(*source && --length) *target++ = *source++; + *target = 0; + } + return target - origin; +} + +//return = strlen(target) +unsigned strmcat(char *target, const char *source, unsigned length) { + const char *origin = target; + while(*target && length) target++, length--; + return (target - origin) + strmcpy(target, source, length); +} + +//return = true when all of source was copied +bool strccpy(char *target, const char *source, unsigned length) { + return !source[strmcpy(target, source, length)]; +} + +//return = true when all of source was copied +bool strccat(char *target, const char *source, unsigned length) { + while(*target && length) target++, length--; + return !source[strmcpy(target, source, length)]; +} + +//return = reserved for future use +void strpcpy(char *&target, const char *source, unsigned &length) { + unsigned offset = strmcpy(target, source, length); + target += offset, length -= offset; +} + +} + +#endif diff --git a/ananke/nall/string/strpos.hpp b/ananke/nall/string/strpos.hpp new file mode 100644 index 00000000..fe563a6c --- /dev/null +++ b/ananke/nall/string/strpos.hpp @@ -0,0 +1,33 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//usage example: +//if(auto position = strpos(str, key)) print(position(), "\n"); +//prints position of key within str; but only if it is found + +namespace nall { + +template +optional ustrpos(const char *str, const char *key) { + const char *base = str; + + while(*str) { + if(quoteskip(str)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) return { true, (unsigned)(str - base) }; + if(str[n] == 0) return { false, 0 }; + if(!chrequal(str[n], key[n])) break; + } + str++; + } + + return { false, 0 }; +} + +optional strpos(const char *str, const char *key) { return ustrpos(str, key); } +optional istrpos(const char *str, const char *key) { return ustrpos(str, key); } +optional qstrpos(const char *str, const char *key) { return ustrpos(str, key); } +optional iqstrpos(const char *str, const char *key) { return ustrpos(str, key); } + +} + +#endif diff --git a/ananke/nall/string/trim.hpp b/ananke/nall/string/trim.hpp new file mode 100644 index 00000000..3e0c914f --- /dev/null +++ b/ananke/nall/string/trim.hpp @@ -0,0 +1,55 @@ +#ifdef NALL_STRING_INTERNAL_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, const char *rkey) { + if(rkey) return ltrim(rtrim(str, rkey), key); + return ltrim(rtrim(str, key), key); +} + +//remove whitespace characters from both left and right sides of string +char* strip(char *s) { + signed n = 0, p = 0; + while(s[n]) { + if(s[n] != ' ' && s[n] != '\t' && s[n] != '\r' && s[n] != '\n') break; + n++; + } + while(s[n]) s[p++] = s[n++]; + s[p--] = 0; + while(p >= 0) { + if(s[p] != ' ' && s[p] != '\t' && s[p] != '\r' && s[p] != '\n') break; + p--; + } + s[++p] = 0; + return s; +} + +} + +#endif diff --git a/ananke/nall/string/utf8.hpp b/ananke/nall/string/utf8.hpp new file mode 100644 index 00000000..77397bf2 --- /dev/null +++ b/ananke/nall/string/utf8.hpp @@ -0,0 +1,43 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +struct UTF8 { + unsigned size; //size of encoded codepoint + uint64_t data; //encoded codepoint + unsigned codepoint; //decoded codepoint +}; + +inline UTF8 utf8_read(const char *s) { + UTF8 utf8; + + if((*s & 0xfe) == 0xfc) utf8.size = 6; + else if((*s & 0xfc) == 0xf8) utf8.size = 5; + else if((*s & 0xf8) == 0xf0) utf8.size = 4; + else if((*s & 0xf0) == 0xe0) utf8.size = 3; + else if((*s & 0xe0) == 0xc0) utf8.size = 2; + else utf8.size = 1; + + utf8.data = 0; + for(unsigned n = 0; n < utf8.size; n++) { + utf8.data = (utf8.data << 8) | (uint8_t)s[n]; + } + + static uint8_t mask[] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; + utf8.codepoint = s[0] & mask[utf8.size]; + for(unsigned n = 1; n < utf8.size; n++) { + utf8.codepoint = (utf8.codepoint << 6) | (s[n] & 0x3f); + } + + return utf8; +} + +inline void utf8_write(char *s, const UTF8 &utf8) { + for(signed n = utf8.size - 1, shift = 0; n >= 0; n--, shift += 8) { + s[n] = utf8.data >> shift; + } +} + +} + +#endif diff --git a/ananke/nall/string/utility.hpp b/ananke/nall/string/utility.hpp new file mode 100644 index 00000000..b2f93d2c --- /dev/null +++ b/ananke/nall/string/utility.hpp @@ -0,0 +1,283 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template +bool chrequal(char x, char y) { + if(Insensitive) return chrlower(x) == chrlower(y); + return x == y; +} + +template +bool quoteskip(T *&p) { + if(Quoted == false) return false; + if(*p != '\'' && *p != '\"') return false; + + while(*p == '\'' || *p == '\"') { + char x = *p++; + while(*p && *p++ != x); + } + return true; +} + +template +bool quotecopy(char *&t, T *&p) { + if(Quoted == false) return false; + if(*p != '\'' && *p != '\"') return false; + + while(*p == '\'' || *p == '\"') { + char x = *p++; + *t++ = x; + while(*p && *p != x) *t++ = *p++; + *t++ = *p++; + } + return true; +} + +string substr(const char *src, unsigned start, unsigned length) { + string dest; + if(length == ~0u) { + //copy entire string + dest.reserve(strlen(src + start) + 1); + strcpy(dest(), src + start); + } else { + //copy partial string + dest.reserve(length + 1); + strmcpy(dest(), src + start, length + 1); + } + return dest; +} + +string sha256(const uint8_t *data, unsigned size) { + sha256_ctx sha; + uint8_t hash[32]; + sha256_init(&sha); + sha256_chunk(&sha, data, size); + sha256_final(&sha); + sha256_hash(&sha, hash); + string result; + for(auto &byte : hash) result.append(hex<2>(byte)); + return result; +} + +/* cast.hpp arithmetic -> string */ + +char* integer(char *result, intmax_t value) { + bool negative = value < 0; + if(negative) value = -value; + + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size++] = negative ? '-' : '+'; + + for(signed x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +char* decimal(char *result, uintmax_t value) { + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + + for(signed x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +/* general-purpose arithmetic -> string */ + +template string integer(intmax_t value) { + bool negative = value < 0; + if(negative) value = -value; + + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size++] = negative ? '-' : '+'; + buffer[size] = 0; + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string linteger(intmax_t value) { + bool negative = value < 0; + if(negative) value = -value; + + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size++] = negative ? '-' : '+'; + buffer[size] = 0; + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = 0, y = size - 1; x < length && y >= 0; x++, y--) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string decimal(uintmax_t value) { + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size] = 0; + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string ldecimal(uintmax_t value) { + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size] = 0; + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = 0, y = size - 1; x < length && y >= 0; x++, y--) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string hex(uintmax_t value) { + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value & 15; + buffer[size++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string binary(uintmax_t value) { + char buffer[256]; + unsigned size = 0; + + do { + unsigned n = value & 1; + buffer[size++] = '0' + n; + value >>= 1; + } while(value); + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +//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, long double value) { + char buffer[256]; + #ifdef _WIN32 + //Windows C-runtime does not support long double via sprintf() + sprintf(buffer, "%f", (double)value); + #else + sprintf(buffer, "%Lf", value); + #endif + + //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(long double value) { + string temp; + temp.reserve(fp(0, value)); + fp(temp(), value); + return temp; +} + +} + +#endif diff --git a/ananke/nall/string/variadic.hpp b/ananke/nall/string/variadic.hpp new file mode 100644 index 00000000..c43bfe86 --- /dev/null +++ b/ananke/nall/string/variadic.hpp @@ -0,0 +1,11 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template inline void print(Args&&... args) { + printf("%s", (const char*)string(std::forward(args)...)); +} + +} + +#endif diff --git a/ananke/nall/string/wildcard.hpp b/ananke/nall/string/wildcard.hpp new file mode 100644 index 00000000..9d2359d5 --- /dev/null +++ b/ananke/nall/string/wildcard.hpp @@ -0,0 +1,78 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +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; +} + +inline bool tokenize(const char *s, const char *p) { + while(*s) { + if(*p == '*') { + while(*s) if(tokenize(s++, p + 1)) return true; + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') p++; + return !*p; +} + +inline bool tokenize(lstring &list, const char *s, const char *p) { + while(*s) { + if(*p == '*') { + const char *b = s; + while(*s) { + if(tokenize(list, s++, p + 1)) { + list.prepend(substr(b, 0, --s - b)); + return true; + } + } + list.prepend(b); + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') { list.prepend(s); p++; } + return !*p; +} + +} + +#endif diff --git a/ananke/nall/string/wrapper.hpp b/ananke/nall/string/wrapper.hpp new file mode 100644 index 00000000..9d62915b --- /dev/null +++ b/ananke/nall/string/wrapper.hpp @@ -0,0 +1,43 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +unsigned string::length() const { return strlen(data); } +unsigned string::capacity() const { return size; } + +template lstring string::split(const char *key) const { lstring result; result.split(key, data); return result; } +template lstring string::isplit(const char *key) const { lstring result; result.isplit(key, data); return result; } +template lstring string::qsplit(const char *key) const { lstring result; result.qsplit(key, data); return result; } +template lstring string::iqsplit(const char *key) const { lstring result; result.iqsplit(key, data); return result; } + +bool string::equals(const char *str) const { return !strcmp(data, str); } +bool string::iequals(const char *str) const { return !istrcmp(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 istrbegin(data, str); } + +bool string::endswith(const char *str) const { return strend(data, str); } +bool string::iendswith(const char *str) const { return istrend(data, str); } + +string& string::lower() { nall::strlower(data); return *this; } +string& string::upper() { nall::strupper(data); return *this; } +string& string::qlower() { nall::qstrlower(data); return *this; } +string& string::qupper() { nall::qstrupper(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, const char *rkey) { nall::trim (data, key, rkey); return *this; } +string& string::strip() { nall::strip(data); return *this; } + +optional string::position(const char *key) const { return strpos(data, key); } +optional string::iposition(const char *key) const { return istrpos(data, key); } +optional string::qposition(const char *key) const { return qstrpos(data, key); } +optional string::iqposition(const char *key) const { return iqstrpos(data, key); } + +} + +#endif diff --git a/ananke/nall/traits.hpp b/ananke/nall/traits.hpp new file mode 100644 index 00000000..6a140c2b --- /dev/null +++ b/ananke/nall/traits.hpp @@ -0,0 +1,33 @@ +#ifndef NALL_TRAITS_HPP +#define NALL_TRAITS_HPP + +#include + +namespace nall { + +template class has_default_constructor { + template class receive_size{}; + template static signed sfinae(receive_size*); + template static char sfinae(...); + +public: + enum : bool { value = sizeof(sfinae(0)) == sizeof(signed) }; +}; + +template struct enable_if { typedef T type; }; +template struct enable_if {}; + +template struct type_if { typedef T type; }; +template struct type_if { typedef F type; }; + +template struct static_and { enum { value = false }; }; +template<> struct static_and { enum { value = true }; }; + +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 }; }; + +} + +#endif diff --git a/ananke/nall/udl.hpp b/ananke/nall/udl.hpp new file mode 100644 index 00000000..30ceefb3 --- /dev/null +++ b/ananke/nall/udl.hpp @@ -0,0 +1,28 @@ +#ifndef NALL_UDL_HPP +#define NALL_UDL_HPP + +//user-defined literals + +#include +#include + +namespace nall { + constexpr inline uintmax_t operator"" _b(const char *n) { return binary(n); } + + //convert to bytes + constexpr inline uintmax_t operator"" _kb(unsigned long long n) { return 1024 * n; } + constexpr inline uintmax_t operator"" _mb(unsigned long long n) { return 1024 * 1024 * n; } + constexpr inline uintmax_t operator"" _gb(unsigned long long n) { return 1024 * 1024 * 1024 * n; } + + //convert to bits + constexpr inline uintmax_t operator"" _kbit(unsigned long long n) { return 1024 * n / 8; } + constexpr inline uintmax_t operator"" _mbit(unsigned long long n) { return 1024 * 1024 * n / 8; } + constexpr inline uintmax_t operator"" _gbit(unsigned long long n) { return 1024 * 1024 * 1024 * n / 8; } + + //convert to hz + constexpr inline uintmax_t operator"" _khz(long double n) { return n * 1000; } + constexpr inline uintmax_t operator"" _mhz(long double n) { return n * 1000000; } + constexpr inline uintmax_t operator"" _ghz(long double n) { return n * 1000000000; } +} + +#endif diff --git a/ananke/nall/unzip.hpp b/ananke/nall/unzip.hpp new file mode 100644 index 00000000..5a7935f6 --- /dev/null +++ b/ananke/nall/unzip.hpp @@ -0,0 +1,126 @@ +#ifndef NALL_UNZIP_HPP +#define NALL_UNZIP_HPP + +#include +#include +#include +#include + +namespace nall { + +struct unzip { + struct File { + string name; + const uint8_t *data; + unsigned size; + unsigned csize; + unsigned cmode; //0 = uncompressed, 8 = deflate + unsigned crc32; + }; + + inline bool open(const string &filename) { + close(); + if(fm.open(filename, filemap::mode::read) == false) return false; + if(open(fm.data(), fm.size()) == false) { + fm.close(); + return false; + } + return true; + } + + inline bool open(const uint8_t *data, unsigned size) { + if(size < 22) return false; + + filedata = data; + filesize = size; + + file.reset(); + + const uint8_t *footer = data + size - 22; + while(true) { + if(footer <= data + 22) return false; + if(read(footer, 4) == 0x06054b50) { + unsigned commentlength = read(footer + 20, 2); + if(footer + 22 + commentlength == data + size) break; + } + footer--; + } + const uint8_t *directory = data + read(footer + 16, 4); + + while(true) { + unsigned signature = read(directory + 0, 4); + if(signature != 0x02014b50) break; + + File file; + file.cmode = read(directory + 10, 2); + file.crc32 = read(directory + 16, 4); + file.csize = read(directory + 20, 4); + file.size = read(directory + 24, 4); + + unsigned namelength = read(directory + 28, 2); + unsigned extralength = read(directory + 30, 2); + unsigned commentlength = read(directory + 32, 2); + + char *filename = new char[namelength + 1]; + memcpy(filename, directory + 46, namelength); + filename[namelength] = 0; + file.name = filename; + delete[] filename; + + unsigned offset = read(directory + 42, 4); + unsigned offsetNL = read(data + offset + 26, 2); + unsigned offsetEL = read(data + offset + 28, 2); + file.data = data + offset + 30 + offsetNL + offsetEL; + + directory += 46 + namelength + extralength + commentlength; + + this->file.append(file); + } + + return true; + } + + inline vector extract(File &file) { + vector buffer; + + if(file.cmode == 0) { + buffer.resize(file.size); + memcpy(buffer.data(), file.data, file.size); + } + + if(file.cmode == 8) { + buffer.resize(file.size); + if(inflate(buffer.data(), buffer.size(), file.data, file.csize) == false) { + buffer.reset(); + } + } + + return buffer; + } + + inline void close() { + if(fm.open()) fm.close(); + } + + ~unzip() { + close(); + } + +protected: + filemap fm; + const uint8_t *filedata; + unsigned filesize; + + unsigned read(const uint8_t *data, unsigned size) { + unsigned result = 0, shift = 0; + while(size--) { result |= *data++ << shift; shift += 8; } + return result; + } + +public: + vector file; +}; + +} + +#endif diff --git a/ananke/nall/ups.hpp b/ananke/nall/ups.hpp new file mode 100644 index 00000000..ffcdb2d7 --- /dev/null +++ b/ananke/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/ananke/nall/utility.hpp b/ananke/nall/utility.hpp new file mode 100644 index 00000000..b3c1e5aa --- /dev/null +++ b/ananke/nall/utility.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_UTILITY_HPP +#define NALL_UTILITY_HPP + +#include +#include + +namespace nall { + template struct base_from_member { + T value; + base_from_member(T value_) : value(value_) {} + }; + + template class optional { + public: + bool valid; + T value; + public: + inline operator bool() const { return valid; } + inline const T& operator()() const { if(!valid) throw; return value; } + inline optional& operator=(const optional &source) { valid = source.valid; value = source.value; return *this; } + inline optional() : valid(false) {} + 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/ananke/nall/varint.hpp b/ananke/nall/varint.hpp new file mode 100644 index 00000000..98e189db --- /dev/null +++ b/ananke/nall/varint.hpp @@ -0,0 +1,168 @@ +#ifndef NALL_VARINT_HPP +#define NALL_VARINT_HPP + +#include +#include + +namespace nall { + template struct uint_t { + private: + typedef typename type_if::type type_t; + type_t data; + + public: + inline operator type_t() const { return data; } + inline type_t operator ++(int) { type_t r = data; data = uclip(data + 1); return r; } + inline type_t operator --(int) { type_t r = data; data = uclip(data - 1); return r; } + inline type_t operator ++() { return data = uclip(data + 1); } + inline type_t operator --() { return data = uclip(data - 1); } + inline type_t operator =(const type_t i) { return data = uclip(i); } + inline type_t operator |=(const type_t i) { return data = uclip(data | i); } + inline type_t operator ^=(const type_t i) { return data = uclip(data ^ i); } + inline type_t operator &=(const type_t i) { return data = uclip(data & i); } + inline type_t operator<<=(const type_t i) { return data = uclip(data << i); } + inline type_t operator>>=(const type_t i) { return data = uclip(data >> i); } + inline type_t operator +=(const type_t i) { return data = uclip(data + i); } + inline type_t operator -=(const type_t i) { return data = uclip(data - i); } + inline type_t operator *=(const type_t i) { return data = uclip(data * i); } + inline type_t operator /=(const type_t i) { return data = uclip(data / i); } + inline type_t operator %=(const type_t i) { return data = uclip(data % i); } + + inline uint_t() : data(0) {} + inline uint_t(const type_t i) : data(uclip(i)) {} + + template inline type_t operator=(const uint_t &i) { return data = uclip((type_t)i); } + template inline uint_t(const uint_t &i) : data(uclip(i)) {} + }; + + template struct int_t { + private: + typedef typename type_if::type type_t; + type_t data; + + public: + inline operator type_t() const { return data; } + inline type_t operator ++(int) { type_t r = data; data = sclip(data + 1); return r; } + inline type_t operator --(int) { type_t r = data; data = sclip(data - 1); return r; } + inline type_t operator ++() { return data = sclip(data + 1); } + inline type_t operator --() { return data = sclip(data - 1); } + inline type_t operator =(const type_t i) { return data = sclip(i); } + inline type_t operator |=(const type_t i) { return data = sclip(data | i); } + inline type_t operator ^=(const type_t i) { return data = sclip(data ^ i); } + inline type_t operator &=(const type_t i) { return data = sclip(data & i); } + inline type_t operator<<=(const type_t i) { return data = sclip(data << i); } + inline type_t operator>>=(const type_t i) { return data = sclip(data >> i); } + inline type_t operator +=(const type_t i) { return data = sclip(data + i); } + inline type_t operator -=(const type_t i) { return data = sclip(data - i); } + inline type_t operator *=(const type_t i) { return data = sclip(data * i); } + inline type_t operator /=(const type_t i) { return data = sclip(data / i); } + inline type_t operator %=(const type_t i) { return data = sclip(data % i); } + + inline int_t() : data(0) {} + inline int_t(const type_t i) : data(sclip(i)) {} + + template inline type_t operator=(const int_t &i) { return data = sclip((type_t)i); } + template inline int_t(const int_t &i) : data(sclip(i)) {} + }; + + template struct varuint_t { + private: + type_t data; + type_t mask; + + public: + inline operator type_t() const { return data; } + inline type_t operator ++(int) { type_t r = data; data = (data + 1) & mask; return r; } + inline type_t operator --(int) { type_t r = data; data = (data - 1) & mask; return r; } + inline type_t operator ++() { return data = (data + 1) & mask; } + inline type_t operator --() { return data = (data - 1) & mask; } + inline type_t operator =(const type_t i) { return data = (i) & mask; } + inline type_t operator |=(const type_t i) { return data = (data | i) & mask; } + inline type_t operator ^=(const type_t i) { return data = (data ^ i) & mask; } + inline type_t operator &=(const type_t i) { return data = (data & i) & mask; } + inline type_t operator<<=(const type_t i) { return data = (data << i) & mask; } + inline type_t operator>>=(const type_t i) { return data = (data >> i) & mask; } + inline type_t operator +=(const type_t i) { return data = (data + i) & mask; } + inline type_t operator -=(const type_t i) { return data = (data - i) & mask; } + inline type_t operator *=(const type_t i) { return data = (data * i) & mask; } + inline type_t operator /=(const type_t i) { return data = (data / i) & mask; } + inline type_t operator %=(const type_t i) { return data = (data % i) & mask; } + + inline void bits(type_t bits) { mask = (1ull << (bits - 1)) + ((1ull << (bits - 1)) - 1); data &= mask; } + inline varuint_t() : data(0ull), mask((type_t)~0ull) {} + inline varuint_t(const type_t i) : data(i), mask((type_t)~0ull) {} + }; +} + +//typedefs + typedef nall::uint_t< 1> uint1_t; + typedef nall::uint_t< 2> uint2_t; + typedef nall::uint_t< 3> uint3_t; + typedef nall::uint_t< 4> uint4_t; + typedef nall::uint_t< 5> uint5_t; + typedef nall::uint_t< 6> uint6_t; + typedef nall::uint_t< 7> uint7_t; +//typedef nall::uint_t< 8> uint8_t; + + typedef nall::uint_t< 9> uint9_t; + typedef nall::uint_t<10> uint10_t; + typedef nall::uint_t<11> uint11_t; + typedef nall::uint_t<12> uint12_t; + typedef nall::uint_t<13> uint13_t; + typedef nall::uint_t<14> uint14_t; + typedef nall::uint_t<15> uint15_t; +//typedef nall::uint_t<16> uint16_t; + + typedef nall::uint_t<17> uint17_t; + typedef nall::uint_t<18> uint18_t; + typedef nall::uint_t<19> uint19_t; + typedef nall::uint_t<20> uint20_t; + typedef nall::uint_t<21> uint21_t; + typedef nall::uint_t<22> uint22_t; + typedef nall::uint_t<23> uint23_t; + typedef nall::uint_t<24> uint24_t; + typedef nall::uint_t<25> uint25_t; + typedef nall::uint_t<26> uint26_t; + typedef nall::uint_t<27> uint27_t; + typedef nall::uint_t<28> uint28_t; + typedef nall::uint_t<29> uint29_t; + typedef nall::uint_t<30> uint30_t; + typedef nall::uint_t<31> uint31_t; +//typedef nall::uint_t<32> uint32_t; + + typedef nall::int_t< 1> int1_t; + typedef nall::int_t< 2> int2_t; + typedef nall::int_t< 3> int3_t; + typedef nall::int_t< 4> int4_t; + typedef nall::int_t< 5> int5_t; + typedef nall::int_t< 6> int6_t; + typedef nall::int_t< 7> int7_t; +//typedef nall::int_t< 8> int8_t; + + typedef nall::int_t< 9> int9_t; + typedef nall::int_t<10> int10_t; + typedef nall::int_t<11> int11_t; + typedef nall::int_t<12> int12_t; + typedef nall::int_t<13> int13_t; + typedef nall::int_t<14> int14_t; + typedef nall::int_t<15> int15_t; +//typedef nall::int_t<16> int16_t; + + typedef nall::int_t<17> int17_t; + typedef nall::int_t<18> int18_t; + typedef nall::int_t<19> int19_t; + typedef nall::int_t<20> int20_t; + typedef nall::int_t<21> int21_t; + typedef nall::int_t<22> int22_t; + typedef nall::int_t<23> int23_t; + typedef nall::int_t<24> int24_t; + typedef nall::int_t<25> int25_t; + typedef nall::int_t<26> int26_t; + typedef nall::int_t<27> int27_t; + typedef nall::int_t<28> int28_t; + typedef nall::int_t<29> int29_t; + typedef nall::int_t<30> int30_t; + typedef nall::int_t<31> int31_t; +//typedef nall::int_t<32> int32_t; + +#endif diff --git a/ananke/nall/vector.hpp b/ananke/nall/vector.hpp new file mode 100644 index 00000000..6818c69d --- /dev/null +++ b/ananke/nall/vector.hpp @@ -0,0 +1,206 @@ +#ifndef NALL_VECTOR_HPP +#define NALL_VECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + template struct vector { + struct exception_out_of_bounds{}; + + protected: + T *pool; + unsigned poolsize; + unsigned objectsize; + + public: + operator bool() const { return pool; } + T* data() { return pool; } + const T* data() const { return pool; } + + bool empty() const { return objectsize == 0; } + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + T* move() { + T *result = pool; + pool = nullptr; + poolsize = 0; + objectsize = 0; + return result; + } + + void reset() { + if(pool) { + for(unsigned n = 0; n < objectsize; n++) pool[n].~T(); + free(pool); + } + pool = nullptr; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned size) { + unsigned outputsize = min(size, objectsize); + size = bit::round(size); //amortize growth + T *copy = (T*)calloc(size, sizeof(T)); + for(unsigned n = 0; n < outputsize; n++) new(copy + n) T(pool[n]); + for(unsigned n = 0; n < objectsize; n++) pool[n].~T(); + free(pool); + pool = copy; + poolsize = size; + objectsize = outputsize; + } + + //requires trivial constructor + void resize(unsigned size) { + if(size == objectsize) return; + if(size < objectsize) return reserve(size); + while(size > objectsize) append(T()); + } + + template + void append(const T& data, Args&&... args) { + append(data); + append(std::forward(args)...); + } + + void append(const T& data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + new(pool + objectsize++) T(data); + } + + bool appendonce(const T& data) { + if(find(data) == true) return false; + append(data); + return true; + } + + void insert(unsigned position, const T& data) { + append(data); + for(signed n = size() - 1; n > position; n--) pool[n] = pool[n - 1]; + pool[position] = data; + } + + void prepend(const T& data) { + insert(0, data); + } + + void remove(unsigned index = ~0u, unsigned count = 1) { + if(index == ~0) index = objectsize ? objectsize - 1 : 0; + for(unsigned n = index; count + n < objectsize; n++) pool[n] = pool[count + n]; + objectsize = (count + index >= objectsize) ? index : objectsize - count; + } + + T take(unsigned index = ~0u) { + if(index == ~0) index = objectsize ? objectsize - 1 : 0; + if(index >= objectsize) throw exception_out_of_bounds(); + T item = pool[index]; + remove(index); + return item; + } + + void reverse() { + unsigned pivot = size() / 2; + for(unsigned l = 0, r = size() - 1; l < pivot; l++, r--) { + std::swap(pool[l], pool[r]); + } + } + + void sort() { + nall::sort(pool, objectsize); + } + + template void sort(const Comparator &lessthan) { + nall::sort(pool, objectsize, lessthan); + } + + optional find(const T& data) { + for(unsigned n = 0; n < size(); n++) if(pool[n] == data) return {true, n}; + return {false, 0u}; + } + + T& first() { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[0]; + } + + T& last() { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[objectsize - 1]; + } + + //access + inline T& operator[](unsigned position) { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[position]; + } + + inline const T& operator[](unsigned position) const { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[position]; + } + + inline T& operator()(unsigned position) { + if(position >= poolsize) reserve(position + 1); + while(position >= objectsize) append(T()); + return pool[position]; + } + + inline const T& operator()(unsigned position, const T& data) const { + if(position >= objectsize) return data; + return pool[position]; + } + + //iteration + T* begin() { return &pool[0]; } + T* end() { return &pool[objectsize]; } + const T* begin() const { return &pool[0]; } + const T* end() const { return &pool[objectsize]; } + + //copy + inline vector& operator=(const vector &source) { + reset(); + reserve(source.capacity()); + for(auto &data : source) append(data); + return *this; + } + + vector(const vector &source) : pool(nullptr), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline vector& operator=(vector &&source) { + reset(); + pool = source.pool, poolsize = source.poolsize, objectsize = source.objectsize; + source.pool = nullptr, source.poolsize = 0, source.objectsize = 0; + return *this; + } + + vector(vector &&source) : pool(nullptr), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + vector() : pool(nullptr), poolsize(0), objectsize(0) { + } + + vector(std::initializer_list list) : pool(nullptr), poolsize(0), objectsize(0) { + for(auto &data : list) append(data); + } + + ~vector() { + reset(); + } + }; +} + +#endif diff --git a/ananke/nall/windows/detour.hpp b/ananke/nall/windows/detour.hpp new file mode 100644 index 00000000..e270f318 --- /dev/null +++ b/ananke/nall/windows/detour.hpp @@ -0,0 +1,192 @@ +#ifndef NALL_WINDOWS_DETOUR_HPP +#define NALL_WINDOWS_DETOUR_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +#define Copy 0 +#define RelNear 1 + +struct detour { + static bool insert(const string &moduleName, const string &functionName, void *&source, void *target); + static bool remove(const string &moduleName, const string &functionName, void *&source); + +protected: + static unsigned length(const uint8_t *function); + static unsigned mirror(uint8_t *target, const uint8_t *source); + + struct opcode { + uint16_t prefix; + unsigned length; + unsigned mode; + uint16_t modify; + }; + static opcode opcodes[]; +}; + +//TODO: +//* fs:, gs: should force another opcode copy +//* conditional branches within +5-byte range should fail +detour::opcode detour::opcodes[] = { + { 0x50, 1 }, //push eax + { 0x51, 1 }, //push ecx + { 0x52, 1 }, //push edx + { 0x53, 1 }, //push ebx + { 0x54, 1 }, //push esp + { 0x55, 1 }, //push ebp + { 0x56, 1 }, //push esi + { 0x57, 1 }, //push edi + { 0x58, 1 }, //pop eax + { 0x59, 1 }, //pop ecx + { 0x5a, 1 }, //pop edx + { 0x5b, 1 }, //pop ebx + { 0x5c, 1 }, //pop esp + { 0x5d, 1 }, //pop ebp + { 0x5e, 1 }, //pop esi + { 0x5f, 1 }, //pop edi + { 0x64, 1 }, //fs: + { 0x65, 1 }, //gs: + { 0x68, 5 }, //push dword + { 0x6a, 2 }, //push byte + { 0x74, 2, RelNear, 0x0f84 }, //je near -> je far + { 0x75, 2, RelNear, 0x0f85 }, //jne near -> jne far + { 0x89, 2 }, //mov reg,reg + { 0x8b, 2 }, //mov reg,reg + { 0x90, 1 }, //nop + { 0xa1, 5 }, //mov eax,[dword] + { 0xeb, 2, RelNear, 0xe9 }, //jmp near -> jmp far +}; + +bool detour::insert(const string &moduleName, const string &functionName, void *&source, void *target) { + HMODULE module = GetModuleHandleW(utf16_t(moduleName)); + if(!module) return false; + + uint8_t *sourceData = (uint8_t*)GetProcAddress(module, functionName); + if(!sourceData) return false; + + unsigned sourceLength = detour::length(sourceData); + if(sourceLength < 5) { + //unable to clone enough bytes to insert hook + #if 1 + string output = { "detour::insert(", moduleName, "::", functionName, ") failed: " }; + for(unsigned n = 0; n < 16; n++) output.append(hex<2>(sourceData[n]), " "); + output.rtrim<1>(" "); + MessageBoxA(0, output, "nall::detour", MB_OK); + #endif + return false; + } + + uint8_t *mirrorData = new uint8_t[512](); + detour::mirror(mirrorData, sourceData); + + DWORD privileges; + VirtualProtect((void*)mirrorData, 512, PAGE_EXECUTE_READWRITE, &privileges); + VirtualProtect((void*)sourceData, 256, PAGE_EXECUTE_READWRITE, &privileges); + uintmax_t address = (uintmax_t)target - ((uintmax_t)sourceData + 5); + sourceData[0] = 0xe9; //jmp target + sourceData[1] = address >> 0; + sourceData[2] = address >> 8; + sourceData[3] = address >> 16; + sourceData[4] = address >> 24; + VirtualProtect((void*)sourceData, 256, privileges, &privileges); + + source = (void*)mirrorData; + return true; +} + +bool detour::remove(const string &moduleName, const string &functionName, void *&source) { + HMODULE module = GetModuleHandleW(utf16_t(moduleName)); + if(!module) return false; + + uint8_t *sourceData = (uint8_t*)GetProcAddress(module, functionName); + if(!sourceData) return false; + + uint8_t *mirrorData = (uint8_t*)source; + if(mirrorData == sourceData) return false; //hook was never installed + + unsigned length = detour::length(256 + mirrorData); + if(length < 5) return false; + + DWORD privileges; + VirtualProtect((void*)sourceData, 256, PAGE_EXECUTE_READWRITE, &privileges); + for(unsigned n = 0; n < length; n++) sourceData[n] = mirrorData[256 + n]; + VirtualProtect((void*)sourceData, 256, privileges, &privileges); + + source = (void*)sourceData; + delete[] mirrorData; + return true; +} + +unsigned detour::length(const uint8_t *function) { + unsigned length = 0; + while(length < 5) { + detour::opcode *opcode = 0; + foreach(op, detour::opcodes) { + if(function[length] == op.prefix) { + opcode = &op; + break; + } + } + if(opcode == 0) break; + length += opcode->length; + } + return length; +} + +unsigned detour::mirror(uint8_t *target, const uint8_t *source) { + const uint8_t *entryPoint = source; + for(unsigned n = 0; n < 256; n++) target[256 + n] = source[n]; + + unsigned size = detour::length(source); + while(size) { + detour::opcode *opcode = 0; + foreach(op, detour::opcodes) { + if(*source == op.prefix) { + opcode = &op; + break; + } + } + + switch(opcode->mode) { + case Copy: + for(unsigned n = 0; n < opcode->length; n++) *target++ = *source++; + break; + case RelNear: { + source++; + uintmax_t sourceAddress = (uintmax_t)source + 1 + (int8_t)*source; + *target++ = opcode->modify; + if(opcode->modify >> 8) *target++ = opcode->modify >> 8; + uintmax_t targetAddress = (uintmax_t)target + 4; + uintmax_t address = sourceAddress - targetAddress; + *target++ = address >> 0; + *target++ = address >> 8; + *target++ = address >> 16; + *target++ = address >> 24; + source += 2; + } break; + } + + size -= opcode->length; + } + + uintmax_t address = (entryPoint + detour::length(entryPoint)) - (target + 5); + *target++ = 0xe9; //jmp entryPoint + *target++ = address >> 0; + *target++ = address >> 8; + *target++ = address >> 16; + *target++ = address >> 24; + + return source - entryPoint; +} + +#undef Implied +#undef RelNear + +} + +#endif diff --git a/ananke/nall/windows/guid.hpp b/ananke/nall/windows/guid.hpp new file mode 100644 index 00000000..386cfc75 --- /dev/null +++ b/ananke/nall/windows/guid.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_WINDOWS_GUID_HPP +#define NALL_WINDOWS_GUID_HPP + +#include +#include + +namespace nall { + +//generate unique GUID +inline string guid() { + random_lfsr lfsr; + lfsr.seed(time(0)); + for(unsigned n = 0; n < 256; n++) lfsr(); + + string output; + for(unsigned n = 0; n < 4; n++) output.append(hex<2>(lfsr())); + output.append("-"); + for(unsigned n = 0; n < 2; n++) output.append(hex<2>(lfsr())); + output.append("-"); + for(unsigned n = 0; n < 2; n++) output.append(hex<2>(lfsr())); + output.append("-"); + for(unsigned n = 0; n < 2; n++) output.append(hex<2>(lfsr())); + output.append("-"); + for(unsigned n = 0; n < 6; n++) output.append(hex<2>(lfsr())); + return {"{", output, "}"}; +} + +} + +#endif diff --git a/ananke/nall/windows/launcher.hpp b/ananke/nall/windows/launcher.hpp new file mode 100644 index 00000000..914683ec --- /dev/null +++ b/ananke/nall/windows/launcher.hpp @@ -0,0 +1,94 @@ +#ifndef NALL_WINDOWS_LAUNCHER_HPP +#define NALL_WINDOWS_LAUNCHER_HPP + +namespace nall { + +//launch a new process and inject specified DLL into it + +bool launch(const char *applicationName, const char *libraryName, uint32_t entryPoint) { + //if a launcher does not send at least one message, a wait cursor will appear + PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0); + MSG msg; + GetMessage(&msg, 0, 0, 0); + + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + memset(&si, 0, sizeof(STARTUPINFOW)); + BOOL result = CreateProcessW( + utf16_t(applicationName), GetCommandLineW(), NULL, NULL, TRUE, + DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS, //do not break if application creates its own processes + NULL, NULL, &si, &pi + ); + if(result == false) return false; + + uint8_t entryData[1024], entryHook[1024] = { + 0x68, 0x00, 0x00, 0x00, 0x00, //push libraryName + 0xb8, 0x00, 0x00, 0x00, 0x00, //mov eax,LoadLibraryW + 0xff, 0xd0, //call eax + 0xcd, 0x03, //int 3 + }; + + entryHook[1] = (uint8_t)((entryPoint + 14) >> 0); + entryHook[2] = (uint8_t)((entryPoint + 14) >> 8); + entryHook[3] = (uint8_t)((entryPoint + 14) >> 16); + entryHook[4] = (uint8_t)((entryPoint + 14) >> 24); + + uint32_t pLoadLibraryW = (uint32_t)GetProcAddress(GetModuleHandleW(L"kernel32"), "LoadLibraryW"); + entryHook[6] = pLoadLibraryW >> 0; + entryHook[7] = pLoadLibraryW >> 8; + entryHook[8] = pLoadLibraryW >> 16; + entryHook[9] = pLoadLibraryW >> 24; + + utf16_t buffer = utf16_t(libraryName); + memcpy(entryHook + 14, buffer, 2 * wcslen(buffer) + 2); + + while(true) { + DEBUG_EVENT event; + WaitForDebugEvent(&event, INFINITE); + + if(event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) break; + + if(event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { + if(event.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) { + if(event.u.Exception.ExceptionRecord.ExceptionAddress == (void*)(entryPoint + 14 - 1)) { + HANDLE hProcess = OpenProcess(0, FALSE, event.dwProcessId); + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, event.dwThreadId); + + CONTEXT context; + context.ContextFlags = CONTEXT_FULL; + GetThreadContext(hThread, &context); + + WriteProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryData, sizeof entryData, NULL); + context.Eip = entryPoint; + SetThreadContext(hThread, &context); + + CloseHandle(hThread); + CloseHandle(hProcess); + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + continue; + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); + continue; + } + + if(event.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { + ReadProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryData, sizeof entryData, NULL); + WriteProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryHook, sizeof entryHook, NULL); + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + continue; + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + } + + return true; +} + +} + +#endif diff --git a/ananke/nall/windows/registry.hpp b/ananke/nall/windows/registry.hpp new file mode 100644 index 00000000..0774e04a --- /dev/null +++ b/ananke/nall/windows/registry.hpp @@ -0,0 +1,120 @@ +#ifndef NALL_WINDOWS_REGISTRY_HPP +#define NALL_WINDOWS_REGISTRY_HPP + +#include +#include + +#include +#ifndef KEY_WOW64_64KEY + #define KEY_WOW64_64KEY 0x0100 +#endif +#ifndef KEY_WOW64_32KEY + #define KEY_WOW64_32KEY 0x0200 +#endif + +#ifndef NWR_FLAGS + #define NWR_FLAGS KEY_WOW64_64KEY +#endif + +#ifndef NWR_SIZE + #define NWR_SIZE 4096 +#endif + +namespace nall { + +struct registry { + static bool exists(const string &name) { + lstring part = name.split("/"); + HKEY handle, rootKey = root(part.take(0)); + string node = part.take(); + string path = part.concatenate("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + wchar_t data[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + LONG result = RegQueryValueExW(handle, utf16_t(node), NULL, NULL, (LPBYTE)&data, (LPDWORD)&size); + RegCloseKey(handle); + if(result == ERROR_SUCCESS) return true; + } + return false; + } + + static string read(const string &name) { + lstring part = name.split("/"); + HKEY handle, rootKey = root(part.take(0)); + string node = part.take(); + string path = part.concatenate("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + wchar_t data[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + LONG result = RegQueryValueExW(handle, utf16_t(node), NULL, NULL, (LPBYTE)&data, (LPDWORD)&size); + RegCloseKey(handle); + if(result == ERROR_SUCCESS) return (const char*)utf8_t(data); + } + return ""; + } + + static void write(const string &name, const string &data = "") { + lstring part = name.split("/"); + HKEY handle, rootKey = root(part.take(0)); + string node = part.take(), path; + DWORD disposition; + for(unsigned n = 0; n < part.size(); n++) { + path.append(part[n]); + if(RegCreateKeyExW(rootKey, utf16_t(path), 0, NULL, 0, NWR_FLAGS | KEY_ALL_ACCESS, NULL, &handle, &disposition) == ERROR_SUCCESS) { + if(n == part.size() - 1) { + RegSetValueExW(handle, utf16_t(node), 0, REG_SZ, (BYTE*)(wchar_t*)utf16_t(data), (data.length() + 1) * sizeof(wchar_t)); + } + RegCloseKey(handle); + } + path.append("\\"); + } + } + + static bool remove(const string &name) { + lstring part = name.split("/"); + HKEY rootKey = root(part.take(0)); + string node = part.take(); + string path = part.concatenate("\\"); + if(node.empty()) return SHDeleteKeyW(rootKey, utf16_t(path)) == ERROR_SUCCESS; + return SHDeleteValueW(rootKey, utf16_t(path), utf16_t(node)) == ERROR_SUCCESS; + } + + static lstring contents(const string &name) { + lstring part = name.split("/"), result; + HKEY handle, rootKey = root(part.take(0)); + part.remove(); + string path = part.concatenate("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + DWORD folders, nodes; + RegQueryInfoKey(handle, NULL, NULL, NULL, &folders, NULL, NULL, &nodes, NULL, NULL, NULL, NULL); + for(unsigned n = 0; n < folders; n++) { + wchar_t name[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + RegEnumKeyEx(handle, n, (wchar_t*)&name, &size, NULL, NULL, NULL, NULL); + result.append({(const char*)utf8_t(name), "/"}); + } + for(unsigned n = 0; n < nodes; n++) { + wchar_t name[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + RegEnumValueW(handle, n, (wchar_t*)&name, &size, NULL, NULL, NULL, NULL); + result.append((const char*)utf8_t(name)); + } + RegCloseKey(handle); + } + return result; + } + +private: + static HKEY root(const string &name) { + if(name == "HKCR") return HKEY_CLASSES_ROOT; + if(name == "HKCC") return HKEY_CURRENT_CONFIG; + if(name == "HKCU") return HKEY_CURRENT_USER; + if(name == "HKLM") return HKEY_LOCAL_MACHINE; + if(name == "HKU" ) return HKEY_USERS; + return NULL; + } +}; + +} + +#endif diff --git a/ananke/nall/windows/utf8.hpp b/ananke/nall/windows/utf8.hpp new file mode 100644 index 00000000..b1374943 --- /dev/null +++ b/ananke/nall/windows/utf8.hpp @@ -0,0 +1,87 @@ +#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 +#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/ananke/nall/xorg/guard.hpp b/ananke/nall/xorg/guard.hpp new file mode 100644 index 00000000..a1282683 --- /dev/null +++ b/ananke/nall/xorg/guard.hpp @@ -0,0 +1,29 @@ +#ifndef NALL_XORG_GUARD_HPP +#define NALL_XORG_GUARD_HPP + +#define None +#undef XlibNone +#define XlibNone 0L +#define Button1 XlibButton1 +#define Button2 XlibButton2 +#define Button3 XlibButton3 +#define Button4 XlibButton4 +#define Button5 XlibButton5 +#define Display XlibDisplay +#define Screen XlibScreen +#define Window XlibWindow + +#else +#undef NALL_XORG_GUARD_HPP + +#undef None +#undef Button1 +#undef Button2 +#undef Button3 +#undef Button4 +#undef Button5 +#undef Display +#undef Screen +#undef Window + +#endif diff --git a/ananke/nall/xorg/xorg.hpp b/ananke/nall/xorg/xorg.hpp new file mode 100644 index 00000000..bcf48b46 --- /dev/null +++ b/ananke/nall/xorg/xorg.hpp @@ -0,0 +1,12 @@ +#ifndef NALL_XORG_XORG_HPP +#define NALL_XORG_XORG_HPP + +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/ananke/nall/zip.hpp b/ananke/nall/zip.hpp new file mode 100644 index 00000000..0a73ccdd --- /dev/null +++ b/ananke/nall/zip.hpp @@ -0,0 +1,95 @@ +#ifndef NALL_ZIP_HPP +#define NALL_ZIP_HPP + +//creates uncompressed ZIP archives + +#include +#include + +namespace nall { + +struct zip { + zip(const string &filename) { + fp.open(filename, file::mode::write); + time_t currentTime = time(0); + tm *info = localtime(¤tTime); + dosTime = (info->tm_hour << 11) | (info->tm_min << 5) | (info->tm_sec >> 1); + dosDate = ((info->tm_year - 80) << 9) | ((1 + info->tm_mon) << 5) + (info->tm_mday); + } + + //append path: append("path/"); + //append file: append("path/file", data, size); + void append(string filename, const uint8_t *data = nullptr, unsigned size = 0u) { + filename.transform("\\", "/"); + uint32_t checksum = crc32_calculate(data, size); + directory.append({filename, checksum, size, fp.offset()}); + + fp.writel(0x04034b50, 4); //signature + fp.writel(0x0014, 2); //minimum version (2.0) + fp.writel(0x0000, 2); //general purpose bit flags + fp.writel(0x0000, 2); //compression method (0 = uncompressed) + fp.writel(dosTime, 2); + fp.writel(dosDate, 2); + fp.writel(checksum, 4); + fp.writel(size, 4); //compressed size + fp.writel(size, 4); //uncompressed size + fp.writel(filename.length(), 2); //file name length + fp.writel(0x0000, 2); //extra field length + fp.print(filename); //file name + + fp.write(data, size); //file data + } + + ~zip() { + //central directory + unsigned baseOffset = fp.offset(); + for(auto &entry : directory) { + fp.writel(0x02014b50, 4); //signature + fp.writel(0x0014, 2); //version made by (2.0) + fp.writel(0x0014, 2); //version needed to extract (2.0) + fp.writel(0x0000, 2); //general purpose bit flags + fp.writel(0x0000, 2); //compression method (0 = uncompressed) + fp.writel(dosTime, 2); + fp.writel(dosDate, 2); + fp.writel(entry.checksum, 4); + fp.writel(entry.size, 4); //compressed size + fp.writel(entry.size, 4); //uncompressed size + fp.writel(entry.filename.length(), 2); //file name length + fp.writel(0x0000, 2); //extra field length + fp.writel(0x0000, 2); //file comment length + fp.writel(0x0000, 2); //disk number start + fp.writel(0x0000, 2); //internal file attributes + fp.writel(0x00000000, 4); //external file attributes + fp.writel(entry.offset, 4); //relative offset of file header + fp.print(entry.filename); + } + unsigned finishOffset = fp.offset(); + + //end of central directory + fp.writel(0x06054b50, 4); //signature + fp.writel(0x0000, 2); //number of this disk + fp.writel(0x0000, 2); //disk where central directory starts + fp.writel(directory.size(), 2); //number of central directory records on this disk + fp.writel(directory.size(), 2); //total number of central directory records + fp.writel(finishOffset - baseOffset, 4); //size of central directory + fp.writel(baseOffset, 4); //offset of central directory + fp.writel(0x0000, 2); //comment length + + fp.close(); + } + +protected: + file fp; + uint16_t dosTime, dosDate; + struct entry_t { + string filename; + uint32_t checksum; + uint32_t size; + uint32_t offset; + }; + vector directory; +}; + +} + +#endif diff --git a/ananke/phoenix/Makefile b/ananke/phoenix/Makefile new file mode 100644 index 00000000..d1c9d981 --- /dev/null +++ b/ananke/phoenix/Makefile @@ -0,0 +1,21 @@ +ifeq ($(platform),x) + ifeq ($(phoenix),) + phoenix := gtk + endif + + ifeq ($(phoenix),gtk) + phoenixflags := -DPHOENIX_GTK `pkg-config --cflags gtk+-2.0` + phoenixlink := `pkg-config --libs gtk+-2.0` + endif + + ifeq ($(phoenix),qt) + phoenixflags := -DPHOENIX_QT `pkg-config --cflags QtCore QtGui` + phoenixlink := `pkg-config --libs QtCore QtGui` + endif +else ifeq ($(platform),win) + phoenixflags := -DPHOENIX_WINDOWS + phoenixlink := -lkernel32 -luser32 -lgdi32 -ladvapi32 -lole32 -lcomctl32 -lcomdlg32 -lshlwapi +else + phoenixflags := -DPHOENIX_REFERENCE + phoenixlink := +endif diff --git a/ananke/phoenix/core/core.cpp b/ananke/phoenix/core/core.cpp new file mode 100644 index 00000000..83767dca --- /dev/null +++ b/ananke/phoenix/core/core.cpp @@ -0,0 +1,1334 @@ +#include "state.hpp" +#include "layout/fixed-layout.cpp" +#include "layout/horizontal-layout.cpp" +#include "layout/vertical-layout.cpp" + +#if defined(PHOENIX_WINDOWS) + #include "../windows/platform.cpp" +#elif defined(PHOENIX_QT) + #include "../qt/platform.cpp" +#elif defined(PHOENIX_GTK) + #include "../gtk/platform.cpp" +#elif defined(PHOENIX_REFERENCE) + #include "../reference/platform.cpp" +#endif + +static bool OS_quit = false; + +//Color +//===== + +uint32_t Color::rgb() const { + return (255 << 24) + (red << 16) + (green << 8) + (blue << 0); +} + +uint32_t Color::rgba() const { + return (alpha << 24) + (red << 16) + (green << 8) + (blue << 0); +} + +//Geometry +//======== + +Position Geometry::position() const { + return { x, y }; +} + +Size Geometry::size() const { + return { width, height }; +} + +string Geometry::text() const { + return { x, ",", y, ",", width, ",", height }; +} + +Geometry::Geometry(const string &text) { + lstring part = text.split(","); + x = integer(part(0, "256")); + y = integer(part(1, "256")); + width = decimal(part(2, "256")); + height = decimal(part(3, "256")); +} + +//Font +//==== + +Geometry Font::geometry(const string &text) { + return pFont::geometry(description, text); +} + +Font::Font(const string &description): +description(description) { +} + +//Desktop +//======= + +Size Desktop::size() { + return pDesktop::size(); +} + +Geometry Desktop::workspace() { + return pDesktop::workspace(); +} + +//Keyboard +//======== + +bool Keyboard::pressed(Keyboard::Scancode scancode) { + return pKeyboard::pressed(scancode); +} + +bool Keyboard::released(Keyboard::Scancode scancode) { + return !pressed(scancode); +} + +vector Keyboard::state() { + return pKeyboard::state(); +} + +//Mouse +//===== + +Position Mouse::position() { + return pMouse::position(); +} + +bool Mouse::pressed(Mouse::Button button) { + return pMouse::pressed(button); +} + +bool Mouse::released(Mouse::Button button) { + return !pressed(button); +} + +//DialogWindow +//============ + +string DialogWindow::fileOpen_(Window &parent, const string &path, const lstring &filter_) { + auto filter = filter_; + if(filter.size() == 0) filter.append("All files (*)"); + return pDialogWindow::fileOpen(parent, path, filter); +} + +string DialogWindow::fileSave_(Window &parent, const string &path, const lstring &filter_) { + auto filter = filter_; + if(filter.size() == 0) filter.append("All files (*)"); + return pDialogWindow::fileSave(parent, path, filter); +} + +string DialogWindow::folderSelect(Window &parent, const string &path) { + return pDialogWindow::folderSelect(parent, path); +} + +//MessageWindow +//============= + +MessageWindow::Response MessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return pMessageWindow::information(parent, text, buttons); +} + +MessageWindow::Response MessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return pMessageWindow::question(parent, text, buttons); +} + +MessageWindow::Response MessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return pMessageWindow::warning(parent, text, buttons); +} + +MessageWindow::Response MessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return pMessageWindow::critical(parent, text, buttons); +} + +//Object +//====== + +Object::Object(pObject &p): +p(p) { + OS::initialize(); + p.constructor(); +} + +Object::~Object() { + p.destructor(); + delete &p; +} + +//OS +//== + +void OS::main() { + return pOS::main(); +} + +bool OS::pendingEvents() { + return pOS::pendingEvents(); +} + +void OS::processEvents() { + return pOS::processEvents(); +} + +void OS::quit() { + OS_quit = true; + return pOS::quit(); +} + +void OS::initialize() { + static bool initialized = false; + if(initialized == false) { + initialized = true; + return pOS::initialize(); + } +} + +//Timer +//===== + +void Timer::setEnabled(bool enabled) { + state.enabled = enabled; + return p.setEnabled(enabled); +} + +void Timer::setInterval(unsigned milliseconds) { + state.milliseconds = milliseconds; + return p.setInterval(milliseconds); +} + +Timer::Timer(): +state(*new State), +base_from_member(*new pTimer(*this)), +Object(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Timer::~Timer() { + p.destructor(); + delete &state; +} + +//Window +//====== + +Window& Window::none() { + return pWindow::none(); +} + +void Window::append_(Layout &layout) { + if(state.layout.append(layout)) { + ((Sizable&)layout).state.window = this; + ((Sizable&)layout).state.layout = 0; + p.append(layout); + layout.synchronizeLayout(); + } +} + +void Window::append_(Menu &menu) { + if(state.menu.append(menu)) { + ((Action&)menu).state.window = this; + p.append(menu); + } +} + +void Window::append_(Widget &widget) { + if(state.widget.append(widget)) { + ((Sizable&)widget).state.window = this; + p.append(widget); + } +} + +Color Window::backgroundColor() { + return p.backgroundColor(); +} + +Geometry Window::frameGeometry() { + Geometry geometry = p.geometry(); + Geometry margin = p.frameMargin(); + return { + geometry.x - margin.x, geometry.y - margin.y, + geometry.width + margin.width, geometry.height + margin.height + }; +} + +Geometry Window::frameMargin() { + return p.frameMargin(); +} + +bool Window::focused() { + return p.focused(); +} + +bool Window::fullScreen() { + return state.fullScreen; +} + +Geometry Window::geometry() { + return p.geometry(); +} + +void Window::ignore() { + state.ignore = true; +} + +void Window::remove_(Layout &layout) { + if(state.layout.remove(layout)) { + p.remove(layout); + ((Sizable&)layout).state.window = 0; + } +} + +void Window::remove_(Menu &menu) { + if(state.menu.remove(menu)) { + p.remove(menu); + ((Action&)menu).state.window = 0; + } +} + +void Window::remove_(Widget &widget) { + if(state.widget.remove(widget)) { + p.remove(widget); + ((Sizable&)widget).state.window = 0; + } +} + +void Window::setBackgroundColor(const Color &color) { + state.backgroundColorOverride = true; + state.backgroundColor = color; + return p.setBackgroundColor(color); +} + +void Window::setFrameGeometry(const Geometry &geometry) { + Geometry margin = p.frameMargin(); + return setGeometry({ + geometry.x + margin.x, geometry.y + margin.y, + geometry.width - margin.width, geometry.height - margin.height + }); +} + +void Window::setFocused() { + return p.setFocused(); +} + +void Window::setFullScreen(bool fullScreen) { + state.fullScreen = fullScreen; + return p.setFullScreen(fullScreen); +} + +void Window::setGeometry(const Geometry &geometry) { + state.geometry = geometry; + return p.setGeometry(geometry); +} + +void Window::setMenuFont(const string &font) { + state.menuFont = font; + return p.setMenuFont(font); +} + +void Window::setMenuVisible(bool visible) { + state.menuVisible = visible; + return p.setMenuVisible(visible); +} + +void Window::setModal(bool modal) { + state.modal = modal; + return p.setModal(modal); +} + +void Window::setResizable(bool resizable) { + state.resizable = resizable; + return p.setResizable(resizable); +} + +void Window::setSmartGeometry(const Geometry &geometry) { + Geometry margin = p.frameMargin(); + return setGeometry({ + geometry.x + margin.x, geometry.y + margin.y, + geometry.width, geometry.height + }); +} + +void Window::setStatusFont(const string &font) { + state.statusFont = font; + return p.setStatusFont(font); +} + +void Window::setStatusText(const string &text) { + state.statusText = text; + return p.setStatusText(text); +} + +void Window::setStatusVisible(bool visible) { + state.statusVisible = visible; + return p.setStatusVisible(visible); +} + +void Window::setTitle(const string &text) { + state.title = text; + return p.setTitle(text); +} + +void Window::setVisible(bool visible) { + state.visible = visible; + synchronizeLayout(); + return p.setVisible(visible); +} + +void Window::setWidgetFont(const string &font) { + state.widgetFont = font; + return p.setWidgetFont(font); +} + +string Window::statusText() { + return state.statusText; +} + +void Window::synchronizeLayout() { + if(visible() && OS_quit == false) setGeometry(geometry()); +} + +bool Window::visible() { + return state.visible; +} + +Window::Window(): +state(*new State), +base_from_member(*new pWindow(*this)), +Object(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Window::~Window() { + p.destructor(); + delete &state; +} + +//Action +//====== + +bool Action::enabled() { + return state.enabled; +} + +void Action::setEnabled(bool enabled) { + state.enabled = enabled; + return p.setEnabled(enabled); +} + +void Action::setVisible(bool visible) { + state.visible = visible; + return p.setVisible(visible); +} + +bool Action::visible() { + return state.visible; +} + +Action::Action(pAction &p): +state(*new State), +Object(p), +p(p) { + p.constructor(); +} + +Action::~Action() { + p.destructor(); + delete &state; +} + +//Menu +//==== + +void Menu::append(const set &list) { + for(auto &action : list) { + if(state.action.append(action)) { + action.state.menu = this; + p.append(action); + } + } +} + +void Menu::remove(const set &list) { + for(auto &action : list) { + if(state.action.remove(action)) { + action.state.menu = 0; + return p.remove(action); + } + } +} + +void Menu::setImage(const image &image) { + state.image = image; + return p.setImage(image); +} + +void Menu::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +Menu::Menu(): +state(*new State), +base_from_member(*new pMenu(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Menu::~Menu() { + p.destructor(); + delete &state; +} + +//Separator +//========= + +Separator::Separator(): +base_from_member(*new pSeparator(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Separator::~Separator() { + p.destructor(); +} + +//Item +//==== + +void Item::setImage(const image &image) { + state.image = image; + return p.setImage(image); +} + +void Item::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +Item::Item(): +state(*new State), +base_from_member(*new pItem(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Item::~Item() { + p.destructor(); + delete &state; +} + +//CheckItem +//========= + +bool CheckItem::checked() { + return p.checked(); +} + +void CheckItem::setChecked(bool checked) { + state.checked = checked; + return p.setChecked(checked); +} + +void CheckItem::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +CheckItem::CheckItem(): +state(*new State), +base_from_member(*new pCheckItem(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +CheckItem::~CheckItem() { + p.destructor(); + delete &state; +} + +//RadioItem +//========= + +void RadioItem::group(const set &list) { + for(auto &item : list) item.p.setGroup(item.state.group = list); + if(list.size()) list[0].setChecked(); +} + +bool RadioItem::checked() { + return p.checked(); +} + +void RadioItem::setChecked() { + for(auto &item : state.group) item.state.checked = false; + state.checked = true; + return p.setChecked(); +} + +void RadioItem::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +string RadioItem::text() { + return state.text; +} + +RadioItem::RadioItem(): +state(*new State), +base_from_member(*new pRadioItem(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +RadioItem::~RadioItem() { + for(auto &item : state.group) { + if(&item != this) item.state.group.remove(*this); + } + p.destructor(); + delete &state; +} + +//Sizable +//======= + +Layout* Sizable::layout() { + return state.layout; +} + +Window* Sizable::window() { + if(state.layout) return state.layout->window(); + return state.window; +} + +Sizable::Sizable(pSizable &p): +state(*new State), +Object(p), +p(p) { + p.constructor(); +} + +Sizable::~Sizable() { + if(layout()) layout()->remove(*this); + p.destructor(); + delete &state; +} + +//Layout +//====== + +void Layout::append(Sizable &sizable) { + sizable.state.layout = this; + sizable.state.window = 0; + + if(dynamic_cast(&sizable)) { + Layout &layout = (Layout&)sizable; + layout.synchronizeLayout(); + } + + if(dynamic_cast(&sizable)) { + Widget &widget = (Widget&)sizable; + if(sizable.window()) sizable.window()->append(widget); + } +} + +void Layout::remove(Sizable &sizable) { + if(dynamic_cast(&sizable)) { + Widget &widget = (Widget&)sizable; + if(sizable.window()) sizable.window()->remove(widget); + } + + sizable.state.layout = 0; + sizable.state.window = 0; +} + +Layout::Layout(): +state(*new State), +base_from_member(*new pLayout(*this)), +Sizable(base_from_member::value), +p(base_from_member::value) { +} + +Layout::Layout(pLayout &p): +state(*new State), +base_from_member(p), +Sizable(p), +p(p) { +} + +Layout::~Layout() { + if(layout()) layout()->remove(*this); + else if(window()) window()->remove(*this); + p.destructor(); + delete &state; +} + +//Widget +//====== + +bool Widget::enabled() { + return state.enabled; +} + +string Widget::font() { + return state.font; +} + +Geometry Widget::geometry() { + return state.geometry; +} + +Geometry Widget::minimumGeometry() { + return p.minimumGeometry(); +} + +void Widget::setEnabled(bool enabled) { + state.enabled = enabled; + return p.setEnabled(enabled); +} + +void Widget::setFocused() { + return p.setFocused(); +} + +void Widget::setFont(const string &font) { + state.font = font; + return p.setFont(font); +} + +void Widget::setGeometry(const Geometry &geometry) { + state.geometry = geometry; + return p.setGeometry(geometry); +} + +void Widget::setVisible(bool visible) { + state.visible = visible; + return p.setVisible(visible); +} + +bool Widget::visible() { + return state.visible; +} + +Widget::Widget(): +state(*new State), +base_from_member(*new pWidget(*this)), +Sizable(base_from_member::value), +p(base_from_member::value) { + state.abstract = true; + p.constructor(); +} + +Widget::Widget(pWidget &p): +state(*new State), +base_from_member(p), +Sizable(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Widget::~Widget() { + p.destructor(); + delete &state; +} + +//Button +//====== + +void Button::setImage(const image &image, Orientation orientation) { + state.image = image; + state.orientation = orientation; + return p.setImage(image, orientation); +} + +void Button::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +Button::Button(): +state(*new State), +base_from_member(*new pButton(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Button::~Button() { + p.destructor(); + delete &state; +} + +//Canvas +//====== + +uint32_t* Canvas::data() { + return state.data; +} + +bool Canvas::setImage(const nall::image &image) { + if(image.data == nullptr || image.width == 0 || image.height == 0) return false; + state.width = image.width; + state.height = image.height; + setSize({ state.width, state.height }); + memcpy(state.data, image.data, state.width * state.height * sizeof(uint32_t)); + return true; +} + +void Canvas::setSize(const Size &size) { + state.width = size.width; + state.height = size.height; + delete[] state.data; + state.data = new uint32_t[size.width * size.height]; + return p.setSize(size); +} + +Size Canvas::size() { + return { state.width, state.height }; +} + +void Canvas::update() { + return p.update(); +} + +Canvas::Canvas(): +state(*new State), +base_from_member(*new pCanvas(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + state.data = new uint32_t[state.width * state.height]; + p.constructor(); +} + +Canvas::~Canvas() { + p.destructor(); + delete[] state.data; + delete &state; +} + +//CheckBox +//======== + +bool CheckBox::checked() { + return p.checked(); +} + +void CheckBox::setChecked(bool checked) { + state.checked = checked; + return p.setChecked(checked); +} + +void CheckBox::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +CheckBox::CheckBox(): +state(*new State), +base_from_member(*new pCheckBox(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +CheckBox::~CheckBox() { + p.destructor(); + delete &state; +} + +//ComboBox +//======== + +void ComboBox::append_(const lstring &list) { + for(auto &text : list) { + state.text.append(text); + p.append(text); + } +} + +void ComboBox::modify(unsigned row, const string &text) { + state.text(row) = text; + p.modify(row, text); +} + +void ComboBox::remove(unsigned row) { + state.text.remove(row); + p.remove(row); +} + +void ComboBox::reset() { + state.selection = 0; + state.text.reset(); + return p.reset(); +} + +unsigned ComboBox::selection() { + return p.selection(); +} + +void ComboBox::setSelection(unsigned row) { + state.selection = row; + return p.setSelection(row); +} + +string ComboBox::text() { + return state.text(selection()); +} + +string ComboBox::text(unsigned row) { + return state.text(row); +} + +ComboBox::ComboBox(): +state(*new State), +base_from_member(*new pComboBox(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +ComboBox::~ComboBox() { + p.destructor(); + delete &state; +} + +//HexEdit +//======= + +void HexEdit::setColumns(unsigned columns) { + state.columns = columns; + return p.setColumns(columns); +} + +void HexEdit::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void HexEdit::setOffset(unsigned offset) { + state.offset = offset; + return p.setOffset(offset); +} + +void HexEdit::setRows(unsigned rows) { + state.rows = rows; + return p.setRows(rows); +} + +void HexEdit::update() { + return p.update(); +} + +HexEdit::HexEdit(): +state(*new State), +base_from_member(*new pHexEdit(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +HexEdit::~HexEdit() { + p.destructor(); + delete &state; +} + +//HorizontalScrollBar +//=================== + +unsigned HorizontalScrollBar::length() { + return state.length; +} + +unsigned HorizontalScrollBar::position() { + return p.position(); +} + +void HorizontalScrollBar::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void HorizontalScrollBar::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +HorizontalScrollBar::HorizontalScrollBar(): +state(*new State), +base_from_member(*new pHorizontalScrollBar(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +HorizontalScrollBar::~HorizontalScrollBar() { + p.destructor(); + delete &state; +} + +//HorizontalSlider +//================ + +unsigned HorizontalSlider::length() { + return state.length; +} + +unsigned HorizontalSlider::position() { + return p.position(); +} + +void HorizontalSlider::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void HorizontalSlider::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +HorizontalSlider::HorizontalSlider(): +state(*new State), +base_from_member(*new pHorizontalSlider(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +HorizontalSlider::~HorizontalSlider() { + p.destructor(); + delete &state; +} + +//Label +//===== + +void Label::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +Label::Label(): +state(*new State), +base_from_member(*new pLabel(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Label::~Label() { + p.destructor(); + delete &state; +} + +//LineEdit +//======== + +void LineEdit::setEditable(bool editable) { + state.editable = editable; + return p.setEditable(editable); +} + +void LineEdit::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +string LineEdit::text() { + return p.text(); +} + +LineEdit::LineEdit(): +state(*new State), +base_from_member(*new pLineEdit(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +LineEdit::~LineEdit() { + p.destructor(); + delete &state; +} + +//ListView +//======== + +void ListView::append_(const lstring &text) { + state.checked.append(false); + state.text.append(text); + return p.append(text); +} + +void ListView::autoSizeColumns() { + return p.autoSizeColumns(); +} + +bool ListView::checked(unsigned row) { + return p.checked(row); +} + +void ListView::modify_(unsigned row, const lstring &text) { + state.text[row] = text; + return p.modify(row, text); +} + +void ListView::remove(unsigned row) { + state.text.remove(row); + state.image.remove(row); + return p.remove(row); +} + +void ListView::reset() { + state.checked.reset(); + state.image.reset(); + state.text.reset(); + return p.reset(); +} + +bool ListView::selected() { + return p.selected(); +} + +unsigned ListView::selection() { + return p.selection(); +} + +void ListView::setCheckable(bool checkable) { + state.checkable = checkable; + return p.setCheckable(checkable); +} + +void ListView::setChecked(unsigned row, bool checked) { + state.checked[row] = checked; + return p.setChecked(row, checked); +} + +void ListView::setHeaderText_(const lstring &text) { + state.headerText = text; + return p.setHeaderText(text); +} + +void ListView::setHeaderVisible(bool visible) { + state.headerVisible = visible; + return p.setHeaderVisible(visible); +} + +void ListView::setImage(unsigned row, unsigned column, const nall::image &image) { + state.image(row)(column) = image; + return p.setImage(row, column, image); +} + +void ListView::setSelected(bool selected) { + state.selected = selected; + return p.setSelected(selected); +} + +void ListView::setSelection(unsigned row) { + state.selected = true; + state.selection = row; + return p.setSelection(row); +} + +ListView::ListView(): +state(*new State), +base_from_member(*new pListView(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +ListView::~ListView() { + p.destructor(); + delete &state; +} + +//ProgressBar +//=========== + +void ProgressBar::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +ProgressBar::ProgressBar(): +state(*new State), +base_from_member(*new pProgressBar(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +ProgressBar::~ProgressBar() { + p.destructor(); + delete &state; +} + +//RadioBox +//======== + +void RadioBox::group(const set &list) { + for(auto &item : list) item.p.setGroup(item.state.group = list); + if(list.size()) list[0].setChecked(); +} + +bool RadioBox::checked() { + return p.checked(); +} + +void RadioBox::setChecked() { + for(auto &item : state.group) item.state.checked = false; + state.checked = true; + return p.setChecked(); +} + +void RadioBox::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +RadioBox::RadioBox(): +state(*new State), +base_from_member(*new pRadioBox(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +RadioBox::~RadioBox() { + for(auto &item : state.group) { + if(&item != this) item.state.group.remove(*this); + } + p.destructor(); + delete &state; +} + +//TextEdit +//======== + +void TextEdit::setCursorPosition(unsigned position) { + state.cursorPosition = position; + return p.setCursorPosition(position); +} + +void TextEdit::setEditable(bool editable) { + state.editable = editable; + return p.setEditable(editable); +} + +void TextEdit::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +void TextEdit::setWordWrap(bool wordWrap) { + state.wordWrap = wordWrap; + return p.setWordWrap(wordWrap); +} + +string TextEdit::text() { + return p.text(); +} + +TextEdit::TextEdit(): +state(*new State), +base_from_member(*new pTextEdit(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +TextEdit::~TextEdit() { + p.destructor(); + delete &state; +} + +//VerticalScrollBar +//================= + +unsigned VerticalScrollBar::length() { + return state.length; +} + +unsigned VerticalScrollBar::position() { + return p.position(); +} + +void VerticalScrollBar::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void VerticalScrollBar::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +VerticalScrollBar::VerticalScrollBar(): +state(*new State), +base_from_member(*new pVerticalScrollBar(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +VerticalScrollBar::~VerticalScrollBar() { + p.destructor(); + delete &state; +} + +//VerticalSlider +//============== + +unsigned VerticalSlider::length() { + return state.length; +} + +unsigned VerticalSlider::position() { + return p.position(); +} + +void VerticalSlider::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void VerticalSlider::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +VerticalSlider::VerticalSlider(): +state(*new State), +base_from_member(*new pVerticalSlider(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +VerticalSlider::~VerticalSlider() { + p.destructor(); + delete &state; +} + +//Viewport +//======== + +uintptr_t Viewport::handle() { + return p.handle(); +} + +Viewport::Viewport(): +base_from_member(*new pViewport(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Viewport::~Viewport() { + p.destructor(); +} diff --git a/ananke/phoenix/core/core.hpp b/ananke/phoenix/core/core.hpp new file mode 100644 index 00000000..a11d756c --- /dev/null +++ b/ananke/phoenix/core/core.hpp @@ -0,0 +1,613 @@ +struct Font; +struct Window; +struct Menu; +struct Sizable; +struct Layout; +struct Widget; + +struct pFont; +struct pObject; +struct pOS; +struct pTimer; +struct pWindow; +struct pAction; +struct pMenu; +struct pSeparator; +struct pItem; +struct pCheckItem; +struct pRadioItem; +struct pSizable; +struct pLayout; +struct pWidget; +struct pButton; +struct pCanvas; +struct pCheckBox; +struct pComboBox; +struct pHexEdit; +struct pHorizontalScrollBar; +struct pHorizontalSlider; +struct pLabel; +struct pLineEdit; +struct pListView; +struct pProgressBar; +struct pRadioBox; +struct pTextEdit; +struct pVerticalScrollBar; +struct pVerticalSlider; +struct pViewport; + +enum : unsigned { + MaximumSize = ~0u, + MinimumSize = 0u, +}; + +struct Color { + uint8_t red, green, blue, alpha; + uint32_t rgb() const; + uint32_t rgba() const; + inline Color() : red(0), green(0), blue(0), alpha(255) {} + inline Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255) : red(red), green(green), blue(blue), alpha(alpha) {} +}; + +struct Position { + signed x, y; + inline Position() : x(0), y(0) {} + template inline Position(X x, Y y) : x(x), y(y) {} +}; + +struct Size { + unsigned width, height; + inline Size() : width(0), height(0) {} + template inline Size(W width, H height) : width(width), height(height) {} +}; + +struct Geometry { + signed x, y; + unsigned width, height; + Position position() const; + Size size() const; + nall::string text() const; + inline Geometry() : x(0), y(0), width(0), height(0) {} + inline Geometry(const Position& position, const Size& size) : x(position.x), y(position.y), width(size.width), height(size.height) {} + template inline Geometry(X x, Y y, W width, H height) : x(x), y(y), width(width), height(height) {} + Geometry(const nall::string &text); +}; + +enum class Orientation : unsigned { Horizontal, Vertical }; + +struct Font { + nall::string description; + Geometry geometry(const nall::string &text); + Font(const nall::string &description = ""); +}; + +struct Desktop { + static Size size(); + static Geometry workspace(); + Desktop() = delete; +}; + +struct Keyboard { + #include "keyboard.hpp" + static bool pressed(Scancode scancode); + static bool released(Scancode scancode); + static nall::vector state(); + Keyboard() = delete; +}; + +struct Mouse { + enum class Button : unsigned { Left, Middle, Right }; + static Position position(); + static bool pressed(Button); + static bool released(Button); + Mouse() = delete; +}; + +struct DialogWindow { + template static nall::string fileOpen(Window &parent, const nall::string &path, const Args&... args) { return fileOpen_(parent, path, { args... }); } + template static nall::string fileSave(Window &parent, const nall::string &path, const Args&... args) { return fileSave_(parent, path, { args... }); } + static nall::string folderSelect(Window &parent, const nall::string &path); + DialogWindow() = delete; + +private: + static nall::string fileOpen_(Window &parent, const nall::string &path, const nall::lstring& filter); + static nall::string fileSave_(Window &parent, const nall::string &path, const nall::lstring& filter); +}; + +struct MessageWindow { + 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); + MessageWindow() = delete; +}; + +struct Object { + Object(pObject &p); + Object& operator=(const Object&) = delete; + Object(const Object&) = delete; + virtual ~Object(); + pObject &p; +}; + +struct OS : Object { + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + OS(); + static void initialize(); +}; + +struct Timer : private nall::base_from_member, Object { + nall::function onTimeout; + + void setEnabled(bool enabled = true); + void setInterval(unsigned milliseconds); + + Timer(); + ~Timer(); + struct State; + State &state; + pTimer &p; +}; + +struct Window : private nall::base_from_member, Object { + nall::function onClose; + nall::function onKeyPress; + nall::function onKeyRelease; + nall::function onMove; + nall::function onSize; + + static Window& none(); + + inline void append() {} + inline void remove() {} + template void append(T &arg, Args&... args) { append_(arg); append(args...); } + template void remove(T &arg, Args&... args) { remove_(arg); remove(args...); } + + void append_(Layout &layout); + void append_(Menu &menu); + void append_(Widget &widget); + Color backgroundColor(); + Geometry frameGeometry(); + Geometry frameMargin(); + bool focused(); + bool fullScreen(); + Geometry geometry(); + void ignore(); + void remove_(Layout &layout); + void remove_(Menu &menu); + void remove_(Widget &widget); + void setBackgroundColor(const Color &color); + void setFrameGeometry(const Geometry &geometry); + void setFocused(); + void setFullScreen(bool fullScreen = true); + void setGeometry(const Geometry &geometry); + void setMenuFont(const nall::string &font); + void setMenuVisible(bool visible = true); + void setModal(bool modal = true); + void setResizable(bool resizable = true); + void setSmartGeometry(const Geometry &geometry); + void setStatusFont(const nall::string &font); + void setStatusText(const nall::string &text); + void setStatusVisible(bool visible = true); + void setTitle(const nall::string &text); + void setVisible(bool visible = true); + void setWidgetFont(const nall::string &font); + nall::string statusText(); + void synchronizeLayout(); + bool visible(); + + Window(); + ~Window(); + struct State; + State &state; + pWindow &p; +}; + +struct Action : Object { + bool enabled(); + void setEnabled(bool enabled = true); + void setVisible(bool visible = true); + bool visible(); + + Action(pAction &p); + ~Action(); + struct State; + State &state; + pAction &p; +}; + +struct Menu : private nall::base_from_member, Action { + template void append(Args&... args) { append({args...}); } + template void remove(Args&... args) { remove({args...}); } + + void append(const nall::set &list); + void remove(const nall::set &list); + void setImage(const nall::image &image = nall::image{}); + void setText(const nall::string &text); + + Menu(); + ~Menu(); + struct State; + State &state; + pMenu &p; +}; + +struct Separator : private nall::base_from_member, Action { + Separator(); + ~Separator(); + pSeparator &p; +}; + +struct Item : private nall::base_from_member, Action { + nall::function onActivate; + + void setImage(const nall::image &image = nall::image{}); + void setText(const nall::string &text); + + Item(); + ~Item(); + struct State; + State &state; + pItem &p; +}; + +struct CheckItem : private nall::base_from_member, Action { + nall::function onToggle; + + bool checked(); + void setChecked(bool checked = true); + void setText(const nall::string &text); + + CheckItem(); + ~CheckItem(); + struct State; + State &state; + pCheckItem &p; +}; + +struct RadioItem : private nall::base_from_member, Action { + template static void group(Args&... args) { group({args...}); } + static void group(const nall::set &list); + + nall::function onActivate; + + bool checked(); + void setChecked(); + void setText(const nall::string &text); + nall::string text(); + + RadioItem(); + ~RadioItem(); + struct State; + State &state; + pRadioItem &p; +}; + +struct Sizable : Object { + virtual bool enabled() = 0; + Layout* layout(); + virtual Geometry minimumGeometry() = 0; + virtual void setEnabled(bool enabled = true) = 0; + virtual void setGeometry(const Geometry &geometry) = 0; + virtual void setVisible(bool visible = true) = 0; + virtual bool visible() = 0; + Window* window(); + + Sizable(pSizable &p); + ~Sizable(); + struct State; + State &state; + pSizable &p; +}; + +struct Layout : private nall::base_from_member, Sizable { + virtual void append(Sizable &sizable); + virtual void remove(Sizable &sizable); + virtual void reset() {} + virtual void synchronizeLayout() = 0; + + Layout(); + Layout(pLayout &p); + ~Layout(); + struct State; + State &state; + pLayout &p; +}; + +struct Widget : private nall::base_from_member, Sizable { + bool enabled(); + nall::string font(); + Geometry geometry(); + Geometry minimumGeometry(); + void setEnabled(bool enabled = true); + void setFocused(); + void setFont(const nall::string &font); + void setGeometry(const Geometry &geometry); + void setVisible(bool visible = true); + bool visible(); + + Widget(); + Widget(pWidget &p); + ~Widget(); + struct State; + State &state; + pWidget &p; +}; + +struct Button : private nall::base_from_member, Widget { + nall::function onActivate; + + void setImage(const nall::image &image = nall::image{}, Orientation = Orientation::Horizontal); + void setText(const nall::string &text); + + Button(); + ~Button(); + struct State; + State &state; + pButton &p; +}; + +struct Canvas : private nall::base_from_member, Widget { + nall::function onMouseLeave; + nall::function onMouseMove; + nall::function onMousePress; + nall::function onMouseRelease; + + uint32_t* data(); + bool setImage(const nall::image &image); + void setSize(const Size &size); + Size size(); + void update(); + + Canvas(); + ~Canvas(); + struct State; + State &state; + pCanvas &p; +}; + +struct CheckBox : private nall::base_from_member, Widget { + nall::function onToggle; + + bool checked(); + void setChecked(bool checked = true); + void setText(const nall::string &text); + + CheckBox(); + ~CheckBox(); + struct State; + State &state; + pCheckBox &p; +}; + +struct ComboBox : private nall::base_from_member, Widget { + nall::function onChange; + + template void append(const Args&... args) { append_({args...}); } + + void append_(const nall::lstring &list); + void modify(unsigned row, const nall::string &text); + void remove(unsigned row); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + nall::string text(); + nall::string text(unsigned row); + + ComboBox(); + ~ComboBox(); + struct State; + State &state; + pComboBox &p; +}; + +struct HexEdit : private nall::base_from_member, Widget { + nall::function onRead; + nall::function onWrite; + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + HexEdit(); + ~HexEdit(); + struct State; + State &state; + pHexEdit &p; +}; + +struct HorizontalScrollBar : private nall::base_from_member, Widget { + nall::function onChange; + + unsigned length(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + HorizontalScrollBar(); + ~HorizontalScrollBar(); + struct State; + State &state; + pHorizontalScrollBar &p; +}; + +struct HorizontalSlider : private nall::base_from_member, Widget { + nall::function onChange; + + unsigned length(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + HorizontalSlider(); + ~HorizontalSlider(); + struct State; + State &state; + pHorizontalSlider &p; +}; + +struct Label : private nall::base_from_member, Widget { + void setText(const nall::string &text); + + Label(); + ~Label(); + struct State; + State &state; + pLabel &p; +}; + +struct LineEdit : private nall::base_from_member, Widget { + nall::function onActivate; + nall::function onChange; + + void setEditable(bool editable = true); + void setText(const nall::string &text); + nall::string text(); + + LineEdit(); + ~LineEdit(); + struct State; + State &state; + pLineEdit &p; +}; + +struct ListView : private nall::base_from_member, Widget { + nall::function onActivate; + nall::function onChange; + nall::function onToggle; + + template void append(const Args&... args) { append_({args...}); } + template void modify(unsigned row, const Args&... args) { modify_(row, {args...}); } + template void setHeaderText(const Args&... args) { setHeaderText_({args...}); } + + void append_(const nall::lstring &list); + void autoSizeColumns(); + bool checked(unsigned row); + void modify_(unsigned row, const nall::lstring &list); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable = true); + void setChecked(unsigned row, bool checked = true); + void setHeaderText_(const nall::lstring &list); + void setHeaderVisible(bool visible = true); + void setImage(unsigned row, unsigned column, const nall::image &image = nall::image{}); + void setSelected(bool selected = true); + void setSelection(unsigned row); + + ListView(); + ~ListView(); + struct State; + State &state; + pListView &p; +}; + +struct ProgressBar : private nall::base_from_member, Widget { + void setPosition(unsigned position); + + ProgressBar(); + ~ProgressBar(); + struct State; + State &state; + pProgressBar &p; +}; + +struct RadioBox : private nall::base_from_member, Widget { + template static void group(Args&... args) { group({args...}); } + static void group(const nall::set &list); + + nall::function onActivate; + + bool checked(); + void setChecked(); + void setText(const nall::string &text); + + RadioBox(); + ~RadioBox(); + struct State; + State &state; + pRadioBox &p; +}; + +struct TextEdit : private nall::base_from_member, Widget { + nall::function onChange; + + void setCursorPosition(unsigned position); + void setEditable(bool editable = true); + void setText(const nall::string &text); + void setWordWrap(bool wordWrap = true); + nall::string text(); + + TextEdit(); + ~TextEdit(); + struct State; + State &state; + pTextEdit &p; +}; + +struct VerticalScrollBar : private nall::base_from_member, Widget { + nall::function onChange; + + unsigned length(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + VerticalScrollBar(); + ~VerticalScrollBar(); + struct State; + State &state; + pVerticalScrollBar &p; +}; + +struct VerticalSlider : private nall::base_from_member, Widget { + nall::function onChange; + + unsigned length(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + VerticalSlider(); + ~VerticalSlider(); + struct State; + State &state; + pVerticalSlider &p; +}; + +struct Viewport : private nall::base_from_member, Widget { + nall::function onMouseLeave; + nall::function onMouseMove; + nall::function onMousePress; + nall::function onMouseRelease; + + uintptr_t handle(); + + Viewport(); + ~Viewport(); + pViewport &p; +}; + +#include "layout/fixed-layout.hpp" +#include "layout/horizontal-layout.hpp" +#include "layout/vertical-layout.hpp" diff --git a/ananke/phoenix/core/keyboard.hpp b/ananke/phoenix/core/keyboard.hpp new file mode 100644 index 00000000..ed04ec05 --- /dev/null +++ b/ananke/phoenix/core/keyboard.hpp @@ -0,0 +1,45 @@ +//each code refers to a physical key +//names are taken assuming: NumLock on, CapsLock off, Shift off +//layout uses US-104 keyboard +enum class Scancode : unsigned { + None, + + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, ScrollLock, Pause, + Insert, Delete, Home, End, PageUp, PageDown, + Up, Down, Left, Right, + + Grave, Number1, Number2, Number3, Number4, Number5, Number6, Number7, Number8, Number9, Number0, Minus, Equal, Backspace, + BracketLeft, BracketRight, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Tab, CapsLock, Return, ShiftLeft, ShiftRight, ControlLeft, ControlRight, SuperLeft, SuperRight, AltLeft, AltRight, Space, Menu, + 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, + + NumLock, Divide, Multiply, Subtract, Add, Enter, Point, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + + Limit, +}; + +//each enum refers to a translated scancode (eg Shift+1 = !) +enum class Keycode : unsigned { + None, + + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, SysRq, ScrollLock, Pause, Break, + Insert, Delete, Home, End, PageUp, PageDown, + Up, Down, Left, Right, + + Grave, Number1, Number2, Number3, Number4, Number5, Number6, Number7, Number8, Number9, Number0, Minus, Equal, Backspace, + Tilde, Exclamation, At, Pound, Dollar, Percent, Power, Ampersand, Asterisk, ParenthesisLeft, ParenthesisRight, Underscore, Plus, + BracketLeft, BracketRight, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + BraceLeft, BraceRight, Pipe, Colon, Quote, CaretLeft, CaretRight, Question, + Tab, CapsLock, Return, ShiftLeft, ShiftRight, ControlLeft, ControlRight, SuperLeft, SuperRight, AltLeft, AltRight, Space, Menu, + 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, 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, + + NumLock, Divide, Multiply, Subtract, Add, Enter, Point, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + KeypadInsert, KeypadDelete, KeypadHome, KeypadEnd, KeypadPageUp, KeypadPageDown, KeypadUp, KeypadDown, KeypadLeft, KeypadRight, KeypadCenter, + + Limit, +}; diff --git a/ananke/phoenix/core/layout/fixed-layout.cpp b/ananke/phoenix/core/layout/fixed-layout.cpp new file mode 100644 index 00000000..71ff3dac --- /dev/null +++ b/ananke/phoenix/core/layout/fixed-layout.cpp @@ -0,0 +1,80 @@ +void FixedLayout::append(Sizable &sizable, const Geometry &geometry) { + children.append({ &sizable, geometry }); + synchronizeLayout(); + if(window()) window()->synchronizeLayout(); +} + +void FixedLayout::append(Sizable &sizable) { + for(auto &child : children) if(child.sizable == &sizable) return; + Layout::append(sizable); + if(window()) window()->synchronizeLayout(); +} + +bool FixedLayout::enabled() { + if(layout()) return state.enabled && layout()->enabled(); + return state.enabled; +} + +Geometry FixedLayout::minimumGeometry() { + unsigned width = MinimumSize, height = MinimumSize; + for(auto &child : children) { + width = max(width, child.sizable->minimumGeometry().width); + height = max(height, child.sizable->minimumGeometry().height); + } + return { 0, 0, width, height }; +} + +void FixedLayout::remove(Sizable &sizable) { + for(unsigned n = 0; n < children.size(); n++) { + if(children[n].sizable == &sizable) { + children.remove(n); + Layout::remove(sizable); + if(window()) window()->synchronizeLayout(); + break; + } + } +} + +void FixedLayout::reset() { + for(auto &child : children) { + if(window() && dynamic_cast(child.sizable)) window()->remove((Widget&)*child.sizable); + } +} + +void FixedLayout::setEnabled(bool enabled) { + state.enabled = enabled; + for(auto &child : children) { + child.sizable->setVisible(dynamic_cast(child.sizable) ? child.sizable->enabled() : enabled); + } +} + +void FixedLayout::setGeometry(const Geometry &geometry) { +} + +void FixedLayout::setVisible(bool visible) { + state.visible = visible; + for(auto &child : children) { + child.sizable->setVisible(dynamic_cast(child.sizable) ? child.sizable->visible() : visible); + } +} + +void FixedLayout::synchronizeLayout() { + for(auto &child : children) { + Layout::append(*child.sizable); + child.sizable->setGeometry(child.geometry); + } +} + +bool FixedLayout::visible() { + if(layout()) return state.visible && layout()->visible(); + return state.visible; +} + +FixedLayout::FixedLayout() { + state.enabled = true; + state.visible = true; +} + +FixedLayout::~FixedLayout() { + while(children.size()) remove(*children[0].sizable); +} diff --git a/ananke/phoenix/core/layout/fixed-layout.hpp b/ananke/phoenix/core/layout/fixed-layout.hpp new file mode 100644 index 00000000..a67f2185 --- /dev/null +++ b/ananke/phoenix/core/layout/fixed-layout.hpp @@ -0,0 +1,27 @@ +struct FixedLayout : Layout { + void append(Sizable &sizable, const Geometry &geometry); + void append(Sizable &sizable); + bool enabled(); + Geometry minimumGeometry(); + void remove(Sizable &sizable); + void reset(); + void setEnabled(bool enabled = true); + void setGeometry(const Geometry &geometry); + void setVisible(bool visible = true); + void synchronizeLayout(); + bool visible(); + FixedLayout(); + ~FixedLayout(); + +//private: + struct State { + bool enabled; + bool visible; + } state; + + struct Children { + Sizable *sizable; + Geometry geometry; + }; + nall::vector children; +}; diff --git a/ananke/phoenix/core/layout/horizontal-layout.cpp b/ananke/phoenix/core/layout/horizontal-layout.cpp new file mode 100644 index 00000000..a1146038 --- /dev/null +++ b/ananke/phoenix/core/layout/horizontal-layout.cpp @@ -0,0 +1,142 @@ +void HorizontalLayout::append(Sizable &sizable, const Size &size, unsigned spacing) { + for(auto &child : children) if(child.sizable == &sizable) return; + children.append({ &sizable, size.width, size.height, spacing }); + synchronizeLayout(); + if(window()) window()->synchronizeLayout(); +} + +void HorizontalLayout::append(Sizable &sizable) { + for(auto &child : children) if(child.sizable == &sizable) return; + Layout::append(sizable); + if(window()) window()->synchronizeLayout(); +} + +bool HorizontalLayout::enabled() { + if(layout()) return state.enabled && layout()->enabled(); + return state.enabled; +} + +Geometry HorizontalLayout::minimumGeometry() { + unsigned width = 0, height = 0; + + for(auto &child : children) { + width += child.spacing; + if(child.width == MinimumSize || child.width == MaximumSize) { + width += child.sizable->minimumGeometry().width; + continue; + } + width += child.width; + } + + for(auto &child : children) { + if(child.height == MinimumSize || child.height == MaximumSize) { + height = max(height, child.sizable->minimumGeometry().height); + continue; + } + height = max(height, child.height); + } + + return { 0, 0, state.margin * 2 + width, state.margin * 2 + height }; +} + +void HorizontalLayout::remove(Sizable &sizable) { + for(unsigned n = 0; n < children.size(); n++) { + if(children[n].sizable == &sizable) { + if(dynamic_cast(children[n].sizable)) { + Layout *layout = (Layout*)children[n].sizable; + layout->reset(); + } + children.remove(n); + Layout::remove(sizable); + if(window()) window()->synchronizeLayout(); + break; + } + } +} + +void HorizontalLayout::reset() { + for(auto &child : children) { + if(window() && dynamic_cast(child.sizable)) ((Layout*)child.sizable)->reset(); + if(window() && dynamic_cast(child.sizable)) window()->remove((Widget&)*child.sizable); + } +} + +void HorizontalLayout::setAlignment(double alignment) { + state.alignment = max(0.0, min(1.0, alignment)); +} + +void HorizontalLayout::setEnabled(bool enabled) { + state.enabled = enabled; + for(auto &child : children) { + child.sizable->setEnabled(dynamic_cast(child.sizable) ? child.sizable->enabled() : enabled); + } +} + +void HorizontalLayout::setGeometry(const Geometry &containerGeometry) { + auto children = this->children; + for(auto &child : children) { + if(child.width == MinimumSize) child.width = child.sizable->minimumGeometry().width; + if(child.height == MinimumSize) child.height = child.sizable->minimumGeometry().height; + } + + Geometry geometry = containerGeometry; + geometry.x += state.margin; + geometry.y += state.margin; + geometry.width -= state.margin * 2; + geometry.height -= state.margin * 2; + + unsigned minimumWidth = 0, maximumWidthCounter = 0; + for(auto &child : children) { + if(child.width == MaximumSize) maximumWidthCounter++; + if(child.width != MaximumSize) minimumWidth += child.width; + minimumWidth += child.spacing; + } + + for(auto &child : children) { + if(child.width == MaximumSize) child.width = (geometry.width - minimumWidth) / maximumWidthCounter; + if(child.height == MaximumSize) child.height = geometry.height; + } + + unsigned maximumHeight = 0; + for(auto &child : children) maximumHeight = max(maximumHeight, child.height); + + for(auto &child : children) { + unsigned pivot = (maximumHeight - child.height) * state.alignment; + Geometry childGeometry = { geometry.x, geometry.y + pivot, child.width, child.height }; + child.sizable->setGeometry(childGeometry); + + geometry.x += child.width + child.spacing; + geometry.width -= child.width + child.spacing; + } +} + +void HorizontalLayout::setMargin(unsigned margin) { + state.margin = margin; +} + +void HorizontalLayout::setVisible(bool visible) { + state.visible = visible; + for(auto &child : children) { + child.sizable->setVisible(dynamic_cast(child.sizable) ? child.sizable->visible() : visible); + } +} + +void HorizontalLayout::synchronizeLayout() { + for(auto &child : children) Layout::append(*child.sizable); +} + +bool HorizontalLayout::visible() { + if(layout()) return state.visible && layout()->visible(); + return state.visible; +} + +HorizontalLayout::HorizontalLayout() { + state.alignment = 0.5; + state.enabled = true; + state.margin = 0; + state.visible = true; +} + +HorizontalLayout::~HorizontalLayout() { + while(children.size()) remove(*children[0].sizable); +} diff --git a/ananke/phoenix/core/layout/horizontal-layout.hpp b/ananke/phoenix/core/layout/horizontal-layout.hpp new file mode 100644 index 00000000..96d4f101 --- /dev/null +++ b/ananke/phoenix/core/layout/horizontal-layout.hpp @@ -0,0 +1,31 @@ +struct HorizontalLayout : public Layout { + void append(Sizable &sizable, const Size &size, unsigned spacing = 0); + void append(Sizable &sizable); + bool enabled(); + Geometry minimumGeometry(); + void remove(Sizable &sizable); + void reset(); + void setAlignment(double alignment); + void setEnabled(bool enabled = true); + void setGeometry(const Geometry &geometry); + void setMargin(unsigned margin); + void setVisible(bool visible = true); + void synchronizeLayout(); + bool visible(); + HorizontalLayout(); + ~HorizontalLayout(); + +//private: + struct State { + double alignment; + bool enabled; + unsigned margin; + bool visible; + } state; + + struct Children { + Sizable *sizable; + unsigned width, height, spacing; + }; + nall::vector children; +}; diff --git a/ananke/phoenix/core/layout/vertical-layout.cpp b/ananke/phoenix/core/layout/vertical-layout.cpp new file mode 100644 index 00000000..4fd6315b --- /dev/null +++ b/ananke/phoenix/core/layout/vertical-layout.cpp @@ -0,0 +1,142 @@ +void VerticalLayout::append(Sizable &sizable, const Size &size, unsigned spacing) { + for(auto &child : children) if(child.sizable == &sizable) return; + children.append({ &sizable, size.width, size.height, spacing }); + synchronizeLayout(); + if(window()) window()->synchronizeLayout(); +} + +void VerticalLayout::append(Sizable &sizable) { + for(auto &child : children) if(child.sizable == &sizable) return; + Layout::append(sizable); + if(window()) window()->synchronizeLayout(); +} + +bool VerticalLayout::enabled() { + if(layout()) return state.enabled && layout()->enabled(); + return state.enabled; +} + +Geometry VerticalLayout::minimumGeometry() { + unsigned width = 0, height = 0; + + for(auto &child : children) { + if(child.width == MinimumSize || child.width == MaximumSize) { + width = max(width, child.sizable->minimumGeometry().width); + continue; + } + width = max(width, child.width); + } + + for(auto &child : children) { + height += child.spacing; + if(child.height == MinimumSize || child.height == MaximumSize) { + height += child.sizable->minimumGeometry().height; + continue; + } + height += child.height; + } + + return { 0, 0, state.margin * 2 + width, state.margin * 2 + height }; +} + +void VerticalLayout::remove(Sizable &sizable) { + for(unsigned n = 0; n < children.size(); n++) { + if(children[n].sizable == &sizable) { + if(dynamic_cast(children[n].sizable)) { + Layout *layout = (Layout*)children[n].sizable; + layout->reset(); + } + children.remove(n); + Layout::remove(sizable); + if(window()) window()->synchronizeLayout(); + break; + } + } +} + +void VerticalLayout::reset() { + for(auto &child : children) { + if(window() && dynamic_cast(child.sizable)) ((Layout*)child.sizable)->reset(); + if(window() && dynamic_cast(child.sizable)) window()->remove((Widget&)*child.sizable); + } +} + +void VerticalLayout::setAlignment(double alignment) { + state.alignment = max(0.0, min(1.0, alignment)); +} + +void VerticalLayout::setEnabled(bool enabled) { + state.enabled = enabled; + for(auto &child : children) { + child.sizable->setEnabled(dynamic_cast(child.sizable) ? child.sizable->enabled() : enabled); + } +} + +void VerticalLayout::setGeometry(const Geometry &containerGeometry) { + auto children = this->children; + for(auto &child : children) { + if(child.width == MinimumSize) child.width = child.sizable->minimumGeometry().width; + if(child.height == MinimumSize) child.height = child.sizable->minimumGeometry().height; + } + + Geometry geometry = containerGeometry; + geometry.x += state.margin; + geometry.y += state.margin; + geometry.width -= state.margin * 2; + geometry.height -= state.margin * 2; + + unsigned minimumHeight = 0, maximumHeightCounter = 0; + for(auto &child : children) { + if(child.height == MaximumSize) maximumHeightCounter++; + if(child.height != MaximumSize) minimumHeight += child.height; + minimumHeight += child.spacing; + } + + for(auto &child : children) { + if(child.width == MaximumSize) child.width = geometry.width; + if(child.height == MaximumSize) child.height = (geometry.height - minimumHeight) / maximumHeightCounter; + } + + unsigned maximumWidth = 0; + for(auto &child : children) maximumWidth = max(maximumWidth, child.width); + + for(auto &child : children) { + unsigned pivot = (maximumWidth - child.width) * state.alignment; + Geometry childGeometry = { geometry.x + pivot, geometry.y, child.width, child.height }; + child.sizable->setGeometry(childGeometry); + + geometry.y += child.height + child.spacing; + geometry.height -= child.height + child.spacing; + } +} + +void VerticalLayout::setMargin(unsigned margin) { + state.margin = margin; +} + +void VerticalLayout::setVisible(bool visible) { + state.visible = visible; + for(auto &child : children) { + child.sizable->setVisible(dynamic_cast(child.sizable) ? child.sizable->visible() : visible); + } +} + +void VerticalLayout::synchronizeLayout() { + for(auto &child : children) Layout::append(*child.sizable); +} + +bool VerticalLayout::visible() { + if(layout()) return state.visible && layout()->visible(); + return state.visible; +} + +VerticalLayout::VerticalLayout() { + state.alignment = 0.0; + state.enabled = true; + state.margin = 0; + state.visible = true; +} + +VerticalLayout::~VerticalLayout() { + while(children.size()) remove(*children[0].sizable); +} diff --git a/ananke/phoenix/core/layout/vertical-layout.hpp b/ananke/phoenix/core/layout/vertical-layout.hpp new file mode 100644 index 00000000..8273dbe2 --- /dev/null +++ b/ananke/phoenix/core/layout/vertical-layout.hpp @@ -0,0 +1,31 @@ +struct VerticalLayout : public Layout { + void append(Sizable &sizable, const Size &size, unsigned spacing = 0); + void append(Sizable &sizable); + bool enabled(); + Geometry minimumGeometry(); + void remove(Sizable &sizable); + void reset(); + void setAlignment(double alignment); + void setEnabled(bool enabled = true); + void setGeometry(const Geometry &geometry); + void setMargin(unsigned margin); + void setVisible(bool visible = true); + void synchronizeLayout(); + bool visible(); + VerticalLayout(); + ~VerticalLayout(); + +//private: + struct State { + double alignment; + bool enabled; + unsigned margin; + bool visible; + } state; + + struct Children { + Sizable *sizable; + unsigned width, height, spacing; + }; + nall::vector children; +}; diff --git a/ananke/phoenix/core/state.hpp b/ananke/phoenix/core/state.hpp new file mode 100644 index 00000000..2d46ca2f --- /dev/null +++ b/ananke/phoenix/core/state.hpp @@ -0,0 +1,278 @@ +struct Timer::State { + bool enabled; + unsigned milliseconds; + + State() { + enabled = false; + milliseconds = 0; + } +}; + +struct Window::State { + bool backgroundColorOverride; + Color backgroundColor; + bool fullScreen; + Geometry geometry; + bool ignore; + set layout; + set menu; + string menuFont; + bool menuVisible; + bool modal; + bool resizable; + string statusFont; + string statusText; + bool statusVisible; + string title; + bool visible; + set widget; + string widgetFont; + + State() { + backgroundColorOverride = false; + backgroundColor = {0, 0, 0, 255}; + fullScreen = false; + geometry = {128, 128, 256, 256}; + ignore = false; + menuVisible = false; + modal = false; + resizable = true; + statusVisible = false; + visible = false; + } +}; + +struct Action::State { + bool enabled; + Menu *menu; + bool visible; + Window *window; + + State() { + enabled = true; + menu = 0; + visible = true; + window = 0; + } +}; + +struct Menu::State { + set action; + nall::image image; + string text; + + State() : image(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0) { + } +}; + +struct Item::State { + nall::image image; + string text; + + State() : image(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0) { + } +}; + +struct CheckItem::State { + bool checked; + string text; + + State() { + checked = false; + } +}; + +struct RadioItem::State { + bool checked; + set group; + string text; + + State() { + checked = true; + } +}; + +struct Sizable::State { + Layout *layout; + Window *window; + + State() { + layout = 0; + window = 0; + } +}; + +struct Layout::State { + State() { + } +}; + +struct Widget::State { + bool abstract; + bool enabled; + string font; + Geometry geometry; + bool visible; + + State() { + abstract = false; + enabled = true; + geometry = {0, 0, 0, 0}; + visible = true; + } +}; + +struct Button::State { + nall::image image; + Orientation orientation; + string text; + + State() : image(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0) { + } +}; + +struct Canvas::State { + uint32_t *data; + unsigned width; + unsigned height; + + State() { + data = nullptr; + width = 256; + height = 256; + } +}; + +struct CheckBox::State { + bool checked; + string text; + + State() { + checked = false; + } +}; + +struct ComboBox::State { + unsigned selection; + vector text; + + State() { + selection = 0; + } +}; + +struct HexEdit::State { + unsigned columns; + unsigned length; + unsigned offset; + unsigned rows; + + State() { + columns = 16; + length = 0; + offset = 0; + rows = 16; + } +}; + +struct HorizontalScrollBar::State { + unsigned length; + unsigned position; + + State() { + length = 101; + position = 0; + } +}; + +struct HorizontalSlider::State { + unsigned length; + unsigned position; + + State() { + length = 101; + position = 0; + } +}; + +struct Label::State { + string text; +}; + +struct LineEdit::State { + bool editable; + string text; + + State() { + editable = true; + } +}; + +struct ListView::State { + bool checkable; + vector checked; + lstring headerText; + bool headerVisible; + vector> image; + bool selected; + unsigned selection; + vector text; + + State() { + checkable = false; + headerVisible = false; + selected = false; + selection = 0; + } +}; + +struct ProgressBar::State { + unsigned position; + + State() { + position = 0; + } +}; + +struct RadioBox::State { + bool checked; + set group; + string text; + + State() { + checked = true; + } +}; + +struct TextEdit::State { + unsigned cursorPosition; + bool editable; + string text; + bool wordWrap; + + State() { + cursorPosition = 0; + editable = true; + wordWrap = true; + } +}; + +struct VerticalScrollBar::State { + unsigned length; + unsigned position; + + State() { + length = 101; + position = 0; + } +}; + +struct VerticalSlider::State { + unsigned length; + unsigned position; + + State() { + length = 101; + position = 0; + } +}; diff --git a/ananke/phoenix/gtk/action/action.cpp b/ananke/phoenix/gtk/action/action.cpp new file mode 100644 index 00000000..950259de --- /dev/null +++ b/ananke/phoenix/gtk/action/action.cpp @@ -0,0 +1,27 @@ +void pAction::setEnabled(bool enabled) { + gtk_widget_set_sensitive(widget, enabled); +} + +void pAction::setVisible(bool visible) { + gtk_widget_set_visible(widget, visible); +} + +void pAction::constructor() { +} + +void pAction::orphan() { +} + +//GTK+ uses _ for mnemonics, __ for _ +//transform so that & is used for mnemonics, && for & +string pAction::mnemonic(string text) { + text.transform("&_", "\x01\x02"); + text.replace("\x01\x01", "&"); + text.transform("\x01", "_"); + text.replace("\x02", "__"); + return text; +} + +void pAction::setFont(const string &font) { + pFont::setFont(widget, font); +} diff --git a/ananke/phoenix/gtk/action/check-item.cpp b/ananke/phoenix/gtk/action/check-item.cpp new file mode 100644 index 00000000..2cc182a6 --- /dev/null +++ b/ananke/phoenix/gtk/action/check-item.cpp @@ -0,0 +1,33 @@ +static void CheckItem_toggle(CheckItem *self) { + if(self->p.locked == false && self->onToggle) self->onToggle(); +} + +bool pCheckItem::checked() { + return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); +} + +void pCheckItem::setChecked(bool checked) { + locked = true; + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), checked); + locked = false; +} + +void pCheckItem::setText(const string &text) { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), mnemonic(text)); +} + +void pCheckItem::constructor() { + widget = gtk_check_menu_item_new_with_mnemonic(""); + setChecked(checkItem.state.checked); + setText(checkItem.state.text); + g_signal_connect_swapped(G_OBJECT(widget), "toggled", G_CALLBACK(CheckItem_toggle), (gpointer)&checkItem); +} + +void pCheckItem::destructor() { + gtk_widget_destroy(widget); +} + +void pCheckItem::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/action/item.cpp b/ananke/phoenix/gtk/action/item.cpp new file mode 100644 index 00000000..afab955e --- /dev/null +++ b/ananke/phoenix/gtk/action/item.cpp @@ -0,0 +1,31 @@ +static void Item_activate(Item *self) { + if(self->onActivate) self->onActivate(); +} + +void pItem::setImage(const image &image) { + if(image.empty() == false) { + GtkImage *gtkImage = CreateImage(image, true); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), (GtkWidget*)gtkImage); + } else { + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), nullptr); + } +} + +void pItem::setText(const string &text) { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), mnemonic(text)); +} + +void pItem::constructor() { + widget = gtk_image_menu_item_new_with_mnemonic(""); + g_signal_connect_swapped(G_OBJECT(widget), "activate", G_CALLBACK(Item_activate), (gpointer)&item); + setText(item.state.text); +} + +void pItem::destructor() { + gtk_widget_destroy(widget); +} + +void pItem::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/action/menu.cpp b/ananke/phoenix/gtk/action/menu.cpp new file mode 100644 index 00000000..92252085 --- /dev/null +++ b/ananke/phoenix/gtk/action/menu.cpp @@ -0,0 +1,51 @@ +void pMenu::append(Action &action) { + action.state.window = this->action.state.window; + + gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), action.p.widget); + if(action.state.window && action.state.window->state.menuFont != "") { + action.p.setFont(action.state.window->state.menuFont); + } + gtk_widget_show(action.p.widget); +} + +void pMenu::remove(Action &action) { + action.p.orphan(); + action.state.window = 0; +} + +void pMenu::setImage(const image &image) { + if(image.empty() == false) { + GtkImage *gtkImage = CreateImage(image, true); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), (GtkWidget*)gtkImage); + } else { + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), nullptr); + } +} + +void pMenu::setText(const string &text) { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), mnemonic(text)); +} + +void pMenu::constructor() { + gtkMenu = gtk_menu_new(); + widget = gtk_image_menu_item_new_with_mnemonic(""); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(widget), gtkMenu); + setText(menu.state.text); +} + +void pMenu::destructor() { + gtk_widget_destroy(gtkMenu); + gtk_widget_destroy(widget); +} + +void pMenu::orphan() { + for(auto &action : menu.state.action) action.p.orphan(); + destructor(); + constructor(); + for(auto &action : menu.state.action) append(action); +} + +void pMenu::setFont(const string &font) { + pAction::setFont(font); + for(auto &item : menu.state.action) item.p.setFont(font); +} diff --git a/ananke/phoenix/gtk/action/radio-item.cpp b/ananke/phoenix/gtk/action/radio-item.cpp new file mode 100644 index 00000000..a599d70b --- /dev/null +++ b/ananke/phoenix/gtk/action/radio-item.cpp @@ -0,0 +1,48 @@ +static void RadioItem_activate(RadioItem *self) { + for(auto &item : self->state.group) item.state.checked = (&item == self); + if(self->p.locked == false && self->checked() && self->onActivate) self->onActivate(); +} + +bool pRadioItem::checked() { + return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); +} + +void pRadioItem::setChecked() { + locked = true; + for(auto &item : radioItem.state.group) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item.p.widget), false); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), true); + locked = false; +} + +void pRadioItem::setGroup(const set &group) { + for(unsigned n = 0; n < group.size(); n++) { + if(n == 0) continue; + GSList *currentGroup = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(group[0].p.widget)); + if(currentGroup != gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(group[n].p.widget))) { + gtk_radio_menu_item_set_group(GTK_RADIO_MENU_ITEM(group[n].p.widget), currentGroup); + } + } +} + +void pRadioItem::setText(const string &text) { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), mnemonic(text)); +} + +void pRadioItem::constructor() { + widget = gtk_radio_menu_item_new_with_mnemonic(0, ""); + setGroup(radioItem.state.group); + setText(radioItem.state.text); + for(auto &item : radioItem.state.group) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item.p.widget), item.state.checked); + } + g_signal_connect_swapped(G_OBJECT(widget), "toggled", G_CALLBACK(RadioItem_activate), (gpointer)&radioItem); +} + +void pRadioItem::destructor() { + gtk_widget_destroy(widget); +} + +void pRadioItem::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/action/separator.cpp b/ananke/phoenix/gtk/action/separator.cpp new file mode 100644 index 00000000..8b7a1a6b --- /dev/null +++ b/ananke/phoenix/gtk/action/separator.cpp @@ -0,0 +1,12 @@ +void pSeparator::constructor() { + widget = gtk_separator_menu_item_new(); +} + +void pSeparator::destructor() { + gtk_widget_destroy(widget); +} + +void pSeparator::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/desktop.cpp b/ananke/phoenix/gtk/desktop.cpp new file mode 100644 index 00000000..2b1801ad --- /dev/null +++ b/ananke/phoenix/gtk/desktop.cpp @@ -0,0 +1,36 @@ +Size pDesktop::size() { + return { + gdk_screen_get_width(gdk_screen_get_default()), + gdk_screen_get_height(gdk_screen_get_default()) + }; +} + +Geometry pDesktop::workspace() { + XlibDisplay *display = XOpenDisplay(0); + int screen = DefaultScreen(display); + + static Atom atom = XlibNone; + if(atom == XlibNone) atom = XInternAtom(display, "_NET_WORKAREA", True); + + int format; + unsigned char *data = 0; + unsigned long items, after; + Atom returnAtom; + + int result = XGetWindowProperty( + display, RootWindow(display, screen), atom, 0, 4, False, XA_CARDINAL, &returnAtom, &format, &items, &after, &data + ); + + XCloseDisplay(display); + + if(result == Success && returnAtom == XA_CARDINAL && format == 32 && items == 4) { + unsigned long *workarea = (unsigned long*)data; + return { (signed)workarea[0], (signed)workarea[1], (unsigned)workarea[2], (unsigned)workarea[3] }; + } + + return { + 0, 0, + gdk_screen_get_width(gdk_screen_get_default()), + gdk_screen_get_height(gdk_screen_get_default()) + }; +} diff --git a/ananke/phoenix/gtk/dialog-window.cpp b/ananke/phoenix/gtk/dialog-window.cpp new file mode 100644 index 00000000..eb04bd64 --- /dev/null +++ b/ananke/phoenix/gtk/dialog-window.cpp @@ -0,0 +1,69 @@ +static string FileDialog(bool save, Window &parent, const string &path, const lstring &filter) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + save == 0 ? "Load File" : "Save File", + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + save == 0 ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)nullptr + ); + + if(path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + + for(auto &filterItem : filter) { + GtkFileFilter *gtkFilter = gtk_file_filter_new(); + gtk_file_filter_set_name(gtkFilter, filterItem); + lstring part; + part.split("(", filterItem); + part[1].rtrim<1>(")"); + lstring list; + list.split(",", part[1]); + for(auto &pattern : list) gtk_file_filter_add_pattern(gtkFilter, pattern); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), gtkFilter); + } + + 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 pDialogWindow::fileOpen(Window &parent, const string &path, const lstring &filter) { + return FileDialog(0, parent, path, filter); +} + +string pDialogWindow::fileSave(Window &parent, const string &path, const lstring &filter) { + return FileDialog(1, parent, path, filter); +} + +string pDialogWindow::folderSelect(Window &parent, const string &path) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + "Select Folder", + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)nullptr + ); + + 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 == "") return ""; + if(name.endswith("/") == false) name.append("/"); + return name; +} diff --git a/ananke/phoenix/gtk/font.cpp b/ananke/phoenix/gtk/font.cpp new file mode 100644 index 00000000..cb6889d1 --- /dev/null +++ b/ananke/phoenix/gtk/font.cpp @@ -0,0 +1,58 @@ +Geometry pFont::geometry(const string &description, const string &text) { + PangoFontDescription *font = create(description); + Geometry geometry = pFont::geometry(font, text); + free(font); + return geometry; +} + +PangoFontDescription* pFont::create(const string &description) { + lstring part; + part.split(",", description); + for(auto &item : part) item.trim(" "); + + string family = "Sans"; + unsigned size = 8u; + bool bold = false; + bool italic = false; + + if(part[0] != "") family = part[0]; + if(part.size() >= 2) size = decimal(part[1]); + if(part.size() >= 3) bold = part[2].position("Bold"); + if(part.size() >= 3) italic = part[2].position("Italic"); + + PangoFontDescription *font = pango_font_description_new(); + pango_font_description_set_family(font, family); + pango_font_description_set_size(font, size * PANGO_SCALE); + pango_font_description_set_weight(font, !bold ? PANGO_WEIGHT_NORMAL : PANGO_WEIGHT_BOLD); + pango_font_description_set_style(font, !italic ? PANGO_STYLE_NORMAL : PANGO_STYLE_OBLIQUE); + return font; +} + +void pFont::free(PangoFontDescription *font) { + pango_font_description_free(font); +} + +Geometry pFont::geometry(PangoFontDescription *font, const string &text) { + PangoContext *context = gdk_pango_context_get_for_screen(gdk_screen_get_default()); + PangoLayout *layout = pango_layout_new(context); + pango_layout_set_font_description(layout, font); + pango_layout_set_text(layout, text, -1); + int width = 0, height = 0; + pango_layout_get_pixel_size(layout, &width, &height); + g_object_unref((gpointer)layout); + return { 0, 0, width, height }; +} + +void pFont::setFont(GtkWidget *widget, const string &font) { + auto gtkFont = pFont::create(font); + pFont::setFont(widget, (gpointer)gtkFont); + pFont::free(gtkFont); +} + +void pFont::setFont(GtkWidget *widget, gpointer font) { + if(font == 0) return; + gtk_widget_modify_font(widget, (PangoFontDescription*)font); + if(GTK_IS_CONTAINER(widget)) { + gtk_container_foreach(GTK_CONTAINER(widget), (GtkCallback)pFont::setFont, font); + } +} diff --git a/ananke/phoenix/gtk/keyboard.cpp b/ananke/phoenix/gtk/keyboard.cpp new file mode 100644 index 00000000..5b346406 --- /dev/null +++ b/ananke/phoenix/gtk/keyboard.cpp @@ -0,0 +1,142 @@ +void pKeyboard::initialize() { + auto append = [](Keyboard::Scancode scancode, unsigned keysym) { + settings->keymap.insert(scancode, XKeysymToKeycode(pOS::display, keysym)); + }; + + append(Keyboard::Scancode::Escape, XK_Escape); + append(Keyboard::Scancode::F1, XK_F1); + append(Keyboard::Scancode::F2, XK_F2); + append(Keyboard::Scancode::F3, XK_F3); + append(Keyboard::Scancode::F4, XK_F4); + append(Keyboard::Scancode::F5, XK_F5); + append(Keyboard::Scancode::F6, XK_F6); + append(Keyboard::Scancode::F7, XK_F7); + append(Keyboard::Scancode::F8, XK_F8); + append(Keyboard::Scancode::F9, XK_F9); + append(Keyboard::Scancode::F10, XK_F10); + append(Keyboard::Scancode::F11, XK_F11); + append(Keyboard::Scancode::F12, XK_F12); + + append(Keyboard::Scancode::PrintScreen, XK_Print); + append(Keyboard::Scancode::ScrollLock, XK_Scroll_Lock); + append(Keyboard::Scancode::Pause, XK_Pause); + + append(Keyboard::Scancode::Insert, XK_Insert); + append(Keyboard::Scancode::Delete, XK_Delete); + append(Keyboard::Scancode::Home, XK_Home); + append(Keyboard::Scancode::End, XK_End); + append(Keyboard::Scancode::PageUp, XK_Prior); + append(Keyboard::Scancode::PageDown, XK_Next); + + append(Keyboard::Scancode::Up, XK_Up); + append(Keyboard::Scancode::Down, XK_Down); + append(Keyboard::Scancode::Left, XK_Left); + append(Keyboard::Scancode::Right, XK_Right); + + append(Keyboard::Scancode::Grave, XK_asciitilde); + append(Keyboard::Scancode::Number1, XK_1); + append(Keyboard::Scancode::Number2, XK_2); + append(Keyboard::Scancode::Number3, XK_3); + append(Keyboard::Scancode::Number4, XK_4); + append(Keyboard::Scancode::Number5, XK_5); + append(Keyboard::Scancode::Number6, XK_6); + append(Keyboard::Scancode::Number7, XK_7); + append(Keyboard::Scancode::Number8, XK_8); + append(Keyboard::Scancode::Number9, XK_9); + append(Keyboard::Scancode::Number0, XK_0); + append(Keyboard::Scancode::Minus, XK_minus); + append(Keyboard::Scancode::Equal, XK_equal); + append(Keyboard::Scancode::Backspace, XK_BackSpace); + + append(Keyboard::Scancode::BracketLeft, XK_bracketleft); + append(Keyboard::Scancode::BracketRight, XK_bracketright); + append(Keyboard::Scancode::Backslash, XK_backslash); + append(Keyboard::Scancode::Semicolon, XK_semicolon); + append(Keyboard::Scancode::Apostrophe, XK_apostrophe); + append(Keyboard::Scancode::Comma, XK_comma); + append(Keyboard::Scancode::Period, XK_period); + append(Keyboard::Scancode::Slash, XK_slash); + + append(Keyboard::Scancode::Tab, XK_Tab); + append(Keyboard::Scancode::CapsLock, XK_Caps_Lock); + append(Keyboard::Scancode::Return, XK_Return); + append(Keyboard::Scancode::ShiftLeft, XK_Shift_L); + append(Keyboard::Scancode::ShiftRight, XK_Shift_R); + append(Keyboard::Scancode::ControlLeft, XK_Control_L); + append(Keyboard::Scancode::ControlRight, XK_Control_R); + append(Keyboard::Scancode::SuperLeft, XK_Super_L); + append(Keyboard::Scancode::SuperRight, XK_Super_R); + append(Keyboard::Scancode::AltLeft, XK_Alt_L); + append(Keyboard::Scancode::AltRight, XK_Alt_R); + append(Keyboard::Scancode::Space, XK_space); + append(Keyboard::Scancode::Menu, XK_Menu); + + append(Keyboard::Scancode::A, XK_A); + append(Keyboard::Scancode::B, XK_B); + append(Keyboard::Scancode::C, XK_C); + append(Keyboard::Scancode::D, XK_D); + append(Keyboard::Scancode::E, XK_E); + append(Keyboard::Scancode::F, XK_F); + append(Keyboard::Scancode::G, XK_G); + append(Keyboard::Scancode::H, XK_H); + append(Keyboard::Scancode::I, XK_I); + append(Keyboard::Scancode::J, XK_J); + append(Keyboard::Scancode::K, XK_K); + append(Keyboard::Scancode::L, XK_L); + append(Keyboard::Scancode::M, XK_M); + append(Keyboard::Scancode::N, XK_N); + append(Keyboard::Scancode::O, XK_O); + append(Keyboard::Scancode::P, XK_P); + append(Keyboard::Scancode::Q, XK_Q); + append(Keyboard::Scancode::R, XK_R); + append(Keyboard::Scancode::S, XK_S); + append(Keyboard::Scancode::T, XK_T); + append(Keyboard::Scancode::U, XK_U); + append(Keyboard::Scancode::V, XK_V); + append(Keyboard::Scancode::W, XK_W); + append(Keyboard::Scancode::X, XK_X); + append(Keyboard::Scancode::Y, XK_Y); + append(Keyboard::Scancode::Z, XK_Z); + + append(Keyboard::Scancode::NumLock, XK_Num_Lock); + append(Keyboard::Scancode::Divide, XK_KP_Divide); + append(Keyboard::Scancode::Multiply, XK_KP_Multiply); + append(Keyboard::Scancode::Subtract, XK_KP_Subtract); + append(Keyboard::Scancode::Add, XK_KP_Add); + append(Keyboard::Scancode::Enter, XK_KP_Enter); + append(Keyboard::Scancode::Point, XK_KP_Decimal); + + append(Keyboard::Scancode::Keypad1, XK_KP_1); + append(Keyboard::Scancode::Keypad2, XK_KP_2); + append(Keyboard::Scancode::Keypad3, XK_KP_3); + append(Keyboard::Scancode::Keypad4, XK_KP_4); + append(Keyboard::Scancode::Keypad5, XK_KP_5); + append(Keyboard::Scancode::Keypad6, XK_KP_6); + append(Keyboard::Scancode::Keypad7, XK_KP_7); + append(Keyboard::Scancode::Keypad8, XK_KP_8); + append(Keyboard::Scancode::Keypad9, XK_KP_9); + append(Keyboard::Scancode::Keypad0, XK_KP_0); +} + +bool pKeyboard::pressed(Keyboard::Scancode scancode) { + char state[256]; + XQueryKeymap(pOS::display, state); + unsigned id = settings->keymap.lhs[scancode]; + return state[id >> 3] & (1 << (id & 7)); +} + +vector pKeyboard::state() { + vector output; + output.resize((unsigned)Keyboard::Scancode::Limit); + for(auto &n : output) n = false; + + char state[256]; + XQueryKeymap(pOS::display, state); + for(auto &n : settings->keymap.rhs) { + if(state[n.name >> 3] & (1 << (n.name & 7))) { + output[(unsigned)n.data] = true; + } + } + + return output; +} diff --git a/ananke/phoenix/gtk/message-window.cpp b/ananke/phoenix/gtk/message-window.cpp new file mode 100644 index 00000000..7cd2172a --- /dev/null +++ b/ananke/phoenix/gtk/message-window.cpp @@ -0,0 +1,61 @@ +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 pMessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == MessageWindow::Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == MessageWindow::Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + 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 pMessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == MessageWindow::Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == MessageWindow::Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + 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 pMessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == MessageWindow::Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == MessageWindow::Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + 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 pMessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == MessageWindow::Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == MessageWindow::Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + 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/ananke/phoenix/gtk/mouse.cpp b/ananke/phoenix/gtk/mouse.cpp new file mode 100644 index 00000000..e00f7ff7 --- /dev/null +++ b/ananke/phoenix/gtk/mouse.cpp @@ -0,0 +1,20 @@ +Position pMouse::position() { + XlibWindow root, child; + int rootx, rooty, winx, winy; + unsigned int mask; + XQueryPointer(pOS::display, DefaultRootWindow(pOS::display), &root, &child, &rootx, &rooty, &winx, &winy, &mask); + return { rootx, rooty }; +} + +bool pMouse::pressed(Mouse::Button button) { + XlibWindow root, child; + int rootx, rooty, winx, winy; + unsigned int mask; + XQueryPointer(pOS::display, DefaultRootWindow(pOS::display), &root, &child, &rootx, &rooty, &winx, &winy, &mask); + switch(button) { + case Mouse::Button::Left: return mask & Button1Mask; + case Mouse::Button::Middle: return mask & Button2Mask; + case Mouse::Button::Right: return mask & Button3Mask; + } + return false; +} diff --git a/ananke/phoenix/gtk/platform.cpp b/ananke/phoenix/gtk/platform.cpp new file mode 100644 index 00000000..c6650dbb --- /dev/null +++ b/ananke/phoenix/gtk/platform.cpp @@ -0,0 +1,87 @@ +#include "platform.hpp" +#include "utility.cpp" +#include "settings.cpp" + +#include "desktop.cpp" +#include "keyboard.cpp" +#include "mouse.cpp" +#include "dialog-window.cpp" +#include "message-window.cpp" + +#include "font.cpp" +#include "timer.cpp" +#include "window.cpp" + +#include "action/action.cpp" +#include "action/menu.cpp" +#include "action/separator.cpp" +#include "action/item.cpp" +#include "action/check-item.cpp" +#include "action/radio-item.cpp" + +#include "widget/widget.cpp" +#include "widget/button.cpp" +#include "widget/canvas.cpp" +#include "widget/check-box.cpp" +#include "widget/combo-box.cpp" +#include "widget/hex-edit.cpp" +#include "widget/horizontal-scroll-bar.cpp" +#include "widget/horizontal-slider.cpp" +#include "widget/label.cpp" +#include "widget/line-edit.cpp" +#include "widget/list-view.cpp" +#include "widget/progress-bar.cpp" +#include "widget/radio-box.cpp" +#include "widget/text-edit.cpp" +#include "widget/vertical-scroll-bar.cpp" +#include "widget/vertical-slider.cpp" +#include "widget/viewport.cpp" + +XlibDisplay* pOS::display = 0; +Font pOS::defaultFont; + +void pOS::main() { + gtk_main(); +} + +bool pOS::pendingEvents() { + return gtk_events_pending(); +} + +void pOS::processEvents() { + while(pendingEvents()) gtk_main_iteration_do(false); +} + +void pOS::quit() { + gtk_main_quit(); +} + +void pOS::initialize() { + display = XOpenDisplay(0); + + settings = new Settings; + settings->load(); + + 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(R"( + style "phoenix-gtk" + { + GtkWindow::resize-grip-width = 0 + GtkWindow::resize-grip-height = 0 + GtkTreeView::vertical-separator = 0 + GtkComboBox::appears-as-list = 1 + } + class "GtkWindow" style "phoenix-gtk" + class "GtkTreeView" style "phoenix-gtk" + # class "GtkComboBox" style "phoenix-gtk" + )"); + + pKeyboard::initialize(); +} diff --git a/ananke/phoenix/gtk/platform.hpp b/ananke/phoenix/gtk/platform.hpp new file mode 100644 index 00000000..344e8239 --- /dev/null +++ b/ananke/phoenix/gtk/platform.hpp @@ -0,0 +1,501 @@ +struct Settings : public configuration { + bidirectional_map keymap; + + unsigned frameGeometryX; + unsigned frameGeometryY; + unsigned frameGeometryWidth; + unsigned frameGeometryHeight; + unsigned menuGeometryHeight; + unsigned statusGeometryHeight; + unsigned windowBackgroundColor; + + void load(); + void save(); + Settings(); +}; + +struct pWindow; +struct pMenu; +struct pLayout; +struct pWidget; + +struct pFont { + static Geometry geometry(const string &description, const string &text); + + static PangoFontDescription* create(const string &description); + static void free(PangoFontDescription *font); + static Geometry geometry(PangoFontDescription *font, const string &text); + static void setFont(GtkWidget *widget, const string &font); + static void setFont(GtkWidget *widget, gpointer font); +}; + +struct pDesktop { + static Size size(); + static Geometry workspace(); +}; + +struct pKeyboard { + static bool pressed(Keyboard::Scancode scancode); + static vector state(); + + static void initialize(); +}; + +struct pMouse { + static Position position(); + static bool pressed(Mouse::Button button); +}; + +struct pDialogWindow { + static string fileOpen(Window &parent, const string &path, const lstring &filter); + static string fileSave(Window &parent, const string &path, const lstring &filter); + static string folderSelect(Window &parent, const string &path); +}; + +struct pMessageWindow { + static MessageWindow::Response information(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response question(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response warning(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response critical(Window &parent, const string &text, MessageWindow::Buttons buttons); +}; + +struct pObject { + Object &object; + bool locked; + + pObject(Object &object) : object(object), locked(false) {} + virtual ~pObject() {} + + void constructor() {} + void destructor() {} +}; + +struct pOS : public pObject { + static XlibDisplay *display; + static Font defaultFont; + + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + static void initialize(); +}; + +struct pTimer : public pObject { + Timer &timer; + + void setEnabled(bool enabled); + void setInterval(unsigned milliseconds); + + pTimer(Timer &timer) : pObject(timer), timer(timer) {} + void constructor(); +}; + +struct pWindow : public pObject { + Window &window; + GtkWidget *widget; + GtkWidget *menuContainer; + GtkWidget *formContainer; + GtkWidget *statusContainer; + GtkWidget *menu; + GtkWidget *status; + GtkAllocation lastAllocation; + bool onSizePending; + + static Window& none(); + + void append(Layout &layout); + void append(Menu &menu); + void append(Widget &widget); + Color backgroundColor(); + bool focused(); + Geometry frameMargin(); + Geometry geometry(); + void remove(Layout &layout); + void remove(Menu &menu); + void remove(Widget &widget); + void setBackgroundColor(const Color &color); + void setFocused(); + void setFullScreen(bool fullScreen); + void setGeometry(const Geometry &geometry); + void setMenuFont(const string &font); + void setMenuVisible(bool visible); + void setModal(bool modal); + void setResizable(bool resizable); + void setStatusFont(const string &font); + void setStatusText(const string &text); + void setStatusVisible(bool visible); + void setTitle(const string &text); + void setVisible(bool visible); + void setWidgetFont(const string &font); + + pWindow(Window &window) : pObject(window), window(window) {} + void constructor(); + unsigned menuHeight(); + unsigned statusHeight(); +}; + +struct pAction : public pObject { + Action &action; + GtkWidget *widget; + + void setEnabled(bool enabled); + void setVisible(bool visible); + + pAction(Action &action) : pObject(action), action(action) {} + void constructor(); + virtual void orphan(); + string mnemonic(string text); + virtual void setFont(const string &font); +}; + +struct pMenu : public pAction { + Menu &menu; + GtkWidget *gtkMenu; + + void append(Action &action); + void remove(Action &action); + void setImage(const image &image); + void setText(const string &text); + + pMenu(Menu &menu) : pAction(menu), menu(menu) {} + void constructor(); + void destructor(); + void orphan(); + void setFont(const string &font); +}; + +struct pSeparator : public pAction { + Separator &separator; + + pSeparator(Separator &separator) : pAction(separator), separator(separator) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pItem : public pAction { + Item &item; + + void setImage(const image &image); + void setText(const string &text); + + pItem(Item &item) : pAction(item), item(item) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pCheckItem : public pAction { + CheckItem &checkItem; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckItem(CheckItem &checkItem) : pAction(checkItem), checkItem(checkItem) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pRadioItem : public pAction { + RadioItem &radioItem; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioItem(RadioItem &radioItem) : pAction(radioItem), radioItem(radioItem) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pSizable : public pObject { + Sizable &sizable; + + pSizable(Sizable &sizable) : pObject(sizable), sizable(sizable) {} +}; + +struct pLayout : public pSizable { + Layout &layout; + + pLayout(Layout &layout) : pSizable(layout), layout(layout) {} +}; + +struct pWidget : public pSizable { + Widget &widget; + GtkWidget *gtkWidget; + + bool enabled(); + virtual Geometry minimumGeometry(); + void setEnabled(bool enabled); + virtual void setFocused(); + virtual void setFont(const string &font); + virtual void setGeometry(const Geometry &geometry); + void setVisible(bool visible); + + pWidget(Widget &widget) : pSizable(widget), widget(widget) {} + void constructor(); + void destructor(); + virtual void orphan(); +}; + +struct pButton : public pWidget { + Button &button; + + Geometry minimumGeometry(); + void setImage(const image &image, Orientation orientation); + void setText(const string &text); + + pButton(Button &button) : pWidget(button), button(button) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pCanvas : public pWidget { + Canvas &canvas; + cairo_surface_t *surface; + + void setSize(const Size &size); + void update(); + + pCanvas(Canvas &canvas) : pWidget(canvas), canvas(canvas) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pCheckBox : public pWidget { + CheckBox &checkBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckBox(CheckBox &checkBox) : pWidget(checkBox), checkBox(checkBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pComboBox : public pWidget { + ComboBox &comboBox; + unsigned itemCounter; + + void append(const string &text); + void modify(unsigned row, const string &text); + void remove(unsigned row); + Geometry minimumGeometry(); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + + pComboBox(ComboBox &comboBox) : pWidget(comboBox), comboBox(comboBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pHexEdit : public pWidget { + HexEdit &hexEdit; + GtkWidget *container; + GtkWidget *subWidget; + GtkWidget *scrollBar; + GtkTextBuffer *textBuffer; + GtkTextMark *textCursor; + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + pHexEdit(HexEdit &hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {} + void constructor(); + void destructor(); + void orphan(); + unsigned cursorPosition(); + bool keyPress(unsigned scancode); + void scroll(unsigned position); + void setCursorPosition(unsigned position); + void setScroll(); + void updateScroll(); +}; + +struct pHorizontalScrollBar : public pWidget { + HorizontalScrollBar &horizontalScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalScrollBar(HorizontalScrollBar &horizontalScrollBar) : pWidget(horizontalScrollBar), horizontalScrollBar(horizontalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pHorizontalSlider : public pWidget { + HorizontalSlider &horizontalSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalSlider(HorizontalSlider &horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLabel : public pWidget { + Label &label; + + Geometry minimumGeometry(); + void setText(const string &text); + + pLabel(Label &label) : pWidget(label), label(label) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLineEdit : public pWidget { + LineEdit &lineEdit; + + Geometry minimumGeometry(); + void setEditable(bool editable); + void setText(const string &text); + string text(); + + pLineEdit(LineEdit &lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pListView : public pWidget { + ListView &listView; + GtkWidget *subWidget; + GtkListStore *store; + struct GtkColumn { + GtkTreeViewColumn *column; + GtkCellRenderer *checkbox, *icon, *text; + GtkWidget *label; + }; + vector column; + + void append(const lstring &text); + void autoSizeColumns(); + bool checked(unsigned row); + void modify(unsigned row, const lstring &text); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable); + void setChecked(unsigned row, bool checked); + void setHeaderText(const lstring &text); + void setHeaderVisible(bool visible); + void setImage(unsigned row, unsigned column, const nall::image &image); + void setSelected(bool selected); + void setSelection(unsigned row); + + pListView(ListView &listView) : pWidget(listView), listView(listView) {} + void constructor(); + void destructor(); + void orphan(); + void setFocused(); + void setFont(const string &font); +}; + +struct pProgressBar : public pWidget { + ProgressBar &progressBar; + + Geometry minimumGeometry(); + void setPosition(unsigned position); + + pProgressBar(ProgressBar &progressBar) : pWidget(progressBar), progressBar(progressBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pRadioBox : public pWidget { + RadioBox &radioBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioBox(RadioBox &radioBox) : pWidget(radioBox), radioBox(radioBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pTextEdit : public pWidget { + TextEdit &textEdit; + GtkWidget *subWidget; + GtkTextBuffer *textBuffer; + + void setCursorPosition(unsigned position); + void setEditable(bool editable); + void setText(const string &text); + void setWordWrap(bool wordWrap); + string text(); + + pTextEdit(TextEdit &textEdit) : pWidget(textEdit), textEdit(textEdit) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pVerticalScrollBar : public pWidget { + VerticalScrollBar &verticalScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalScrollBar(VerticalScrollBar &verticalScrollBar) : pWidget(verticalScrollBar), verticalScrollBar(verticalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pVerticalSlider : public pWidget { + VerticalSlider &verticalSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalSlider(VerticalSlider &verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pViewport : public pWidget { + Viewport &viewport; + + uintptr_t handle(); + + pViewport(Viewport &viewport) : pWidget(viewport), viewport(viewport) {} + void constructor(); + void destructor(); + void orphan(); +}; diff --git a/ananke/phoenix/gtk/settings.cpp b/ananke/phoenix/gtk/settings.cpp new file mode 100644 index 00000000..aeb28bba --- /dev/null +++ b/ananke/phoenix/gtk/settings.cpp @@ -0,0 +1,25 @@ +static Settings *settings = nullptr; + +void Settings::load() { + string path = { userpath(), ".config/phoenix/gtk.cfg" }; + configuration::load(path); +} + +void Settings::save() { + string path = { userpath(), ".config/" }; + mkdir(path, 0755); + path.append("phoenix/"); + mkdir(path, 0755); + path.append("gtk.cfg"); + configuration::save(path); +} + +Settings::Settings() { + append(frameGeometryX = 4, "frameGeometryX"); + append(frameGeometryY = 24, "frameGeometryY"); + append(frameGeometryWidth = 8, "frameGeometryWidth"); + append(frameGeometryHeight = 28, "frameGeometryHeight"); + append(menuGeometryHeight = 20, "menuGeometryHeight"); + append(statusGeometryHeight = 20, "statusGeometryHeight"); + append(windowBackgroundColor = 0xedeceb, "windowBackgroundColor"); +} diff --git a/ananke/phoenix/gtk/timer.cpp b/ananke/phoenix/gtk/timer.cpp new file mode 100644 index 00000000..d04183f8 --- /dev/null +++ b/ananke/phoenix/gtk/timer.cpp @@ -0,0 +1,24 @@ +static guint Timer_trigger(pTimer *self) { + //timer may have been disabled prior to triggering, so check state + if(self->timer.state.enabled) { + if(self->timer.onTimeout) self->timer.onTimeout(); + } + //callback may have disabled timer, so check state again + if(self->timer.state.enabled) { + g_timeout_add(self->timer.state.milliseconds, (GSourceFunc)Timer_trigger, (gpointer)self); + } + //kill this timer instance (it is spawned above if needed again) + return false; +} + +void pTimer::setEnabled(bool enabled) { + if(enabled) { + g_timeout_add(timer.state.milliseconds, (GSourceFunc)Timer_trigger, (gpointer)this); + } +} + +void pTimer::setInterval(unsigned milliseconds) { +} + +void pTimer::constructor() { +} diff --git a/ananke/phoenix/gtk/utility.cpp b/ananke/phoenix/gtk/utility.cpp new file mode 100644 index 00000000..29e87bb8 --- /dev/null +++ b/ananke/phoenix/gtk/utility.cpp @@ -0,0 +1,200 @@ +static GdkPixbuf* CreatePixbuf(const nall::image &image, bool scale = false) { + nall::image gdkImage = image; + gdkImage.transform(0, 32, 255u << 24, 255u << 0, 255u << 8, 255u << 16); + if(scale) gdkImage.scale(15, 15, Interpolation::Linear); + + GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, gdkImage.width, gdkImage.height); + memcpy(gdk_pixbuf_get_pixels(pixbuf), gdkImage.data, gdkImage.width * gdkImage.height * 4); + + return pixbuf; +} + +static GtkImage* CreateImage(const nall::image &image, bool scale = false) { + GdkPixbuf *pixbuf = CreatePixbuf(image, scale); + GtkImage *gtkImage = (GtkImage*)gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(pixbuf); + return gtkImage; +} + +static Keyboard::Keycode Keysym(unsigned keysym) { + switch(keysym) { + case GDK_Escape: return Keyboard::Keycode::Escape; + case GDK_F1: return Keyboard::Keycode::F1; + case GDK_F2: return Keyboard::Keycode::F2; + case GDK_F3: return Keyboard::Keycode::F3; + case GDK_F4: return Keyboard::Keycode::F4; + case GDK_F5: return Keyboard::Keycode::F5; + case GDK_F6: return Keyboard::Keycode::F6; + case GDK_F7: return Keyboard::Keycode::F7; + case GDK_F8: return Keyboard::Keycode::F8; + case GDK_F9: return Keyboard::Keycode::F9; + case GDK_F10: return Keyboard::Keycode::F10; + case GDK_F11: return Keyboard::Keycode::F11; + case GDK_F12: return Keyboard::Keycode::F12; + + case GDK_Print: return Keyboard::Keycode::PrintScreen; + //Keyboard::Keycode::SysRq + case GDK_Scroll_Lock: return Keyboard::Keycode::ScrollLock; + case GDK_Pause: return Keyboard::Keycode::Pause; + //Keyboard::Keycode::Break + + case GDK_Insert: return Keyboard::Keycode::Insert; + case GDK_Delete: return Keyboard::Keycode::Delete; + case GDK_Home: return Keyboard::Keycode::Home; + case GDK_End: return Keyboard::Keycode::End; + case GDK_Prior: return Keyboard::Keycode::PageUp; + case GDK_Next: return Keyboard::Keycode::PageDown; + + case GDK_Up: return Keyboard::Keycode::Up; + case GDK_Down: return Keyboard::Keycode::Down; + case GDK_Left: return Keyboard::Keycode::Left; + case GDK_Right: return Keyboard::Keycode::Right; + + case GDK_grave: return Keyboard::Keycode::Grave; + case GDK_1: return Keyboard::Keycode::Number1; + case GDK_2: return Keyboard::Keycode::Number2; + case GDK_3: return Keyboard::Keycode::Number3; + case GDK_4: return Keyboard::Keycode::Number4; + case GDK_5: return Keyboard::Keycode::Number5; + case GDK_6: return Keyboard::Keycode::Number6; + case GDK_7: return Keyboard::Keycode::Number7; + case GDK_8: return Keyboard::Keycode::Number8; + case GDK_9: return Keyboard::Keycode::Number9; + case GDK_0: return Keyboard::Keycode::Number0; + case GDK_minus: return Keyboard::Keycode::Minus; + case GDK_equal: return Keyboard::Keycode::Equal; + case GDK_BackSpace: return Keyboard::Keycode::Backspace; + + case GDK_asciitilde: return Keyboard::Keycode::Tilde; + case GDK_exclam: return Keyboard::Keycode::Exclamation; + case GDK_at: return Keyboard::Keycode::At; + case GDK_numbersign: return Keyboard::Keycode::Pound; + case GDK_dollar: return Keyboard::Keycode::Dollar; + case GDK_percent: return Keyboard::Keycode::Percent; + case GDK_asciicircum: return Keyboard::Keycode::Power; + case GDK_ampersand: return Keyboard::Keycode::Ampersand; + case GDK_asterisk: return Keyboard::Keycode::Asterisk; + case GDK_parenleft: return Keyboard::Keycode::ParenthesisLeft; + case GDK_parenright: return Keyboard::Keycode::ParenthesisRight; + case GDK_underscore: return Keyboard::Keycode::Underscore; + case GDK_plus: return Keyboard::Keycode::Plus; + + case GDK_bracketleft: return Keyboard::Keycode::BracketLeft; + case GDK_bracketright: return Keyboard::Keycode::BracketRight; + case GDK_backslash: return Keyboard::Keycode::Backslash; + case GDK_semicolon: return Keyboard::Keycode::Semicolon; + case GDK_apostrophe: return Keyboard::Keycode::Apostrophe; + case GDK_comma: return Keyboard::Keycode::Comma; + case GDK_period: return Keyboard::Keycode::Period; + case GDK_slash: return Keyboard::Keycode::Slash; + + case GDK_braceleft: return Keyboard::Keycode::BraceLeft; + case GDK_braceright: return Keyboard::Keycode::BraceRight; + case GDK_bar: return Keyboard::Keycode::Pipe; + case GDK_colon: return Keyboard::Keycode::Colon; + case GDK_quotedbl: return Keyboard::Keycode::Quote; + case GDK_less: return Keyboard::Keycode::CaretLeft; + case GDK_greater: return Keyboard::Keycode::CaretRight; + case GDK_question: return Keyboard::Keycode::Question; + + case GDK_Tab: return Keyboard::Keycode::Tab; + case GDK_Caps_Lock: return Keyboard::Keycode::CapsLock; + case GDK_Return: return Keyboard::Keycode::Return; + case GDK_Shift_L: return Keyboard::Keycode::ShiftLeft; + case GDK_Shift_R: return Keyboard::Keycode::ShiftRight; + case GDK_Control_L: return Keyboard::Keycode::ControlLeft; + case GDK_Control_R: return Keyboard::Keycode::ControlRight; + case GDK_Super_L: return Keyboard::Keycode::SuperLeft; + case GDK_Super_R: return Keyboard::Keycode::SuperRight; + case GDK_Alt_L: return Keyboard::Keycode::AltLeft; + case GDK_Alt_R: return Keyboard::Keycode::AltRight; + case GDK_space: return Keyboard::Keycode::Space; + case GDK_Menu: return Keyboard::Keycode::Menu; + + case GDK_A: return Keyboard::Keycode::A; + case GDK_B: return Keyboard::Keycode::B; + case GDK_C: return Keyboard::Keycode::C; + case GDK_D: return Keyboard::Keycode::D; + case GDK_E: return Keyboard::Keycode::E; + case GDK_F: return Keyboard::Keycode::F; + case GDK_G: return Keyboard::Keycode::G; + case GDK_H: return Keyboard::Keycode::H; + case GDK_I: return Keyboard::Keycode::I; + case GDK_J: return Keyboard::Keycode::J; + case GDK_K: return Keyboard::Keycode::K; + case GDK_L: return Keyboard::Keycode::L; + case GDK_M: return Keyboard::Keycode::M; + case GDK_N: return Keyboard::Keycode::N; + case GDK_O: return Keyboard::Keycode::O; + case GDK_P: return Keyboard::Keycode::P; + case GDK_Q: return Keyboard::Keycode::Q; + case GDK_R: return Keyboard::Keycode::R; + case GDK_S: return Keyboard::Keycode::S; + case GDK_T: return Keyboard::Keycode::T; + case GDK_U: return Keyboard::Keycode::U; + case GDK_V: return Keyboard::Keycode::V; + case GDK_W: return Keyboard::Keycode::W; + case GDK_X: return Keyboard::Keycode::X; + case GDK_Y: return Keyboard::Keycode::Y; + case GDK_Z: return Keyboard::Keycode::Z; + + case GDK_a: return Keyboard::Keycode::a; + case GDK_b: return Keyboard::Keycode::b; + case GDK_c: return Keyboard::Keycode::c; + case GDK_d: return Keyboard::Keycode::d; + case GDK_e: return Keyboard::Keycode::e; + case GDK_f: return Keyboard::Keycode::f; + case GDK_g: return Keyboard::Keycode::g; + case GDK_h: return Keyboard::Keycode::h; + case GDK_i: return Keyboard::Keycode::i; + case GDK_j: return Keyboard::Keycode::j; + case GDK_k: return Keyboard::Keycode::k; + case GDK_l: return Keyboard::Keycode::l; + case GDK_m: return Keyboard::Keycode::m; + case GDK_n: return Keyboard::Keycode::n; + case GDK_o: return Keyboard::Keycode::o; + case GDK_p: return Keyboard::Keycode::p; + case GDK_q: return Keyboard::Keycode::q; + case GDK_r: return Keyboard::Keycode::r; + case GDK_s: return Keyboard::Keycode::s; + case GDK_t: return Keyboard::Keycode::t; + case GDK_u: return Keyboard::Keycode::u; + case GDK_v: return Keyboard::Keycode::v; + case GDK_w: return Keyboard::Keycode::w; + case GDK_x: return Keyboard::Keycode::x; + case GDK_y: return Keyboard::Keycode::y; + case GDK_z: return Keyboard::Keycode::z; + + case GDK_Num_Lock: return Keyboard::Keycode::NumLock; + case GDK_KP_Divide: return Keyboard::Keycode::Divide; + case GDK_KP_Multiply: return Keyboard::Keycode::Multiply; + case GDK_KP_Subtract: return Keyboard::Keycode::Subtract; + case GDK_KP_Add: return Keyboard::Keycode::Add; + case GDK_KP_Enter: return Keyboard::Keycode::Enter; + case GDK_KP_Decimal: return Keyboard::Keycode::Point; + + case GDK_KP_1: return Keyboard::Keycode::Keypad1; + case GDK_KP_2: return Keyboard::Keycode::Keypad2; + case GDK_KP_3: return Keyboard::Keycode::Keypad3; + case GDK_KP_4: return Keyboard::Keycode::Keypad4; + case GDK_KP_5: return Keyboard::Keycode::Keypad5; + case GDK_KP_6: return Keyboard::Keycode::Keypad6; + case GDK_KP_7: return Keyboard::Keycode::Keypad7; + case GDK_KP_8: return Keyboard::Keycode::Keypad8; + case GDK_KP_9: return Keyboard::Keycode::Keypad9; + case GDK_KP_0: return Keyboard::Keycode::Keypad0; + + case GDK_KP_Home: return Keyboard::Keycode::KeypadHome; + case GDK_KP_End: return Keyboard::Keycode::KeypadEnd; + case GDK_KP_Page_Up: return Keyboard::Keycode::KeypadPageUp; + case GDK_KP_Page_Down: return Keyboard::Keycode::KeypadPageDown; + case GDK_KP_Up: return Keyboard::Keycode::KeypadUp; + case GDK_KP_Down: return Keyboard::Keycode::KeypadDown; + case GDK_KP_Left: return Keyboard::Keycode::KeypadLeft; + case GDK_KP_Right: return Keyboard::Keycode::KeypadRight; + case GDK_KP_Begin: return Keyboard::Keycode::KeypadCenter; + case GDK_KP_Insert: return Keyboard::Keycode::KeypadInsert; + case GDK_KP_Delete: return Keyboard::Keycode::KeypadDelete; + } + return Keyboard::Keycode::None; +} diff --git a/ananke/phoenix/gtk/widget/button.cpp b/ananke/phoenix/gtk/widget/button.cpp new file mode 100644 index 00000000..6f646a25 --- /dev/null +++ b/ananke/phoenix/gtk/widget/button.cpp @@ -0,0 +1,53 @@ +static void Button_activate(Button *self) { + if(self->onActivate) self->onActivate(); +} + +Geometry pButton::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, button.state.text); + + if(button.state.orientation == Orientation::Horizontal) { + geometry.width += button.state.image.width; + geometry.height = max(button.state.image.height, geometry.height); + } + + if(button.state.orientation == Orientation::Vertical) { + geometry.width = max(button.state.image.width, geometry.width); + geometry.height += button.state.image.height; + } + + return { 0, 0, geometry.width + 24, geometry.height + 12 }; +} + +void pButton::setImage(const image &image, Orientation orientation) { + if(image.empty() == false) { + GtkImage *gtkImage = CreateImage(image); + gtk_button_set_image(GTK_BUTTON(gtkWidget), (GtkWidget*)gtkImage); + } else { + gtk_button_set_image(GTK_BUTTON(gtkWidget), nullptr); + } + switch(orientation) { + case Orientation::Horizontal: gtk_button_set_image_position(GTK_BUTTON(gtkWidget), GTK_POS_LEFT); break; + case Orientation::Vertical: gtk_button_set_image_position(GTK_BUTTON(gtkWidget), GTK_POS_TOP); break; + } +} + +void pButton::setText(const string &text) { + gtk_button_set_label(GTK_BUTTON(gtkWidget), text); + setFont(widget.state.font); +} + +void pButton::constructor() { + gtkWidget = gtk_button_new(); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "clicked", G_CALLBACK(Button_activate), (gpointer)&button); + + setText(button.state.text); +} + +void pButton::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pButton::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/canvas.cpp b/ananke/phoenix/gtk/widget/canvas.cpp new file mode 100644 index 00000000..9d17dc82 --- /dev/null +++ b/ananke/phoenix/gtk/widget/canvas.cpp @@ -0,0 +1,70 @@ +static gboolean Canvas_expose(GtkWidget *widget, GdkEvent *event, pCanvas *self) { + cairo_t *context = gdk_cairo_create(gtk_widget_get_window(widget)); + cairo_set_source_surface(context, self->surface, 0, 0); + cairo_paint(context); + cairo_destroy(context); + return true; +} + +static gboolean Canvas_mouseLeave(GtkWidget *widget, GdkEventButton *event, pCanvas *self) { + if(self->canvas.onMouseLeave) self->canvas.onMouseLeave(); + return true; +} + +static gboolean Canvas_mouseMove(GtkWidget *widget, GdkEventButton *event, pCanvas *self) { + if(self->canvas.onMouseMove) self->canvas.onMouseMove({ (signed)event->x, (signed)event->y }); + return true; +} + +static gboolean Canvas_mousePress(GtkWidget *widget, GdkEventButton *event, pCanvas *self) { + if(self->canvas.onMousePress) switch(event->button) { + case 1: self->canvas.onMousePress(Mouse::Button::Left); break; + case 2: self->canvas.onMousePress(Mouse::Button::Middle); break; + case 3: self->canvas.onMousePress(Mouse::Button::Right); break; + } + return true; +} + +static gboolean Canvas_mouseRelease(GtkWidget *widget, GdkEventButton *event, pCanvas *self) { + if(self->canvas.onMouseRelease) switch(event->button) { + case 1: self->canvas.onMouseRelease(Mouse::Button::Left); break; + case 2: self->canvas.onMouseRelease(Mouse::Button::Middle); break; + case 3: self->canvas.onMouseRelease(Mouse::Button::Right); break; + } + return true; +} + +void pCanvas::setSize(const Size &size) { + cairo_surface_destroy(surface); + surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, canvas.state.width, canvas.state.height); +} + +void pCanvas::update() { + memcpy(cairo_image_surface_get_data(surface), canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + if(gtk_widget_get_realized(gtkWidget) == false) return; + gdk_window_invalidate_rect(gtk_widget_get_window(gtkWidget), 0, true); +} + +void pCanvas::constructor() { + surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, canvas.state.width, canvas.state.height); + memcpy(cairo_image_surface_get_data(surface), canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + gtkWidget = gtk_drawing_area_new(); + gtk_widget_set_double_buffered(gtkWidget, false); + gtk_widget_add_events(gtkWidget, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK); + g_signal_connect(G_OBJECT(gtkWidget), "button_press_event", G_CALLBACK(Canvas_mousePress), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "button_release_event", G_CALLBACK(Canvas_mouseRelease), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "expose_event", G_CALLBACK(Canvas_expose), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "leave_notify_event", G_CALLBACK(Canvas_mouseLeave), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "motion_notify_event", G_CALLBACK(Canvas_mouseMove), (gpointer)this); +} + +void pCanvas::destructor() { + gtk_widget_destroy(gtkWidget); + cairo_surface_destroy(surface); +} + +void pCanvas::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/check-box.cpp b/ananke/phoenix/gtk/widget/check-box.cpp new file mode 100644 index 00000000..b6493f32 --- /dev/null +++ b/ananke/phoenix/gtk/widget/check-box.cpp @@ -0,0 +1,40 @@ +static void CheckBox_toggle(CheckBox *self) { + self->state.checked = self->checked(); + if(self->p.locked == false && self->onToggle) self->onToggle(); +} + +bool pCheckBox::checked() { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtkWidget)); +} + +Geometry pCheckBox::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, checkBox.state.text); + return { 0, 0, geometry.width + 28, geometry.height + 4 }; +} + +void pCheckBox::setChecked(bool checked) { + locked = true; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkWidget), checked); + locked = false; +} + +void pCheckBox::setText(const string &text) { + gtk_button_set_label(GTK_BUTTON(gtkWidget), text); +} + +void pCheckBox::constructor() { + gtkWidget = gtk_check_button_new_with_label(""); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "toggled", G_CALLBACK(CheckBox_toggle), (gpointer)&checkBox); + + setChecked(checkBox.state.checked); + setText(checkBox.state.text); +} + +void pCheckBox::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pCheckBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/combo-box.cpp b/ananke/phoenix/gtk/widget/combo-box.cpp new file mode 100644 index 00000000..648d587f --- /dev/null +++ b/ananke/phoenix/gtk/widget/combo-box.cpp @@ -0,0 +1,73 @@ +static void ComboBox_change(ComboBox *self) { + if(self->p.locked == false) { + self->state.selection = self->selection(); + if(self->onChange) self->onChange(); + } +} + +void pComboBox::append(const string &text) { + gtk_combo_box_append_text(GTK_COMBO_BOX(gtkWidget), text); + if(itemCounter++ == 0) setSelection(0); +} + +Geometry pComboBox::minimumGeometry() { + unsigned maximumWidth = 0; + for(auto &item : comboBox.state.text) maximumWidth = max(maximumWidth, pFont::geometry(widget.state.font, item).width); + + Geometry geometry = pFont::geometry(widget.state.font, " "); + return { 0, 0, maximumWidth + 44, geometry.height + 12 }; +} + +void pComboBox::modify(unsigned row, const string &text) { + locked = true; + unsigned position = selection(); + gtk_combo_box_remove_text(GTK_COMBO_BOX(gtkWidget), row); + gtk_combo_box_insert_text(GTK_COMBO_BOX(gtkWidget), row, text); + gtk_combo_box_set_active(GTK_COMBO_BOX(gtkWidget), position); + locked = false; +} + +void pComboBox::remove(unsigned row) { + locked = true; + unsigned position = selection(); + gtk_combo_box_remove_text(GTK_COMBO_BOX(gtkWidget), row); + if(position == row) gtk_combo_box_set_active(GTK_COMBO_BOX(gtkWidget), 0); + locked = false; +} + +void pComboBox::reset() { + locked = true; + gtk_list_store_clear(GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(gtkWidget)))); + itemCounter = 0; + locked = false; +} + +unsigned pComboBox::selection() { + return gtk_combo_box_get_active(GTK_COMBO_BOX(gtkWidget)); +} + +void pComboBox::setSelection(unsigned row) { + locked = true; + gtk_combo_box_set_active(GTK_COMBO_BOX(gtkWidget), row); + locked = false; +} + +void pComboBox::constructor() { + itemCounter = 0; + gtkWidget = gtk_combo_box_new_text(); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "changed", G_CALLBACK(ComboBox_change), (gpointer)&comboBox); + + locked = true; + for(auto &text : comboBox.state.text) append(text); + locked = false; + setSelection(comboBox.state.selection); +} + +void pComboBox::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pComboBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/hex-edit.cpp b/ananke/phoenix/gtk/widget/hex-edit.cpp new file mode 100644 index 00000000..9c0dbdef --- /dev/null +++ b/ananke/phoenix/gtk/widget/hex-edit.cpp @@ -0,0 +1,264 @@ +static bool HexEdit_keyPress(GtkWidget *widget, GdkEventKey *event, HexEdit *self) { + return self->p.keyPress(event->keyval); +} + +static bool HexEdit_scroll(GtkRange *range, GtkScrollType scroll, gdouble value, HexEdit *self) { + self->p.scroll((unsigned)value); + return false; +} + +void pHexEdit::setColumns(unsigned columns) { + setScroll(); + update(); +} + +void pHexEdit::setLength(unsigned length) { + setScroll(); + update(); +} + +void pHexEdit::setOffset(unsigned offset) { + setScroll(); + updateScroll(); + update(); +} + +void pHexEdit::setRows(unsigned rows) { + setScroll(); + update(); +} + +void pHexEdit::update() { + if(!hexEdit.onRead) { + gtk_text_buffer_set_text(textBuffer, "", -1); + return; + } + + unsigned position = cursorPosition(); + + string output; + unsigned offset = hexEdit.state.offset; + for(unsigned row = 0; row < hexEdit.state.rows; row++) { + output.append(hex<8>(offset)); + output.append(" "); + + string hexdata; + string ansidata = " "; + for(unsigned column = 0; column < hexEdit.state.columns; column++) { + if(offset < hexEdit.state.length) { + uint8_t data = hexEdit.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 >= hexEdit.state.length) break; + if(row != hexEdit.state.rows - 1) output.append("\n"); + } + + gtk_text_buffer_set_text(textBuffer, output, -1); + if(position == 0) position = 10; //start at first position where hex values can be entered + setCursorPosition(position); +} + +void pHexEdit::constructor() { + gtkWidget = gtk_hbox_new(false, 0); + + container = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(container), GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(container), GTK_SHADOW_ETCHED_IN); + + subWidget = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(subWidget), false); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), GTK_WRAP_NONE); + gtk_container_add(GTK_CONTAINER(container), subWidget); + g_signal_connect(G_OBJECT(subWidget), "key-press-event", G_CALLBACK(HexEdit_keyPress), (gpointer)&hexEdit); + + scrollBar = gtk_vscrollbar_new((GtkAdjustment*)0); + gtk_range_set_range(GTK_RANGE(scrollBar), 0, 255); + gtk_range_set_increments(GTK_RANGE(scrollBar), 1, 16); + gtk_widget_set_sensitive(scrollBar, false); + g_signal_connect(G_OBJECT(scrollBar), "change-value", G_CALLBACK(HexEdit_scroll), (gpointer)&hexEdit); + + gtk_box_pack_start(GTK_BOX(gtkWidget), container, true, true, 0); + gtk_box_pack_start(GTK_BOX(gtkWidget), scrollBar, false, false, 1); + + textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(subWidget)); + textCursor = gtk_text_buffer_get_mark(textBuffer, "insert"); + + gtk_widget_show(scrollBar); + gtk_widget_show(subWidget); + gtk_widget_show(container); + + setColumns(hexEdit.state.columns); + setRows(hexEdit.state.rows); + setLength(hexEdit.state.length); + setOffset(hexEdit.state.offset); + update(); +} + +void pHexEdit::destructor() { + gtk_widget_destroy(scrollBar); + gtk_widget_destroy(subWidget); + gtk_widget_destroy(container); + gtk_widget_destroy(gtkWidget); +} + +void pHexEdit::orphan() { + destructor(); + constructor(); +} + +unsigned pHexEdit::cursorPosition() { + GtkTextIter iter; + gtk_text_buffer_get_iter_at_mark(textBuffer, &iter, textCursor); + return gtk_text_iter_get_offset(&iter); +} + +bool pHexEdit::keyPress(unsigned scancode) { + if(!hexEdit.onRead) return false; + + unsigned position = cursorPosition(); + unsigned lineWidth = 10 + (hexEdit.state.columns * 3) + 1 + hexEdit.state.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 + (hexEdit.state.columns * 3 - 1)); + return true; + } + + if(scancode == GDK_Up) { + if(cursorY != 0) return false; + + signed newOffset = hexEdit.state.offset - hexEdit.state.columns; + if(newOffset >= 0) { + hexEdit.setOffset(newOffset); + update(); + } + return true; + } + + if(scancode == GDK_Down) { + if(cursorY != hexEdit.state.rows - 1) return false; + + signed newOffset = hexEdit.state.offset + hexEdit.state.columns; + if(newOffset + hexEdit.state.columns * hexEdit.state.rows - (hexEdit.state.columns - 1) <= hexEdit.state.length) { + hexEdit.setOffset(newOffset); + update(); + } + return true; + } + + if(scancode == GDK_Page_Up) { + signed newOffset = hexEdit.state.offset - hexEdit.state.columns * hexEdit.state.rows; + if(newOffset >= 0) { + hexEdit.setOffset(newOffset); + } else { + hexEdit.setOffset(0); + } + update(); + return true; + } + + if(scancode == GDK_Page_Down) { + signed newOffset = hexEdit.state.offset + hexEdit.state.columns * hexEdit.state.rows; + for(unsigned n = 0; n < hexEdit.state.rows; n++) { + if(newOffset + hexEdit.state.columns * hexEdit.state.rows - (hexEdit.state.columns - 1) <= hexEdit.state.length) { + hexEdit.setOffset(newOffset); + update(); + break; + } + newOffset -= hexEdit.state.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 < hexEdit.state.columns) { + //not in ANSI region + unsigned offset = hexEdit.state.offset + (cursorY * hexEdit.state.columns + cursorX); + + if(offset >= hexEdit.state.length) return false; //do not edit past end of data + uint8_t data = hexEdit.onRead(offset); + + //write modified value + if(cursorNibble == 1) { + data = (data & 0xf0) | (scancode << 0); + } else { + data = (data & 0x0f) | (scancode << 4); + } + if(hexEdit.onWrite) hexEdit.onWrite(offset, data); + + //auto-advance cursor to next nibble/byte + position++; + if(cursorNibble && cursorX != hexEdit.state.columns - 1) position++; + setCursorPosition(position); + + //refresh output to reflect modified data + update(); + } + } + } + + return true; +} + +void pHexEdit::scroll(unsigned position) { + unsigned rows = hexEdit.state.length / hexEdit.state.columns; + if(position >= rows) position = rows - 1; + hexEdit.setOffset(position * hexEdit.state.columns); +} + +void pHexEdit::setCursorPosition(unsigned position) { + GtkTextIter iter; + gtk_text_buffer_get_iter_at_mark(textBuffer, &iter, textCursor); + + //GTK+ will throw many errors to the terminal if you set iterator past end of buffer + GtkTextIter endIter; + gtk_text_buffer_get_end_iter(textBuffer, &iter); + unsigned endPosition = gtk_text_iter_get_offset(&iter); + + gtk_text_iter_set_offset(&iter, min(position, endPosition)); + gtk_text_buffer_place_cursor(textBuffer, &iter); +} + +void pHexEdit::setScroll() { + unsigned rows = hexEdit.state.length / hexEdit.state.columns; + if(rows) rows--; + if(rows) { + gtk_range_set_range(GTK_RANGE(scrollBar), 0, rows); + gtk_widget_set_sensitive(scrollBar, true); + } else { + gtk_widget_set_sensitive(scrollBar, false); + } +} + +void pHexEdit::updateScroll() { + unsigned row = hexEdit.state.offset / hexEdit.state.columns; + gtk_range_set_value(GTK_RANGE(scrollBar), row); +} diff --git a/ananke/phoenix/gtk/widget/horizontal-scroll-bar.cpp b/ananke/phoenix/gtk/widget/horizontal-scroll-bar.cpp new file mode 100644 index 00000000..0d765e92 --- /dev/null +++ b/ananke/phoenix/gtk/widget/horizontal-scroll-bar.cpp @@ -0,0 +1,42 @@ +static void HorizontalScrollBar_change(HorizontalScrollBar *self) { + if(self->state.position == self->position()) return; + self->state.position = self->position(); + if(self->p.locked == false && self->onChange) self->onChange(); +} + +Geometry pHorizontalScrollBar::minimumGeometry() { + return { 0, 0, 0, 20 }; +} + +unsigned pHorizontalScrollBar::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(gtkWidget)); +} + +void pHorizontalScrollBar::setLength(unsigned length) { + locked = true; + length += length == 0; + gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); + gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); + locked = false; +} + +void pHorizontalScrollBar::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(gtkWidget), position); +} + +void pHorizontalScrollBar::constructor() { + gtkWidget = gtk_hscrollbar_new(0); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(HorizontalScrollBar_change), (gpointer)&horizontalScrollBar); + + setLength(horizontalScrollBar.state.length); + setPosition(horizontalScrollBar.state.position); +} + +void pHorizontalScrollBar::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pHorizontalScrollBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/horizontal-slider.cpp b/ananke/phoenix/gtk/widget/horizontal-slider.cpp new file mode 100644 index 00000000..2d19bae6 --- /dev/null +++ b/ananke/phoenix/gtk/widget/horizontal-slider.cpp @@ -0,0 +1,41 @@ +static void HorizontalSlider_change(HorizontalSlider *self) { + if(self->state.position == self->position()) return; + self->state.position = self->position(); + if(self->onChange) self->onChange(); +} + +Geometry pHorizontalSlider::minimumGeometry() { + return { 0, 0, 0, 20 }; +} + +unsigned pHorizontalSlider::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(gtkWidget)); +} + +void pHorizontalSlider::setLength(unsigned length) { + length += length == 0; + gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); + gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); +} + +void pHorizontalSlider::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(gtkWidget), position); +} + +void pHorizontalSlider::constructor() { + gtkWidget = gtk_hscale_new_with_range(0, 100, 1); + gtk_scale_set_draw_value(GTK_SCALE(gtkWidget), false); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(HorizontalSlider_change), (gpointer)&horizontalSlider); + + setLength(horizontalSlider.state.length); + setPosition(horizontalSlider.state.position); +} + +void pHorizontalSlider::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pHorizontalSlider::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/label.cpp b/ananke/phoenix/gtk/widget/label.cpp new file mode 100644 index 00000000..8b5cec4f --- /dev/null +++ b/ananke/phoenix/gtk/widget/label.cpp @@ -0,0 +1,24 @@ +Geometry pLabel::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, label.state.text); + return { 0, 0, geometry.width, geometry.height }; +} + +void pLabel::setText(const string &text) { + gtk_label_set_text(GTK_LABEL(gtkWidget), text); +} + +void pLabel::constructor() { + gtkWidget = gtk_label_new(""); + gtk_misc_set_alignment(GTK_MISC(gtkWidget), 0.0, 0.5); + + setText(label.state.text); +} + +void pLabel::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pLabel::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/line-edit.cpp b/ananke/phoenix/gtk/widget/line-edit.cpp new file mode 100644 index 00000000..8dbe9ef4 --- /dev/null +++ b/ananke/phoenix/gtk/widget/line-edit.cpp @@ -0,0 +1,45 @@ +static void LineEdit_activate(LineEdit *self) { + if(self->onActivate) self->onActivate(); +} + +static void LineEdit_change(LineEdit *self) { + self->state.text = self->text(); + if(self->p.locked == false && self->onChange) self->onChange(); +} + +Geometry pLineEdit::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, lineEdit.state.text); + return { 0, 0, geometry.width + 10, geometry.height + 10 }; +} + +void pLineEdit::setEditable(bool editable) { + gtk_editable_set_editable(GTK_EDITABLE(gtkWidget), editable); +} + +void pLineEdit::setText(const string &text) { + locked = true; + gtk_entry_set_text(GTK_ENTRY(gtkWidget), text); + locked = false; +} + +string pLineEdit::text() { + return gtk_entry_get_text(GTK_ENTRY(gtkWidget)); +} + +void pLineEdit::constructor() { + gtkWidget = gtk_entry_new(); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "activate", G_CALLBACK(LineEdit_activate), (gpointer)&lineEdit); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "changed", G_CALLBACK(LineEdit_change), (gpointer)&lineEdit); + + setEditable(lineEdit.state.editable); + setText(lineEdit.state.text); +} + +void pLineEdit::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pLineEdit::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/list-view.cpp b/ananke/phoenix/gtk/widget/list-view.cpp new file mode 100644 index 00000000..5572c1f7 --- /dev/null +++ b/ananke/phoenix/gtk/widget/list-view.cpp @@ -0,0 +1,217 @@ +static void ListView_activate(ListView *self) { + if(self->onActivate) self->onActivate(); +} + +static void ListView_change(ListView *self) { + if(self->state.selected == false || self->state.selection != self->selection()) { + self->state.selected = true; + self->state.selection = self->selection(); + if(self->onChange) self->onChange(); + } +} + +static void ListView_toggle(GtkCellRendererToggle *cell, gchar *path, ListView *self) { + unsigned row = decimal(path); + self->setChecked(row, !self->checked(row)); + if(self->onToggle) self->onToggle(row); +} + +void pListView::append(const lstring &text) { + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + for(unsigned n = 0; n < text.size(); n++) gtk_list_store_set(store, &iter, 1 + n * 2 + 1, (const char*)text[n], -1); +} + +void pListView::autoSizeColumns() { + gtk_tree_view_columns_autosize(GTK_TREE_VIEW(subWidget)); +} + +bool pListView::checked(unsigned row) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + bool state; + if(gtk_tree_model_get_iter_from_string(model, &iter, string(row)) == false) return false; + gtk_tree_model_get(model, &iter, 0, &state, -1); + return state; +} + +void pListView::modify(unsigned row, const lstring &text) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + gtk_tree_model_get_iter_from_string(model, &iter, string(row)); + for(unsigned n = 0; n < text.size(); n++) gtk_list_store_set(store, &iter, 1 + n * 2 + 1, (const char*)text[n], -1); +} + +void pListView::remove(unsigned row) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + gtk_tree_model_get_iter_from_string(model, &iter, string(row)); + gtk_list_store_remove(store, &iter); +} + +void pListView::reset() { + listView.state.selected = false; + listView.state.selection = 0; + gtk_list_store_clear(GTK_LIST_STORE(store)); + gtk_tree_view_set_model(GTK_TREE_VIEW(subWidget), GTK_TREE_MODEL(store)); + //reset gtk_scrolled_window scrollbar position to 0,0 (top-left), as ListView is now empty + gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(gtkWidget), 0); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(gtkWidget), 0); +} + +bool pListView::selected() { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(subWidget)); + return gtk_tree_selection_get_selected(selection, 0, 0); +} + +unsigned pListView::selection() { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(subWidget)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + if(gtk_tree_selection_get_selected(selection, 0, &iter) == false) return listView.state.selection; + char *path = gtk_tree_model_get_string_from_iter(model, &iter); + unsigned row = decimal(path); + g_free(path); + return row; +} + +void pListView::setCheckable(bool checkable) { + gtk_cell_renderer_set_visible(column(0).checkbox, checkable); +} + +void pListView::setChecked(unsigned row, bool checked) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + gtk_tree_model_get_iter_from_string(model, &iter, string(row)); + gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, checked, -1); +} + +void pListView::setHeaderText(const lstring &text) { + destructor(); + constructor(); +} + +void pListView::setHeaderVisible(bool visible) { + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(subWidget), visible); +} + +void pListView::setImage(unsigned row, unsigned column, const nall::image &image) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + gtk_tree_model_get_iter_from_string(model, &iter, string(row)); + if(image.empty() == false) { + GdkPixbuf *pixbuf = CreatePixbuf(image, true); + gtk_list_store_set(store, &iter, 1 + column * 2, pixbuf, -1); + } else { + gtk_list_store_set(store, &iter, 1 + column * 2, nullptr, -1); + } +} + +void pListView::setSelected(bool selected) { + if(selected == false) { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(subWidget)); + gtk_tree_selection_unselect_all(selection); + } else { + setSelection(listView.state.selection); + } +} + +void pListView::setSelection(unsigned row) { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(subWidget)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + gtk_tree_selection_unselect_all(selection); + GtkTreeIter iter; + if(gtk_tree_model_get_iter_from_string(model, &iter, string(row)) == false) return; + gtk_tree_selection_select_iter(selection, &iter); + + //scroll window to selected item + char *path = gtk_tree_model_get_string_from_iter(model, &iter); + GtkTreePath *treePath = gtk_tree_path_new_from_string(path); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(subWidget), treePath, nullptr, true, 0.5, 0.0); + gtk_tree_path_free(treePath); + g_free(path); +} + +void pListView::constructor() { + gtkWidget = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(gtkWidget), GTK_SHADOW_ETCHED_IN); + + lstring headerText = listView.state.headerText; + if(headerText.size() == 0) headerText.append(""); //ListView must have at least one column + + column.reset(); + vector gtype; + for(auto &text : headerText) { + GtkColumn cell; + cell.label = gtk_label_new(text); + cell.column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(cell.column, true); + gtk_tree_view_column_set_title(cell.column, ""); + + if(column.size() == 0) { //first column checkbox + cell.checkbox = gtk_cell_renderer_toggle_new(); + gtk_tree_view_column_pack_start(cell.column, cell.checkbox, false); + gtk_tree_view_column_set_attributes(cell.column, cell.checkbox, "active", gtype.size(), nullptr); + gtype.append(G_TYPE_BOOLEAN); + g_signal_connect(cell.checkbox, "toggled", G_CALLBACK(ListView_toggle), (gpointer)&listView); + } + + cell.icon = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(cell.column, cell.icon, false); + gtk_tree_view_column_set_attributes(cell.column, cell.icon, "pixbuf", gtype.size(), nullptr); + gtype.append(GDK_TYPE_PIXBUF); + + cell.text = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(cell.column, cell.text, false); + gtk_tree_view_column_set_attributes(cell.column, cell.text, "text", gtype.size(), nullptr); + gtype.append(G_TYPE_STRING); + + column.append(cell); + } + + store = gtk_list_store_newv(gtype.size(), gtype.data()); + subWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + gtk_container_add(GTK_CONTAINER(gtkWidget), subWidget); + g_object_unref(G_OBJECT(store)); + + for(auto &cell : column) { + gtk_tree_view_column_set_widget(GTK_TREE_VIEW_COLUMN(cell.column), cell.label); + gtk_tree_view_append_column(GTK_TREE_VIEW(subWidget), cell.column); + gtk_widget_show(cell.label); + } + + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(subWidget), headerText.size() >= 2); //two or more columns + checkbox column + gtk_tree_view_set_search_column(GTK_TREE_VIEW(subWidget), 2); + + g_signal_connect_swapped(G_OBJECT(subWidget), "cursor-changed", G_CALLBACK(ListView_change), (gpointer)&listView); + g_signal_connect_swapped(G_OBJECT(subWidget), "row-activated", G_CALLBACK(ListView_activate), (gpointer)&listView); + + gtk_widget_show(subWidget); + + setHeaderVisible(listView.state.headerVisible); + setCheckable(listView.state.checkable); + for(auto &text : listView.state.text) append(text); + for(unsigned n = 0; n < listView.state.checked.size(); n++) setChecked(n, listView.state.checked[n]); + if(listView.state.selected) setSelection(listView.state.selection); + autoSizeColumns(); +} + +void pListView::destructor() { + gtk_widget_destroy(subWidget); + gtk_widget_destroy(gtkWidget); +} + +void pListView::orphan() { + destructor(); + constructor(); +} + +void pListView::setFocused() { + gtk_widget_grab_focus(subWidget); +} + +void pListView::setFont(const string &font) { + pFont::setFont(gtkWidget, font); + for(auto &cell : column) pFont::setFont(cell.label, font); +} diff --git a/ananke/phoenix/gtk/widget/progress-bar.cpp b/ananke/phoenix/gtk/widget/progress-bar.cpp new file mode 100644 index 00000000..972170b8 --- /dev/null +++ b/ananke/phoenix/gtk/widget/progress-bar.cpp @@ -0,0 +1,23 @@ +Geometry pProgressBar::minimumGeometry() { + return { 0, 0, 0, 25 }; +} + +void pProgressBar::setPosition(unsigned position) { + position = position <= 100 ? position : 0; + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gtkWidget), (double)position / 100.0); +} + +void pProgressBar::constructor() { + gtkWidget = gtk_progress_bar_new(); + + setPosition(progressBar.state.position); +} + +void pProgressBar::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pProgressBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/radio-box.cpp b/ananke/phoenix/gtk/widget/radio-box.cpp new file mode 100644 index 00000000..36aff3fa --- /dev/null +++ b/ananke/phoenix/gtk/widget/radio-box.cpp @@ -0,0 +1,50 @@ +static void RadioBox_activate(RadioBox *self) { + if(self->p.locked == false && self->checked() && self->onActivate) self->onActivate(); +} + +bool pRadioBox::checked() { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtkWidget)); +} + +Geometry pRadioBox::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, radioBox.state.text); +//Font &font = pWidget::font(); +//Geometry geometry = font.geometry(radioBox.state.text); + return { 0, 0, geometry.width + 28, geometry.height + 4 }; +} + +void pRadioBox::setChecked() { + locked = true; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkWidget), true); + locked = false; +} + +void pRadioBox::setGroup(const set &group) { + for(unsigned n = 0; n < group.size(); n++) { + if(n == 0) continue; + GSList *currentGroup = gtk_radio_button_get_group(GTK_RADIO_BUTTON(group[0].p.gtkWidget)); + if(currentGroup != gtk_radio_button_get_group(GTK_RADIO_BUTTON(gtkWidget))) { + gtk_radio_button_set_group(GTK_RADIO_BUTTON(gtkWidget), currentGroup); + } + } +} + +void pRadioBox::setText(const string &text) { + gtk_button_set_label(GTK_BUTTON(gtkWidget), text); +} + +void pRadioBox::constructor() { + gtkWidget = gtk_radio_button_new_with_label(0, ""); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "toggled", G_CALLBACK(RadioBox_activate), (gpointer)&radioBox); + + setText(radioBox.state.text); +} + +void pRadioBox::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pRadioBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/text-edit.cpp b/ananke/phoenix/gtk/widget/text-edit.cpp new file mode 100644 index 00000000..08812fb3 --- /dev/null +++ b/ananke/phoenix/gtk/widget/text-edit.cpp @@ -0,0 +1,66 @@ +static void TextEdit_change(TextEdit *self) { + self->state.text = self->text(); + if(self->p.locked == false && self->onChange) self->onChange(); +} + +void pTextEdit::setCursorPosition(unsigned position) { + GtkTextMark *mark = gtk_text_buffer_get_mark(textBuffer, "insert"); + GtkTextIter iter; + gtk_text_buffer_get_end_iter(textBuffer, &iter); + gtk_text_iter_set_offset(&iter, min(position, gtk_text_iter_get_offset(&iter))); + gtk_text_buffer_place_cursor(textBuffer, &iter); + gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(subWidget), mark); +} + +void pTextEdit::setEditable(bool editable) { + gtk_text_view_set_editable(GTK_TEXT_VIEW(subWidget), editable); +} + +void pTextEdit::setText(const string &text) { + locked = true; + gtk_text_buffer_set_text(textBuffer, text, -1); + locked = false; +} + +void pTextEdit::setWordWrap(bool wordWrap) { + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), wordWrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget), + wordWrap ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS, + GTK_POLICY_ALWAYS); +} + +string pTextEdit::text() { + GtkTextIter start, end; + gtk_text_buffer_get_start_iter(textBuffer, &start); + gtk_text_buffer_get_end_iter(textBuffer, &end); + char *temp = gtk_text_buffer_get_text(textBuffer, &start, &end, true); + string text = temp; + g_free(temp); + return text; +} + +void pTextEdit::constructor() { + gtkWidget = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(gtkWidget), GTK_SHADOW_ETCHED_IN); + subWidget = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), GTK_WRAP_WORD_CHAR); + gtk_container_add(GTK_CONTAINER(gtkWidget), subWidget); + textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(subWidget)); + g_signal_connect_swapped(G_OBJECT(textBuffer), "changed", G_CALLBACK(TextEdit_change), (gpointer)&textEdit); + gtk_widget_show(subWidget); + + setEditable(textEdit.state.editable); + setText(textEdit.state.text); + setWordWrap(textEdit.state.wordWrap); +} + +void pTextEdit::destructor() { + gtk_widget_destroy(subWidget); + gtk_widget_destroy(gtkWidget); +} + +void pTextEdit::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/vertical-scroll-bar.cpp b/ananke/phoenix/gtk/widget/vertical-scroll-bar.cpp new file mode 100644 index 00000000..e3bde589 --- /dev/null +++ b/ananke/phoenix/gtk/widget/vertical-scroll-bar.cpp @@ -0,0 +1,42 @@ +static void VerticalScrollBar_change(VerticalScrollBar *self) { + if(self->state.position == self->position()) return; + self->state.position = self->position(); + if(self->p.locked == false && self->onChange) self->onChange(); +} + +Geometry pVerticalScrollBar::minimumGeometry() { + return { 0, 0, 20, 0 }; +} + +unsigned pVerticalScrollBar::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(gtkWidget)); +} + +void pVerticalScrollBar::setLength(unsigned length) { + locked = true; + length += length == 0; + gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); + gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); + locked = false; +} + +void pVerticalScrollBar::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(gtkWidget), position); +} + +void pVerticalScrollBar::constructor() { + gtkWidget = gtk_vscrollbar_new(0); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(VerticalScrollBar_change), (gpointer)&verticalScrollBar); + + setLength(verticalScrollBar.state.length); + setPosition(verticalScrollBar.state.position); +} + +void pVerticalScrollBar::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pVerticalScrollBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/vertical-slider.cpp b/ananke/phoenix/gtk/widget/vertical-slider.cpp new file mode 100644 index 00000000..3c68489e --- /dev/null +++ b/ananke/phoenix/gtk/widget/vertical-slider.cpp @@ -0,0 +1,41 @@ +static void VerticalSlider_change(VerticalSlider *self) { + if(self->state.position == self->position()) return; + self->state.position = self->position(); + if(self->onChange) self->onChange(); +} + +Geometry pVerticalSlider::minimumGeometry() { + return { 0, 0, 20, 0 }; +} + +unsigned pVerticalSlider::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(gtkWidget)); +} + +void pVerticalSlider::setLength(unsigned length) { + length += length == 0; + gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); + gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); +} + +void pVerticalSlider::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(gtkWidget), position); +} + +void pVerticalSlider::constructor() { + gtkWidget = gtk_vscale_new_with_range(0, 100, 1); + gtk_scale_set_draw_value(GTK_SCALE(gtkWidget), false); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(VerticalSlider_change), (gpointer)&verticalSlider); + + setLength(verticalSlider.state.length); + setPosition(verticalSlider.state.position); +} + +void pVerticalSlider::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pVerticalSlider::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/viewport.cpp b/ananke/phoenix/gtk/widget/viewport.cpp new file mode 100644 index 00000000..e842a2e5 --- /dev/null +++ b/ananke/phoenix/gtk/widget/viewport.cpp @@ -0,0 +1,58 @@ +static gboolean Viewport_mouseLeave(GtkWidget *widget, GdkEventButton *event, pViewport *self) { + if(self->viewport.onMouseLeave) self->viewport.onMouseLeave(); + return true; +} + +static gboolean Viewport_mouseMove(GtkWidget *widget, GdkEventButton *event, pViewport *self) { + if(self->viewport.onMouseMove) self->viewport.onMouseMove({ (signed)event->x, (signed)event->y }); + return true; +} + +static gboolean Viewport_mousePress(GtkWidget *widget, GdkEventButton *event, pViewport *self) { + if(self->viewport.onMousePress) switch(event->button) { + case 1: self->viewport.onMousePress(Mouse::Button::Left); break; + case 2: self->viewport.onMousePress(Mouse::Button::Middle); break; + case 3: self->viewport.onMousePress(Mouse::Button::Right); break; + } + return true; +} + +static gboolean Viewport_mouseRelease(GtkWidget *widget, GdkEventButton *event, pViewport *self) { + if(self->viewport.onMouseRelease) switch(event->button) { + case 1: self->viewport.onMouseRelease(Mouse::Button::Left); break; + case 2: self->viewport.onMouseRelease(Mouse::Button::Middle); break; + case 3: self->viewport.onMouseRelease(Mouse::Button::Right); break; + } + return true; +} + +uintptr_t pViewport::handle() { + return GDK_WINDOW_XID(gtk_widget_get_window(gtkWidget)); +} + +void pViewport::constructor() { + gtkWidget = gtk_drawing_area_new(); +//gtk_widget_set_double_buffered(gtkWidget, false); + gtk_widget_add_events(gtkWidget, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK); + g_signal_connect(G_OBJECT(gtkWidget), "button_press_event", G_CALLBACK(Viewport_mousePress), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "button_release_event", G_CALLBACK(Viewport_mouseRelease), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "leave_notify_event", G_CALLBACK(Viewport_mouseLeave), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "motion_notify_event", G_CALLBACK(Viewport_mouseMove), (gpointer)this); + + GdkColor color; + color.pixel = 0; + color.red = 0; + color.green = 0; + color.blue = 0; + gtk_widget_modify_bg(gtkWidget, GTK_STATE_NORMAL, &color); +} + +void pViewport::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pViewport::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/widget.cpp b/ananke/phoenix/gtk/widget/widget.cpp new file mode 100644 index 00000000..dceb8266 --- /dev/null +++ b/ananke/phoenix/gtk/widget/widget.cpp @@ -0,0 +1,47 @@ +Geometry pWidget::minimumGeometry() { + return { 0, 0, 0, 0 }; +} + +bool pWidget::enabled() { + return gtk_widget_get_sensitive(gtkWidget); +} + +void pWidget::setEnabled(bool enabled) { + if(widget.state.abstract) enabled = false; + if(sizable.state.layout && sizable.state.layout->enabled() == false) enabled = false; + gtk_widget_set_sensitive(gtkWidget, enabled); +} + +void pWidget::setFocused() { + gtk_widget_grab_focus(gtkWidget); +} + +void pWidget::setFont(const string &font) { + pFont::setFont(gtkWidget, font); +} + +void pWidget::setGeometry(const Geometry &geometry) { + if(sizable.window() && sizable.window()->visible()) gtk_fixed_move(GTK_FIXED(sizable.window()->p.formContainer), gtkWidget, geometry.x, geometry.y); + unsigned width = (signed)geometry.width <= 0 ? 1U : geometry.width; + unsigned height = (signed)geometry.height <= 0 ? 1U : geometry.height; + gtk_widget_set_size_request(gtkWidget, width, height); +} + +void pWidget::setVisible(bool visible) { + if(widget.state.abstract) visible = false; + if(sizable.state.layout && sizable.state.layout->visible() == false) visible = false; + gtk_widget_set_visible(gtkWidget, visible); +} + +void pWidget::constructor() { + if(widget.state.abstract) gtkWidget = gtk_label_new(""); +} + +void pWidget::destructor() { + if(widget.state.abstract) gtk_widget_destroy(gtkWidget); +} + +void pWidget::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/window.cpp b/ananke/phoenix/gtk/window.cpp new file mode 100644 index 00000000..ae1521a3 --- /dev/null +++ b/ananke/phoenix/gtk/window.cpp @@ -0,0 +1,352 @@ +static gint Window_close(GtkWidget *widget, GdkEvent *event, Window *window) { + window->state.ignore = false; + if(window->onClose) window->onClose(); + if(window->state.ignore == false) window->setVisible(false); + return true; +} + +static gboolean Window_expose(GtkWidget *widget, GdkEvent *event, Window *window) { + if(window->state.backgroundColorOverride == false) return false; + cairo_t *context = gdk_cairo_create(widget->window); + + Color color = window->backgroundColor(); + double red = (double)color.red / 255.0; + double green = (double)color.green / 255.0; + double blue = (double)color.blue / 255.0; + double alpha = (double)color.alpha / 255.0; + + if(gdk_screen_is_composited(gdk_screen_get_default())) { + cairo_set_source_rgba(context, red, green, blue, alpha); + } else { + cairo_set_source_rgb(context, red, green, blue); + } + + cairo_set_operator(context, CAIRO_OPERATOR_SOURCE); + cairo_paint(context); + cairo_destroy(context); + + return false; +} + +static gboolean Window_configure(GtkWidget *widget, GdkEvent *event, Window *window) { + if(gtk_widget_get_realized(window->p.widget) == false) return false; + GdkWindow *gdkWindow = gtk_widget_get_window(widget); + + GdkRectangle border, client; + gdk_window_get_frame_extents(gdkWindow, &border); + gdk_window_get_geometry(gdkWindow, 0, 0, &client.width, &client.height, 0); + gdk_window_get_origin(gdkWindow, &client.x, &client.y); + + if(window->state.fullScreen == false) { + //update geometry settings + settings->frameGeometryX = client.x - border.x; + settings->frameGeometryY = client.y - border.y; + settings->frameGeometryWidth = border.width - client.width; + settings->frameGeometryHeight = border.height - client.height; + if(window->state.backgroundColorOverride == false) { + GdkColor color = widget->style->bg[GTK_STATE_NORMAL]; + settings->windowBackgroundColor + = ((uint8_t)(color.red >> 8) << 16) + + ((uint8_t)(color.green >> 8) << 8) + + ((uint8_t)(color.blue >> 8) << 0); + } + settings->save(); + } + + Geometry geometry = { + client.x, + client.y + window->p.menuHeight(), + client.width, + client.height - window->p.menuHeight() - window->p.statusHeight() + }; + + //move + if(geometry.x != window->state.geometry.x || geometry.y != window->state.geometry.y) { + if(window->state.fullScreen == false) { + window->state.geometry.x = geometry.x; + window->state.geometry.y = geometry.y; + } + if(window->p.locked == false && window->onMove) window->onMove(); + } + + //size + if(geometry.width != window->state.geometry.width || geometry.height != window->state.geometry.height) { + window->p.onSizePending = true; + } + + return false; +} + +static gboolean Window_keyPressEvent(GtkWidget *widget, GdkEventKey *event, Window *window) { + Keyboard::Keycode key = Keysym(event->keyval); + if(key != Keyboard::Keycode::None && window->onKeyPress) window->onKeyPress(key); + return false; +} + +static gboolean Window_keyReleaseEvent(GtkWidget *widget, GdkEventKey *event, Window *window) { + Keyboard::Keycode key = Keysym(event->keyval); + if(key != Keyboard::Keycode::None && window->onKeyRelease) window->onKeyRelease(key); + return false; +} + +static void Window_sizeAllocate(GtkWidget *widget, GtkAllocation *allocation, Window *window) { + //size-allocate sent from gtk_fixed_move(); detect if layout unchanged and return + if(allocation->width == window->p.lastAllocation.width + && allocation->height == window->p.lastAllocation.height) return; + + window->state.geometry.width = allocation->width; + window->state.geometry.height = allocation->height; + + for(auto &layout : window->state.layout) { + Geometry geometry = window->geometry(); + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); + } + + if(window->p.onSizePending && window->p.locked == false && window->onSize) { + window->p.onSizePending = false; + window->onSize(); + } + + window->p.lastAllocation = *allocation; +} + +static void Window_sizeRequest(GtkWidget *widget, GtkRequisition *requisition, Window *window) { + requisition->width = window->state.geometry.width; + requisition->height = window->state.geometry.height; +} + +Window& pWindow::none() { + static Window *window = nullptr; + if(window == nullptr) window = new Window; + return *window; +} + +void pWindow::append(Layout &layout) { + Geometry geometry = this->geometry(); + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); +} + +void pWindow::append(Menu &menu) { + if(window.state.menuFont != "") menu.p.setFont(window.state.menuFont); + else menu.p.setFont("Sans, 8"); + gtk_menu_shell_append(GTK_MENU_SHELL(this->menu), menu.p.widget); + gtk_widget_show(menu.p.widget); +} + +void pWindow::append(Widget &widget) { + ((Sizable&)widget).state.window = &window; + gtk_fixed_put(GTK_FIXED(formContainer), widget.p.gtkWidget, 0, 0); + if(widget.state.font != "") widget.p.setFont(widget.state.font); + else if(window.state.widgetFont != "") widget.p.setFont(window.state.widgetFont); + else widget.p.setFont("Sans, 8"); + widget.setVisible(widget.visible()); +} + +Color pWindow::backgroundColor() { + if(window.state.backgroundColorOverride) return window.state.backgroundColor; + return { + (uint8_t)(settings->windowBackgroundColor >> 16), + (uint8_t)(settings->windowBackgroundColor >> 8), + (uint8_t)(settings->windowBackgroundColor >> 0), + 255 + }; +} + +Geometry pWindow::frameMargin() { + if(window.state.fullScreen) return { + 0, + menuHeight(), + 0, + menuHeight() + statusHeight() + }; + + return { + settings->frameGeometryX, + settings->frameGeometryY + menuHeight(), + settings->frameGeometryWidth, + settings->frameGeometryHeight + menuHeight() + statusHeight() + }; +} + +bool pWindow::focused() { + return gtk_window_is_active(GTK_WINDOW(widget)); +} + +Geometry pWindow::geometry() { + if(window.state.fullScreen == true) return { + 0, + menuHeight(), + Desktop::size().width, + Desktop::size().height - menuHeight() - statusHeight() + }; + + return window.state.geometry; +} + +void pWindow::remove(Layout &layout) { +} + +void pWindow::remove(Menu &menu) { + menu.p.orphan(); +} + +void pWindow::remove(Widget &widget) { + widget.p.orphan(); +} + +void pWindow::setBackgroundColor(const Color &color) { + GdkColor gdkColor; + gdkColor.pixel = (color.red << 16) | (color.green << 8) | (color.blue << 0); + gdkColor.red = (color.red << 8) | (color.red << 0); + gdkColor.green = (color.green << 8) | (color.green << 0); + gdkColor.blue = (color.blue << 8) | (color.blue << 0); + gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &gdkColor); +} + +void pWindow::setFocused() { + gtk_window_present(GTK_WINDOW(widget)); +} + +void pWindow::setFullScreen(bool fullScreen) { + if(fullScreen == false) { + gtk_window_unfullscreen(GTK_WINDOW(widget)); + } else { + gtk_window_fullscreen(GTK_WINDOW(widget)); + } +} + +void pWindow::setGeometry(const Geometry &geometry) { + Geometry margin = frameMargin(); + gtk_window_move(GTK_WINDOW(widget), geometry.x - margin.x, geometry.y - margin.y); + + GdkGeometry geom; + geom.min_width = window.state.resizable ? 1 : window.state.geometry.width; + geom.min_height = window.state.resizable ? 1 : window.state.geometry.height; + gtk_window_set_geometry_hints(GTK_WINDOW(widget), GTK_WIDGET(widget), &geom, GDK_HINT_MIN_SIZE); + +//gtk_window_set_policy(GTK_WINDOW(widget), true, true, false); + gtk_widget_set_size_request(formContainer, geometry.width, geometry.height); + gtk_window_resize(GTK_WINDOW(widget), geometry.width, geometry.height + menuHeight() + statusHeight()); +} + +void pWindow::setMenuFont(const string &font) { + for(auto &item : window.state.menu) item.p.setFont(font); +} + +void pWindow::setMenuVisible(bool visible) { + gtk_widget_set_visible(menu, visible); +} + +void pWindow::setModal(bool modal) { + gtk_window_set_modal(GTK_WINDOW(widget), modal); +} + +void pWindow::setResizable(bool resizable) { + gtk_window_set_resizable(GTK_WINDOW(widget), resizable); + gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(status), resizable); +} + +void pWindow::setStatusFont(const string &font) { + pFont::setFont(status, font); +} + +void pWindow::setStatusText(const string &text) { + gtk_statusbar_pop(GTK_STATUSBAR(status), 1); + gtk_statusbar_push(GTK_STATUSBAR(status), 1, text); +} + +void pWindow::setStatusVisible(bool visible) { + gtk_widget_set_visible(status, visible); +} + +void pWindow::setTitle(const string &text) { + gtk_window_set_title(GTK_WINDOW(widget), text); +} + +void pWindow::setVisible(bool visible) { + gtk_widget_set_visible(widget, visible); + if(visible) { + if(gtk_widget_get_visible(menu)) { + GtkAllocation allocation; + gtk_widget_get_allocation(menu, &allocation); + settings->menuGeometryHeight = allocation.height; + } + + if(gtk_widget_get_visible(status)) { + GtkAllocation allocation; + gtk_widget_get_allocation(status, &allocation); + settings->statusGeometryHeight = allocation.height; + } + } +} + +void pWindow::setWidgetFont(const string &font) { + for(auto &item : window.state.widget) { + if(item.state.font == "") item.setFont(font); + } +} + +void pWindow::constructor() { + lastAllocation.width = 0; + lastAllocation.height = 0; + onSizePending = false; + + widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + if(gdk_screen_is_composited(gdk_screen_get_default())) { + gtk_widget_set_colormap(widget, gdk_screen_get_rgba_colormap(gdk_screen_get_default())); + } else { + gtk_widget_set_colormap(widget, gdk_screen_get_rgb_colormap(gdk_screen_get_default())); + } + + gtk_window_set_resizable(GTK_WINDOW(widget), true); + #if GTK_MAJOR_VERSION >= 3 + gtk_window_set_has_resize_grip(GTK_WINDOW(widget), false); + #endif + + gtk_widget_set_app_paintable(widget, true); + gtk_widget_add_events(widget, GDK_CONFIGURE); + + menuContainer = gtk_vbox_new(false, 0); + gtk_container_add(GTK_CONTAINER(widget), menuContainer); + gtk_widget_show(menuContainer); + + menu = gtk_menu_bar_new(); + gtk_box_pack_start(GTK_BOX(menuContainer), menu, false, false, 0); + + formContainer = gtk_fixed_new(); + gtk_box_pack_start(GTK_BOX(menuContainer), formContainer, true, true, 0); + gtk_widget_show(formContainer); + + statusContainer = gtk_event_box_new(); + status = gtk_statusbar_new(); + gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(status), true); + gtk_container_add(GTK_CONTAINER(statusContainer), status); + gtk_box_pack_start(GTK_BOX(menuContainer), statusContainer, false, false, 0); + gtk_widget_show(statusContainer); + + setTitle(""); + setResizable(window.state.resizable); + setGeometry(window.state.geometry); + setMenuFont("Sans, 8"); + setStatusFont("Sans, 8"); + + g_signal_connect(G_OBJECT(widget), "delete-event", G_CALLBACK(Window_close), (gpointer)&window); + g_signal_connect(G_OBJECT(widget), "expose-event", G_CALLBACK(Window_expose), (gpointer)&window); + g_signal_connect(G_OBJECT(widget), "configure-event", G_CALLBACK(Window_configure), (gpointer)&window); + g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(Window_keyPressEvent), (gpointer)&window); + g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(Window_keyPressEvent), (gpointer)&window); + + g_signal_connect(G_OBJECT(formContainer), "size-allocate", G_CALLBACK(Window_sizeAllocate), (gpointer)&window); + g_signal_connect(G_OBJECT(formContainer), "size-request", G_CALLBACK(Window_sizeRequest), (gpointer)&window); +} + +unsigned pWindow::menuHeight() { + return window.state.menuVisible ? settings->menuGeometryHeight : 0; +} + +unsigned pWindow::statusHeight() { + return window.state.statusVisible ? settings->statusGeometryHeight : 0; +} diff --git a/ananke/phoenix/phoenix.cpp b/ananke/phoenix/phoenix.cpp new file mode 100644 index 00000000..eaa66b97 --- /dev/null +++ b/ananke/phoenix/phoenix.cpp @@ -0,0 +1,52 @@ +#ifndef PHOENIX_CPP +#define PHOENIX_CPP + +#if defined(PHOENIX_WINDOWS) + #define UNICODE + #define WINVER 0x0501 + #define _WIN32_WINNT 0x0501 + #define _WIN32_IE 0x0600 + #define __MSVCRT_VERSION__ 0x0601 + #define NOMINMAX + + #include + #include + #include + #include + #include + #include + #include + #include +#elif defined(PHOENIX_QT) + #include + #include + #include + #define XK_MISCELLANY + #define XK_LATIN1 + #include + #include + #undef XK_MISCELLANY + #undef XK_LATIN1 + #include +#elif defined(PHOENIX_GTK) + #include + #include + #include + #include + #include + #include + #include + #include +#elif defined(PHOENIX_REFERENCE) +#else + #error "phoenix: unrecognized target" +#endif + +#include "phoenix.hpp" +using namespace nall; + +namespace phoenix { + #include "core/core.cpp" +} + +#endif diff --git a/ananke/phoenix/phoenix.hpp b/ananke/phoenix/phoenix.hpp new file mode 100644 index 00000000..8a6129c4 --- /dev/null +++ b/ananke/phoenix/phoenix.hpp @@ -0,0 +1,19 @@ +#ifndef PHOENIX_HPP +#define PHOENIX_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace phoenix { + #include "core/core.hpp" +} + +#endif diff --git a/ananke/phoenix/qt/action/action.cpp b/ananke/phoenix/qt/action/action.cpp new file mode 100644 index 00000000..62efaa2a --- /dev/null +++ b/ananke/phoenix/qt/action/action.cpp @@ -0,0 +1,49 @@ +void pAction::setEnabled(bool enabled) { + if(dynamic_cast(&action)) { + ((Menu&)action).p.qtMenu->setEnabled(enabled); + } else if(dynamic_cast(&action)) { + ((Separator&)action).p.qtAction->setEnabled(enabled); + } else if(dynamic_cast(&action)) { + ((Item&)action).p.qtAction->setEnabled(enabled); + } else if(dynamic_cast(&action)) { + ((CheckItem&)action).p.qtAction->setEnabled(enabled); + } else if(dynamic_cast(&action)) { + ((RadioItem&)action).p.qtAction->setEnabled(enabled); + } +} + +void pAction::setFont(const string &font) { + QFont qtFont = pFont::create(font); + + if(dynamic_cast(&action)) { + ((Menu&)action).p.setFont(font); + } else if(dynamic_cast(&action)) { + ((Separator&)action).p.qtAction->setFont(qtFont); + } else if(dynamic_cast(&action)) { + ((Item&)action).p.qtAction->setFont(qtFont); + } else if(dynamic_cast(&action)) { + ((CheckItem&)action).p.qtAction->setFont(qtFont); + } else if(dynamic_cast(&action)) { + ((RadioItem&)action).p.qtAction->setFont(qtFont); + } +} + +void pAction::setVisible(bool visible) { + if(dynamic_cast(&action)) { + ((Menu&)action).p.qtMenu->menuAction()->setVisible(visible); + } else if(dynamic_cast(&action)) { + ((Separator&)action).p.qtAction->setVisible(visible); + } else if(dynamic_cast(&action)) { + ((Item&)action).p.qtAction->setVisible(visible); + } else if(dynamic_cast(&action)) { + ((CheckItem&)action).p.qtAction->setVisible(visible); + } else if(dynamic_cast(&action)) { + ((RadioItem&)action).p.qtAction->setVisible(visible); + } +} + +void pAction::constructor() { +} + +void pAction::destructor() { +} diff --git a/ananke/phoenix/qt/action/check-item.cpp b/ananke/phoenix/qt/action/check-item.cpp new file mode 100644 index 00000000..ef451e73 --- /dev/null +++ b/ananke/phoenix/qt/action/check-item.cpp @@ -0,0 +1,27 @@ +bool pCheckItem::checked() { + return qtAction->isChecked(); +} + +void pCheckItem::setChecked(bool checked) { + qtAction->setChecked(checked); +} + +void pCheckItem::setText(const string &text) { + qtAction->setText(QString::fromUtf8(text)); +} + +void pCheckItem::constructor() { + qtAction = new QAction(0); + qtAction->setCheckable(true); + connect(qtAction, SIGNAL(triggered()), SLOT(onToggle())); +} + +void pCheckItem::destructor() { + if(action.state.menu) action.state.menu->remove(checkItem); + delete qtAction; +} + +void pCheckItem::onToggle() { + checkItem.state.checked = checked(); + if(checkItem.onToggle) checkItem.onToggle(); +} diff --git a/ananke/phoenix/qt/action/item.cpp b/ananke/phoenix/qt/action/item.cpp new file mode 100644 index 00000000..7f142289 --- /dev/null +++ b/ananke/phoenix/qt/action/item.cpp @@ -0,0 +1,21 @@ +void pItem::setImage(const image &image) { + qtAction->setIcon(CreateIcon(image)); +} + +void pItem::setText(const string &text) { + qtAction->setText(QString::fromUtf8(text)); +} + +void pItem::constructor() { + qtAction = new QAction(0); + connect(qtAction, SIGNAL(triggered()), SLOT(onActivate())); +} + +void pItem::destructor() { + if(action.state.menu) action.state.menu->remove(item); + delete qtAction; +} + +void pItem::onActivate() { + if(item.onActivate) item.onActivate(); +} diff --git a/ananke/phoenix/qt/action/menu.cpp b/ananke/phoenix/qt/action/menu.cpp new file mode 100644 index 00000000..43d89a1e --- /dev/null +++ b/ananke/phoenix/qt/action/menu.cpp @@ -0,0 +1,51 @@ +void pMenu::append(Action &action) { + if(dynamic_cast(&action)) { + qtMenu->addMenu(((Menu&)action).p.qtMenu); + } else if(dynamic_cast(&action)) { + qtMenu->addAction(((Separator&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->addAction(((Item&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->addAction(((CheckItem&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->addAction(((RadioItem&)action).p.qtAction); + } +} + +void pMenu::remove(Action &action) { + if(dynamic_cast(&action)) { + //QMenu::removeMenu() does not exist + qtMenu->clear(); + for(auto &action : menu.state.action) append(action); + } else if(dynamic_cast(&action)) { + qtMenu->removeAction(((Separator&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->removeAction(((Item&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->removeAction(((CheckItem&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->removeAction(((CheckItem&)action).p.qtAction); + } +} + +void pMenu::setFont(const string &font) { + qtMenu->setFont(pFont::create(font)); + for(auto &item : menu.state.action) item.p.setFont(font); +} + +void pMenu::setImage(const image &image) { + qtMenu->setIcon(CreateIcon(image)); +} + +void pMenu::setText(const string &text) { + qtMenu->setTitle(QString::fromUtf8(text)); +} + +void pMenu::constructor() { + qtMenu = new QMenu; +} + +void pMenu::destructor() { + if(action.state.menu) action.state.menu->remove(menu); + delete qtMenu; +} diff --git a/ananke/phoenix/qt/action/radio-item.cpp b/ananke/phoenix/qt/action/radio-item.cpp new file mode 100644 index 00000000..66cf6c6a --- /dev/null +++ b/ananke/phoenix/qt/action/radio-item.cpp @@ -0,0 +1,41 @@ +bool pRadioItem::checked() { + return qtAction->isChecked(); +} + +void pRadioItem::setChecked() { + locked = true; + for(auto &item : radioItem.state.group) { + bool checkState = item.p.qtAction == qtAction; + item.state.checked = checkState; + item.p.qtAction->setChecked(checkState); + } + locked = false; +} + +void pRadioItem::setGroup(const set &group) { +} + +void pRadioItem::setText(const string &text) { + qtAction->setText(QString::fromUtf8(text)); +} + +void pRadioItem::constructor() { + qtAction = new QAction(0); + qtGroup = new QActionGroup(0); + qtAction->setCheckable(true); + qtAction->setActionGroup(qtGroup); + qtAction->setChecked(true); + connect(qtAction, SIGNAL(triggered()), SLOT(onActivate())); +} + +void pRadioItem::destructor() { + if(action.state.menu) action.state.menu->remove(radioItem); + delete qtAction; +} + +void pRadioItem::onActivate() { + if(radioItem.state.checked == false) { + setChecked(); + if(locked == false && radioItem.onActivate) radioItem.onActivate(); + } +} diff --git a/ananke/phoenix/qt/action/separator.cpp b/ananke/phoenix/qt/action/separator.cpp new file mode 100644 index 00000000..95e66b6c --- /dev/null +++ b/ananke/phoenix/qt/action/separator.cpp @@ -0,0 +1,9 @@ +void pSeparator::constructor() { + qtAction = new QAction(0); + qtAction->setSeparator(true); +} + +void pSeparator::destructor() { + if(action.state.menu) action.state.menu->remove(separator); + delete qtAction; +} diff --git a/ananke/phoenix/qt/desktop.cpp b/ananke/phoenix/qt/desktop.cpp new file mode 100644 index 00000000..554106b5 --- /dev/null +++ b/ananke/phoenix/qt/desktop.cpp @@ -0,0 +1,9 @@ +Size pDesktop::size() { + QRect rect = QApplication::desktop()->screenGeometry(); + return { rect.width(), rect.height() }; +} + +Geometry pDesktop::workspace() { + QRect rect = QApplication::desktop()->availableGeometry(); + return { rect.x(), rect.y(), rect.width(), rect.height() }; +} diff --git a/ananke/phoenix/qt/dialog-window.cpp b/ananke/phoenix/qt/dialog-window.cpp new file mode 100644 index 00000000..680a6e2e --- /dev/null +++ b/ananke/phoenix/qt/dialog-window.cpp @@ -0,0 +1,57 @@ +string pDialogWindow::fileOpen(Window &parent, const string &path, const lstring &filter) { + string filterList; + for(auto &item : filter) { + filterList.append(item); + filterList.append(";;"); + } + filterList.rtrim<1>(";;"); + + //convert filter list from phoenix to Qt format, example: + //"Text, XML files (*.txt,*.xml)" -> "Text, XML files (*.txt *.xml)" + signed parenthesis = 0; + for(auto &n : filterList) { + if(n == '(') parenthesis++; + if(n == ')') parenthesis--; + if(n == ',' && parenthesis) n = ' '; + } + + QString filename = QFileDialog::getOpenFileName( + &parent != &Window::none() ? parent.p.qtWindow : nullptr, "Open File", + QString::fromUtf8(path), QString::fromUtf8(filterList) + ); + return filename.toUtf8().constData(); +} + +string pDialogWindow::fileSave(Window &parent, const string &path, const lstring &filter) { + string filterList; + for(auto &item : filter) { + filterList.append(item); + filterList.append(";;"); + } + filterList.rtrim<1>(";;"); + + //convert filter list from phoenix to Qt format, example: + //"Text, XML files (*.txt,*.xml)" -> "Text, XML files (*.txt *.xml)" + signed parenthesis = 0; + for(auto &n : filterList) { + if(n == '(') parenthesis++; + if(n == ')') parenthesis--; + if(n == ',' && parenthesis) n = ' '; + } + + QString filename = QFileDialog::getSaveFileName( + &parent != &Window::none() ? parent.p.qtWindow : nullptr, "Save File", + QString::fromUtf8(path), QString::fromUtf8(filterList) + ); + return filename.toUtf8().constData(); +} + +string pDialogWindow::folderSelect(Window &parent, const string &path) { + QString directory = QFileDialog::getExistingDirectory( + &parent != &Window::none() ? parent.p.qtWindow : nullptr, "Select Directory", + QString::fromUtf8(path), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks + ); + string name = directory.toUtf8().constData(); + if(name != "" && name.endswith("/") == false) name.append("/"); + return name; +} diff --git a/ananke/phoenix/qt/font.cpp b/ananke/phoenix/qt/font.cpp new file mode 100644 index 00000000..bb3261cd --- /dev/null +++ b/ananke/phoenix/qt/font.cpp @@ -0,0 +1,40 @@ +Geometry pFont::geometry(const string &description, const string &text) { + return pFont::geometry(pFont::create(description), text); +} + +QFont pFont::create(const string &description) { + lstring part; + part.split(",", description); + for(auto &item : part) item.trim(" "); + + string family = "Sans"; + unsigned size = 8u; + bool bold = false; + bool italic = false; + + if(part[0] != "") family = part[0]; + if(part.size() >= 2) size = decimal(part[1]); + if(part.size() >= 3) bold = part[2].position("Bold"); + if(part.size() >= 3) italic = part[2].position("Italic"); + + QFont qtFont; + qtFont.setFamily(family); + qtFont.setPointSize(size); + if(bold) qtFont.setBold(true); + if(italic) qtFont.setItalic(true); + return qtFont; +} + +Geometry pFont::geometry(const QFont &qtFont, const string &text) { + QFontMetrics metrics(qtFont); + + lstring lines; + lines.split("\n", text); + + unsigned maxWidth = 0; + for(auto &line : lines) { + maxWidth = max(maxWidth, metrics.width(line)); + } + + return { 0, 0, maxWidth, metrics.height() * lines.size() }; +} diff --git a/ananke/phoenix/qt/keyboard.cpp b/ananke/phoenix/qt/keyboard.cpp new file mode 100644 index 00000000..5b346406 --- /dev/null +++ b/ananke/phoenix/qt/keyboard.cpp @@ -0,0 +1,142 @@ +void pKeyboard::initialize() { + auto append = [](Keyboard::Scancode scancode, unsigned keysym) { + settings->keymap.insert(scancode, XKeysymToKeycode(pOS::display, keysym)); + }; + + append(Keyboard::Scancode::Escape, XK_Escape); + append(Keyboard::Scancode::F1, XK_F1); + append(Keyboard::Scancode::F2, XK_F2); + append(Keyboard::Scancode::F3, XK_F3); + append(Keyboard::Scancode::F4, XK_F4); + append(Keyboard::Scancode::F5, XK_F5); + append(Keyboard::Scancode::F6, XK_F6); + append(Keyboard::Scancode::F7, XK_F7); + append(Keyboard::Scancode::F8, XK_F8); + append(Keyboard::Scancode::F9, XK_F9); + append(Keyboard::Scancode::F10, XK_F10); + append(Keyboard::Scancode::F11, XK_F11); + append(Keyboard::Scancode::F12, XK_F12); + + append(Keyboard::Scancode::PrintScreen, XK_Print); + append(Keyboard::Scancode::ScrollLock, XK_Scroll_Lock); + append(Keyboard::Scancode::Pause, XK_Pause); + + append(Keyboard::Scancode::Insert, XK_Insert); + append(Keyboard::Scancode::Delete, XK_Delete); + append(Keyboard::Scancode::Home, XK_Home); + append(Keyboard::Scancode::End, XK_End); + append(Keyboard::Scancode::PageUp, XK_Prior); + append(Keyboard::Scancode::PageDown, XK_Next); + + append(Keyboard::Scancode::Up, XK_Up); + append(Keyboard::Scancode::Down, XK_Down); + append(Keyboard::Scancode::Left, XK_Left); + append(Keyboard::Scancode::Right, XK_Right); + + append(Keyboard::Scancode::Grave, XK_asciitilde); + append(Keyboard::Scancode::Number1, XK_1); + append(Keyboard::Scancode::Number2, XK_2); + append(Keyboard::Scancode::Number3, XK_3); + append(Keyboard::Scancode::Number4, XK_4); + append(Keyboard::Scancode::Number5, XK_5); + append(Keyboard::Scancode::Number6, XK_6); + append(Keyboard::Scancode::Number7, XK_7); + append(Keyboard::Scancode::Number8, XK_8); + append(Keyboard::Scancode::Number9, XK_9); + append(Keyboard::Scancode::Number0, XK_0); + append(Keyboard::Scancode::Minus, XK_minus); + append(Keyboard::Scancode::Equal, XK_equal); + append(Keyboard::Scancode::Backspace, XK_BackSpace); + + append(Keyboard::Scancode::BracketLeft, XK_bracketleft); + append(Keyboard::Scancode::BracketRight, XK_bracketright); + append(Keyboard::Scancode::Backslash, XK_backslash); + append(Keyboard::Scancode::Semicolon, XK_semicolon); + append(Keyboard::Scancode::Apostrophe, XK_apostrophe); + append(Keyboard::Scancode::Comma, XK_comma); + append(Keyboard::Scancode::Period, XK_period); + append(Keyboard::Scancode::Slash, XK_slash); + + append(Keyboard::Scancode::Tab, XK_Tab); + append(Keyboard::Scancode::CapsLock, XK_Caps_Lock); + append(Keyboard::Scancode::Return, XK_Return); + append(Keyboard::Scancode::ShiftLeft, XK_Shift_L); + append(Keyboard::Scancode::ShiftRight, XK_Shift_R); + append(Keyboard::Scancode::ControlLeft, XK_Control_L); + append(Keyboard::Scancode::ControlRight, XK_Control_R); + append(Keyboard::Scancode::SuperLeft, XK_Super_L); + append(Keyboard::Scancode::SuperRight, XK_Super_R); + append(Keyboard::Scancode::AltLeft, XK_Alt_L); + append(Keyboard::Scancode::AltRight, XK_Alt_R); + append(Keyboard::Scancode::Space, XK_space); + append(Keyboard::Scancode::Menu, XK_Menu); + + append(Keyboard::Scancode::A, XK_A); + append(Keyboard::Scancode::B, XK_B); + append(Keyboard::Scancode::C, XK_C); + append(Keyboard::Scancode::D, XK_D); + append(Keyboard::Scancode::E, XK_E); + append(Keyboard::Scancode::F, XK_F); + append(Keyboard::Scancode::G, XK_G); + append(Keyboard::Scancode::H, XK_H); + append(Keyboard::Scancode::I, XK_I); + append(Keyboard::Scancode::J, XK_J); + append(Keyboard::Scancode::K, XK_K); + append(Keyboard::Scancode::L, XK_L); + append(Keyboard::Scancode::M, XK_M); + append(Keyboard::Scancode::N, XK_N); + append(Keyboard::Scancode::O, XK_O); + append(Keyboard::Scancode::P, XK_P); + append(Keyboard::Scancode::Q, XK_Q); + append(Keyboard::Scancode::R, XK_R); + append(Keyboard::Scancode::S, XK_S); + append(Keyboard::Scancode::T, XK_T); + append(Keyboard::Scancode::U, XK_U); + append(Keyboard::Scancode::V, XK_V); + append(Keyboard::Scancode::W, XK_W); + append(Keyboard::Scancode::X, XK_X); + append(Keyboard::Scancode::Y, XK_Y); + append(Keyboard::Scancode::Z, XK_Z); + + append(Keyboard::Scancode::NumLock, XK_Num_Lock); + append(Keyboard::Scancode::Divide, XK_KP_Divide); + append(Keyboard::Scancode::Multiply, XK_KP_Multiply); + append(Keyboard::Scancode::Subtract, XK_KP_Subtract); + append(Keyboard::Scancode::Add, XK_KP_Add); + append(Keyboard::Scancode::Enter, XK_KP_Enter); + append(Keyboard::Scancode::Point, XK_KP_Decimal); + + append(Keyboard::Scancode::Keypad1, XK_KP_1); + append(Keyboard::Scancode::Keypad2, XK_KP_2); + append(Keyboard::Scancode::Keypad3, XK_KP_3); + append(Keyboard::Scancode::Keypad4, XK_KP_4); + append(Keyboard::Scancode::Keypad5, XK_KP_5); + append(Keyboard::Scancode::Keypad6, XK_KP_6); + append(Keyboard::Scancode::Keypad7, XK_KP_7); + append(Keyboard::Scancode::Keypad8, XK_KP_8); + append(Keyboard::Scancode::Keypad9, XK_KP_9); + append(Keyboard::Scancode::Keypad0, XK_KP_0); +} + +bool pKeyboard::pressed(Keyboard::Scancode scancode) { + char state[256]; + XQueryKeymap(pOS::display, state); + unsigned id = settings->keymap.lhs[scancode]; + return state[id >> 3] & (1 << (id & 7)); +} + +vector pKeyboard::state() { + vector output; + output.resize((unsigned)Keyboard::Scancode::Limit); + for(auto &n : output) n = false; + + char state[256]; + XQueryKeymap(pOS::display, state); + for(auto &n : settings->keymap.rhs) { + if(state[n.name >> 3] & (1 << (n.name & 7))) { + output[(unsigned)n.data] = true; + } + } + + return output; +} diff --git a/ananke/phoenix/qt/message-window.cpp b/ananke/phoenix/qt/message-window.cpp new file mode 100644 index 00000000..7bceba0f --- /dev/null +++ b/ananke/phoenix/qt/message-window.cpp @@ -0,0 +1,47 @@ +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; + + //MessageWindow was closed via window manager, rather than by a button; assume a cancel/no response + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::information(&parent != &Window::none() ? parent.p.qtWindow : nullptr, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response pMessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::question(&parent != &Window::none() ? parent.p.qtWindow : nullptr, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response pMessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::warning(&parent != &Window::none() ? parent.p.qtWindow : nullptr, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response pMessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::critical(&parent != &Window::none() ? parent.p.qtWindow : nullptr, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} diff --git a/ananke/phoenix/qt/mouse.cpp b/ananke/phoenix/qt/mouse.cpp new file mode 100644 index 00000000..4ea06cc9 --- /dev/null +++ b/ananke/phoenix/qt/mouse.cpp @@ -0,0 +1,14 @@ +Position pMouse::position() { + QPoint point = QCursor::pos(); + return { point.x(), point.y() }; +} + +bool pMouse::pressed(Mouse::Button button) { + Qt::MouseButtons buttons = QApplication::mouseButtons(); + switch(button) { + case Mouse::Button::Left: return buttons & Qt::LeftButton; + case Mouse::Button::Middle: return buttons & Qt::MidButton; + case Mouse::Button::Right: return buttons & Qt::RightButton; + } + return false; +} diff --git a/ananke/phoenix/qt/platform.cpp b/ananke/phoenix/qt/platform.cpp new file mode 100644 index 00000000..86f659c8 --- /dev/null +++ b/ananke/phoenix/qt/platform.cpp @@ -0,0 +1,91 @@ +//Qt 4.8.0 and earlier improperly define the QLOCATION macro +//in C++11, it is detected as a malformed user-defined literal +//below is a workaround to fix compilation errors caused by this +#undef QLOCATION +#define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__) + +#include "platform.moc.hpp" +#include "platform.moc" +#include "utility.cpp" +#include "settings.cpp" + +#include "desktop.cpp" +#include "keyboard.cpp" +#include "mouse.cpp" +#include "dialog-window.cpp" +#include "message-window.cpp" + +#include "font.cpp" +#include "timer.cpp" +#include "window.cpp" + +#include "action/action.cpp" +#include "action/menu.cpp" +#include "action/separator.cpp" +#include "action/item.cpp" +#include "action/check-item.cpp" +#include "action/radio-item.cpp" + +#include "widget/widget.cpp" +#include "widget/button.cpp" +#include "widget/canvas.cpp" +#include "widget/check-box.cpp" +#include "widget/combo-box.cpp" +#include "widget/hex-edit.cpp" +#include "widget/horizontal-scroll-bar.cpp" +#include "widget/horizontal-slider.cpp" +#include "widget/label.cpp" +#include "widget/line-edit.cpp" +#include "widget/list-view.cpp" +#include "widget/progress-bar.cpp" +#include "widget/radio-box.cpp" +#include "widget/text-edit.cpp" +#include "widget/vertical-scroll-bar.cpp" +#include "widget/vertical-slider.cpp" +#include "widget/viewport.cpp" + +XlibDisplay* pOS::display = 0; + +void pOS::main() { + QApplication::exec(); +} + +bool pOS::pendingEvents() { + return QApplication::hasPendingEvents(); +} + +void pOS::processEvents() { + while(pendingEvents()) QApplication::processEvents(); +} + +void pOS::quit() { + QApplication::quit(); + //note: QApplication cannot be deleted; or libQtGui will crash + qtApplication = 0; +} + +void pOS::syncX() { + for(unsigned n = 0; n < 8; n++) { + QApplication::syncX(); + OS::processEvents(); + usleep(2000); + } +} + +void pOS::initialize() { + display = XOpenDisplay(0); + + settings = new Settings; + settings->load(); + + static int argc = 1; + static char *argv[2]; + argv[0] = new char[8]; + argv[1] = 0; + strcpy(argv[0], "phoenix"); + char **argvp = argv; + + qtApplication = new QApplication(argc, argvp); + + pKeyboard::initialize(); +} diff --git a/ananke/phoenix/qt/platform.moc b/ananke/phoenix/qt/platform.moc new file mode 100644 index 00000000..bf31cfa0 --- /dev/null +++ b/ananke/phoenix/qt/platform.moc @@ -0,0 +1,1105 @@ +/**************************************************************************** +** Meta object code from reading C++ file 'platform.moc.hpp' +** +** Created: Thu Aug 9 18:10:14 2012 +** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3) +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#if !defined(Q_MOC_OUTPUT_REVISION) +#error "The header file 'platform.moc.hpp' doesn't include ." +#elif Q_MOC_OUTPUT_REVISION != 62 +#error "This file was generated using the moc from 4.6.3. 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_pTimer[] = { + + // 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 + 8, 7, 7, 7, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pTimer[] = { + "pTimer\0\0onTimeout()\0" +}; + +const QMetaObject pTimer::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pTimer, + qt_meta_data_pTimer, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pTimer::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pTimer::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pTimer::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pTimer)) + return static_cast(const_cast< pTimer*>(this)); + if (!strcmp(_clname, "pObject")) + return static_cast< pObject*>(const_cast< pTimer*>(this)); + return QObject::qt_metacast(_clname); +} + +int pTimer::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTimeout(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pWindow[] = { + + // 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_pWindow[] = { + "pWindow\0" +}; + +const QMetaObject pWindow::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pWindow, + qt_meta_data_pWindow, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pWindow::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pWindow::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pWindow::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pWindow)) + return static_cast(const_cast< pWindow*>(this)); + if (!strcmp(_clname, "pObject")) + return static_cast< pObject*>(const_cast< pWindow*>(this)); + return QObject::qt_metacast(_clname); +} + +int pWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_pItem[] = { + + // 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 + 7, 6, 6, 6, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pItem[] = { + "pItem\0\0onActivate()\0" +}; + +const QMetaObject pItem::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pItem, + qt_meta_data_pItem, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pItem::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pItem::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pItem::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pItem)) + return static_cast(const_cast< pItem*>(this)); + if (!strcmp(_clname, "pAction")) + return static_cast< pAction*>(const_cast< pItem*>(this)); + return QObject::qt_metacast(_clname); +} + +int pItem::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pCheckItem[] = { + + // 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 + 12, 11, 11, 11, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pCheckItem[] = { + "pCheckItem\0\0onToggle()\0" +}; + +const QMetaObject pCheckItem::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pCheckItem, + qt_meta_data_pCheckItem, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pCheckItem::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pCheckItem::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pCheckItem::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pCheckItem)) + return static_cast(const_cast< pCheckItem*>(this)); + if (!strcmp(_clname, "pAction")) + return static_cast< pAction*>(const_cast< pCheckItem*>(this)); + return QObject::qt_metacast(_clname); +} + +int pCheckItem::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onToggle(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pRadioItem[] = { + + // 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 + 12, 11, 11, 11, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pRadioItem[] = { + "pRadioItem\0\0onActivate()\0" +}; + +const QMetaObject pRadioItem::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pRadioItem, + qt_meta_data_pRadioItem, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pRadioItem::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pRadioItem::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pRadioItem::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pRadioItem)) + return static_cast(const_cast< pRadioItem*>(this)); + if (!strcmp(_clname, "pAction")) + return static_cast< pAction*>(const_cast< pRadioItem*>(this)); + return QObject::qt_metacast(_clname); +} + +int pRadioItem::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pButton[] = { + + // 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 + 9, 8, 8, 8, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pButton[] = { + "pButton\0\0onActivate()\0" +}; + +const QMetaObject pButton::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pButton, + qt_meta_data_pButton, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pButton::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pButton::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pButton::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pButton)) + return static_cast(const_cast< pButton*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pButton*>(this)); + return QObject::qt_metacast(_clname); +} + +int pButton::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pCanvas[] = { + + // 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_pCanvas[] = { + "pCanvas\0" +}; + +const QMetaObject pCanvas::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pCanvas, + qt_meta_data_pCanvas, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pCanvas::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pCanvas::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pCanvas::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pCanvas)) + return static_cast(const_cast< pCanvas*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pCanvas*>(this)); + return QObject::qt_metacast(_clname); +} + +int pCanvas::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_pCheckBox[] = { + + // 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 + 11, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pCheckBox[] = { + "pCheckBox\0\0onToggle()\0" +}; + +const QMetaObject pCheckBox::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pCheckBox, + qt_meta_data_pCheckBox, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pCheckBox::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pCheckBox::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pCheckBox::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pCheckBox)) + return static_cast(const_cast< pCheckBox*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pCheckBox*>(this)); + return QObject::qt_metacast(_clname); +} + +int pCheckBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onToggle(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pComboBox[] = { + + // 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 + 11, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pComboBox[] = { + "pComboBox\0\0onChange()\0" +}; + +const QMetaObject pComboBox::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pComboBox, + qt_meta_data_pComboBox, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pComboBox::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pComboBox::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pComboBox::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pComboBox)) + return static_cast(const_cast< pComboBox*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pComboBox*>(this)); + return QObject::qt_metacast(_clname); +} + +int pComboBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::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_pHexEdit[] = { + + // 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 + 10, 9, 9, 9, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pHexEdit[] = { + "pHexEdit\0\0onScroll()\0" +}; + +const QMetaObject pHexEdit::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pHexEdit, + qt_meta_data_pHexEdit, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pHexEdit::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pHexEdit::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pHexEdit::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pHexEdit)) + return static_cast(const_cast< pHexEdit*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pHexEdit*>(this)); + return QObject::qt_metacast(_clname); +} + +int pHexEdit::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onScroll(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pHorizontalScrollBar[] = { + + // 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_pHorizontalScrollBar[] = { + "pHorizontalScrollBar\0\0onChange()\0" +}; + +const QMetaObject pHorizontalScrollBar::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pHorizontalScrollBar, + qt_meta_data_pHorizontalScrollBar, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pHorizontalScrollBar::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pHorizontalScrollBar::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pHorizontalScrollBar::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pHorizontalScrollBar)) + return static_cast(const_cast< pHorizontalScrollBar*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pHorizontalScrollBar*>(this)); + return QObject::qt_metacast(_clname); +} + +int pHorizontalScrollBar::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::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_pHorizontalSlider[] = { + + // 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 + 19, 18, 18, 18, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pHorizontalSlider[] = { + "pHorizontalSlider\0\0onChange()\0" +}; + +const QMetaObject pHorizontalSlider::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pHorizontalSlider, + qt_meta_data_pHorizontalSlider, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pHorizontalSlider::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pHorizontalSlider::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pHorizontalSlider::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pHorizontalSlider)) + return static_cast(const_cast< pHorizontalSlider*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pHorizontalSlider*>(this)); + return QObject::qt_metacast(_clname); +} + +int pHorizontalSlider::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::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_pLineEdit[] = { + + // 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 + 11, 10, 10, 10, 0x0a, + 24, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pLineEdit[] = { + "pLineEdit\0\0onActivate()\0onChange()\0" +}; + +const QMetaObject pLineEdit::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pLineEdit, + qt_meta_data_pLineEdit, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pLineEdit::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pLineEdit::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pLineEdit::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pLineEdit)) + return static_cast(const_cast< pLineEdit*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pLineEdit*>(this)); + return QObject::qt_metacast(_clname); +} + +int pLineEdit::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::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_pListView[] = { + + // 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 + 11, 10, 10, 10, 0x0a, + 29, 24, 10, 10, 0x0a, + 56, 24, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pListView[] = { + "pListView\0\0onActivate()\0item\0" + "onChange(QTreeWidgetItem*)\0" + "onToggle(QTreeWidgetItem*)\0" +}; + +const QMetaObject pListView::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pListView, + qt_meta_data_pListView, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pListView::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pListView::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pListView::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pListView)) + return static_cast(const_cast< pListView*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pListView*>(this)); + return QObject::qt_metacast(_clname); +} + +int pListView::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + case 1: onChange((*reinterpret_cast< QTreeWidgetItem*(*)>(_a[1]))); break; + case 2: onToggle((*reinterpret_cast< QTreeWidgetItem*(*)>(_a[1]))); break; + default: ; + } + _id -= 3; + } + return _id; +} +static const uint qt_meta_data_pRadioBox[] = { + + // 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 + 11, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pRadioBox[] = { + "pRadioBox\0\0onActivate()\0" +}; + +const QMetaObject pRadioBox::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pRadioBox, + qt_meta_data_pRadioBox, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pRadioBox::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pRadioBox::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pRadioBox::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pRadioBox)) + return static_cast(const_cast< pRadioBox*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pRadioBox*>(this)); + return QObject::qt_metacast(_clname); +} + +int pRadioBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pTextEdit[] = { + + // 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 + 11, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pTextEdit[] = { + "pTextEdit\0\0onChange()\0" +}; + +const QMetaObject pTextEdit::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pTextEdit, + qt_meta_data_pTextEdit, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pTextEdit::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pTextEdit::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pTextEdit::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pTextEdit)) + return static_cast(const_cast< pTextEdit*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pTextEdit*>(this)); + return QObject::qt_metacast(_clname); +} + +int pTextEdit::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::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_pVerticalScrollBar[] = { + + // 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 + 20, 19, 19, 19, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pVerticalScrollBar[] = { + "pVerticalScrollBar\0\0onChange()\0" +}; + +const QMetaObject pVerticalScrollBar::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pVerticalScrollBar, + qt_meta_data_pVerticalScrollBar, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pVerticalScrollBar::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pVerticalScrollBar::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pVerticalScrollBar::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pVerticalScrollBar)) + return static_cast(const_cast< pVerticalScrollBar*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pVerticalScrollBar*>(this)); + return QObject::qt_metacast(_clname); +} + +int pVerticalScrollBar::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::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_pVerticalSlider[] = { + + // 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 + 17, 16, 16, 16, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pVerticalSlider[] = { + "pVerticalSlider\0\0onChange()\0" +}; + +const QMetaObject pVerticalSlider::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pVerticalSlider, + qt_meta_data_pVerticalSlider, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pVerticalSlider::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pVerticalSlider::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pVerticalSlider::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pVerticalSlider)) + return static_cast(const_cast< pVerticalSlider*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pVerticalSlider*>(this)); + return QObject::qt_metacast(_clname); +} + +int pVerticalSlider::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::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; +} +QT_END_MOC_NAMESPACE diff --git a/ananke/phoenix/qt/platform.moc.hpp b/ananke/phoenix/qt/platform.moc.hpp new file mode 100644 index 00000000..9e75fdc8 --- /dev/null +++ b/ananke/phoenix/qt/platform.moc.hpp @@ -0,0 +1,633 @@ +static QApplication *qtApplication = nullptr; + +struct Settings : public configuration { + bidirectional_map keymap; + + unsigned frameGeometryX; + unsigned frameGeometryY; + unsigned frameGeometryWidth; + unsigned frameGeometryHeight; + unsigned menuGeometryHeight; + unsigned statusGeometryHeight; + + void load(); + void save(); + Settings(); +}; + +struct pWindow; +struct pMenu; +struct pLayout; +struct pWidget; + +struct pFont { + static Geometry geometry(const string &description, const string &text); + + static QFont create(const string &description); + static Geometry geometry(const QFont &qtFont, const string &text); +}; + +struct pDesktop { + static Size size(); + static Geometry workspace(); +}; + +struct pKeyboard { + static bool pressed(Keyboard::Scancode scancode); + static vector state(); + + static void initialize(); +}; + +struct pMouse { + static Position position(); + static bool pressed(Mouse::Button button); +}; + +struct pDialogWindow { + static string fileOpen(Window &parent, const string &path, const lstring &filter); + static string fileSave(Window &parent, const string &path, const lstring &filter); + static string folderSelect(Window &parent, const string &path); +}; + +struct pMessageWindow { + static MessageWindow::Response information(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response question(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response warning(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response critical(Window &parent, const string &text, MessageWindow::Buttons buttons); +}; + +struct pObject { + Object &object; + bool locked; + + pObject(Object &object) : object(object), locked(false) {} + virtual ~pObject() {} + void constructor() {} + void destructor() {} +}; + +struct pOS : public pObject { + static XlibDisplay *display; + + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + static void initialize(); + static void syncX(); +}; + +struct pTimer : public QObject, public pObject { + Q_OBJECT + +public: + Timer &timer; + QTimer *qtTimer; + + void setEnabled(bool enabled); + void setInterval(unsigned milliseconds); + + pTimer(Timer &timer) : pObject(timer), timer(timer) {} + void constructor(); + void destructor(); + +public slots: + void onTimeout(); +}; + +struct pWindow : public QObject, public pObject { + Q_OBJECT + +public: + Window &window; + struct QtWindow : public QWidget { + pWindow &self; + void closeEvent(QCloseEvent*); + void keyPressEvent(QKeyEvent*); + void keyReleaseEvent(QKeyEvent*); + void moveEvent(QMoveEvent*); + void resizeEvent(QResizeEvent*); + QSize sizeHint() const; + QtWindow(pWindow &self) : self(self) {} + } *qtWindow; + QVBoxLayout *qtLayout; + QMenuBar *qtMenu; + QStatusBar *qtStatus; + QWidget *qtContainer; + + static Window& none(); + + void append(Layout &layout); + void append(Menu &menu); + void append(Widget &widget); + Color backgroundColor(); + Geometry frameMargin(); + bool focused(); + Geometry geometry(); + void remove(Layout &layout); + void remove(Menu &menu); + void remove(Widget &widget); + void setBackgroundColor(const Color &color); + void setFocused(); + void setFullScreen(bool fullScreen); + void setGeometry(const Geometry &geometry); + void setMenuFont(const string &font); + void setMenuVisible(bool visible); + void setModal(bool modal); + void setResizable(bool resizable); + void setStatusFont(const string &font); + void setStatusText(const string &text); + void setStatusVisible(bool visible); + void setTitle(const string &text); + void setVisible(bool visible); + void setWidgetFont(const string &font); + + pWindow(Window &window) : pObject(window), window(window) {} + void constructor(); + void destructor(); + void updateFrameGeometry(); +}; + +struct pAction : public pObject { + Action &action; + + void setEnabled(bool enabled); + void setFont(const string &font); + void setVisible(bool visible); + + pAction(Action &action) : pObject(action), action(action) {} + void constructor(); + void destructor(); +}; + +struct pMenu : public pAction { + Menu &menu; + QMenu *qtMenu; + + void append(Action &action); + void remove(Action &action); + void setFont(const string &font); + void setImage(const image &image); + void setText(const string &text); + + pMenu(Menu &menu) : pAction(menu), menu(menu) {} + void constructor(); + void destructor(); +}; + +struct pSeparator : public pAction { + Separator &separator; + QAction *qtAction; + + pSeparator(Separator &separator) : pAction(separator), separator(separator) {} + void constructor(); + void destructor(); +}; + +struct pItem : public QObject, public pAction { + Q_OBJECT + +public: + Item &item; + QAction *qtAction; + + void setImage(const image &image); + void setText(const string &text); + + pItem(Item &item) : pAction(item), item(item) {} + void constructor(); + void destructor(); + +public slots: + void onActivate(); +}; + +struct pCheckItem : public QObject, public pAction { + Q_OBJECT + +public: + CheckItem &checkItem; + QAction *qtAction; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckItem(CheckItem &checkItem) : pAction(checkItem), checkItem(checkItem) {} + void constructor(); + void destructor(); + +public slots: + void onToggle(); +}; + +struct pRadioItem : public QObject, public pAction { + Q_OBJECT + +public: + RadioItem &radioItem; + QAction *qtAction; + QActionGroup *qtGroup; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioItem(RadioItem &radioItem) : pAction(radioItem), radioItem(radioItem) {} + void constructor(); + void destructor(); + +public slots: + void onActivate(); +}; + +struct pSizable : public pObject { + Sizable &sizable; + + pSizable(Sizable &sizable) : pObject(sizable), sizable(sizable) {} + + void constructor() {} + void destructor() {} +}; + +struct pLayout : public pSizable { + Layout &layout; + + pLayout(Layout &layout) : pSizable(layout), layout(layout) {} + + void constructor() {} + void destructor() {} +}; + +struct pWidget : public pSizable { + Widget &widget; + QWidget *qtWidget; + + virtual Geometry minimumGeometry(); + void setEnabled(bool enabled); + void setFocused(); + void setFont(const string &font); + virtual void setGeometry(const Geometry &geometry); + void setVisible(bool visible); + + pWidget(Widget &widget) : pSizable(widget), widget(widget) {} + void constructor(); + void synchronizeState(); + void destructor(); + virtual void orphan(); +}; + +struct pButton : public QObject, public pWidget { + Q_OBJECT + +public: + Button &button; + QToolButton *qtButton; + + Geometry minimumGeometry(); + void setImage(const image &image, Orientation orientation); + void setText(const string &text); + + pButton(Button &button) : pWidget(button), button(button) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onActivate(); +}; + +struct pCanvas : public QObject, public pWidget { + Q_OBJECT + +public: + Canvas &canvas; + QImage *qtImage; + struct QtCanvas : public QWidget { + pCanvas &self; + void leaveEvent(QEvent*); + void mouseMoveEvent(QMouseEvent*); + void mousePressEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void paintEvent(QPaintEvent*); + QtCanvas(pCanvas &self); + } *qtCanvas; + + void setSize(const Size &size); + void update(); + + pCanvas(Canvas &canvas) : pWidget(canvas), canvas(canvas) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: +}; + +struct pCheckBox : public QObject, public pWidget { + Q_OBJECT + +public: + CheckBox &checkBox; + QCheckBox *qtCheckBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckBox(CheckBox &checkBox) : pWidget(checkBox), checkBox(checkBox) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onToggle(); +}; + +struct pComboBox : public QObject, public pWidget { + Q_OBJECT + +public: + ComboBox &comboBox; + QComboBox *qtComboBox; + + void append(const string &text); + void modify(unsigned row, const string &text); + void remove(unsigned row); + Geometry minimumGeometry(); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + + pComboBox(ComboBox &comboBox) : pWidget(comboBox), comboBox(comboBox) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pHexEdit : public QObject, public pWidget { + Q_OBJECT + +public: + HexEdit &hexEdit; + struct QtHexEdit : public QTextEdit { + pHexEdit &self; + void keyPressEvent(QKeyEvent*); + void keyPressEventAcknowledge(QKeyEvent*); + QtHexEdit(pHexEdit &self) : self(self) {} + } *qtHexEdit; + QHBoxLayout *qtLayout; + QScrollBar *qtScroll; + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + pHexEdit(HexEdit &hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {} + void constructor(); + void destructor(); + void orphan(); + void keyPressEvent(QKeyEvent*); + +public slots: + void onScroll(); +}; + +struct pHorizontalScrollBar : public QObject, public pWidget { + Q_OBJECT + +public: + HorizontalScrollBar &horizontalScrollBar; + QScrollBar *qtScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalScrollBar(HorizontalScrollBar &horizontalScrollBar) : pWidget(horizontalScrollBar), horizontalScrollBar(horizontalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pHorizontalSlider : public QObject, public pWidget { + Q_OBJECT + +public: + HorizontalSlider &horizontalSlider; + QSlider *qtSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalSlider(HorizontalSlider &horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pLabel : public pWidget { + Label &label; + QLabel *qtLabel; + + Geometry minimumGeometry(); + void setText(const string &text); + + pLabel(Label &label) : pWidget(label), label(label) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLineEdit : public QObject, public pWidget { + Q_OBJECT + +public: + LineEdit &lineEdit; + QLineEdit *qtLineEdit; + + Geometry minimumGeometry(); + void setEditable(bool editable); + void setText(const string &text); + string text(); + + pLineEdit(LineEdit &lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onActivate(); + void onChange(); +}; + +struct pListView : public QObject, public pWidget { + Q_OBJECT + +public: + ListView &listView; + QTreeWidget *qtListView; + + void append(const lstring &text); + void autoSizeColumns(); + bool checked(unsigned row); + void modify(unsigned row, const lstring &text); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable); + void setChecked(unsigned row, bool checked); + void setHeaderText(const lstring &text); + void setHeaderVisible(bool visible); + void setImage(unsigned row, unsigned column, const nall::image &image); + void setSelected(bool selected); + void setSelection(unsigned row); + + pListView(ListView &listView) : pWidget(listView), listView(listView) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onActivate(); + void onChange(QTreeWidgetItem *item); + void onToggle(QTreeWidgetItem *item); +}; + +struct pProgressBar : public pWidget { + ProgressBar &progressBar; + QProgressBar *qtProgressBar; + + Geometry minimumGeometry(); + void setPosition(unsigned position); + + pProgressBar(ProgressBar &progressBar) : pWidget(progressBar), progressBar(progressBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pRadioBox : public QObject, public pWidget { + Q_OBJECT + +public: + RadioBox &radioBox; + QRadioButton *qtRadioBox; + QButtonGroup *qtGroup; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioBox(RadioBox &radioBox) : pWidget(radioBox), radioBox(radioBox) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onActivate(); +}; + +struct pTextEdit : public QObject, public pWidget { + Q_OBJECT + +public: + TextEdit &textEdit; + QTextEdit *qtTextEdit; + + void setCursorPosition(unsigned position); + void setEditable(bool editable); + void setText(const string &text); + void setWordWrap(bool wordWrap); + string text(); + + pTextEdit(TextEdit &textEdit) : pWidget(textEdit), textEdit(textEdit) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pVerticalScrollBar : public QObject, public pWidget { + Q_OBJECT + +public: + VerticalScrollBar &verticalScrollBar; + QScrollBar *qtScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalScrollBar(VerticalScrollBar &verticalScrollBar) : pWidget(verticalScrollBar), verticalScrollBar(verticalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pVerticalSlider : public QObject, public pWidget { + Q_OBJECT + +public: + VerticalSlider &verticalSlider; + QSlider *qtSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalSlider(VerticalSlider &verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pViewport : public pWidget { + Viewport &viewport; + struct QtViewport : public QWidget { + pViewport &self; + void leaveEvent(QEvent*); + void mouseMoveEvent(QMouseEvent*); + void mousePressEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + QtViewport(pViewport &self); + } *qtViewport; + + uintptr_t handle(); + + pViewport(Viewport &viewport) : pWidget(viewport), viewport(viewport) {} + void constructor(); + void destructor(); + void orphan(); +}; diff --git a/ananke/phoenix/qt/settings.cpp b/ananke/phoenix/qt/settings.cpp new file mode 100644 index 00000000..90d3a76e --- /dev/null +++ b/ananke/phoenix/qt/settings.cpp @@ -0,0 +1,24 @@ +static Settings *settings = nullptr; + +void Settings::load() { + string path = { userpath(), ".config/phoenix/qt.cfg" }; + configuration::load(path); +} + +void Settings::save() { + string path = { userpath(), ".config/" }; + mkdir(path, 0755); + path.append("phoenix/"); + mkdir(path, 0755); + path.append("qt.cfg"); + configuration::save(path); +} + +Settings::Settings() { + append(frameGeometryX = 4, "frameGeometryX"); + append(frameGeometryY = 24, "frameGeometryY"); + append(frameGeometryWidth = 8, "frameGeometryWidth"); + append(frameGeometryHeight = 28, "frameGeometryHeight"); + append(menuGeometryHeight = 20, "menuGeometryHeight"); + append(statusGeometryHeight = 20, "statusGeometryHeight"); +} diff --git a/ananke/phoenix/qt/timer.cpp b/ananke/phoenix/qt/timer.cpp new file mode 100644 index 00000000..61f00ba8 --- /dev/null +++ b/ananke/phoenix/qt/timer.cpp @@ -0,0 +1,25 @@ +void pTimer::setEnabled(bool enabled) { + if(enabled) { + qtTimer->start(); + } else { + qtTimer->stop(); + } +} + +void pTimer::setInterval(unsigned milliseconds) { + qtTimer->setInterval(milliseconds); +} + +void pTimer::constructor() { + qtTimer = new QTimer; + qtTimer->setInterval(0); + connect(qtTimer, SIGNAL(timeout()), SLOT(onTimeout())); +} + +void pTimer::destructor() { + delete qtTimer; +} + +void pTimer::onTimeout() { + if(timer.onTimeout) timer.onTimeout(); +} diff --git a/ananke/phoenix/qt/utility.cpp b/ananke/phoenix/qt/utility.cpp new file mode 100644 index 00000000..400df0a2 --- /dev/null +++ b/ananke/phoenix/qt/utility.cpp @@ -0,0 +1,190 @@ +static QIcon CreateIcon(const nall::image &image, bool scale = false) { + nall::image qtBuffer = image; + qtBuffer.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + if(scale) qtBuffer.scale(16, 16, Interpolation::Linear); + QImage qtImage(qtBuffer.data, qtBuffer.width, qtBuffer.height, QImage::Format_ARGB32); + return QIcon(QPixmap::fromImage(qtImage)); +} + +static Keyboard::Keycode Keysym(int keysym) { + switch(keysym) { + case XK_Escape: return Keyboard::Keycode::Escape; + case XK_F1: return Keyboard::Keycode::F1; + case XK_F2: return Keyboard::Keycode::F2; + case XK_F3: return Keyboard::Keycode::F3; + case XK_F4: return Keyboard::Keycode::F4; + case XK_F5: return Keyboard::Keycode::F5; + case XK_F6: return Keyboard::Keycode::F6; + case XK_F7: return Keyboard::Keycode::F7; + case XK_F8: return Keyboard::Keycode::F8; + case XK_F9: return Keyboard::Keycode::F9; + case XK_F10: return Keyboard::Keycode::F10; + case XK_F11: return Keyboard::Keycode::F11; + case XK_F12: return Keyboard::Keycode::F12; + + case XK_Print: return Keyboard::Keycode::PrintScreen; + //Keyboard::Keycode::SysRq + case XK_Scroll_Lock: return Keyboard::Keycode::ScrollLock; + case XK_Pause: return Keyboard::Keycode::Pause; + //Keyboard::Keycode::Break + + case XK_Insert: return Keyboard::Keycode::Insert; + case XK_Delete: return Keyboard::Keycode::Delete; + case XK_Home: return Keyboard::Keycode::Home; + case XK_End: return Keyboard::Keycode::End; + case XK_Prior: return Keyboard::Keycode::PageUp; + case XK_Next: return Keyboard::Keycode::PageDown; + + case XK_Up: return Keyboard::Keycode::Up; + case XK_Down: return Keyboard::Keycode::Down; + case XK_Left: return Keyboard::Keycode::Left; + case XK_Right: return Keyboard::Keycode::Right; + + case XK_grave: return Keyboard::Keycode::Grave; + case XK_1: return Keyboard::Keycode::Number1; + case XK_2: return Keyboard::Keycode::Number2; + case XK_3: return Keyboard::Keycode::Number3; + case XK_4: return Keyboard::Keycode::Number4; + case XK_5: return Keyboard::Keycode::Number5; + case XK_6: return Keyboard::Keycode::Number6; + case XK_7: return Keyboard::Keycode::Number7; + case XK_8: return Keyboard::Keycode::Number8; + case XK_9: return Keyboard::Keycode::Number9; + case XK_0: return Keyboard::Keycode::Number0; + case XK_minus: return Keyboard::Keycode::Minus; + case XK_equal: return Keyboard::Keycode::Equal; + case XK_BackSpace: return Keyboard::Keycode::Backspace; + + case XK_asciitilde: return Keyboard::Keycode::Tilde; + case XK_exclam: return Keyboard::Keycode::Exclamation; + case XK_at: return Keyboard::Keycode::At; + case XK_numbersign: return Keyboard::Keycode::Pound; + case XK_dollar: return Keyboard::Keycode::Dollar; + case XK_percent: return Keyboard::Keycode::Percent; + case XK_asciicircum: return Keyboard::Keycode::Power; + case XK_ampersand: return Keyboard::Keycode::Ampersand; + case XK_asterisk: return Keyboard::Keycode::Asterisk; + case XK_parenleft: return Keyboard::Keycode::ParenthesisLeft; + case XK_parenright: return Keyboard::Keycode::ParenthesisRight; + case XK_underscore: return Keyboard::Keycode::Underscore; + case XK_plus: return Keyboard::Keycode::Plus; + + case XK_bracketleft: return Keyboard::Keycode::BracketLeft; + case XK_bracketright: return Keyboard::Keycode::BracketRight; + case XK_backslash: return Keyboard::Keycode::Backslash; + case XK_semicolon: return Keyboard::Keycode::Semicolon; + case XK_apostrophe: return Keyboard::Keycode::Apostrophe; + case XK_comma: return Keyboard::Keycode::Comma; + case XK_period: return Keyboard::Keycode::Period; + case XK_slash: return Keyboard::Keycode::Slash; + + case XK_braceleft: return Keyboard::Keycode::BraceLeft; + case XK_braceright: return Keyboard::Keycode::BraceRight; + case XK_bar: return Keyboard::Keycode::Pipe; + case XK_colon: return Keyboard::Keycode::Colon; + case XK_quotedbl: return Keyboard::Keycode::Quote; + case XK_less: return Keyboard::Keycode::CaretLeft; + case XK_greater: return Keyboard::Keycode::CaretRight; + case XK_question: return Keyboard::Keycode::Question; + + case XK_Tab: return Keyboard::Keycode::Tab; + case XK_Caps_Lock: return Keyboard::Keycode::CapsLock; + case XK_Return: return Keyboard::Keycode::Return; + case XK_Shift_L: return Keyboard::Keycode::ShiftLeft; + case XK_Shift_R: return Keyboard::Keycode::ShiftRight; + case XK_Control_L: return Keyboard::Keycode::ControlLeft; + case XK_Control_R: return Keyboard::Keycode::ControlRight; + case XK_Super_L: return Keyboard::Keycode::SuperLeft; + case XK_Super_R: return Keyboard::Keycode::SuperRight; + case XK_Alt_L: return Keyboard::Keycode::AltLeft; + case XK_Alt_R: return Keyboard::Keycode::AltRight; + case XK_space: return Keyboard::Keycode::Space; + case XK_Menu: return Keyboard::Keycode::Menu; + + case XK_A: return Keyboard::Keycode::A; + case XK_B: return Keyboard::Keycode::B; + case XK_C: return Keyboard::Keycode::C; + case XK_D: return Keyboard::Keycode::D; + case XK_E: return Keyboard::Keycode::E; + case XK_F: return Keyboard::Keycode::F; + case XK_G: return Keyboard::Keycode::G; + case XK_H: return Keyboard::Keycode::H; + case XK_I: return Keyboard::Keycode::I; + case XK_J: return Keyboard::Keycode::J; + case XK_K: return Keyboard::Keycode::K; + case XK_L: return Keyboard::Keycode::L; + case XK_M: return Keyboard::Keycode::M; + case XK_N: return Keyboard::Keycode::N; + case XK_O: return Keyboard::Keycode::O; + case XK_P: return Keyboard::Keycode::P; + case XK_Q: return Keyboard::Keycode::Q; + case XK_R: return Keyboard::Keycode::R; + case XK_S: return Keyboard::Keycode::S; + case XK_T: return Keyboard::Keycode::T; + case XK_U: return Keyboard::Keycode::U; + case XK_V: return Keyboard::Keycode::V; + case XK_W: return Keyboard::Keycode::W; + case XK_X: return Keyboard::Keycode::X; + case XK_Y: return Keyboard::Keycode::Y; + case XK_Z: return Keyboard::Keycode::Z; + + case XK_a: return Keyboard::Keycode::a; + case XK_b: return Keyboard::Keycode::b; + case XK_c: return Keyboard::Keycode::c; + case XK_d: return Keyboard::Keycode::d; + case XK_e: return Keyboard::Keycode::e; + case XK_f: return Keyboard::Keycode::f; + case XK_g: return Keyboard::Keycode::g; + case XK_h: return Keyboard::Keycode::h; + case XK_i: return Keyboard::Keycode::i; + case XK_j: return Keyboard::Keycode::j; + case XK_k: return Keyboard::Keycode::k; + case XK_l: return Keyboard::Keycode::l; + case XK_m: return Keyboard::Keycode::m; + case XK_n: return Keyboard::Keycode::n; + case XK_o: return Keyboard::Keycode::o; + case XK_p: return Keyboard::Keycode::p; + case XK_q: return Keyboard::Keycode::q; + case XK_r: return Keyboard::Keycode::r; + case XK_s: return Keyboard::Keycode::s; + case XK_t: return Keyboard::Keycode::t; + case XK_u: return Keyboard::Keycode::u; + case XK_v: return Keyboard::Keycode::v; + case XK_w: return Keyboard::Keycode::w; + case XK_x: return Keyboard::Keycode::x; + case XK_y: return Keyboard::Keycode::y; + case XK_z: return Keyboard::Keycode::z; + + case XK_Num_Lock: return Keyboard::Keycode::NumLock; + case XK_KP_Divide: return Keyboard::Keycode::Divide; + case XK_KP_Multiply: return Keyboard::Keycode::Multiply; + case XK_KP_Subtract: return Keyboard::Keycode::Subtract; + case XK_KP_Add: return Keyboard::Keycode::Add; + case XK_KP_Enter: return Keyboard::Keycode::Enter; + case XK_KP_Decimal: return Keyboard::Keycode::Point; + + case XK_KP_1: return Keyboard::Keycode::Keypad1; + case XK_KP_2: return Keyboard::Keycode::Keypad2; + case XK_KP_3: return Keyboard::Keycode::Keypad3; + case XK_KP_4: return Keyboard::Keycode::Keypad4; + case XK_KP_5: return Keyboard::Keycode::Keypad5; + case XK_KP_6: return Keyboard::Keycode::Keypad6; + case XK_KP_7: return Keyboard::Keycode::Keypad7; + case XK_KP_8: return Keyboard::Keycode::Keypad8; + case XK_KP_9: return Keyboard::Keycode::Keypad9; + case XK_KP_0: return Keyboard::Keycode::Keypad0; + + case XK_KP_Home: return Keyboard::Keycode::KeypadHome; + case XK_KP_End: return Keyboard::Keycode::KeypadEnd; + case XK_KP_Page_Up: return Keyboard::Keycode::KeypadPageUp; + case XK_KP_Page_Down: return Keyboard::Keycode::KeypadPageDown; + case XK_KP_Up: return Keyboard::Keycode::KeypadUp; + case XK_KP_Down: return Keyboard::Keycode::KeypadDown; + case XK_KP_Left: return Keyboard::Keycode::KeypadLeft; + case XK_KP_Right: return Keyboard::Keycode::KeypadRight; + case XK_KP_Begin: return Keyboard::Keycode::KeypadCenter; + case XK_KP_Insert: return Keyboard::Keycode::KeypadInsert; + case XK_KP_Delete: return Keyboard::Keycode::KeypadDelete; + } + return Keyboard::Keycode::None; +} diff --git a/ananke/phoenix/qt/widget/button.cpp b/ananke/phoenix/qt/widget/button.cpp new file mode 100644 index 00000000..0e1522b3 --- /dev/null +++ b/ananke/phoenix/qt/widget/button.cpp @@ -0,0 +1,52 @@ +Geometry pButton::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), button.state.text); + + if(button.state.orientation == Orientation::Horizontal) { + geometry.width += button.state.image.width; + geometry.height = max(button.state.image.height, geometry.height); + } + + if(button.state.orientation == Orientation::Vertical) { + geometry.width = max(button.state.image.width, geometry.width); + geometry.height += button.state.image.height; + } + + return { 0, 0, geometry.width + 20, geometry.height + 12 }; +} + +void pButton::setImage(const image &image, Orientation orientation) { + qtButton->setIconSize(QSize(image.width, image.height)); + qtButton->setIcon(CreateIcon(image)); + qtButton->setStyleSheet("text-align: top;"); + switch(orientation) { + case Orientation::Horizontal: qtButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); break; + case Orientation::Vertical: qtButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); break; + } +} + +void pButton::setText(const string &text) { + qtButton->setText(QString::fromUtf8(text)); +} + +void pButton::constructor() { + qtWidget = qtButton = new QToolButton; + qtButton->setToolButtonStyle(Qt::ToolButtonTextOnly); + connect(qtButton, SIGNAL(released()), SLOT(onActivate())); + + pWidget::synchronizeState(); + setText(button.state.text); +} + +void pButton::destructor() { + delete qtButton; + qtWidget = qtButton = 0; +} + +void pButton::orphan() { + destructor(); + constructor(); +} + +void pButton::onActivate() { + if(button.onActivate) button.onActivate(); +} diff --git a/ananke/phoenix/qt/widget/canvas.cpp b/ananke/phoenix/qt/widget/canvas.cpp new file mode 100644 index 00000000..245c1e9f --- /dev/null +++ b/ananke/phoenix/qt/widget/canvas.cpp @@ -0,0 +1,73 @@ +void pCanvas::setSize(const Size &size) { + delete qtImage; + qtImage = new QImage(size.width, size.height, QImage::Format_ARGB32); +} + +void pCanvas::update() { + uint32_t *dp = (uint32_t*)qtImage->bits(), *sp = (uint32_t*)canvas.state.data; + for(unsigned n = 0; n < canvas.state.width * canvas.state.height; n++) *dp++ = 0xff000000 | *sp++; + qtCanvas->update(); +} + +void pCanvas::constructor() { + qtWidget = qtCanvas = new QtCanvas(*this); + qtCanvas->setMouseTracking(true); + qtImage = new QImage(canvas.state.width, canvas.state.height, QImage::Format_ARGB32); + memcpy(qtImage->bits(), canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + + pWidget::synchronizeState(); + update(); +} + +void pCanvas::destructor() { + delete qtCanvas; + delete qtImage; + qtWidget = qtCanvas = 0; + qtImage = 0; +} + +void pCanvas::orphan() { + destructor(); + constructor(); +} + +void pCanvas::QtCanvas::leaveEvent(QEvent *event) { + if(self.canvas.onMouseLeave) self.canvas.onMouseLeave(); +} + +void pCanvas::QtCanvas::mouseMoveEvent(QMouseEvent *event) { + if(self.canvas.onMouseMove) self.canvas.onMouseMove({ event->pos().x(), event->pos().y() }); +} + +void pCanvas::QtCanvas::mousePressEvent(QMouseEvent *event) { + if(self.canvas.onMousePress == false) return; + switch(event->button()) { + case Qt::LeftButton: self.canvas.onMousePress(Mouse::Button::Left); break; + case Qt::MidButton: self.canvas.onMousePress(Mouse::Button::Middle); break; + case Qt::RightButton: self.canvas.onMousePress(Mouse::Button::Right); break; + } +} + +void pCanvas::QtCanvas::mouseReleaseEvent(QMouseEvent *event) { + if(self.canvas.onMouseRelease == false) return; + switch(event->button()) { + case Qt::LeftButton: self.canvas.onMouseRelease(Mouse::Button::Left); break; + case Qt::MidButton: self.canvas.onMouseRelease(Mouse::Button::Middle); break; + case Qt::RightButton: self.canvas.onMouseRelease(Mouse::Button::Right); break; + } +} + +void pCanvas::QtCanvas::paintEvent(QPaintEvent *event) { + QPainter painter(self.qtCanvas); + painter.drawImage(0, 0, *self.qtImage); + +//this will scale the source image to fit the target widget size (nearest-neighbor): +//painter.drawImage( +// QRect(0, 0, geometry().width(), geometry().height()), +// *self.qtImage, +// QRect(0, 0, self.canvas.state.width, self.canvas.state.height) +//); +} + +pCanvas::QtCanvas::QtCanvas(pCanvas &self) : self(self) { +} diff --git a/ananke/phoenix/qt/widget/check-box.cpp b/ananke/phoenix/qt/widget/check-box.cpp new file mode 100644 index 00000000..c45bb326 --- /dev/null +++ b/ananke/phoenix/qt/widget/check-box.cpp @@ -0,0 +1,42 @@ +bool pCheckBox::checked() { + return qtCheckBox->isChecked(); +} + +Geometry pCheckBox::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), checkBox.state.text); + return { 0, 0, geometry.width + 26, geometry.height + 6 }; +} + +void pCheckBox::setChecked(bool checked) { + locked = true; + qtCheckBox->setChecked(checked); + locked = false; +} + +void pCheckBox::setText(const string &text) { + qtCheckBox->setText(QString::fromUtf8(text)); +} + +void pCheckBox::constructor() { + qtWidget = qtCheckBox = new QCheckBox; + connect(qtCheckBox, SIGNAL(stateChanged(int)), SLOT(onToggle())); + + pWidget::synchronizeState(); + setChecked(checkBox.state.checked); + setText(checkBox.state.text); +} + +void pCheckBox::destructor() { + delete qtCheckBox; + qtWidget = qtCheckBox = 0; +} + +void pCheckBox::orphan() { + destructor(); + constructor(); +} + +void pCheckBox::onToggle() { + checkBox.state.checked = checked(); + if(locked == false && checkBox.onToggle) checkBox.onToggle(); +} diff --git a/ananke/phoenix/qt/widget/combo-box.cpp b/ananke/phoenix/qt/widget/combo-box.cpp new file mode 100644 index 00000000..1dfa609d --- /dev/null +++ b/ananke/phoenix/qt/widget/combo-box.cpp @@ -0,0 +1,68 @@ +void pComboBox::append(const string &text) { + locked = true; + qtComboBox->addItem(QString::fromUtf8(text)); + locked = false; +} + +Geometry pComboBox::minimumGeometry() { + unsigned maximumWidth = 0; + for(auto &text : comboBox.state.text) maximumWidth = max(maximumWidth, pFont::geometry(qtWidget->font(), text).width); + Geometry geometry = pFont::geometry(qtWidget->font(), " "); + return { 0, 0, maximumWidth + 32, geometry.height + 12 }; +} + +void pComboBox::modify(unsigned row, const string &text) { + qtComboBox->setItemText(row, text); +} + +void pComboBox::remove(unsigned row) { + locked = true; + unsigned position = selection(); + qtComboBox->removeItem(row); + if(position == row) qtComboBox->setCurrentIndex(0); + locked = false; +} + +void pComboBox::reset() { + locked = true; + while(qtComboBox->count()) qtComboBox->removeItem(0); + locked = false; +} + +unsigned pComboBox::selection() { + signed index = qtComboBox->currentIndex(); + return index >= 0 ? index : 0; +} + +void pComboBox::setSelection(unsigned row) { + locked = true; + qtComboBox->setCurrentIndex(row); + locked = false; +} + +void pComboBox::constructor() { + qtWidget = qtComboBox = new QComboBox; + connect(qtComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + unsigned selection = comboBox.state.selection; + locked = true; + for(auto &text : comboBox.state.text) append(text); + locked = false; + setSelection(selection); +} + +void pComboBox::destructor() { + delete qtComboBox; + qtWidget = qtComboBox = 0; +} + +void pComboBox::orphan() { + destructor(); + constructor(); +} + +void pComboBox::onChange() { + comboBox.state.selection = selection(); + if(locked == false && comboBox.onChange) comboBox.onChange(); +} diff --git a/ananke/phoenix/qt/widget/hex-edit.cpp b/ananke/phoenix/qt/widget/hex-edit.cpp new file mode 100644 index 00000000..fdeebe4a --- /dev/null +++ b/ananke/phoenix/qt/widget/hex-edit.cpp @@ -0,0 +1,191 @@ +void pHexEdit::setColumns(unsigned columns) { + update(); +} + +void pHexEdit::setLength(unsigned length) { + //add one if last row is not equal to column length (eg only part of the row is present) + bool indivisible = hexEdit.state.columns == 0 || (hexEdit.state.length % hexEdit.state.columns) != 0; + qtScroll->setRange(0, hexEdit.state.length / hexEdit.state.columns + indivisible - hexEdit.state.rows); + update(); +} + +void pHexEdit::setOffset(unsigned offset) { + locked = true; + qtScroll->setSliderPosition(hexEdit.state.offset / hexEdit.state.columns); + locked = false; + update(); +} + +void pHexEdit::setRows(unsigned rows) { + qtScroll->setPageStep(hexEdit.state.rows); + update(); +} + +void pHexEdit::update() { + if(!hexEdit.onRead) { + qtHexEdit->setPlainText(""); + return; + } + + unsigned cursorPosition = qtHexEdit->textCursor().position(); + + string output; + unsigned offset = hexEdit.state.offset; + for(unsigned row = 0; row < hexEdit.state.rows; row++) { + output.append(hex<8>(offset)); + output.append(" "); + + string hexdata; + string ansidata = " "; + + for(unsigned column = 0; column < hexEdit.state.columns; column++) { + if(offset < hexEdit.state.length) { + uint8_t data = hexEdit.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 >= hexEdit.state.length) break; + if(row != hexEdit.state.rows - 1) output.append("\n"); + } + + qtHexEdit->setPlainText(QString::fromUtf8(output)); + QTextCursor cursor = qtHexEdit->textCursor(); + cursor.setPosition(cursorPosition); + qtHexEdit->setTextCursor(cursor); +} + +void pHexEdit::constructor() { + qtWidget = qtHexEdit = new QtHexEdit(*this); + + qtHexEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + qtHexEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + qtLayout = new QHBoxLayout; + qtLayout->setAlignment(Qt::AlignRight); + qtLayout->setMargin(0); + qtLayout->setSpacing(0); + qtHexEdit->setLayout(qtLayout); + + qtScroll = new QScrollBar(Qt::Vertical); + qtScroll->setSingleStep(1); + qtLayout->addWidget(qtScroll); + + connect(qtScroll, SIGNAL(actionTriggered(int)), SLOT(onScroll())); + + pWidget::synchronizeState(); + setColumns(hexEdit.state.columns); + setRows(hexEdit.state.rows); + setLength(hexEdit.state.length); + setOffset(hexEdit.state.offset); + update(); +} + +void pHexEdit::destructor() { + delete qtScroll; + delete qtLayout; + delete qtHexEdit; + qtWidget = qtHexEdit = 0; + qtLayout = 0; + qtScroll = 0; +} + +void pHexEdit::orphan() { + destructor(); + constructor(); +} + +void pHexEdit::keyPressEvent(QKeyEvent *event) { + if(!hexEdit.onRead) return; + + QTextCursor cursor = qtHexEdit->textCursor(); + unsigned lineWidth = 10 + (hexEdit.state.columns * 3) + 1 + hexEdit.state.columns + 1; + unsigned cursorY = cursor.position() / lineWidth; + unsigned cursorX = cursor.position() % lineWidth; + + unsigned nibble; + switch(event->key()) { + case Qt::Key_0: nibble = 0; break; + case Qt::Key_1: nibble = 1; break; + case Qt::Key_2: nibble = 2; break; + case Qt::Key_3: nibble = 3; break; + case Qt::Key_4: nibble = 4; break; + case Qt::Key_5: nibble = 5; break; + case Qt::Key_6: nibble = 6; break; + case Qt::Key_7: nibble = 7; break; + case Qt::Key_8: nibble = 8; break; + case Qt::Key_9: nibble = 9; break; + case Qt::Key_A: nibble = 10; break; + case Qt::Key_B: nibble = 11; break; + case Qt::Key_C: nibble = 12; break; + case Qt::Key_D: nibble = 13; break; + case Qt::Key_E: nibble = 14; break; + case Qt::Key_F: nibble = 15; break; + default: { + //allow navigation keys to move cursor, but block text input + qtHexEdit->setTextInteractionFlags(Qt::TextInteractionFlags( + Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse + )); + qtHexEdit->keyPressEventAcknowledge(event); + qtHexEdit->setTextInteractionFlags(Qt::TextEditorInteraction); + return; + } + } + + 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 < hexEdit.state.columns) { + //not in ANSI region + unsigned offset = hexEdit.state.offset + (cursorY * hexEdit.state.columns + cursorX); + + if(offset >= hexEdit.state.length) return; //do not edit past end of file + uint8_t data = hexEdit.onRead(offset); + + //write modified value + if(cursorNibble == 1) { + data = (data & 0xf0) | (nibble << 0); + } else { + data = (data & 0x0f) | (nibble << 4); + } + if(hexEdit.onWrite) hexEdit.onWrite(offset, data); + + //auto-advance cursor to next nibble/byte + unsigned step = 1; + if(cursorNibble && cursorX != hexEdit.state.columns - 1) step = 2; + cursor.setPosition(cursor.position() + step); + qtHexEdit->setTextCursor(cursor); + + //refresh output to reflect modified data + update(); + } + } + } +} + +void pHexEdit::onScroll() { + if(locked) return; + unsigned offset = qtScroll->sliderPosition(); + hexEdit.state.offset = offset * hexEdit.state.columns; + update(); +} + +void pHexEdit::QtHexEdit::keyPressEvent(QKeyEvent *event) { + self.keyPressEvent(event); +} + +void pHexEdit::QtHexEdit::keyPressEventAcknowledge(QKeyEvent *event) { + QTextEdit::keyPressEvent(event); +} diff --git a/ananke/phoenix/qt/widget/horizontal-scroll-bar.cpp b/ananke/phoenix/qt/widget/horizontal-scroll-bar.cpp new file mode 100644 index 00000000..6127c301 --- /dev/null +++ b/ananke/phoenix/qt/widget/horizontal-scroll-bar.cpp @@ -0,0 +1,43 @@ +Geometry pHorizontalScrollBar::minimumGeometry() { + return { 0, 0, 0, 15 }; +} + +unsigned pHorizontalScrollBar::position() { + return qtScrollBar->value(); +} + +void pHorizontalScrollBar::setLength(unsigned length) { + length += length == 0; + qtScrollBar->setRange(0, length - 1); + qtScrollBar->setPageStep(length >> 3); +} + +void pHorizontalScrollBar::setPosition(unsigned position) { + qtScrollBar->setValue(position); +} + +void pHorizontalScrollBar::constructor() { + qtWidget = qtScrollBar = new QScrollBar(Qt::Horizontal); + qtScrollBar->setRange(0, 100); + qtScrollBar->setPageStep(101 >> 3); + connect(qtScrollBar, SIGNAL(valueChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + setLength(horizontalScrollBar.state.length); + setPosition(horizontalScrollBar.state.position); +} + +void pHorizontalScrollBar::destructor() { + delete qtScrollBar; + qtWidget = qtScrollBar = 0; +} + +void pHorizontalScrollBar::orphan() { + destructor(); + constructor(); +} + +void pHorizontalScrollBar::onChange() { + horizontalScrollBar.state.position = position(); + if(horizontalScrollBar.onChange) horizontalScrollBar.onChange(); +} diff --git a/ananke/phoenix/qt/widget/horizontal-slider.cpp b/ananke/phoenix/qt/widget/horizontal-slider.cpp new file mode 100644 index 00000000..5401aae1 --- /dev/null +++ b/ananke/phoenix/qt/widget/horizontal-slider.cpp @@ -0,0 +1,43 @@ +Geometry pHorizontalSlider::minimumGeometry() { + return { 0, 0, 0, 20 }; +} + +unsigned pHorizontalSlider::position() { + return qtSlider->value(); +} + +void pHorizontalSlider::setLength(unsigned length) { + length += length == 0; + qtSlider->setRange(0, length - 1); + qtSlider->setPageStep(length >> 3); +} + +void pHorizontalSlider::setPosition(unsigned position) { + qtSlider->setValue(position); +} + +void pHorizontalSlider::constructor() { + qtWidget = qtSlider = new QSlider(Qt::Horizontal); + qtSlider->setRange(0, 100); + qtSlider->setPageStep(101 >> 3); + connect(qtSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + setLength(horizontalSlider.state.length); + setPosition(horizontalSlider.state.position); +} + +void pHorizontalSlider::destructor() { + delete qtSlider; + qtWidget = qtSlider = 0; +} + +void pHorizontalSlider::orphan() { + destructor(); + constructor(); +} + +void pHorizontalSlider::onChange() { + horizontalSlider.state.position = position(); + if(horizontalSlider.onChange) horizontalSlider.onChange(); +} diff --git a/ananke/phoenix/qt/widget/label.cpp b/ananke/phoenix/qt/widget/label.cpp new file mode 100644 index 00000000..8dd86eb6 --- /dev/null +++ b/ananke/phoenix/qt/widget/label.cpp @@ -0,0 +1,25 @@ +Geometry pLabel::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), label.state.text); + return { 0, 0, geometry.width, geometry.height }; +} + +void pLabel::setText(const string &text) { + qtLabel->setText(QString::fromUtf8(text)); +} + +void pLabel::constructor() { + qtWidget = qtLabel = new QLabel; + + pWidget::synchronizeState(); + setText(label.state.text); +} + +void pLabel::destructor() { + delete qtLabel; + qtWidget = qtLabel = 0; +} + +void pLabel::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/qt/widget/line-edit.cpp b/ananke/phoenix/qt/widget/line-edit.cpp new file mode 100644 index 00000000..a13f3b81 --- /dev/null +++ b/ananke/phoenix/qt/widget/line-edit.cpp @@ -0,0 +1,45 @@ +Geometry pLineEdit::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), lineEdit.state.text); + return { 0, 0, geometry.width + 12, geometry.height + 12 }; +} + +void pLineEdit::setEditable(bool editable) { + qtLineEdit->setReadOnly(!editable); +} + +void pLineEdit::setText(const string &text) { + qtLineEdit->setText(QString::fromUtf8(text)); +} + +string pLineEdit::text() { + return qtLineEdit->text().toUtf8().constData(); +} + +void pLineEdit::constructor() { + qtWidget = qtLineEdit = new QLineEdit; + connect(qtLineEdit, SIGNAL(returnPressed()), SLOT(onActivate())); + connect(qtLineEdit, SIGNAL(textEdited(const QString&)), SLOT(onChange())); + + pWidget::synchronizeState(); + setEditable(lineEdit.state.editable); + setText(lineEdit.state.text); +} + +void pLineEdit::destructor() { + delete qtLineEdit; + qtWidget = qtLineEdit = 0; +} + +void pLineEdit::orphan() { + destructor(); + constructor(); +} + +void pLineEdit::onActivate() { + if(lineEdit.onActivate) lineEdit.onActivate(); +} + +void pLineEdit::onChange() { + lineEdit.state.text = text(); + if(lineEdit.onChange) lineEdit.onChange(); +} diff --git a/ananke/phoenix/qt/widget/list-view.cpp b/ananke/phoenix/qt/widget/list-view.cpp new file mode 100644 index 00000000..a81c092d --- /dev/null +++ b/ananke/phoenix/qt/widget/list-view.cpp @@ -0,0 +1,164 @@ +void pListView::append(const lstring &text) { + locked = true; + auto items = qtListView->findItems("", Qt::MatchContains); + QTreeWidgetItem *item = new QTreeWidgetItem(qtListView); + + item->setData(0, Qt::UserRole, (unsigned)items.size()); + if(listView.state.checkable) item->setCheckState(0, Qt::Unchecked); + for(unsigned n = 0; n < text.size(); n++) { + item->setText(n, QString::fromUtf8(text[n])); + } + locked = false; +} + +void pListView::autoSizeColumns() { + for(unsigned n = 0; n < listView.state.headerText.size(); n++) qtListView->resizeColumnToContents(n); +} + +bool pListView::checked(unsigned row) { + QTreeWidgetItem *item = qtListView->topLevelItem(row); + return item ? item->checkState(0) == Qt::Checked : false; +} + +void pListView::modify(unsigned row, const lstring &text) { + locked = true; + QTreeWidgetItem *item = qtListView->topLevelItem(row); + if(item == nullptr) return; + for(unsigned n = 0; n < text.size(); n++) { + item->setText(n, QString::fromUtf8(text[n])); + } + locked = false; +} + +void pListView::remove(unsigned row) { + locked = true; + QTreeWidgetItem *item = qtListView->topLevelItem(row); + if(item == nullptr) return; + delete item; + locked = false; +} + +void pListView::reset() { + qtListView->clear(); +} + +bool pListView::selected() { + QTreeWidgetItem *item = qtListView->currentItem(); + return (item && item->isSelected() == true); +} + +unsigned pListView::selection() { + QTreeWidgetItem *item = qtListView->currentItem(); + if(item == 0) return 0; + return item->data(0, Qt::UserRole).toUInt(); +} + +void pListView::setCheckable(bool checkable) { + if(checkable) { + auto items = qtListView->findItems("", Qt::MatchContains); + for(unsigned n = 0; n < items.size(); n++) items[n]->setCheckState(0, Qt::Unchecked); + } +} + +void pListView::setChecked(unsigned row, bool checked) { + locked = true; + QTreeWidgetItem *item = qtListView->topLevelItem(row); + if(item) item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); + locked = false; +} + +void pListView::setHeaderText(const lstring &text) { + QStringList labels; + for(auto &column : text) labels << QString::fromUtf8(column); + + qtListView->setColumnCount(text.size()); + qtListView->setAlternatingRowColors(text.size() >= 2); + qtListView->setHeaderLabels(labels); + autoSizeColumns(); +} + +void pListView::setHeaderVisible(bool visible) { + qtListView->setHeaderHidden(!visible); + autoSizeColumns(); +} + +void pListView::setImage(unsigned row, unsigned column, const nall::image &image) { + QTreeWidgetItem *item = qtListView->topLevelItem(row); + if(item) { + if(image.empty() == 0) item->setIcon(column, CreateIcon(image)); + if(image.empty() == 1) item->setIcon(column, QIcon()); + } +} + +void pListView::setSelected(bool selected) { + QTreeWidgetItem *item = qtListView->currentItem(); + if(item) item->setSelected(selected); +} + +void pListView::setSelection(unsigned row) { + locked = true; + QTreeWidgetItem *item = qtListView->currentItem(); + if(item) item->setSelected(false); + qtListView->setCurrentItem(0); + auto items = qtListView->findItems("", Qt::MatchContains); + for(unsigned n = 0; n < items.size(); n++) { + if(items[n]->data(0, Qt::UserRole).toUInt() == row) { + qtListView->setCurrentItem(items[n]); + break; + } + } + locked = false; +} + +void pListView::constructor() { + qtWidget = qtListView = new QTreeWidget; + qtListView->setAllColumnsShowFocus(true); + qtListView->setRootIsDecorated(false); + + connect(qtListView, SIGNAL(itemActivated(QTreeWidgetItem*, int)), SLOT(onActivate())); + connect(qtListView, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), SLOT(onChange(QTreeWidgetItem*))); + connect(qtListView, SIGNAL(itemChanged(QTreeWidgetItem*, int)), SLOT(onToggle(QTreeWidgetItem*))); + + pWidget::synchronizeState(); + setCheckable(listView.state.checkable); + setHeaderText(listView.state.headerText.size() ? listView.state.headerText : lstring{ " " }); + setHeaderVisible(listView.state.headerVisible); + for(auto &row : listView.state.text) append(row); + if(listView.state.checkable) { + for(unsigned n = 0; n < listView.state.checked.size(); n++) { + setChecked(n, listView.state.checked[n]); + } + } + setSelected(listView.state.selected); + if(listView.state.selected) setSelection(listView.state.selection); + autoSizeColumns(); +} + +void pListView::destructor() { + delete qtListView; + qtWidget = qtListView = 0; +} + +void pListView::orphan() { + destructor(); + constructor(); +} + +void pListView::onActivate() { + if(locked == false && listView.onActivate) listView.onActivate(); +} + +void pListView::onChange(QTreeWidgetItem *item) { + //Qt bug workaround: clicking items with mouse does not mark items as selected + if(item) item->setSelected(true); + listView.state.selected = selected(); + if(listView.state.selected) listView.state.selection = selection(); + if(locked == false && listView.onChange) listView.onChange(); +} + +void pListView::onToggle(QTreeWidgetItem *item) { + unsigned row = item->data(0, Qt::UserRole).toUInt(); + bool checkState = checked(row); + listView.state.checked[row] = checkState; + if(locked == false && listView.onToggle) listView.onToggle(row); +} diff --git a/ananke/phoenix/qt/widget/progress-bar.cpp b/ananke/phoenix/qt/widget/progress-bar.cpp new file mode 100644 index 00000000..8178bb66 --- /dev/null +++ b/ananke/phoenix/qt/widget/progress-bar.cpp @@ -0,0 +1,26 @@ +Geometry pProgressBar::minimumGeometry() { + return { 0, 0, 0, 25 }; +} + +void pProgressBar::setPosition(unsigned position) { + qtProgressBar->setValue(position); +} + +void pProgressBar::constructor() { + qtWidget = qtProgressBar = new QProgressBar; + qtProgressBar->setRange(0, 100); + qtProgressBar->setTextVisible(false); + + pWidget::synchronizeState(); + setPosition(progressBar.state.position); +} + +void pProgressBar::destructor() { + delete qtProgressBar; + qtWidget = qtProgressBar = 0; +} + +void pProgressBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/qt/widget/radio-box.cpp b/ananke/phoenix/qt/widget/radio-box.cpp new file mode 100644 index 00000000..bf640fd2 --- /dev/null +++ b/ananke/phoenix/qt/widget/radio-box.cpp @@ -0,0 +1,64 @@ +bool pRadioBox::checked() { + return qtRadioBox->isChecked(); +} + +Geometry pRadioBox::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), radioBox.state.text); + return { 0, 0, geometry.width + 26, geometry.height + 6 }; +} + +void pRadioBox::setChecked() { + locked = true; + for(auto &item : radioBox.state.group) { + bool checkState = item.p.qtRadioBox == qtRadioBox; + item.state.checked = checkState; + item.p.qtRadioBox->setChecked(checkState); + } + locked = false; +} + +void pRadioBox::setGroup(const set &group) { + locked = true; + if(qtGroup) { + delete qtGroup; + qtGroup = 0; + } + if(group.size() > 0 && qtRadioBox == group[0].p.qtRadioBox) { + qtGroup = new QButtonGroup; + for(auto &item : group) qtGroup->addButton(item.p.qtRadioBox); + setChecked(); + } + locked = false; +} + +void pRadioBox::setText(const string &text) { + qtRadioBox->setText(QString::fromUtf8(text)); +} + +void pRadioBox::constructor() { + qtWidget = qtRadioBox = new QRadioButton; + qtGroup = new QButtonGroup; + qtGroup->addButton(qtRadioBox); + qtRadioBox->setChecked(true); + connect(qtRadioBox, SIGNAL(toggled(bool)), SLOT(onActivate())); + + pWidget::synchronizeState(); + setGroup(radioBox.state.group); + setText(radioBox.state.text); +} + +void pRadioBox::destructor() { + delete qtGroup; + delete qtRadioBox; + qtWidget = qtRadioBox = 0; + qtGroup = 0; +} + +void pRadioBox::orphan() { + destructor(); + constructor(); +} + +void pRadioBox::onActivate() { + if(locked == false && checked() && radioBox.onActivate) radioBox.onActivate(); +} diff --git a/ananke/phoenix/qt/widget/text-edit.cpp b/ananke/phoenix/qt/widget/text-edit.cpp new file mode 100644 index 00000000..8cdbe573 --- /dev/null +++ b/ananke/phoenix/qt/widget/text-edit.cpp @@ -0,0 +1,50 @@ +void pTextEdit::setCursorPosition(unsigned position) { + QTextCursor cursor = qtTextEdit->textCursor(); + unsigned lastCharacter = strlen(qtTextEdit->toPlainText().toUtf8().constData()); + cursor.setPosition(min(position, lastCharacter)); + qtTextEdit->setTextCursor(cursor); +} + +void pTextEdit::setEditable(bool editable) { + qtTextEdit->setReadOnly(!editable); +} + +void pTextEdit::setText(const string &text) { + qtTextEdit->setPlainText(QString::fromUtf8(text)); +} + +void pTextEdit::setWordWrap(bool wordWrap) { + qtTextEdit->setWordWrapMode(wordWrap ? QTextOption::WordWrap : QTextOption::NoWrap); + qtTextEdit->setHorizontalScrollBarPolicy(wordWrap ? Qt::ScrollBarAlwaysOff : Qt::ScrollBarAlwaysOn); + qtTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); +} + +string pTextEdit::text() { + return qtTextEdit->toPlainText().toUtf8().constData(); +} + +void pTextEdit::constructor() { + qtWidget = qtTextEdit = new QTextEdit; + connect(qtTextEdit, SIGNAL(textChanged()), SLOT(onChange())); + + pWidget::synchronizeState(); + setEditable(textEdit.state.editable); + setText(textEdit.state.text); + setWordWrap(textEdit.state.wordWrap); +} + +void pTextEdit::destructor() { + if(sizable.state.layout) sizable.state.layout->remove(textEdit); + delete qtTextEdit; + qtWidget = qtTextEdit = 0; +} + +void pTextEdit::orphan() { + destructor(); + constructor(); +} + +void pTextEdit::onChange() { + textEdit.state.text = text(); + if(textEdit.onChange) textEdit.onChange(); +} diff --git a/ananke/phoenix/qt/widget/vertical-scroll-bar.cpp b/ananke/phoenix/qt/widget/vertical-scroll-bar.cpp new file mode 100644 index 00000000..74d68ca6 --- /dev/null +++ b/ananke/phoenix/qt/widget/vertical-scroll-bar.cpp @@ -0,0 +1,43 @@ +Geometry pVerticalScrollBar::minimumGeometry() { + return { 0, 0, 15, 0 }; +} + +unsigned pVerticalScrollBar::position() { + return qtScrollBar->value(); +} + +void pVerticalScrollBar::setLength(unsigned length) { + length += length == 0; + qtScrollBar->setRange(0, length - 1); + qtScrollBar->setPageStep(length >> 3); +} + +void pVerticalScrollBar::setPosition(unsigned position) { + qtScrollBar->setValue(position); +} + +void pVerticalScrollBar::constructor() { + qtWidget = qtScrollBar = new QScrollBar(Qt::Vertical); + qtScrollBar->setRange(0, 100); + qtScrollBar->setPageStep(101 >> 3); + connect(qtScrollBar, SIGNAL(valueChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + setLength(verticalScrollBar.state.length); + setPosition(verticalScrollBar.state.position); +} + +void pVerticalScrollBar::destructor() { + delete qtScrollBar; + qtWidget = qtScrollBar = 0; +} + +void pVerticalScrollBar::orphan() { + destructor(); + constructor(); +} + +void pVerticalScrollBar::onChange() { + verticalScrollBar.state.position = position(); + if(verticalScrollBar.onChange) verticalScrollBar.onChange(); +} diff --git a/ananke/phoenix/qt/widget/vertical-slider.cpp b/ananke/phoenix/qt/widget/vertical-slider.cpp new file mode 100644 index 00000000..500adb07 --- /dev/null +++ b/ananke/phoenix/qt/widget/vertical-slider.cpp @@ -0,0 +1,43 @@ +Geometry pVerticalSlider::minimumGeometry() { + return { 0, 0, 20, 0 }; +} + +unsigned pVerticalSlider::position() { + return qtSlider->value(); +} + +void pVerticalSlider::setLength(unsigned length) { + length += length == 0; + qtSlider->setRange(0, length - 1); + qtSlider->setPageStep(length >> 3); +} + +void pVerticalSlider::setPosition(unsigned position) { + qtSlider->setValue(position); +} + +void pVerticalSlider::constructor() { + qtWidget = qtSlider = new QSlider(Qt::Vertical); + qtSlider->setRange(0, 100); + qtSlider->setPageStep(101 >> 3); + connect(qtSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + setLength(verticalSlider.state.length); + setPosition(verticalSlider.state.position); +} + +void pVerticalSlider::destructor() { + delete qtSlider; + qtWidget = qtSlider = 0; +} + +void pVerticalSlider::orphan() { + destructor(); + constructor(); +} + +void pVerticalSlider::onChange() { + verticalSlider.state.position = position(); + if(verticalSlider.onChange) verticalSlider.onChange(); +} diff --git a/ananke/phoenix/qt/widget/viewport.cpp b/ananke/phoenix/qt/widget/viewport.cpp new file mode 100644 index 00000000..1b67d776 --- /dev/null +++ b/ananke/phoenix/qt/widget/viewport.cpp @@ -0,0 +1,51 @@ +uintptr_t pViewport::handle() { + return (uintptr_t)qtViewport->winId(); +} + +void pViewport::constructor() { + qtWidget = qtViewport = new QtViewport(*this); + qtViewport->setMouseTracking(true); + qtViewport->setAttribute(Qt::WA_PaintOnScreen, true); + qtViewport->setStyleSheet("background: #000000"); + + pWidget::synchronizeState(); +} + +void pViewport::destructor() { + delete qtViewport; + qtWidget = qtViewport = nullptr; +} + +void pViewport::orphan() { + destructor(); + constructor(); +} + +void pViewport::QtViewport::leaveEvent(QEvent *event) { + if(self.viewport.onMouseLeave) self.viewport.onMouseLeave(); +} + +void pViewport::QtViewport::mouseMoveEvent(QMouseEvent *event) { + if(self.viewport.onMouseMove) self.viewport.onMouseMove({ event->pos().x(), event->pos().y() }); +} + +void pViewport::QtViewport::mousePressEvent(QMouseEvent *event) { + if(self.viewport.onMousePress == false) return; + switch(event->button()) { + case Qt::LeftButton: self.viewport.onMousePress(Mouse::Button::Left); break; + case Qt::MidButton: self.viewport.onMousePress(Mouse::Button::Middle); break; + case Qt::RightButton: self.viewport.onMousePress(Mouse::Button::Right); break; + } +} + +void pViewport::QtViewport::mouseReleaseEvent(QMouseEvent *event) { + if(self.viewport.onMouseRelease == false) return; + switch(event->button()) { + case Qt::LeftButton: self.viewport.onMouseRelease(Mouse::Button::Left); break; + case Qt::MidButton: self.viewport.onMouseRelease(Mouse::Button::Middle); break; + case Qt::RightButton: self.viewport.onMouseRelease(Mouse::Button::Right); break; + } +} + +pViewport::QtViewport::QtViewport(pViewport &self) : self(self) { +} diff --git a/ananke/phoenix/qt/widget/widget.cpp b/ananke/phoenix/qt/widget/widget.cpp new file mode 100644 index 00000000..0bc4901c --- /dev/null +++ b/ananke/phoenix/qt/widget/widget.cpp @@ -0,0 +1,52 @@ +Geometry pWidget::minimumGeometry() { + return { 0, 0, 0, 0 }; +} + +void pWidget::setEnabled(bool enabled) { + if(widget.state.abstract) enabled = false; + if(sizable.state.layout && sizable.state.layout->enabled() == false) enabled = false; + qtWidget->setEnabled(enabled); +} + +void pWidget::setFocused() { + qtWidget->setFocus(Qt::OtherFocusReason); +} + +void pWidget::setFont(const string &font) { + qtWidget->setFont(pFont::create(font)); +} + +void pWidget::setGeometry(const Geometry &geometry) { + qtWidget->setGeometry(geometry.x, geometry.y, geometry.width, geometry.height); +} + +void pWidget::setVisible(bool visible) { + if(widget.state.abstract) visible = false; + if(sizable.state.layout == 0) visible = false; + if(sizable.state.layout && sizable.state.layout->visible() == false) visible = false; + qtWidget->setVisible(visible); +} + +void pWidget::constructor() { + if(widget.state.abstract) qtWidget = new QWidget; +} + +//pWidget::constructor() called before p{Derived}::constructor(); ergo qtWidget is not yet valid +//pWidget::synchronizeState() is called to finish construction of p{Derived}::constructor() +void pWidget::synchronizeState() { + setEnabled(widget.state.enabled); + setFont(widget.state.font); +//setVisible(widget.state.visible); +} + +void pWidget::destructor() { + if(widget.state.abstract) { + delete qtWidget; + qtWidget = 0; + } +} + +void pWidget::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/qt/window.cpp b/ananke/phoenix/qt/window.cpp new file mode 100644 index 00000000..c6cb35d6 --- /dev/null +++ b/ananke/phoenix/qt/window.cpp @@ -0,0 +1,288 @@ +Window& pWindow::none() { + static Window *window = nullptr; + if(window == nullptr) window = new Window; + return *window; +} + +void pWindow::append(Layout &layout) { + Geometry geometry = window.state.geometry; + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); +} + +void pWindow::append(Menu &menu) { + if(window.state.menuFont != "") menu.p.setFont(window.state.menuFont); + else menu.p.setFont("Sans, 8"); + qtMenu->addMenu(menu.p.qtMenu); +} + +void pWindow::append(Widget &widget) { + if(widget.state.font == "") { + if(window.state.widgetFont != "") widget.p.setFont(window.state.widgetFont); + else widget.p.setFont("Sans, 8"); + } + widget.p.qtWidget->setParent(qtContainer); + widget.setVisible(widget.visible()); +} + +Color pWindow::backgroundColor() { + if(window.state.backgroundColorOverride) return window.state.backgroundColor; + QColor color = qtWindow->palette().color(QPalette::ColorRole::Window); + return { (uint8_t)color.red(), (uint8_t)color.green(), (uint8_t)color.blue(), (uint8_t)color.alpha() }; +} + +Geometry pWindow::frameMargin() { + unsigned menuHeight = window.state.menuVisible ? settings->menuGeometryHeight : 0; + unsigned statusHeight = window.state.statusVisible ? settings->statusGeometryHeight : 0; + if(window.state.fullScreen) return { 0, menuHeight, 0, menuHeight + statusHeight }; + return { + settings->frameGeometryX, + settings->frameGeometryY + menuHeight, + settings->frameGeometryWidth, + settings->frameGeometryHeight + menuHeight + statusHeight + }; +} + +bool pWindow::focused() { + return qtWindow->isActiveWindow() && !qtWindow->isMinimized(); +} + +Geometry pWindow::geometry() { + if(window.state.fullScreen) { + unsigned menuHeight = window.state.menuVisible ? qtMenu->height() : 0; + unsigned statusHeight = window.state.statusVisible ? qtStatus->height() : 0; + return { 0, menuHeight, Desktop::size().width, Desktop::size().height - menuHeight - statusHeight }; + } + return window.state.geometry; +} + +void pWindow::remove(Layout &layout) { +} + +void pWindow::remove(Menu &menu) { + //QMenuBar::removeMenu() does not exist + qtMenu->clear(); + for(auto &menu : window.state.menu) append(menu); +} + +void pWindow::remove(Widget &widget) { + //bugfix: orphan() destroys and recreates widgets (to disassociate them from their parent); + //attempting to create widget again after QApplication::quit() crashes libQtGui + if(qtApplication) widget.p.orphan(); +} + +void pWindow::setBackgroundColor(const Color &color) { + QPalette palette; + palette.setColor(QPalette::Window, QColor(color.red, color.green, color.blue, color.alpha)); + qtContainer->setPalette(palette); + qtContainer->setAutoFillBackground(true); + qtWindow->setAttribute(Qt::WA_TranslucentBackground, color.alpha != 255); +} + +void pWindow::setFocused() { + qtWindow->raise(); + qtWindow->activateWindow(); +} + +void pWindow::setFullScreen(bool fullScreen) { + if(fullScreen == false) { + setResizable(window.state.resizable); + qtWindow->showNormal(); + qtWindow->adjustSize(); + } else { + qtLayout->setSizeConstraint(QLayout::SetDefaultConstraint); + qtContainer->setFixedSize(Desktop::size().width - frameMargin().width, Desktop::size().height - frameMargin().height); + qtWindow->showFullScreen(); + } +} + +void pWindow::setGeometry(const Geometry &geometry_) { + locked = true; + OS::processEvents(); + QApplication::syncX(); + Geometry geometry = geometry_, margin = frameMargin(); + + setResizable(window.state.resizable); + qtWindow->move(geometry.x - frameMargin().x, geometry.y - frameMargin().y); + //qtWindow->adjustSize() fails if larger than 2/3rds screen size + qtWindow->resize(qtWindow->sizeHint()); + qtWindow->setMinimumSize(1, 1); + qtContainer->setMinimumSize(1, 1); + + for(auto &layout : window.state.layout) { + geometry = geometry_; + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); + } + locked = false; +} + +void pWindow::setMenuFont(const string &font) { + qtMenu->setFont(pFont::create(font)); + for(auto &item : window.state.menu) item.p.setFont(font); +} + +void pWindow::setMenuVisible(bool visible) { + qtMenu->setVisible(visible); + setGeometry(window.state.geometry); +} + +void pWindow::setModal(bool modal) { + qtWindow->setWindowModality(modal ? Qt::ApplicationModal : Qt::NonModal); +} + +void pWindow::setResizable(bool resizable) { + if(resizable) { + qtLayout->setSizeConstraint(QLayout::SetDefaultConstraint); + qtContainer->setMinimumSize(window.state.geometry.width, window.state.geometry.height); + } else { + qtLayout->setSizeConstraint(QLayout::SetFixedSize); + qtContainer->setFixedSize(window.state.geometry.width, window.state.geometry.height); + } + qtStatus->setSizeGripEnabled(resizable); +} + +void pWindow::setStatusFont(const string &font) { + qtStatus->setFont(pFont::create(font)); +} + +void pWindow::setStatusText(const string &text) { + qtStatus->showMessage(QString::fromUtf8(text), 0); +} + +void pWindow::setStatusVisible(bool visible) { + qtStatus->setVisible(visible); + setGeometry(window.state.geometry); +} + +void pWindow::setTitle(const string &text) { + qtWindow->setWindowTitle(QString::fromUtf8(text)); +} + +void pWindow::setVisible(bool visible) { + locked = true; + qtWindow->setVisible(visible); + if(visible) { + updateFrameGeometry(); + setGeometry(window.state.geometry); + } + locked = false; +} + +void pWindow::setWidgetFont(const string &font) { + for(auto &item : window.state.widget) { + if(!item.state.font) item.setFont(font); + } +} + +void pWindow::constructor() { + qtWindow = new QtWindow(*this); + qtWindow->setWindowTitle(" "); + + qtLayout = new QVBoxLayout(qtWindow); + qtLayout->setMargin(0); + qtLayout->setSpacing(0); + qtWindow->setLayout(qtLayout); + + qtMenu = new QMenuBar(qtWindow); + qtMenu->setVisible(false); + qtLayout->addWidget(qtMenu); + + qtContainer = new QWidget(qtWindow); + qtContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + qtContainer->setVisible(true); + qtLayout->addWidget(qtContainer); + + qtStatus = new QStatusBar(qtWindow); + qtStatus->setSizeGripEnabled(true); + qtStatus->setVisible(false); + qtLayout->addWidget(qtStatus); + + setGeometry(window.state.geometry); + setMenuFont("Sans, 8"); + setStatusFont("Sans, 8"); +} + +void pWindow::destructor() { + delete qtStatus; + delete qtContainer; + delete qtMenu; + delete qtLayout; + delete qtWindow; +} + +void pWindow::updateFrameGeometry() { + pOS::syncX(); + QRect border = qtWindow->frameGeometry(); + QRect client = qtWindow->geometry(); + + settings->frameGeometryX = client.x() - border.x(); + settings->frameGeometryY = client.y() - border.y(); + settings->frameGeometryWidth = border.width() - client.width(); + settings->frameGeometryHeight = border.height() - client.height(); + + if(window.state.menuVisible) { + pOS::syncX(); + settings->menuGeometryHeight = qtMenu->height(); + } + + if(window.state.statusVisible) { + pOS::syncX(); + settings->statusGeometryHeight = qtStatus->height(); + } + + settings->save(); +} + +void pWindow::QtWindow::closeEvent(QCloseEvent *event) { + self.window.state.ignore = false; + event->ignore(); + if(self.window.onClose) self.window.onClose(); + if(self.window.state.ignore == false) hide(); +} + +void pWindow::QtWindow::moveEvent(QMoveEvent *event) { + if(self.locked == false && self.window.state.fullScreen == false && self.qtWindow->isVisible() == true) { + self.window.state.geometry.x += event->pos().x() - event->oldPos().x(); + self.window.state.geometry.y += event->pos().y() - event->oldPos().y(); + } + + if(self.locked == false) { + if(self.window.onMove) self.window.onMove(); + } +} + +void pWindow::QtWindow::keyPressEvent(QKeyEvent *event) { + Keyboard::Keycode sym = Keysym(event->nativeVirtualKey()); + if(sym != Keyboard::Keycode::None && self.window.onKeyPress) self.window.onKeyPress(sym); +} + +void pWindow::QtWindow::keyReleaseEvent(QKeyEvent *event) { + Keyboard::Keycode sym = Keysym(event->nativeVirtualKey()); + if(sym != Keyboard::Keycode::None && self.window.onKeyRelease) self.window.onKeyRelease(sym); +} + +void pWindow::QtWindow::resizeEvent(QResizeEvent*) { + if(self.locked == false && self.window.state.fullScreen == false && self.qtWindow->isVisible() == true) { + self.window.state.geometry.width = self.qtContainer->geometry().width(); + self.window.state.geometry.height = self.qtContainer->geometry().height(); + } + + for(auto &layout : self.window.state.layout) { + Geometry geometry = self.geometry(); + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); + } + + if(self.locked == false) { + if(self.window.onSize) self.window.onSize(); + } +} + +QSize pWindow::QtWindow::sizeHint() const { + unsigned width = self.window.state.geometry.width; + unsigned height = self.window.state.geometry.height; + if(self.window.state.menuVisible) height += settings->menuGeometryHeight; + if(self.window.state.statusVisible) height += settings->statusGeometryHeight; + return QSize(width, height); +} diff --git a/ananke/phoenix/reference/action/action.cpp b/ananke/phoenix/reference/action/action.cpp new file mode 100644 index 00000000..0bc6bc3f --- /dev/null +++ b/ananke/phoenix/reference/action/action.cpp @@ -0,0 +1,8 @@ +void pAction::setEnabled(bool enabled) { +} + +void pAction::setVisible(bool visible) { +} + +void pAction::constructor() { +} diff --git a/ananke/phoenix/reference/action/check-item.cpp b/ananke/phoenix/reference/action/check-item.cpp new file mode 100644 index 00000000..26970fc8 --- /dev/null +++ b/ananke/phoenix/reference/action/check-item.cpp @@ -0,0 +1,15 @@ +bool pCheckItem::checked() { + return false; +} + +void pCheckItem::setChecked(bool checked) { +} + +void pCheckItem::setText(const string &text) { +} + +void pCheckItem::constructor() { +} + +void pCheckItem::destructor() { +} diff --git a/ananke/phoenix/reference/action/item.cpp b/ananke/phoenix/reference/action/item.cpp new file mode 100644 index 00000000..438ed32f --- /dev/null +++ b/ananke/phoenix/reference/action/item.cpp @@ -0,0 +1,11 @@ +void pItem::setImage(const image &image) { +} + +void pItem::setText(const string &text) { +} + +void pItem::constructor() { +} + +void pItem::destructor() { +} diff --git a/ananke/phoenix/reference/action/menu.cpp b/ananke/phoenix/reference/action/menu.cpp new file mode 100644 index 00000000..4f0a65a8 --- /dev/null +++ b/ananke/phoenix/reference/action/menu.cpp @@ -0,0 +1,17 @@ +void pMenu::append(Action &action) { +} + +void pMenu::remove(Action &action) { +} + +void pMenu::setImage(const image &image) { +} + +void pMenu::setText(const string &text) { +} + +void pMenu::constructor() { +} + +void pMenu::destructor() { +} diff --git a/ananke/phoenix/reference/action/radio-item.cpp b/ananke/phoenix/reference/action/radio-item.cpp new file mode 100644 index 00000000..e87a4deb --- /dev/null +++ b/ananke/phoenix/reference/action/radio-item.cpp @@ -0,0 +1,18 @@ +bool pRadioItem::checked() { + return false; +} + +void pRadioItem::setChecked() { +} + +void pRadioItem::setGroup(const set &group) { +} + +void pRadioItem::setText(const string &text) { +} + +void pRadioItem::constructor() { +} + +void pRadioItem::destructor() { +} diff --git a/ananke/phoenix/reference/action/separator.cpp b/ananke/phoenix/reference/action/separator.cpp new file mode 100644 index 00000000..24a45c04 --- /dev/null +++ b/ananke/phoenix/reference/action/separator.cpp @@ -0,0 +1,5 @@ +void pSeparator::constructor() { +} + +void pSeparator::destructor() { +} diff --git a/ananke/phoenix/reference/desktop.cpp b/ananke/phoenix/reference/desktop.cpp new file mode 100644 index 00000000..a96eb1f0 --- /dev/null +++ b/ananke/phoenix/reference/desktop.cpp @@ -0,0 +1,8 @@ +Size pDesktop::size() { + return { 0, 0 }; +} + +Geometry pDesktop::workspace() { + return { 0, 0, 0, 0 }; +} + diff --git a/ananke/phoenix/reference/dialog-window.cpp b/ananke/phoenix/reference/dialog-window.cpp new file mode 100644 index 00000000..c7d089ae --- /dev/null +++ b/ananke/phoenix/reference/dialog-window.cpp @@ -0,0 +1,11 @@ +string pDialogWindow::fileOpen(Window &parent, const string &path, const lstring &filter) { + return ""; +} + +string pDialogWindow::fileSave(Window &parent, const string &path, const lstring &filter) { + return ""; +} + +string pDialogWindow::folderSelect(Window &parent, const string &path) { + return ""; +} diff --git a/ananke/phoenix/reference/font.cpp b/ananke/phoenix/reference/font.cpp new file mode 100644 index 00000000..bfda5c06 --- /dev/null +++ b/ananke/phoenix/reference/font.cpp @@ -0,0 +1,3 @@ +Geometry pFont::geometry(const string &description, const string &text) { + return { 0, 0, 0, 0 }; +} diff --git a/ananke/phoenix/reference/keyboard.cpp b/ananke/phoenix/reference/keyboard.cpp new file mode 100644 index 00000000..40b3a1a7 --- /dev/null +++ b/ananke/phoenix/reference/keyboard.cpp @@ -0,0 +1,10 @@ +bool pKeyboard::pressed(Keyboard::Scancode scancode) { + return false; +} + +vector pKeyboard::state() { + vector output; + output.resize((unsigned)Keyboard::Scancode::Limit); + for(auto &n : output) n = false; + return output; +} diff --git a/ananke/phoenix/reference/message-window.cpp b/ananke/phoenix/reference/message-window.cpp new file mode 100644 index 00000000..84a287f5 --- /dev/null +++ b/ananke/phoenix/reference/message-window.cpp @@ -0,0 +1,15 @@ +MessageWindow::Response pMessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow::Response::Ok; +} diff --git a/ananke/phoenix/reference/mouse.cpp b/ananke/phoenix/reference/mouse.cpp new file mode 100644 index 00000000..f103a15a --- /dev/null +++ b/ananke/phoenix/reference/mouse.cpp @@ -0,0 +1,7 @@ +Position pMouse::position() { + return { 0, 0 }; +} + +bool pMouse::pressed(Mouse::Button button) { + return false; +} diff --git a/ananke/phoenix/reference/platform.cpp b/ananke/phoenix/reference/platform.cpp new file mode 100644 index 00000000..e64f7eab --- /dev/null +++ b/ananke/phoenix/reference/platform.cpp @@ -0,0 +1,52 @@ +#include "platform.hpp" + +#include "desktop.cpp" +#include "keyboard.cpp" +#include "mouse.cpp" +#include "dialog-window.cpp" +#include "message-window.cpp" + +#include "font.cpp" +#include "timer.cpp" +#include "window.cpp" + +#include "action/action.cpp" +#include "action/menu.cpp" +#include "action/separator.cpp" +#include "action/item.cpp" +#include "action/check-item.cpp" +#include "action/radio-item.cpp" + +#include "widget/widget.cpp" +#include "widget/button.cpp" +#include "widget/canvas.cpp" +#include "widget/check-box.cpp" +#include "widget/combo-box.cpp" +#include "widget/hex-edit.cpp" +#include "widget/horizontal-scroll-bar.cpp" +#include "widget/horizontal-slider.cpp" +#include "widget/label.cpp" +#include "widget/line-edit.cpp" +#include "widget/list-view.cpp" +#include "widget/progress-bar.cpp" +#include "widget/radio-box.cpp" +#include "widget/text-edit.cpp" +#include "widget/vertical-scroll-bar.cpp" +#include "widget/vertical-slider.cpp" +#include "widget/viewport.cpp" + +void pOS::main() { +} + +bool pOS::pendingEvents() { + return false; +} + +void pOS::processEvents() { +} + +void pOS::quit() { +} + +void pOS::initialize() { +} diff --git a/ananke/phoenix/reference/platform.hpp b/ananke/phoenix/reference/platform.hpp new file mode 100644 index 00000000..3acc9d55 --- /dev/null +++ b/ananke/phoenix/reference/platform.hpp @@ -0,0 +1,383 @@ +struct pFont; +struct pWindow; +struct pMenu; +struct pLayout; +struct pWidget; + +struct pFont { + static Geometry geometry(const string &description, const string &text); +}; + +struct pDesktop { + static Size size(); + static Geometry workspace(); +}; + +struct pKeyboard { + static bool pressed(Keyboard::Scancode scancode); + static vector state(); +}; + +struct pMouse { + static Position position(); + static bool pressed(Mouse::Button button); +}; + +struct pDialogWindow { + static string fileOpen(Window &parent, const string &path, const lstring &filter); + static string fileSave(Window &parent, const string &path, const lstring &filter); + static string folderSelect(Window &parent, const string &path); +}; + +struct pMessageWindow { + static MessageWindow::Response information(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response question(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response warning(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response critical(Window &parent, const string &text, MessageWindow::Buttons buttons); +}; + +struct pObject { + Object &object; + bool locked; + + pObject(Object &object) : object(object), locked(locked) {} + virtual ~pObject() {} + + void constructor() {} + void destructor() {} +}; + +struct pOS : public pObject { + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + static void initialize(); +}; + +struct pTimer : public pObject { + Timer &timer; + + void setEnabled(bool enabled); + void setInterval(unsigned milliseconds); + + pTimer(Timer &timer) : pObject(timer), timer(timer) {} + void constructor(); +}; + +struct pWindow : public pObject { + Window &window; + + static Window& none(); + + void append(Layout &layout); + void append(Menu &menu); + void append(Widget &widget); + Color backgroundColor(); + bool focused(); + Geometry frameMargin(); + Geometry geometry(); + void remove(Layout &layout); + void remove(Menu &menu); + void remove(Widget &widget); + void setBackgroundColor(const Color &color); + void setFocused(); + void setFullScreen(bool fullScreen); + void setGeometry(const Geometry &geometry); + void setMenuFont(const string &font); + void setMenuVisible(bool visible); + void setModal(bool modal); + void setResizable(bool resizable); + void setStatusFont(const string &font); + void setStatusText(const string &text); + void setStatusVisible(bool visible); + void setTitle(const string &text); + void setVisible(bool visible); + void setWidgetFont(const string &font); + + pWindow(Window &window) : pObject(window), window(window) {} + void constructor(); +}; + +struct pAction : public pObject { + Action &action; + + void setEnabled(bool enabled); + void setVisible(bool visible); + + pAction(Action &action) : pObject(action), action(action) {} + void constructor(); +}; + +struct pMenu : public pAction { + Menu &menu; + + void append(Action &action); + void remove(Action &action); + void setImage(const image &image); + void setText(const string &text); + + pMenu(Menu &menu) : pAction(menu), menu(menu) {} + void constructor(); + void destructor(); +}; + +struct pSeparator : public pAction { + Separator &separator; + + pSeparator(Separator &separator) : pAction(separator), separator(separator) {} + void constructor(); + void destructor(); +}; + +struct pItem : public pAction { + Item &item; + + void setImage(const image &image); + void setText(const string &text); + + pItem(Item &item) : pAction(item), item(item) {} + void constructor(); + void destructor(); +}; + +struct pCheckItem : public pAction { + CheckItem &checkItem; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckItem(CheckItem &checkItem) : pAction(checkItem), checkItem(checkItem) {} + void constructor(); + void destructor(); +}; + +struct pRadioItem : public pAction { + RadioItem &radioItem; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioItem(RadioItem &radioItem) : pAction(radioItem), radioItem(radioItem) {} + void constructor(); + void destructor(); +}; + +struct pSizable : public pObject { + Sizable &sizable; + + pSizable(Sizable &sizable) : pObject(sizable), sizable(sizable) {} +}; + +struct pLayout : public pSizable { + Layout &layout; + + pLayout(Layout &layout) : pSizable(layout), layout(layout) {} +}; + +struct pWidget : public pSizable { + Widget &widget; + + bool enabled(); + Geometry minimumGeometry(); + void setEnabled(bool enabled); + void setFocused(); + void setFont(const string &font); + void setGeometry(const Geometry &geometry); + void setVisible(bool visible); + + pWidget(Widget &widget) : pSizable(widget), widget(widget) {} + void constructor(); +}; + +struct pButton : public pWidget { + Button &button; + + void setImage(const image &image, Orientation orientation); + void setText(const string &text); + + pButton(Button &button) : pWidget(button), button(button) {} + void constructor(); +}; + +struct pCanvas : public pWidget { + Canvas &canvas; + + void setSize(const Size &size); + void update(); + + pCanvas(Canvas &canvas) : pWidget(canvas), canvas(canvas) {} + void constructor(); +}; + +struct pCheckBox : public pWidget { + CheckBox &checkBox; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckBox(CheckBox &checkBox) : pWidget(checkBox), checkBox(checkBox) {} + void constructor(); +}; + +struct pComboBox : public pWidget { + ComboBox &comboBox; + + void append(const string &text); + void modify(unsigned row, const string &text); + void remove(unsigned row); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + + pComboBox(ComboBox &comboBox) : pWidget(comboBox), comboBox(comboBox) {} + void constructor(); +}; + +struct pHexEdit : public pWidget { + HexEdit &hexEdit; + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + pHexEdit(HexEdit &hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {} + void constructor(); +}; + +struct pHorizontalScrollBar : public pWidget { + HorizontalScrollBar &horizontalScrollBar; + + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalScrollBar(HorizontalScrollBar &horizontalScrollBar) : pWidget(horizontalScrollBar), horizontalScrollBar(horizontalScrollBar) {} + void constructor(); +}; + +struct pHorizontalSlider : public pWidget { + HorizontalSlider &horizontalSlider; + + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalSlider(HorizontalSlider &horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {} + void constructor(); +}; + +struct pLabel : public pWidget { + Label &label; + + void setText(const string &text); + + pLabel(Label &label) : pWidget(label), label(label) {} + void constructor(); +}; + +struct pLineEdit : public pWidget { + LineEdit &lineEdit; + + void setEditable(bool editable); + void setText(const string &text); + string text(); + + pLineEdit(LineEdit &lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {} + void constructor(); +}; + +struct pListView : public pWidget { + ListView &listView; + + void append(const lstring &text); + void autoSizeColumns(); + bool checked(unsigned row); + void modify(unsigned row, const lstring &text); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable); + void setChecked(unsigned row, bool checked); + void setHeaderText(const lstring &text); + void setHeaderVisible(bool visible); + void setImage(unsigned row, unsigned column, const image &image); + void setSelected(bool selected); + void setSelection(unsigned row); + + pListView(ListView &listView) : pWidget(listView), listView(listView) {} + void constructor(); +}; + +struct pProgressBar : public pWidget { + ProgressBar &progressBar; + + void setPosition(unsigned position); + + pProgressBar(ProgressBar &progressBar) : pWidget(progressBar), progressBar(progressBar) {} + void constructor(); +}; + +struct pRadioBox : public pWidget { + RadioBox &radioBox; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioBox(RadioBox &radioBox) : pWidget(radioBox), radioBox(radioBox) {} + void constructor(); +}; + +struct pTextEdit : public pWidget { + TextEdit &textEdit; + + void setCursorPosition(unsigned position); + void setEditable(bool editable); + void setText(const string &text); + void setWordWrap(bool wordWrap); + string text(); + + pTextEdit(TextEdit &textEdit) : pWidget(textEdit), textEdit(textEdit) {} + void constructor(); +}; + +struct pVerticalScrollBar : public pWidget { + VerticalScrollBar &verticalScrollBar; + + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalScrollBar(VerticalScrollBar &verticalScrollBar) : pWidget(verticalScrollBar), verticalScrollBar(verticalScrollBar) {} + void constructor(); +}; + +struct pVerticalSlider : public pWidget { + VerticalSlider &verticalSlider; + + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalSlider(VerticalSlider &verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {} + void constructor(); +}; + +struct pViewport : public pWidget { + Viewport &viewport; + + uintptr_t handle(); + + pViewport(Viewport &viewport) : pWidget(viewport), viewport(viewport) {} + void constructor(); +}; diff --git a/ananke/phoenix/reference/timer.cpp b/ananke/phoenix/reference/timer.cpp new file mode 100644 index 00000000..6cbe571a --- /dev/null +++ b/ananke/phoenix/reference/timer.cpp @@ -0,0 +1,8 @@ +void pTimer::setEnabled(bool enabled) { +} + +void pTimer::setInterval(unsigned milliseconds) { +} + +void pTimer::constructor() { +} diff --git a/ananke/phoenix/reference/widget/button.cpp b/ananke/phoenix/reference/widget/button.cpp new file mode 100644 index 00000000..fc06c371 --- /dev/null +++ b/ananke/phoenix/reference/widget/button.cpp @@ -0,0 +1,8 @@ +void pButton::setImage(const image &image, Orientation orientation) { +} + +void pButton::setText(const string &text) { +} + +void pButton::constructor() { +} diff --git a/ananke/phoenix/reference/widget/canvas.cpp b/ananke/phoenix/reference/widget/canvas.cpp new file mode 100644 index 00000000..953cfa77 --- /dev/null +++ b/ananke/phoenix/reference/widget/canvas.cpp @@ -0,0 +1,8 @@ +void pCanvas::setSize(const Size &size) { +} + +void pCanvas::update() { +} + +void pCanvas::constructor() { +} diff --git a/ananke/phoenix/reference/widget/check-box.cpp b/ananke/phoenix/reference/widget/check-box.cpp new file mode 100644 index 00000000..c5aec216 --- /dev/null +++ b/ananke/phoenix/reference/widget/check-box.cpp @@ -0,0 +1,12 @@ +bool pCheckBox::checked() { + return false; +} + +void pCheckBox::setChecked(bool checked) { +} + +void pCheckBox::setText(const string &text) { +} + +void pCheckBox::constructor() { +} diff --git a/ananke/phoenix/reference/widget/combo-box.cpp b/ananke/phoenix/reference/widget/combo-box.cpp new file mode 100644 index 00000000..297d7369 --- /dev/null +++ b/ananke/phoenix/reference/widget/combo-box.cpp @@ -0,0 +1,21 @@ +void pComboBox::append(const string &text) { +} + +void pComboBox::modify(unsigned row, const string &text) { +} + +void pComboBox::remove(unsigned row) { +} + +void pComboBox::reset() { +} + +unsigned pComboBox::selection() { + return 0; +} + +void pComboBox::setSelection(unsigned row) { +} + +void pComboBox::constructor() { +} diff --git a/ananke/phoenix/reference/widget/hex-edit.cpp b/ananke/phoenix/reference/widget/hex-edit.cpp new file mode 100644 index 00000000..40bf9f5d --- /dev/null +++ b/ananke/phoenix/reference/widget/hex-edit.cpp @@ -0,0 +1,17 @@ +void pHexEdit::setColumns(unsigned columns) { +} + +void pHexEdit::setLength(unsigned length) { +} + +void pHexEdit::setOffset(unsigned offset) { +} + +void pHexEdit::setRows(unsigned rows) { +} + +void pHexEdit::update() { +} + +void pHexEdit::constructor() { +} diff --git a/ananke/phoenix/reference/widget/horizontal-scroll-bar.cpp b/ananke/phoenix/reference/widget/horizontal-scroll-bar.cpp new file mode 100644 index 00000000..352b3393 --- /dev/null +++ b/ananke/phoenix/reference/widget/horizontal-scroll-bar.cpp @@ -0,0 +1,12 @@ +unsigned pHorizontalScrollBar::position() { + return 0; +} + +void pHorizontalScrollBar::setLength(unsigned length) { +} + +void pHorizontalScrollBar::setPosition(unsigned position) { +} + +void pHorizontalScrollBar::constructor() { +} diff --git a/ananke/phoenix/reference/widget/horizontal-slider.cpp b/ananke/phoenix/reference/widget/horizontal-slider.cpp new file mode 100644 index 00000000..0a4a8392 --- /dev/null +++ b/ananke/phoenix/reference/widget/horizontal-slider.cpp @@ -0,0 +1,12 @@ +unsigned pHorizontalSlider::position() { + return 0; +} + +void pHorizontalSlider::setLength(unsigned length) { +} + +void pHorizontalSlider::setPosition(unsigned position) { +} + +void pHorizontalSlider::constructor() { +} diff --git a/ananke/phoenix/reference/widget/label.cpp b/ananke/phoenix/reference/widget/label.cpp new file mode 100644 index 00000000..25600d2a --- /dev/null +++ b/ananke/phoenix/reference/widget/label.cpp @@ -0,0 +1,5 @@ +void pLabel::setText(const string &text) { +} + +void pLabel::constructor() { +} diff --git a/ananke/phoenix/reference/widget/line-edit.cpp b/ananke/phoenix/reference/widget/line-edit.cpp new file mode 100644 index 00000000..96b9ac6c --- /dev/null +++ b/ananke/phoenix/reference/widget/line-edit.cpp @@ -0,0 +1,11 @@ +void pLineEdit::setEditable(bool editable) { +} + +void pLineEdit::setText(const string &text) { +} + +string pLineEdit::text() { +} + +void pLineEdit::constructor() { +} diff --git a/ananke/phoenix/reference/widget/list-view.cpp b/ananke/phoenix/reference/widget/list-view.cpp new file mode 100644 index 00000000..6e90e0a3 --- /dev/null +++ b/ananke/phoenix/reference/widget/list-view.cpp @@ -0,0 +1,49 @@ +void pListView::append(const lstring &text) { +} + +void pListView::autoSizeColumns() { +} + +bool pListView::checked(unsigned row) { +} + +void pListView::modify(unsigned row, const lstring &text) { +} + +void pListView::remove(unsigned row) { +} + +void pListView::reset() { +} + +bool pListView::selected() { + return false; +} + +unsigned pListView::selection() { + return 0; +} + +void pListView::setCheckable(bool checkable) { +} + +void pListView::setChecked(unsigned row, bool checked) { +} + +void pListView::setHeaderText(const lstring &text) { +} + +void pListView::setHeaderVisible(bool visible) { +} + +void pListView::setImage(unsigned row, unsigned column, const image &image) { +} + +void pListView::setSelected(bool selected) { +} + +void pListView::setSelection(unsigned row) { +} + +void pListView::constructor() { +} diff --git a/ananke/phoenix/reference/widget/progress-bar.cpp b/ananke/phoenix/reference/widget/progress-bar.cpp new file mode 100644 index 00000000..b4905a85 --- /dev/null +++ b/ananke/phoenix/reference/widget/progress-bar.cpp @@ -0,0 +1,5 @@ +void pProgressBar::setPosition(unsigned position) { +} + +void pProgressBar::constructor() { +} diff --git a/ananke/phoenix/reference/widget/radio-box.cpp b/ananke/phoenix/reference/widget/radio-box.cpp new file mode 100644 index 00000000..f6aebcbe --- /dev/null +++ b/ananke/phoenix/reference/widget/radio-box.cpp @@ -0,0 +1,15 @@ +bool pRadioBox::checked() { + return false; +} + +void pRadioBox::setChecked() { +} + +void pRadioBox::setGroup(const set &group) { +} + +void pRadioBox::setText(const string &text) { +} + +void pRadioBox::constructor() { +} diff --git a/ananke/phoenix/reference/widget/text-edit.cpp b/ananke/phoenix/reference/widget/text-edit.cpp new file mode 100644 index 00000000..74121b2d --- /dev/null +++ b/ananke/phoenix/reference/widget/text-edit.cpp @@ -0,0 +1,17 @@ +void pTextEdit::setCursorPosition(unsigned position) { +} + +void pTextEdit::setEditable(bool editable) { +} + +void pTextEdit::setText(const string &text) { +} + +void pTextEdit::setWordWrap(bool wordWrap) { +} + +string pTextEdit::text() { +} + +void pTextEdit::constructor() { +} diff --git a/ananke/phoenix/reference/widget/vertical-scroll-bar.cpp b/ananke/phoenix/reference/widget/vertical-scroll-bar.cpp new file mode 100644 index 00000000..26795248 --- /dev/null +++ b/ananke/phoenix/reference/widget/vertical-scroll-bar.cpp @@ -0,0 +1,12 @@ +unsigned pVerticalScrollBar::position() { + return 0; +} + +void pVerticalScrollBar::setLength(unsigned length) { +} + +void pVerticalScrollBar::setPosition(unsigned position) { +} + +void pVerticalScrollBar::constructor() { +} diff --git a/ananke/phoenix/reference/widget/vertical-slider.cpp b/ananke/phoenix/reference/widget/vertical-slider.cpp new file mode 100644 index 00000000..a6d8ae00 --- /dev/null +++ b/ananke/phoenix/reference/widget/vertical-slider.cpp @@ -0,0 +1,12 @@ +unsigned pVerticalSlider::position() { + return 0; +} + +void pVerticalSlider::setLength(unsigned length) { +} + +void pVerticalSlider::setPosition(unsigned position) { +} + +void pVerticalSlider::constructor() { +} diff --git a/ananke/phoenix/reference/widget/viewport.cpp b/ananke/phoenix/reference/widget/viewport.cpp new file mode 100644 index 00000000..9d398438 --- /dev/null +++ b/ananke/phoenix/reference/widget/viewport.cpp @@ -0,0 +1,6 @@ +uintptr_t pViewport::handle() { + return 0; +} + +void pViewport::constructor() { +} diff --git a/ananke/phoenix/reference/widget/widget.cpp b/ananke/phoenix/reference/widget/widget.cpp new file mode 100644 index 00000000..49a6c79e --- /dev/null +++ b/ananke/phoenix/reference/widget/widget.cpp @@ -0,0 +1,25 @@ +bool pWidget::enabled() { + return false; +} + +Geometry pWidget::minimumGeometry() { + return { 0, 0, 0, 0 }; +} + +void pWidget::setEnabled(bool enabled) { +} + +void pWidget::setFocused() { +} + +void pWidget::setFont(const string &font) { +} + +void pWidget::setGeometry(const Geometry &geometry) { +} + +void pWidget::setVisible(bool visible) { +} + +void pWidget::constructor() { +} diff --git a/ananke/phoenix/reference/window.cpp b/ananke/phoenix/reference/window.cpp new file mode 100644 index 00000000..aca2cc2d --- /dev/null +++ b/ananke/phoenix/reference/window.cpp @@ -0,0 +1,84 @@ +Window& pWindow::none() { + static Window *window = nullptr; + if(window == nullptr) window = new Window; + return *window; +} + +void pWindow::append(Layout &layout) { +} + +void pWindow::append(Menu &menu) { +} + +void pWindow::append(Widget &widget) { +} + +Color pWindow::backgroundColor() { + return {0, 0, 0, 255}; +} + +bool pWindow::focused() { + return false; +} + +Geometry pWindow::frameMargin() { + return {0, 0, 0, 0}; +} + +Geometry pWindow::geometry() { + return {0, 0, 0, 0}; +} + +void pWindow::remove(Layout &layout) { +} + +void pWindow::remove(Menu &menu) { +} + +void pWindow::remove(Widget &widget) { +} + +void pWindow::setBackgroundColor(const Color &color) { +} + +void pWindow::setFocused() { +} + +void pWindow::setFullScreen(bool fullScreen) { +} + +void pWindow::setGeometry(const Geometry &geometry) { +} + +void pWindow::setMenuFont(const string &font) { +} + +void pWindow::setMenuVisible(bool visible) { +} + +void pWindow::setModal(bool modal) { +} + +void pWindow::setResizable(bool resizable) { +} + +void pWindow::setStatusFont(const string &font) { +} + +void pWindow::setStatusText(const string &text) { +} + +void pWindow::setStatusVisible(bool visible) { +} + +void pWindow::setTitle(const string &text) { +} + +void pWindow::setVisible(bool visible) { +} + +void pWindow::setWidgetFont(const string &font) { +} + +void pWindow::constructor() { +} diff --git a/ananke/phoenix/sync.sh b/ananke/phoenix/sync.sh new file mode 100644 index 00000000..40ee3d98 --- /dev/null +++ b/ananke/phoenix/sync.sh @@ -0,0 +1,9 @@ +synchronize() { + if [ -d ../"$1" ]; then + test -d "$1" && rm -r "$1" + cp -r ../"$1" ./"$1" + fi +} + +synchronize "nall" +rm -r nall/test diff --git a/ananke/phoenix/windows/action/action.cpp b/ananke/phoenix/windows/action/action.cpp new file mode 100644 index 00000000..b80208d1 --- /dev/null +++ b/ananke/phoenix/windows/action/action.cpp @@ -0,0 +1,12 @@ +void pAction::setEnabled(bool enabled) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pAction::setVisible(bool visible) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pAction::constructor() { + parentMenu = 0; + parentWindow = 0; +} diff --git a/ananke/phoenix/windows/action/check-item.cpp b/ananke/phoenix/windows/action/check-item.cpp new file mode 100644 index 00000000..195deabd --- /dev/null +++ b/ananke/phoenix/windows/action/check-item.cpp @@ -0,0 +1,18 @@ +bool pCheckItem::checked() { + return checkItem.state.checked; +} + +void pCheckItem::setChecked(bool checked) { + if(parentMenu) CheckMenuItem(parentMenu->p.hmenu, id, checked ? MF_CHECKED : MF_UNCHECKED); +} + +void pCheckItem::setText(const string &text) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pCheckItem::constructor() { +} + +void pCheckItem::destructor() { + if(parentMenu) parentMenu->remove(checkItem); +} diff --git a/ananke/phoenix/windows/action/item.cpp b/ananke/phoenix/windows/action/item.cpp new file mode 100644 index 00000000..2804bfcb --- /dev/null +++ b/ananke/phoenix/windows/action/item.cpp @@ -0,0 +1,29 @@ +void pItem::setImage(const image &image) { + createBitmap(); + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pItem::setText(const string &text) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pItem::constructor() { + createBitmap(); +} + +void pItem::destructor() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(parentMenu) parentMenu->remove(item); +} + +void pItem::createBitmap() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + + if(item.state.image.width && item.state.image.height) { + nall::image nallImage = item.state.image; + nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + nallImage.alphaBlend(GetSysColor(COLOR_MENU)); //Windows does not alpha blend menu icons properly (leaves black outline) + nallImage.scale(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), Interpolation::Linear); + hbitmap = CreateBitmap(nallImage); + } +} diff --git a/ananke/phoenix/windows/action/menu.cpp b/ananke/phoenix/windows/action/menu.cpp new file mode 100644 index 00000000..5d9da04e --- /dev/null +++ b/ananke/phoenix/windows/action/menu.cpp @@ -0,0 +1,109 @@ +void pMenu::append(Action &action) { + action.p.parentMenu = &menu; + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pMenu::remove(Action &action) { + if(parentWindow) parentWindow->p.updateMenu(); + action.p.parentMenu = 0; +} + +void pMenu::setImage(const image &image) { + createBitmap(); + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pMenu::setText(const string &text) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pMenu::constructor() { + hmenu = 0; + createBitmap(); +} + +void pMenu::destructor() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(parentMenu) { + parentMenu->remove(menu); + } else if(parentWindow) { + //belongs to window's main menubar + parentWindow->remove(menu); + } +} + +void pMenu::createBitmap() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + + if(menu.state.image.width && menu.state.image.height) { + nall::image nallImage = menu.state.image; + nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + nallImage.alphaBlend(GetSysColor(COLOR_MENU)); //Windows does not alpha blend menu icons properly (leaves black outline) + nallImage.scale(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), Interpolation::Linear); + hbitmap = CreateBitmap(nallImage); + } +} + +//Windows actions lack the ability to toggle visibility. +//To support this, menus must be destroyed and recreated when toggling any action's visibility. +void pMenu::update(Window &parentWindow, Menu *parentMenu) { + this->parentMenu = parentMenu; + this->parentWindow = &parentWindow; + + if(hmenu) DestroyMenu(hmenu); + hmenu = CreatePopupMenu(); + + for(auto &action : menu.state.action) { + action.p.parentMenu = &menu; + action.p.parentWindow = &parentWindow; + + unsigned enabled = action.state.enabled ? 0 : MF_GRAYED; + if(dynamic_cast(&action)) { + Menu &item = (Menu&)action; + if(action.state.visible) { + item.p.update(parentWindow, &menu); + AppendMenu(hmenu, MF_STRING | MF_POPUP | enabled, (UINT_PTR)item.p.hmenu, utf16_t(item.state.text)); + + if(item.state.image.width && item.state.image.height) { + MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; + //Windows XP and below displays MIIM_BITMAP + hbmpItem in its own column (separate from check/radio marks) + //this causes too much spacing, so use a custom checkmark image instead + mii.fMask = MIIM_CHECKMARKS; + mii.hbmpUnchecked = item.p.hbitmap; + SetMenuItemInfo(hmenu, (UINT_PTR)item.p.hmenu, FALSE, &mii); + } + } + } else if(dynamic_cast(&action)) { + Separator &item = (Separator&)action; + if(action.state.visible) { + AppendMenu(hmenu, MF_SEPARATOR | enabled, item.p.id, L""); + } + } else if(dynamic_cast(&action)) { + Item &item = (Item&)action; + if(action.state.visible) { + AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); + + if(item.state.image.width && item.state.image.height) { + MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; + //Windows XP and below displays MIIM_BITMAP + hbmpItem in its own column (separate from check/radio marks) + //this causes too much spacing, so use a custom checkmark image instead + mii.fMask = MIIM_CHECKMARKS; + mii.hbmpUnchecked = item.p.hbitmap; + SetMenuItemInfo(hmenu, item.p.id, FALSE, &mii); + } + } + } else if(dynamic_cast(&action)) { + CheckItem &item = (CheckItem&)action; + if(action.state.visible) { + AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); + } + if(item.state.checked) item.setChecked(); + } else if(dynamic_cast(&action)) { + RadioItem &item = (RadioItem&)action; + if(action.state.visible) { + AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); + } + if(item.state.checked) item.setChecked(); + } + } +} diff --git a/ananke/phoenix/windows/action/radio-item.cpp b/ananke/phoenix/windows/action/radio-item.cpp new file mode 100644 index 00000000..6b4f3a31 --- /dev/null +++ b/ananke/phoenix/windows/action/radio-item.cpp @@ -0,0 +1,26 @@ +bool pRadioItem::checked() { + return radioItem.state.checked; +} + +void pRadioItem::setChecked() { + for(auto &item : radioItem.state.group) { + //CheckMenuRadioItem takes: lo, hi, id; checking only id when lo <= id <= hi + //phoenix does not force IDs to be linear, so to uncheck id, we use: lo == hi == id + 1 (out of range) + //to check id, we use: lo == hi == id (only ID, but in range) + if(item.p.parentMenu) CheckMenuRadioItem(item.p.parentMenu->p.hmenu, item.p.id, item.p.id, item.p.id + (id != item.p.id), MF_BYCOMMAND); + } +} + +void pRadioItem::setGroup(const set &group) { +} + +void pRadioItem::setText(const string &text) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pRadioItem::constructor() { +} + +void pRadioItem::destructor() { + if(parentMenu) parentMenu->remove(radioItem); +} diff --git a/ananke/phoenix/windows/action/separator.cpp b/ananke/phoenix/windows/action/separator.cpp new file mode 100644 index 00000000..fac38eca --- /dev/null +++ b/ananke/phoenix/windows/action/separator.cpp @@ -0,0 +1,6 @@ +void pSeparator::constructor() { +} + +void pSeparator::destructor() { + if(parentMenu) parentMenu->remove(separator); +} diff --git a/ananke/phoenix/windows/desktop.cpp b/ananke/phoenix/windows/desktop.cpp new file mode 100644 index 00000000..956ba521 --- /dev/null +++ b/ananke/phoenix/windows/desktop.cpp @@ -0,0 +1,9 @@ +Size pDesktop::size() { + return { GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) }; +} + +Geometry pDesktop::workspace() { + RECT rc; + SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0); + return { rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top }; +} diff --git a/ananke/phoenix/windows/dialog-window.cpp b/ananke/phoenix/windows/dialog-window.cpp new file mode 100644 index 00000000..5ef21153 --- /dev/null +++ b/ananke/phoenix/windows/dialog-window.cpp @@ -0,0 +1,98 @@ +static string FileDialog(bool save, Window &parent, const string &path, const lstring &filter) { + string dir = path; + dir.replace("/", "\\"); + + string filterList; + for(auto &filterItem : filter) { + lstring part; + part.split("(", filterItem); + if(part.size() != 2) continue; + part[1].rtrim<1>(")"); + part[1].replace(" ", ""); + part[1].transform(",", ";"); + filterList.append(string(filterItem, "\t", part[1], "\t")); + } + + utf16_t wfilter(filterList); + utf16_t wdir(dir); + wchar_t wfilename[PATH_MAX + 1] = L""; + + wchar_t *p = wfilter; + while(*p != L'\0') { + if(*p == L'\t') *p = L'\0'; + p++; + } + + if(path.empty() == false) { + //clear COMDLG32 MRU (most recently used) file list + //this is required in order for lpstrInitialDir to be honored in Windows 7 and above + registry::remove("HKCU/Software/Microsoft/Windows/CurrentVersion/Explorer/ComDlg32/LastVisitedPidlMRU/"); + registry::remove("HKCU/Software/Microsoft/Windows/CurrentVersion/Explorer/ComDlg32/OpenSavePidlMRU/"); + } + + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = &parent != &Window::none() ? parent.p.hwnd : 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 = (save == false ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn)); + if(result == false) return ""; + string name = (const char*)utf8_t(wfilename); + name.transform("\\", "/"); + return name; +} + +string pDialogWindow::fileOpen(Window &parent, const string &path, const lstring &filter) { + return FileDialog(false, parent, path, filter); +} + +string pDialogWindow::fileSave(Window &parent, const string &path, const lstring &filter) { + return FileDialog(true, parent, path, filter); +} + +static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT msg, LPARAM lparam, LPARAM lpdata) { + if(msg == BFFM_INITIALIZED) { + if(lpdata) SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpdata); + } + + return 0; +} + +string pDialogWindow::folderSelect(Window &parent, const string &path) { + wchar_t wfilename[PATH_MAX + 1] = L""; + utf16_t wpath(string{path}.transform("/", "\\")); + + BROWSEINFO bi; + bi.hwndOwner = &parent != &Window::none() ? parent.p.hwnd : 0; + bi.pidlRoot = NULL; + bi.pszDisplayName = wfilename; + bi.lpszTitle = L""; + bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS; + bi.lpfn = BrowseCallbackProc; + bi.lParam = (LPARAM)(wchar_t*)wpath; + 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 = (const char*)utf8_t(wfilename); + if(name == "") return ""; + name.transform("\\", "/"); + if(name.endswith("/") == false) name.append("/"); + return name; +} \ No newline at end of file diff --git a/ananke/phoenix/windows/font.cpp b/ananke/phoenix/windows/font.cpp new file mode 100644 index 00000000..de42f24d --- /dev/null +++ b/ananke/phoenix/windows/font.cpp @@ -0,0 +1,44 @@ +Geometry pFont::geometry(const string &description, const string &text) { + HFONT hfont = pFont::create(description); + Geometry geometry = pFont::geometry(hfont, text); + pFont::free(hfont); + return geometry; +} + +HFONT pFont::create(const string &description) { + lstring part; + part.split(",", description); + for(auto &item : part) item.trim(" "); + + string family = "Sans"; + unsigned size = 8u; + bool bold = false; + bool italic = false; + + if(part[0] != "") family = part[0]; + if(part.size() >= 2) size = decimal(part[1]); + if(part.size() >= 3) bold = part[2].position("Bold"); + if(part.size() >= 3) italic = part[2].position("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(family) + ); +} + +void pFont::free(HFONT hfont) { + DeleteObject(hfont); +} + +Geometry pFont::geometry(HFONT hfont, const string &text_) { + //temporary fix: empty text string returns height of zero; bad for eg Button height + string text = (text_ == "" ? " " : text_); + + HDC hdc = GetDC(0); + SelectObject(hdc, hfont); + RECT rc = { 0, 0, 0, 0 }; + DrawText(hdc, utf16_t(text), -1, &rc, DT_CALCRECT); + ReleaseDC(0, hdc); + return { 0, 0, rc.right, rc.bottom }; +} diff --git a/ananke/phoenix/windows/keyboard.cpp b/ananke/phoenix/windows/keyboard.cpp new file mode 100644 index 00000000..1edffcb3 --- /dev/null +++ b/ananke/phoenix/windows/keyboard.cpp @@ -0,0 +1,137 @@ +void pKeyboard::initialize() { + auto append = [](Keyboard::Scancode scancode, unsigned keysym) { + settings->keymap.insert(scancode, keysym); + }; + + append(Keyboard::Scancode::Escape, VK_ESCAPE); + append(Keyboard::Scancode::F1, VK_F1); + append(Keyboard::Scancode::F2, VK_F2); + append(Keyboard::Scancode::F3, VK_F3); + append(Keyboard::Scancode::F4, VK_F4); + append(Keyboard::Scancode::F5, VK_F5); + append(Keyboard::Scancode::F6, VK_F6); + append(Keyboard::Scancode::F7, VK_F7); + append(Keyboard::Scancode::F8, VK_F8); + append(Keyboard::Scancode::F9, VK_F9); + append(Keyboard::Scancode::F10, VK_F10); + append(Keyboard::Scancode::F11, VK_F11); + append(Keyboard::Scancode::F12, VK_F12); + + append(Keyboard::Scancode::PrintScreen, VK_SNAPSHOT); + append(Keyboard::Scancode::ScrollLock, VK_SCROLL); + append(Keyboard::Scancode::Pause, VK_PAUSE); + + append(Keyboard::Scancode::Insert, VK_INSERT); + append(Keyboard::Scancode::Delete, VK_DELETE); + append(Keyboard::Scancode::Home, VK_HOME); + append(Keyboard::Scancode::End, VK_END); + append(Keyboard::Scancode::PageUp, VK_PRIOR); + append(Keyboard::Scancode::PageDown, VK_NEXT); + + append(Keyboard::Scancode::Up, VK_UP); + append(Keyboard::Scancode::Down, VK_DOWN); + append(Keyboard::Scancode::Left, VK_LEFT); + append(Keyboard::Scancode::Right, VK_RIGHT); + + append(Keyboard::Scancode::Grave, VK_OEM_3); + append(Keyboard::Scancode::Number1, '1'); + append(Keyboard::Scancode::Number2, '2'); + append(Keyboard::Scancode::Number3, '3'); + append(Keyboard::Scancode::Number4, '4'); + append(Keyboard::Scancode::Number5, '5'); + append(Keyboard::Scancode::Number6, '6'); + append(Keyboard::Scancode::Number7, '7'); + append(Keyboard::Scancode::Number8, '8'); + append(Keyboard::Scancode::Number9, '9'); + append(Keyboard::Scancode::Number0, '0'); + append(Keyboard::Scancode::Minus, VK_OEM_MINUS); + append(Keyboard::Scancode::Equal, VK_OEM_PLUS); + append(Keyboard::Scancode::Backspace, VK_BACK); + + append(Keyboard::Scancode::BracketLeft, VK_OEM_4); + append(Keyboard::Scancode::BracketRight, VK_OEM_6); + append(Keyboard::Scancode::Backslash, VK_OEM_5); + append(Keyboard::Scancode::Semicolon, VK_OEM_1); + append(Keyboard::Scancode::Apostrophe, VK_OEM_7); + append(Keyboard::Scancode::Comma, VK_OEM_COMMA); + append(Keyboard::Scancode::Period, VK_OEM_PERIOD); + append(Keyboard::Scancode::Slash, VK_OEM_2); + + append(Keyboard::Scancode::Tab, VK_TAB); + append(Keyboard::Scancode::CapsLock, VK_CAPITAL); + append(Keyboard::Scancode::Return, VK_RETURN); + append(Keyboard::Scancode::ShiftLeft, VK_LSHIFT); + append(Keyboard::Scancode::ShiftRight, VK_RSHIFT); + append(Keyboard::Scancode::ControlLeft, VK_LCONTROL); + append(Keyboard::Scancode::ControlRight, VK_RCONTROL); + append(Keyboard::Scancode::SuperLeft, VK_LWIN); + append(Keyboard::Scancode::SuperRight, VK_RWIN); + append(Keyboard::Scancode::AltLeft, VK_LMENU); + append(Keyboard::Scancode::AltRight, VK_RMENU); + append(Keyboard::Scancode::Space, VK_SPACE); + append(Keyboard::Scancode::Menu, VK_APPS); + + append(Keyboard::Scancode::A, 'A'); + append(Keyboard::Scancode::B, 'B'); + append(Keyboard::Scancode::C, 'C'); + append(Keyboard::Scancode::D, 'D'); + append(Keyboard::Scancode::E, 'E'); + append(Keyboard::Scancode::F, 'F'); + append(Keyboard::Scancode::G, 'G'); + append(Keyboard::Scancode::H, 'H'); + append(Keyboard::Scancode::I, 'I'); + append(Keyboard::Scancode::J, 'J'); + append(Keyboard::Scancode::K, 'K'); + append(Keyboard::Scancode::L, 'L'); + append(Keyboard::Scancode::M, 'M'); + append(Keyboard::Scancode::N, 'N'); + append(Keyboard::Scancode::O, 'O'); + append(Keyboard::Scancode::P, 'P'); + append(Keyboard::Scancode::Q, 'Q'); + append(Keyboard::Scancode::R, 'R'); + append(Keyboard::Scancode::S, 'S'); + append(Keyboard::Scancode::T, 'T'); + append(Keyboard::Scancode::U, 'U'); + append(Keyboard::Scancode::V, 'V'); + append(Keyboard::Scancode::W, 'W'); + append(Keyboard::Scancode::X, 'X'); + append(Keyboard::Scancode::Y, 'Y'); + append(Keyboard::Scancode::Z, 'Z'); + + append(Keyboard::Scancode::NumLock, VK_NUMLOCK); + append(Keyboard::Scancode::Divide, VK_DIVIDE); + append(Keyboard::Scancode::Multiply, VK_MULTIPLY); + append(Keyboard::Scancode::Subtract, VK_SUBTRACT); + append(Keyboard::Scancode::Add, VK_ADD); +//append(Keyboard::Scancode::Enter, ...); + append(Keyboard::Scancode::Point, VK_DECIMAL); + + append(Keyboard::Scancode::Keypad1, VK_NUMPAD1); + append(Keyboard::Scancode::Keypad2, VK_NUMPAD2); + append(Keyboard::Scancode::Keypad3, VK_NUMPAD3); + append(Keyboard::Scancode::Keypad4, VK_NUMPAD4); + append(Keyboard::Scancode::Keypad5, VK_NUMPAD5); + append(Keyboard::Scancode::Keypad6, VK_NUMPAD6); + append(Keyboard::Scancode::Keypad7, VK_NUMPAD7); + append(Keyboard::Scancode::Keypad8, VK_NUMPAD8); + append(Keyboard::Scancode::Keypad9, VK_NUMPAD9); + append(Keyboard::Scancode::Keypad0, VK_NUMPAD0); +} + +bool pKeyboard::pressed(Keyboard::Scancode scancode) { + return GetAsyncKeyState(settings->keymap.lhs[scancode]) & 0x8000; +} + +vector pKeyboard::state() { + vector output; + output.resize((unsigned)Keyboard::Scancode::Limit); + for(auto &n : output) n = false; + + for(auto &n : settings->keymap.rhs) { + if(GetAsyncKeyState(n.name) & 0x8000) { + output[(unsigned)n.data] = true; + } + } + + return output; +} diff --git a/ananke/phoenix/windows/message-window.cpp b/ananke/phoenix/windows/message-window.cpp new file mode 100644 index 00000000..fca126f4 --- /dev/null +++ b/ananke/phoenix/windows/message-window.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 pMessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONINFORMATION; + if(buttons == MessageWindow::Buttons::Ok) flags |= MB_OK; + if(buttons == MessageWindow::Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == MessageWindow::Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::none() ? parent.p.hwnd : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response pMessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONQUESTION; + if(buttons == MessageWindow::Buttons::Ok) flags |= MB_OK; + if(buttons == MessageWindow::Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == MessageWindow::Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::none() ? parent.p.hwnd : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response pMessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONWARNING; + if(buttons == MessageWindow::Buttons::Ok) flags |= MB_OK; + if(buttons == MessageWindow::Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == MessageWindow::Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::none() ? parent.p.hwnd : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response pMessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONERROR; + if(buttons == MessageWindow::Buttons::Ok) flags |= MB_OK; + if(buttons == MessageWindow::Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == MessageWindow::Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::none() ? parent.p.hwnd : 0, utf16_t(text), L"", flags)); +} diff --git a/ananke/phoenix/windows/mouse.cpp b/ananke/phoenix/windows/mouse.cpp new file mode 100644 index 00000000..e5004645 --- /dev/null +++ b/ananke/phoenix/windows/mouse.cpp @@ -0,0 +1,14 @@ +Position pMouse::position() { + POINT point = { 0 }; + GetCursorPos(&point); + return { point.x, point.y }; +} + +bool pMouse::pressed(Mouse::Button button) { + switch(button) { + case Mouse::Button::Left: return GetAsyncKeyState(VK_LBUTTON) & 0x8000; + case Mouse::Button::Middle: return GetAsyncKeyState(VK_MBUTTON) & 0x8000; + case Mouse::Button::Right: return GetAsyncKeyState(VK_RBUTTON) & 0x8000; + } + return false; +} \ No newline at end of file diff --git a/ananke/phoenix/windows/object.cpp b/ananke/phoenix/windows/object.cpp new file mode 100644 index 00000000..78811d79 --- /dev/null +++ b/ananke/phoenix/windows/object.cpp @@ -0,0 +1,13 @@ +vector pObject::objects; + +pObject::pObject(Object &object) : object(object) { + static unsigned uniqueId = 100; + objects.append(this); + id = uniqueId++; + locked = false; +} + +pObject* pObject::find(unsigned id) { + for(auto &item : objects) if(item->id == id) return item; + return 0; +} diff --git a/ananke/phoenix/windows/phoenix.Manifest b/ananke/phoenix/windows/phoenix.Manifest new file mode 100644 index 00000000..45fbb4cd --- /dev/null +++ b/ananke/phoenix/windows/phoenix.Manifest @@ -0,0 +1,14 @@ + + + + + + + + + + + true + + + diff --git a/ananke/phoenix/windows/phoenix.rc b/ananke/phoenix/windows/phoenix.rc new file mode 100644 index 00000000..89fb8dc2 --- /dev/null +++ b/ananke/phoenix/windows/phoenix.rc @@ -0,0 +1 @@ +1 24 "phoenix.Manifest" diff --git a/ananke/phoenix/windows/platform.cpp b/ananke/phoenix/windows/platform.cpp new file mode 100644 index 00000000..1214f2c2 --- /dev/null +++ b/ananke/phoenix/windows/platform.cpp @@ -0,0 +1,479 @@ +#include "platform.hpp" +#include "utility.cpp" +#include "settings.cpp" + +#include "desktop.cpp" +#include "keyboard.cpp" +#include "mouse.cpp" +#include "dialog-window.cpp" +#include "message-window.cpp" + +#include "object.cpp" +#include "font.cpp" +#include "timer.cpp" +#include "window.cpp" + +#include "action/action.cpp" +#include "action/menu.cpp" +#include "action/separator.cpp" +#include "action/item.cpp" +#include "action/check-item.cpp" +#include "action/radio-item.cpp" + +#include "widget/widget.cpp" +#include "widget/button.cpp" +#include "widget/canvas.cpp" +#include "widget/check-box.cpp" +#include "widget/combo-box.cpp" +#include "widget/hex-edit.cpp" +#include "widget/horizontal-scroll-bar.cpp" +#include "widget/horizontal-slider.cpp" +#include "widget/label.cpp" +#include "widget/line-edit.cpp" +#include "widget/list-view.cpp" +#include "widget/progress-bar.cpp" +#include "widget/radio-box.cpp" +#include "widget/text-edit.cpp" +#include "widget/vertical-scroll-bar.cpp" +#include "widget/vertical-slider.cpp" +#include "widget/viewport.cpp" + +static bool OS_keyboardProc(HWND, UINT, WPARAM, LPARAM); +static void OS_processDialogMessage(MSG&); +static LRESULT CALLBACK OS_windowProc(HWND, UINT, WPARAM, LPARAM); + +void pOS::main() { + MSG msg; + while(GetMessage(&msg, 0, 0, 0)) { + OS_processDialogMessage(msg); + } +} + +bool pOS::pendingEvents() { + MSG msg; + return PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE); +} + +void pOS::processEvents() { + while(pendingEvents()) { + MSG msg; + if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { + OS_processDialogMessage(msg); + } + } +} + +void OS_processDialogMessage(MSG &msg) { + if(msg.message == WM_KEYDOWN || msg.message == WM_KEYUP + || msg.message == WM_SYSKEYDOWN || msg.message == WM_SYSKEYUP) { + if(OS_keyboardProc(msg.hwnd, msg.message, msg.wParam, msg.lParam)) { + DispatchMessage(&msg); + return; + } + } + + if(!IsDialogMessage(GetForegroundWindow(), &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void pOS::quit() { + osQuit = true; + PostQuitMessage(0); +} + +void pOS::initialize() { + CoInitialize(0); + InitCommonControls(); + + WNDCLASS wc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); + 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 = GetSysColorBrush(COLOR_3DFACE); + 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 = GetSysColorBrush(COLOR_3DFACE); + 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); + + settings = new Settings; + pKeyboard::initialize(); +} + +static bool OS_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + if(msg != WM_KEYDOWN && msg != WM_SYSKEYDOWN && msg != WM_KEYUP && msg != WM_SYSKEYUP) return false; + + GUITHREADINFO info; + memset(&info, 0, sizeof(GUITHREADINFO)); + info.cbSize = sizeof(GUITHREADINFO); + GetGUIThreadInfo(GetCurrentThreadId(), &info); + Object *object = (Object*)GetWindowLongPtr(info.hwndFocus, GWLP_USERDATA); + if(object == nullptr) return false; + + if(dynamic_cast(object)) { + Window &window = (Window&)*object; + if(pWindow::modal.size() > 0 && !pWindow::modal.find(&window.p)) return false; + Keyboard::Keycode keysym = Keysym(wparam, lparam); + if(keysym != Keyboard::Keycode::None) { + if((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && window.onKeyPress) window.onKeyPress(keysym); + if((msg == WM_KEYUP || msg == WM_SYSKEYUP) && window.onKeyRelease) window.onKeyRelease(keysym); + } + return false; + } + + if(msg == WM_KEYDOWN) { + if(dynamic_cast(object)) { + ListView &listView = (ListView&)*object; + if(wparam == VK_RETURN) { + if(listView.onActivate) listView.onActivate(); + } + } else if(dynamic_cast(object)) { + LineEdit &lineEdit = (LineEdit&)*object; + if(wparam == VK_RETURN) { + if(lineEdit.onActivate) lineEdit.onActivate(); + } + } else if(dynamic_cast(object)) { + TextEdit &textEdit = (TextEdit&)*object; + if(wparam == 'A' && GetKeyState(VK_CONTROL) < 0) { + //Ctrl+A = select all text + //note: this is not a standard accelerator on Windows + Edit_SetSel(textEdit.p.hwnd, 0, ~0); + return true; + } else if(wparam == 'V' && GetKeyState(VK_CONTROL) < 0) { + //Ctrl+V = paste text + //note: this formats Unix (LF) and OS9 (CR) line-endings to Windows (CR+LF) line-endings + //this is necessary as the EDIT control only supports Windows line-endings + OpenClipboard(hwnd); + HANDLE handle = GetClipboardData(CF_UNICODETEXT); + if(handle) { + wchar_t *text = (wchar_t*)GlobalLock(handle); + if(text) { + string data = (const char*)utf8_t(text); + data.replace("\r\n", "\n"); + data.replace("\r", "\n"); + data.replace("\n", "\r\n"); + GlobalUnlock(handle); + utf16_t output(data); + HGLOBAL resource = GlobalAlloc(GMEM_MOVEABLE, (wcslen(output) + 1) * sizeof(wchar_t)); + if(resource) { + wchar_t *write = (wchar_t*)GlobalLock(resource); + if(write) { + wcscpy(write, output); + GlobalUnlock(write); + if(SetClipboardData(CF_UNICODETEXT, resource) == FALSE) { + GlobalFree(resource); + } + } + } + } + } + CloseClipboard(); + return false; + } + } + } + + return false; +} + +static LRESULT CALLBACK OS_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Object *object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!object || !dynamic_cast(object)) return DefWindowProc(hwnd, msg, wparam, lparam); + Window &window = (Window&)*object; + + bool process = true; + if(pWindow::modal.size() > 0 && !pWindow::modal.find(&window.p)) process = false; + if(osQuit) process = false; + + if(process) switch(msg) { + case WM_CLOSE: { + window.state.ignore = false; + if(window.onClose) window.onClose(); + if(window.state.ignore == false) { + window.setVisible(false); + window.setModal(false); + } + return TRUE; + } + + case WM_MOVE: { + if(window.p.locked) break; + + Geometry geometry = window.geometry(); + window.state.geometry.x = geometry.x; + window.state.geometry.y = geometry.y; + + if(window.onMove) window.onMove(); + break; + } + + case WM_SIZE: { + if(window.p.locked) break; + SetWindowPos(window.p.hstatus, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); + + Geometry geometry = window.geometry(); + window.state.geometry.width = geometry.width; + window.state.geometry.height = geometry.height; + + for(auto &layout : window.state.layout) { + Geometry geom = window.geometry(); + geom.x = geom.y = 0; + layout.setGeometry(geom); + } + + if(window.onSize) window.onSize(); + break; + } + + case WM_GETMINMAXINFO: { + MINMAXINFO *mmi = (MINMAXINFO*)lparam; + //mmi->ptMinTrackSize.x = 256 + window.p.frameMargin().width; + //mmi->ptMinTrackSize.y = 256 + window.p.frameMargin().height; + //return TRUE; + break; + } + + case WM_ERASEBKGND: { + if(window.p.brush == 0) break; + RECT rc; + GetClientRect(window.p.hwnd, &rc); + PAINTSTRUCT ps; + BeginPaint(window.p.hwnd, &ps); + FillRect(ps.hdc, &rc, window.p.brush); + EndPaint(window.p.hwnd, &ps); + return TRUE; + } + + case WM_CTLCOLORBTN: + case WM_CTLCOLORSTATIC: { + Object *object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + if(object && window.p.brush) { + HDC hdc = (HDC)wparam; + SetBkColor((HDC)wparam, window.p.brushColor); + return (INT_PTR)window.p.brush; + } + break; + } + + case WM_COMMAND: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.p.hwnd, id); + if(control == 0) { + pObject *object = (pObject*)pObject::find(id); + if(!object) break; + if(dynamic_cast(object)) { + Item &item = ((pItem*)object)->item; + if(item.onActivate) item.onActivate(); + } else if(dynamic_cast(object)) { + CheckItem &checkItem = ((pCheckItem*)object)->checkItem; + checkItem.setChecked(!checkItem.state.checked); + if(checkItem.onToggle) checkItem.onToggle(); + } else if(dynamic_cast(object)) { + RadioItem &radioItem = ((pRadioItem*)object)->radioItem; + if(radioItem.state.checked == false) { + radioItem.setChecked(); + if(radioItem.onActivate) radioItem.onActivate(); + } + } + } else { + Object *object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(!object) break; + if(dynamic_cast(object)) { + Button &button = (Button&)*object; + if(button.onActivate) button.onActivate(); + } else if(dynamic_cast(object)) { + CheckBox &checkBox = (CheckBox&)*object; + checkBox.setChecked(!checkBox.state.checked); + if(checkBox.onToggle) checkBox.onToggle(); + } else if(dynamic_cast(object)) { + ComboBox &comboBox = (ComboBox&)*object; + if(HIWORD(wparam) == CBN_SELCHANGE) { + if(comboBox.state.selection != comboBox.selection()) { + comboBox.state.selection = comboBox.selection(); + if(comboBox.onChange) comboBox.onChange(); + } + } + } else if(dynamic_cast(object)) { + LineEdit &lineEdit = (LineEdit&)*object; + if(HIWORD(wparam) == EN_CHANGE) { + if(lineEdit.p.locked == false && lineEdit.onChange) lineEdit.onChange(); + } + } else if(dynamic_cast(object)) { + RadioBox &radioBox = (RadioBox&)*object; + if(radioBox.state.checked == false) { + radioBox.setChecked(); + if(radioBox.onActivate) radioBox.onActivate(); + } + } else if(dynamic_cast(object)) { + TextEdit &textEdit = (TextEdit&)*object; + if(HIWORD(wparam) == EN_CHANGE) { + if(textEdit.p.locked == false && textEdit.onChange) textEdit.onChange(); + } + } + } + break; + } + + case WM_NOTIFY: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.p.hwnd, id); + if(control == 0) break; + Object *object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object == 0) break; + if(dynamic_cast(object)) { + ListView &listView = (ListView&)*object; + 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(listView.p.locked == false && listView.onToggle) listView.onToggle(nmlistview->iItem); + } else if((nmlistview->uOldState & LVIS_FOCUSED) && !(nmlistview->uNewState & LVIS_FOCUSED)) { + listView.p.lostFocus = true; + } else if(!(nmlistview->uOldState & LVIS_SELECTED) && (nmlistview->uNewState & LVIS_SELECTED)) { + listView.p.lostFocus = false; + listView.state.selected = true; + listView.state.selection = listView.selection(); + if(listView.p.locked == false && listView.onChange) listView.onChange(); + } else if(listView.p.lostFocus == false && listView.selected() == false) { + listView.p.lostFocus = false; + listView.state.selected = false; + listView.state.selection = 0; + if(listView.p.locked == false && listView.onChange) listView.onChange(); + } + } else if(nmhdr->code == LVN_ITEMACTIVATE) { + if(listView.onActivate) listView.onActivate(); + } else if(nmhdr->code == NM_CUSTOMDRAW) { + LPNMLVCUSTOMDRAW lvcd = (LPNMLVCUSTOMDRAW)nmhdr; + switch(lvcd->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + if(listView.state.headerText.size() >= 2) { + //draw alternating row colors of there are two or more columns + if(lvcd->nmcd.dwItemSpec % 2) lvcd->clrTextBk = GetSysColor(COLOR_WINDOW) ^ 0x070707; + } + return CDRF_DODEFAULT; + default: + return CDRF_DODEFAULT; + } + } + } + break; + } + + case WM_HSCROLL: + case WM_VSCROLL: { + Object *object = 0; + if(lparam) { + object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + } else { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.p.hwnd, id); + if(control == 0) break; + object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + } + if(object == 0) break; + + if(dynamic_cast(object) + || dynamic_cast(object)) { + SCROLLINFO info; + memset(&info, 0, sizeof(SCROLLINFO)); + info.cbSize = sizeof(SCROLLINFO); + info.fMask = SIF_ALL; + GetScrollInfo((HWND)lparam, SB_CTL, &info); + + switch(LOWORD(wparam)) { + case SB_LEFT: info.nPos = info.nMin; break; + case SB_RIGHT: info.nPos = info.nMax; break; + case SB_LINELEFT: info.nPos--; break; + case SB_LINERIGHT: info.nPos++; break; + case SB_PAGELEFT: info.nPos -= info.nMax >> 3; break; + case SB_PAGERIGHT: info.nPos += info.nMax >> 3; break; + case SB_THUMBTRACK: info.nPos = info.nTrackPos; break; + } + + info.fMask = SIF_POS; + SetScrollInfo((HWND)lparam, SB_CTL, &info, TRUE); + + //Windows may clamp position to scrollbar range + GetScrollInfo((HWND)lparam, SB_CTL, &info); + + if(dynamic_cast(object)) { + HorizontalScrollBar &horizontalScrollBar = (HorizontalScrollBar&)*object; + if(horizontalScrollBar.state.position != info.nPos) { + horizontalScrollBar.state.position = info.nPos; + if(horizontalScrollBar.onChange) horizontalScrollBar.onChange(); + } + } else { + VerticalScrollBar &verticalScrollBar = (VerticalScrollBar&)*object; + if(verticalScrollBar.state.position != info.nPos) { + verticalScrollBar.state.position = info.nPos; + if(verticalScrollBar.onChange) verticalScrollBar.onChange(); + } + } + + return TRUE; + } + + if(dynamic_cast(object)) { + HorizontalSlider &horizontalSlider = (HorizontalSlider&)*object; + if(horizontalSlider.state.position != horizontalSlider.position()) { + horizontalSlider.state.position = horizontalSlider.position(); + if(horizontalSlider.onChange) horizontalSlider.onChange(); + } + } else if(dynamic_cast(object)) { + VerticalSlider &verticalSlider = (VerticalSlider&)*object; + if(verticalSlider.state.position != verticalSlider.position()) { + verticalSlider.state.position = verticalSlider.position(); + if(verticalSlider.onChange) verticalSlider.onChange(); + } + } + + break; + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/ananke/phoenix/windows/platform.hpp b/ananke/phoenix/windows/platform.hpp new file mode 100644 index 00000000..56277a92 --- /dev/null +++ b/ananke/phoenix/windows/platform.hpp @@ -0,0 +1,483 @@ +struct Settings { + bidirectional_map keymap; +}; + +struct pFont; +struct pObject; +struct pWindow; +struct pMenu; +struct pLayout; +struct pWidget; + +static bool osQuit = false; + +struct pFont { + static Geometry geometry(const string &description, const string &text); + + static HFONT create(const string &description); + static void free(HFONT hfont); + static Geometry geometry(HFONT hfont, const string &text); +}; + +struct pDesktop { + static Size size(); + static Geometry workspace(); +}; + +struct pKeyboard { + static bool pressed(Keyboard::Scancode scancode); + static vector state(); + + static void initialize(); +}; + +struct pMouse { + static Position position(); + static bool pressed(Mouse::Button button); +}; + +struct pDialogWindow { + static string fileOpen(Window &parent, const string &path, const lstring &filter); + static string fileSave(Window &parent, const string &path, const lstring &filter); + static string folderSelect(Window &parent, const string &path); +}; + +struct pMessageWindow { + static MessageWindow::Response information(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response question(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response warning(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response critical(Window &parent, const string &text, MessageWindow::Buttons buttons); +}; + +struct pObject { + static vector objects; + + Object &object; + uintptr_t id; + bool locked; + + pObject(Object &object); + static pObject* find(unsigned id); + virtual ~pObject() {} + + void constructor() {} + void destructor() {} +}; + +struct pOS : public pObject { + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + static void initialize(); +}; + +struct pTimer : public pObject { + Timer &timer; + UINT_PTR htimer; + + void setEnabled(bool enabled); + void setInterval(unsigned milliseconds); + + pTimer(Timer &timer) : pObject(timer), timer(timer) {} + void constructor(); +}; + +struct pWindow : public pObject { + static vector modal; + static void updateModality(); + + Window &window; + HWND hwnd; + HMENU hmenu; + HWND hstatus; + HFONT hstatusfont; + HBRUSH brush; + COLORREF brushColor; + + static Window& none(); + + void append(Layout &layout); + void append(Menu &menu); + void append(Widget &widget); + Color backgroundColor(); + bool focused(); + Geometry frameMargin(); + Geometry geometry(); + void remove(Layout &layout); + void remove(Menu &menu); + void remove(Widget &widget); + void setBackgroundColor(const Color &color); + void setFocused(); + void setFullScreen(bool fullScreen); + void setGeometry(const Geometry &geometry); + void setMenuFont(const string &font); + void setMenuVisible(bool visible); + void setModal(bool modal); + void setResizable(bool resizable); + void setStatusFont(const string &font); + void setStatusText(const string &text); + void setStatusVisible(bool visible); + void setTitle(const string &text); + void setVisible(bool visible); + void setWidgetFont(const string &font); + + pWindow(Window &window) : pObject(window), window(window) {} + void constructor(); + void destructor(); + void updateMenu(); +}; + +struct pAction : public pObject { + Action &action; + Menu *parentMenu; + Window *parentWindow; + + void setEnabled(bool enabled); + void setVisible(bool visible); + + pAction(Action &action) : pObject(action), action(action) {} + void constructor(); +}; + +struct pMenu : public pAction { + Menu &menu; + HMENU hmenu; + HBITMAP hbitmap; + + void append(Action &action); + void remove(Action &action); + void setImage(const image &image); + void setText(const string &text); + + pMenu(Menu &menu) : pAction(menu), menu(menu), hbitmap(0) {} + void constructor(); + void destructor(); + void createBitmap(); + void update(Window &parentWindow, Menu *parentMenu = 0); +}; + +struct pSeparator : public pAction { + Separator &separator; + + pSeparator(Separator &separator) : pAction(separator), separator(separator) {} + void constructor(); + void destructor(); +}; + +struct pItem : public pAction { + Item &item; + HBITMAP hbitmap; + + void setImage(const image &image); + void setText(const string &text); + + pItem(Item &item) : pAction(item), item(item), hbitmap(0) {} + void constructor(); + void destructor(); + void createBitmap(); +}; + +struct pCheckItem : public pAction { + CheckItem &checkItem; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckItem(CheckItem &checkItem) : pAction(checkItem), checkItem(checkItem) {} + void constructor(); + void destructor(); +}; + +struct pRadioItem : public pAction { + RadioItem &radioItem; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioItem(RadioItem &radioItem) : pAction(radioItem), radioItem(radioItem) {} + void constructor(); + void destructor(); +}; + +struct pSizable : public pObject { + Sizable &sizable; + + pSizable(Sizable &sizable) : pObject(sizable), sizable(sizable) {} +}; + +struct pLayout : public pSizable { + Layout &layout; + + pLayout(Layout &layout) : pSizable(layout), layout(layout) {} +}; + +struct pWidget : public pSizable { + Widget &widget; + Window *parentWindow; + HWND hwnd; + HFONT hfont; + + bool enabled(); + virtual Geometry minimumGeometry(); + void setEnabled(bool enabled); + void setFocused(); + void setFont(const string &font); + virtual void setGeometry(const Geometry &geometry); + void setVisible(bool visible); + + pWidget(Widget &widget) : pSizable(widget), widget(widget) { parentWindow = &Window::none(); } + void constructor(); + void destructor(); + virtual void orphan(); + void setDefaultFont(); + void synchronize(); +}; + +struct pButton : public pWidget { + Button &button; + HBITMAP hbitmap; + HIMAGELIST himagelist; + + Geometry minimumGeometry(); + void setImage(const image &image, Orientation orientation); + void setText(const string &text); + + pButton(Button &button) : pWidget(button), button(button), hbitmap(0), himagelist(0) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pCanvas : public pWidget { + Canvas &canvas; + uint32_t *data; + + void setSize(const Size &size); + void update(); + + pCanvas(Canvas &canvas) : pWidget(canvas), canvas(canvas) {} + void constructor(); + void destructor(); + void orphan(); + void paint(); +}; + +struct pCheckBox : public pWidget { + CheckBox &checkBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckBox(CheckBox &checkBox) : pWidget(checkBox), checkBox(checkBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pComboBox : public pWidget { + ComboBox &comboBox; + + void append(const string &text); + void modify(unsigned row, const string &text); + void remove(unsigned row); + Geometry minimumGeometry(); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + + pComboBox(ComboBox &comboBox) : pWidget(comboBox), comboBox(comboBox) {} + void constructor(); + void destructor(); + void orphan(); + void setGeometry(const Geometry &geometry); +}; + +struct pHexEdit : public pWidget { + HexEdit &hexEdit; + LRESULT CALLBACK (*windowProc)(HWND, UINT, LPARAM, WPARAM); + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + pHexEdit(HexEdit &hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {} + void constructor(); + void destructor(); + void orphan(); + bool keyPress(unsigned key); +}; + +struct pHorizontalScrollBar : public pWidget { + HorizontalScrollBar &horizontalScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalScrollBar(HorizontalScrollBar &horizontalScrollBar) : pWidget(horizontalScrollBar), horizontalScrollBar(horizontalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pHorizontalSlider : public pWidget { + HorizontalSlider &horizontalSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalSlider(HorizontalSlider &horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLabel : public pWidget { + Label &label; + + Geometry minimumGeometry(); + void setText(const string &text); + + pLabel(Label &label) : pWidget(label), label(label) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLineEdit : public pWidget { + LineEdit &lineEdit; + + Geometry minimumGeometry(); + void setEditable(bool editable); + void setText(const string &text); + string text(); + + pLineEdit(LineEdit &lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pListView : public pWidget { + ListView &listView; + HIMAGELIST imageList; + vector> imageMap; + vector images; + bool lostFocus; + + void append(const lstring &text); + void autoSizeColumns(); + bool checked(unsigned row); + void modify(unsigned row, const lstring &text); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable); + void setChecked(unsigned row, bool checked); + void setHeaderText(const lstring &text); + void setHeaderVisible(bool visible); + void setImage(unsigned row, unsigned column, const image &image); + void setSelected(bool selected); + void setSelection(unsigned row); + + pListView(ListView &listView) : pWidget(listView), listView(listView), imageList(nullptr) {} + void constructor(); + void destructor(); + void orphan(); + void setGeometry(const Geometry &geometry); + void buildImageList(); +}; + +struct pProgressBar : public pWidget { + ProgressBar &progressBar; + + Geometry minimumGeometry(); + void setPosition(unsigned position); + + pProgressBar(ProgressBar &progressBar) : pWidget(progressBar), progressBar(progressBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pRadioBox : public pWidget { + RadioBox &radioBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioBox(RadioBox &radioBox) : pWidget(radioBox), radioBox(radioBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pTextEdit : public pWidget { + TextEdit &textEdit; + + void setCursorPosition(unsigned position); + void setEditable(bool editable); + void setText(const string &text); + void setWordWrap(bool wordWrap); + string text(); + + pTextEdit(TextEdit &textEdit) : pWidget(textEdit), textEdit(textEdit) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pVerticalScrollBar : public pWidget { + VerticalScrollBar &verticalScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalScrollBar(VerticalScrollBar &verticalScrollBar) : pWidget(verticalScrollBar), verticalScrollBar(verticalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pVerticalSlider : public pWidget { + VerticalSlider &verticalSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalSlider(VerticalSlider &verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pViewport : public pWidget { + Viewport &viewport; + + uintptr_t handle(); + + pViewport(Viewport &viewport) : pWidget(viewport), viewport(viewport) {} + void constructor(); + void destructor(); + void orphan(); +}; diff --git a/ananke/phoenix/windows/settings.cpp b/ananke/phoenix/windows/settings.cpp new file mode 100644 index 00000000..343fc9fb --- /dev/null +++ b/ananke/phoenix/windows/settings.cpp @@ -0,0 +1 @@ +static Settings *settings = nullptr; \ No newline at end of file diff --git a/ananke/phoenix/windows/timer.cpp b/ananke/phoenix/windows/timer.cpp new file mode 100644 index 00000000..99fb5c00 --- /dev/null +++ b/ananke/phoenix/windows/timer.cpp @@ -0,0 +1,31 @@ +static vector timers; + +static void CALLBACK Timer_timeoutProc(HWND hwnd, UINT msg, UINT_PTR timerID, DWORD time) { + for(auto &timer : timers) { + if(timer->htimer == timerID) { + if(timer->timer.onTimeout) timer->timer.onTimeout(); + return; + } + } +} + +void pTimer::setEnabled(bool enabled) { + if(htimer) { + KillTimer(NULL, htimer); + htimer = 0; + } + + if(enabled == true) { + htimer = SetTimer(NULL, 0U, timer.state.milliseconds, Timer_timeoutProc); + } +} + +void pTimer::setInterval(unsigned milliseconds) { + //destroy and recreate timer if interval changed + setEnabled(timer.state.enabled); +} + +void pTimer::constructor() { + timers.append(this); + htimer = 0; +} diff --git a/ananke/phoenix/windows/utility.cpp b/ananke/phoenix/windows/utility.cpp new file mode 100644 index 00000000..c247d5cc --- /dev/null +++ b/ananke/phoenix/windows/utility.cpp @@ -0,0 +1,150 @@ +static const unsigned Windows2000 = 0x0500; +static const unsigned WindowsXP = 0x0501; +static const unsigned WindowsVista = 0x0600; +static const unsigned Windows7 = 0x0601; + +static unsigned OsVersion() { + OSVERSIONINFO versionInfo = { 0 }; + versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&versionInfo); + return (versionInfo.dwMajorVersion << 8) + (versionInfo.dwMajorVersion << 0); +} + +static HBITMAP CreateBitmap(const image &image) { + HDC hdc = GetDC(0); + BITMAPINFO bitmapInfo; + memset(&bitmapInfo, 0, sizeof(BITMAPINFO)); + bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo.bmiHeader.biWidth = image.width; + bitmapInfo.bmiHeader.biHeight = -image.height; //bitmaps are stored upside down unless we negate height + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biBitCount = 32; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + bitmapInfo.bmiHeader.biSizeImage = image.width * image.height * 4; + void *bits = nullptr; + HBITMAP hbitmap = CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS, &bits, NULL, 0); + if(bits) memcpy(bits, image.data, image.width * image.height * 4); + ReleaseDC(0, hdc); + return hbitmap; +} + +static Keyboard::Keycode Keysym(unsigned keysym, unsigned keyflags) { + #define pressed(keysym) (GetAsyncKeyState(keysym) & 0x8000) + #define enabled(keysym) (GetKeyState(keysym)) + #define shifted() (pressed(VK_LSHIFT) || pressed(VK_RSHIFT)) + #define extended() (keyflags & (1 << 24)) + + switch(keysym) { + case VK_ESCAPE: return Keyboard::Keycode::Escape; + case VK_F1: return Keyboard::Keycode::F1; + case VK_F2: return Keyboard::Keycode::F2; + case VK_F3: return Keyboard::Keycode::F3; + case VK_F4: return Keyboard::Keycode::F4; + case VK_F5: return Keyboard::Keycode::F5; + case VK_F6: return Keyboard::Keycode::F6; + case VK_F7: return Keyboard::Keycode::F7; + case VK_F8: return Keyboard::Keycode::F8; + case VK_F9: return Keyboard::Keycode::F9; + //Keyboard::Keycode::F10 (should be captured under VK_MENU from WM_SYSKEY(UP,DOWN); but this is not working...) + case VK_F11: return Keyboard::Keycode::F11; + case VK_F12: return Keyboard::Keycode::F12; + + //Keyboard::Keycode::PrintScreen + //Keyboard::Keycode::SysRq + case VK_SCROLL: return Keyboard::Keycode::ScrollLock; + case VK_PAUSE: return Keyboard::Keycode::Pause; + //Keyboard::Keycode::Break + + case VK_INSERT: return extended() ? Keyboard::Keycode::Insert : Keyboard::Keycode::KeypadInsert; + case VK_DELETE: return extended() ? Keyboard::Keycode::Delete : Keyboard::Keycode::KeypadDelete; + case VK_HOME: return extended() ? Keyboard::Keycode::Home : Keyboard::Keycode::KeypadHome; + case VK_END: return extended() ? Keyboard::Keycode::End : Keyboard::Keycode::KeypadEnd; + case VK_PRIOR: return extended() ? Keyboard::Keycode::PageUp : Keyboard::Keycode::KeypadPageUp; + case VK_NEXT: return extended() ? Keyboard::Keycode::PageDown : Keyboard::Keycode::KeypadPageDown; + + case VK_UP: return extended() ? Keyboard::Keycode::Up : Keyboard::Keycode::KeypadUp; + case VK_DOWN: return extended() ? Keyboard::Keycode::Down : Keyboard::Keycode::KeypadDown; + case VK_LEFT: return extended() ? Keyboard::Keycode::Left : Keyboard::Keycode::KeypadLeft; + case VK_RIGHT: return extended() ? Keyboard::Keycode::Right : Keyboard::Keycode::KeypadRight; + + case VK_OEM_3: return !shifted() ? Keyboard::Keycode::Grave : Keyboard::Keycode::Tilde; + case '1': return !shifted() ? Keyboard::Keycode::Number1 : Keyboard::Keycode::Exclamation; + case '2': return !shifted() ? Keyboard::Keycode::Number2 : Keyboard::Keycode::At; + case '3': return !shifted() ? Keyboard::Keycode::Number3 : Keyboard::Keycode::Pound; + case '4': return !shifted() ? Keyboard::Keycode::Number4 : Keyboard::Keycode::Dollar; + case '5': return !shifted() ? Keyboard::Keycode::Number5 : Keyboard::Keycode::Percent; + case '6': return !shifted() ? Keyboard::Keycode::Number6 : Keyboard::Keycode::Power; + case '7': return !shifted() ? Keyboard::Keycode::Number7 : Keyboard::Keycode::Ampersand; + case '8': return !shifted() ? Keyboard::Keycode::Number8 : Keyboard::Keycode::Asterisk; + case '9': return !shifted() ? Keyboard::Keycode::Number9 : Keyboard::Keycode::ParenthesisLeft; + case '0': return !shifted() ? Keyboard::Keycode::Number0 : Keyboard::Keycode::ParenthesisRight; + case VK_OEM_MINUS: return !shifted() ? Keyboard::Keycode::Minus : Keyboard::Keycode::Underscore; + case VK_OEM_PLUS: return !shifted() ? Keyboard::Keycode::Equal : Keyboard::Keycode::Plus; + case VK_BACK: return Keyboard::Keycode::Backspace; + + case VK_OEM_4: return !shifted() ? Keyboard::Keycode::BracketLeft : Keyboard::Keycode::BraceLeft; + case VK_OEM_6: return !shifted() ? Keyboard::Keycode::BracketRight : Keyboard::Keycode::BraceRight; + case VK_OEM_5: return !shifted() ? Keyboard::Keycode::Backslash : Keyboard::Keycode::Pipe; + case VK_OEM_1: return !shifted() ? Keyboard::Keycode::Semicolon : Keyboard::Keycode::Colon; + case VK_OEM_7: return !shifted() ? Keyboard::Keycode::Apostrophe : Keyboard::Keycode::Quote; + case VK_OEM_COMMA: return !shifted() ? Keyboard::Keycode::Comma : Keyboard::Keycode::CaretLeft; + case VK_OEM_PERIOD: return !shifted() ? Keyboard::Keycode::Period : Keyboard::Keycode::CaretRight; + case VK_OEM_2: return !shifted() ? Keyboard::Keycode::Slash : Keyboard::Keycode::Question; + + case VK_TAB: return Keyboard::Keycode::Tab; + case VK_CAPITAL: return Keyboard::Keycode::CapsLock; + case VK_RETURN: return !extended() ? Keyboard::Keycode::Return : Keyboard::Keycode::Enter; + case VK_SHIFT: return !pressed(VK_RSHIFT) ? Keyboard::Keycode::ShiftLeft : Keyboard::Keycode::ShiftRight; + case VK_CONTROL: return !pressed(VK_RCONTROL) ? Keyboard::Keycode::ControlLeft : Keyboard::Keycode::ControlRight; + case VK_LWIN: return Keyboard::Keycode::SuperLeft; + case VK_RWIN: return Keyboard::Keycode::SuperRight; + case VK_MENU: + if(keyflags & (1 << 24)) return Keyboard::Keycode::AltRight; + return Keyboard::Keycode::AltLeft; + case VK_SPACE: return Keyboard::Keycode::Space; + case VK_APPS: return Keyboard::Keycode::Menu; + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': + case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + if(enabled(VK_CAPITAL)) { + if(shifted()) { + return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A'); + } else { + return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A'); + } + } else { + if(shifted()) { + return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A'); + } else { + return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A'); + } + } + break; + + case VK_NUMLOCK: return Keyboard::Keycode::NumLock; + case VK_DIVIDE: return Keyboard::Keycode::Divide; + case VK_MULTIPLY: return Keyboard::Keycode::Multiply; + case VK_SUBTRACT: return Keyboard::Keycode::Subtract; + case VK_ADD: return Keyboard::Keycode::Add; + case VK_DECIMAL: return Keyboard::Keycode::Point; + case VK_NUMPAD1: return Keyboard::Keycode::Keypad1; + case VK_NUMPAD2: return Keyboard::Keycode::Keypad2; + case VK_NUMPAD3: return Keyboard::Keycode::Keypad3; + case VK_NUMPAD4: return Keyboard::Keycode::Keypad4; + case VK_NUMPAD5: return Keyboard::Keycode::Keypad5; + case VK_NUMPAD6: return Keyboard::Keycode::Keypad6; + case VK_NUMPAD7: return Keyboard::Keycode::Keypad7; + case VK_NUMPAD8: return Keyboard::Keycode::Keypad8; + case VK_NUMPAD9: return Keyboard::Keycode::Keypad9; + case VK_NUMPAD0: return Keyboard::Keycode::Keypad0; + + case VK_CLEAR: return Keyboard::Keycode::KeypadCenter; + } + + return Keyboard::Keycode::None; + + #undef pressed + #undef enabled + #undef shifted + #undef extended +} diff --git a/ananke/phoenix/windows/widget/button.cpp b/ananke/phoenix/windows/widget/button.cpp new file mode 100644 index 00000000..41e7e283 --- /dev/null +++ b/ananke/phoenix/windows/widget/button.cpp @@ -0,0 +1,90 @@ +#ifndef Button_SetImageList + //MinGW/32-bit has painfully outdated platform headers ... + typedef struct { + HIMAGELIST himl; + RECT margin; + UINT uAlign; + } BUTTON_IMAGELIST, *PBUTTON_IMAGELIST; + + #define BUTTON_IMAGELIST_ALIGN_LEFT 0 + #define BUTTON_IMAGELIST_ALIGN_RIGHT 1 + #define BUTTON_IMAGELIST_ALIGN_TOP 2 + #define BUTTON_IMAGELIST_ALIGN_BOTTOM 3 + #define BUTTON_IMAGELIST_ALIGN_CENTER 4 + + #define BCM_FIRST 0x1600 + #define BCM_SETIMAGELIST (BCM_FIRST+2) + #define Button_SetImageList(hwnd, pbuttonImagelist) (WINBOOL)SNDMSG((hwnd),BCM_SETIMAGELIST,0,(LPARAM)(pbuttonImagelist)) +#endif + +Geometry pButton::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, button.state.text); + + if(button.state.orientation == Orientation::Horizontal) { + geometry.width += button.state.image.width; + geometry.height = max(button.state.image.height, geometry.height); + } + + if(button.state.orientation == Orientation::Vertical) { + geometry.width = max(button.state.image.width, geometry.width); + geometry.height += button.state.image.height; + } + + return { 0, 0, geometry.width + 20, geometry.height + 10 }; +} + +void pButton::setImage(const image &image, Orientation orientation) { + nall::image nallImage = image; + nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } + + if(OsVersion() >= WindowsVista) { + hbitmap = CreateBitmap(nallImage); + SendMessage(hwnd, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbitmap); + switch(orientation) { + case Orientation::Horizontal: SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~BS_TOP); break; + case Orientation::Vertical: SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | BS_TOP); break; + } + } else { + //Windows XP and earlier cannot display bitmaps and text at the same time with BM_SETIMAGE + //Use BCM_SETIMAGELIST instead. It does not support alpha blending, so blend with button color + //The XP theme and above use a gradient fade background, so it won't be a perfect match there + nallImage.alphaBlend(GetSysColor(COLOR_BTNFACE)); + hbitmap = CreateBitmap(nallImage); + himagelist = ImageList_Create(nallImage.width, nallImage.height, ILC_COLOR32, 1, 0); + ImageList_Add(himagelist, hbitmap, NULL); + BUTTON_IMAGELIST list; + list.himl = himagelist; + switch(orientation) { + case Orientation::Horizontal: SetRect(&list.margin, 5, 0, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT; break; + case Orientation::Vertical: SetRect(&list.margin, 0, 5, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_TOP; break; + } + Button_SetImageList(hwnd, &list); + } +} + +void pButton::setText(const string &text) { + SetWindowText(hwnd, utf16_t(text)); +} + +void pButton::constructor() { + hwnd = CreateWindow(L"BUTTON", L"", WS_CHILD | WS_TABSTOP, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&button); + setDefaultFont(); + setImage(button.state.image, button.state.orientation); + setText(button.state.text); + synchronize(); +} + +void pButton::destructor() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } + DestroyWindow(hwnd); +} + +void pButton::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/canvas.cpp b/ananke/phoenix/windows/widget/canvas.cpp new file mode 100644 index 00000000..f2be9e38 --- /dev/null +++ b/ananke/phoenix/windows/widget/canvas.cpp @@ -0,0 +1,92 @@ +static LRESULT CALLBACK Canvas_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Object *object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); + if(!dynamic_cast(object)) return DefWindowProc(hwnd, msg, wparam, lparam); + Canvas &canvas = (Canvas&)*object; + + if(msg == WM_GETDLGCODE) { + return DLGC_STATIC | DLGC_WANTCHARS; + } + + if(msg == WM_PAINT) { + canvas.p.paint(); + return TRUE; + } + + if(msg == WM_MOUSEMOVE) { + TRACKMOUSEEVENT tracker = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, hwnd }; + TrackMouseEvent(&tracker); + if(canvas.onMouseMove) canvas.onMouseMove({ (int16_t)LOWORD(lparam), (int16_t)HIWORD(lparam) }); + } + + if(msg == WM_MOUSELEAVE) { + if(canvas.onMouseLeave) canvas.onMouseLeave(); + } + + if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { + if(canvas.onMousePress) switch(msg) { + case WM_LBUTTONDOWN: canvas.onMousePress(Mouse::Button::Left); break; + case WM_MBUTTONDOWN: canvas.onMousePress(Mouse::Button::Middle); break; + case WM_RBUTTONDOWN: canvas.onMousePress(Mouse::Button::Right); break; + } + } + + if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) { + if(canvas.onMouseRelease) switch(msg) { + case WM_LBUTTONUP: canvas.onMouseRelease(Mouse::Button::Left); break; + case WM_MBUTTONUP: canvas.onMouseRelease(Mouse::Button::Middle); break; + case WM_RBUTTONUP: canvas.onMouseRelease(Mouse::Button::Right); break; + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +void pCanvas::setSize(const Size &size) { + delete[] data; + data = new uint32_t[size.width * size.height]; +} + +void pCanvas::update() { + memcpy(data, canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + InvalidateRect(hwnd, 0, false); +} + +void pCanvas::constructor() { + data = new uint32_t[canvas.state.width * canvas.state.height]; + memcpy(data, canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + hwnd = CreateWindow(L"phoenix_canvas", L"", WS_CHILD, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&canvas); + synchronize(); +} + +void pCanvas::destructor() { + DestroyWindow(hwnd); + delete[] data; +} + +void pCanvas::orphan() { + destructor(); + constructor(); +} + +void pCanvas::paint() { + RECT rc; + GetClientRect(hwnd, &rc); + unsigned width = canvas.state.width, height = canvas.state.height; + + BITMAPINFO bmi; + memset(&bmi, 0, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; //GDI stores bitmaps upside now; negative height flips bitmap + bmi.bmiHeader.biSizeImage = sizeof(uint32_t) * width * height; + + PAINTSTRUCT ps; + BeginPaint(hwnd, &ps); + SetDIBitsToDevice(ps.hdc, 0, 0, width, height, 0, 0, 0, height, (void*)data, &bmi, DIB_RGB_COLORS); + EndPaint(hwnd, &ps); +} diff --git a/ananke/phoenix/windows/widget/check-box.cpp b/ananke/phoenix/windows/widget/check-box.cpp new file mode 100644 index 00000000..8f0d2eb8 --- /dev/null +++ b/ananke/phoenix/windows/widget/check-box.cpp @@ -0,0 +1,39 @@ +bool pCheckBox::checked() { + return SendMessage(hwnd, BM_GETCHECK, 0, 0); +} + +Geometry pCheckBox::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, checkBox.state.text); + return { 0, 0, geometry.width + 20, geometry.height + 4 }; +} + +void pCheckBox::setChecked(bool checked) { + SendMessage(hwnd, BM_SETCHECK, (WPARAM)checked, 0); +} + +void pCheckBox::setText(const string &text) { + SetWindowText(hwnd, utf16_t(text)); +} + +void pCheckBox::constructor() { + hwnd = CreateWindow( + L"BUTTON", L"", + WS_CHILD | WS_TABSTOP | BS_CHECKBOX, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&checkBox); + setDefaultFont(); + if(checkBox.state.checked) setChecked(true); + setText(checkBox.state.text); + synchronize(); + +} + +void pCheckBox::destructor() { + DestroyWindow(hwnd); +} + +void pCheckBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/combo-box.cpp b/ananke/phoenix/windows/widget/combo-box.cpp new file mode 100644 index 00000000..bff1170d --- /dev/null +++ b/ananke/phoenix/windows/widget/combo-box.cpp @@ -0,0 +1,70 @@ +void pComboBox::append(const string &text) { + SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)(wchar_t*)utf16_t(text)); + if(SendMessage(hwnd, CB_GETCOUNT, 0, 0) == 1) setSelection(0); +} + +Geometry pComboBox::minimumGeometry() { + unsigned maximumWidth = 0; + for(auto &text : comboBox.state.text) maximumWidth = max(maximumWidth, pFont::geometry(hfont, text).width); + return { 0, 0, maximumWidth + 24, pFont::geometry(hfont, " ").height + 10 }; +} + +void pComboBox::modify(unsigned row, const string &text) { + locked = true; + unsigned position = selection(); + SendMessage(hwnd, CB_DELETESTRING, row, 0); + SendMessage(hwnd, CB_INSERTSTRING, row, (LPARAM)(wchar_t*)utf16_t(text)); + setSelection(position); + locked = false; +} + +void pComboBox::remove(unsigned row) { + locked = true; + unsigned position = selection(); + SendMessage(hwnd, CB_DELETESTRING, row, 0); + if(position == row) setSelection(0); + locked = false; +} + +void pComboBox::reset() { + SendMessage(hwnd, CB_RESETCONTENT, 0, 0); +} + +unsigned pComboBox::selection() { + return SendMessage(hwnd, CB_GETCURSEL, 0, 0); +} + +void pComboBox::setSelection(unsigned row) { + SendMessage(hwnd, CB_SETCURSEL, row, 0); +} + +void pComboBox::constructor() { + hwnd = CreateWindow( + L"COMBOBOX", L"", + WS_CHILD | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, + 0, 0, 0, 0, + parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&comboBox); + setDefaultFont(); + for(auto &text : comboBox.state.text) append(text); + setSelection(comboBox.state.selection); + synchronize(); +} + +void pComboBox::destructor() { + DestroyWindow(hwnd); +} + +void pComboBox::orphan() { + destructor(); + constructor(); +} + +void pComboBox::setGeometry(const Geometry &geometry) { + SetWindowPos(hwnd, NULL, geometry.x, geometry.y, geometry.width, 1, SWP_NOZORDER); + RECT rc; + GetWindowRect(hwnd, &rc); + unsigned adjustedHeight = geometry.height - ((rc.bottom - rc.top) - SendMessage(hwnd, CB_GETITEMHEIGHT, (WPARAM)-1, 0)); + SendMessage(hwnd, CB_SETITEMHEIGHT, (WPARAM)-1, adjustedHeight); +} diff --git a/ananke/phoenix/windows/widget/hex-edit.cpp b/ananke/phoenix/windows/widget/hex-edit.cpp new file mode 100644 index 00000000..789f4faf --- /dev/null +++ b/ananke/phoenix/windows/widget/hex-edit.cpp @@ -0,0 +1,136 @@ +static LRESULT CALLBACK HexEdit_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + HexEdit &hexEdit = *(HexEdit*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(msg == WM_CHAR) { + if(hexEdit.p.keyPress(wparam)) return 0; + } + return hexEdit.p.windowProc(hwnd, msg, wparam, lparam); +} + +void pHexEdit::setColumns(unsigned columns) { + update(); +} + +void pHexEdit::setLength(unsigned length) { + update(); +} + +void pHexEdit::setOffset(unsigned offset) { + update(); +} + +void pHexEdit::setRows(unsigned rows) { + update(); +} + +void pHexEdit::update() { + if(!hexEdit.onRead) { + SetWindowText(hwnd, L""); + return; + } + + unsigned cursorPosition = Edit_GetSel(hwnd); + + string output; + unsigned offset = hexEdit.state.offset; + for(unsigned row = 0; row < hexEdit.state.rows; row++) { + output.append(hex<8>(offset)); + output.append(" "); + + string hexdata; + string ansidata = " "; + for(unsigned column = 0; column < hexEdit.state.columns; column++) { + if(offset < hexEdit.state.length) { + uint8_t data = hexEdit.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 >= hexEdit.state.length) break; + if(row != hexEdit.state.rows - 1) output.append("\r\n"); + } + + SetWindowText(hwnd, utf16_t(output)); + Edit_SetSel(hwnd, LOWORD(cursorPosition), HIWORD(cursorPosition)); +} + +void pHexEdit::constructor() { + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_TABSTOP | ES_READONLY | ES_MULTILINE | ES_WANTRETURN, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&hexEdit); + setDefaultFont(); + update(); + + windowProc = (LRESULT CALLBACK (*)(HWND, UINT, LPARAM, WPARAM))GetWindowLongPtr(hwnd, GWLP_WNDPROC); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)HexEdit_windowProc); + synchronize(); +} + +void pHexEdit::destructor() { + DestroyWindow(hwnd); +} + +void pHexEdit::orphan() { + destructor(); + constructor(); +} + +bool pHexEdit::keyPress(unsigned scancode) { + if(!hexEdit.onRead) return false; + + unsigned position = LOWORD(Edit_GetSel(hwnd)); + unsigned lineWidth = 10 + (hexEdit.state.columns * 3) + 1 + hexEdit.state.columns + 2; + unsigned cursorY = position / lineWidth; + unsigned cursorX = position % lineWidth; + + //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; + + 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 < hexEdit.state.columns) { + //not in ANSI region + unsigned offset = hexEdit.state.offset + (cursorY * hexEdit.state.columns + cursorX); + + if(offset >= hexEdit.state.length) return false; //do not edit past end of data + uint8_t data = hexEdit.onRead(offset); + + //write modified value + if(cursorNibble == 1) { + data = (data & 0xf0) | (scancode << 0); + } else { + data = (data & 0x0f) | (scancode << 4); + } + if(hexEdit.onWrite) hexEdit.onWrite(offset, data); + + //auto-advance cursor to next nibble or byte + position++; + if(cursorNibble && cursorX != hexEdit.state.columns - 1) position++; + Edit_SetSel(hwnd, position, position); + + //refresh output to reflect modified data + update(); + } + } + } + + return true; +} diff --git a/ananke/phoenix/windows/widget/horizontal-scroll-bar.cpp b/ananke/phoenix/windows/widget/horizontal-scroll-bar.cpp new file mode 100644 index 00000000..250ac247 --- /dev/null +++ b/ananke/phoenix/windows/widget/horizontal-scroll-bar.cpp @@ -0,0 +1,38 @@ +Geometry pHorizontalScrollBar::minimumGeometry() { + return { 0, 0, 0, 18 }; +} + +unsigned pHorizontalScrollBar::position() { + return GetScrollPos(hwnd, SB_CTL); +} + +void pHorizontalScrollBar::setLength(unsigned length) { + length += (length == 0); + SetScrollRange(hwnd, SB_CTL, 0, length - 1, TRUE); + horizontalScrollBar.setPosition(0); +} + +void pHorizontalScrollBar::setPosition(unsigned position) { + SetScrollPos(hwnd, SB_CTL, position, TRUE); +} + +void pHorizontalScrollBar::constructor() { + hwnd = CreateWindow( + L"SCROLLBAR", L"", WS_CHILD | WS_TABSTOP | SBS_HORZ, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&horizontalScrollBar); + unsigned position = horizontalScrollBar.state.position; + setLength(horizontalScrollBar.state.length); + horizontalScrollBar.setPosition(position); + synchronize(); +} + +void pHorizontalScrollBar::destructor() { + DestroyWindow(hwnd); +} + +void pHorizontalScrollBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/horizontal-slider.cpp b/ananke/phoenix/windows/widget/horizontal-slider.cpp new file mode 100644 index 00000000..807086ae --- /dev/null +++ b/ananke/phoenix/windows/widget/horizontal-slider.cpp @@ -0,0 +1,39 @@ +Geometry pHorizontalSlider::minimumGeometry() { + return { 0, 0, 0, 25 }; +} + +unsigned pHorizontalSlider::position() { + return SendMessage(hwnd, TBM_GETPOS, 0, 0); +} + +void pHorizontalSlider::setLength(unsigned length) { + length += (length == 0); + SendMessage(hwnd, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(hwnd, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); + horizontalSlider.setPosition(0); +} + +void pHorizontalSlider::setPosition(unsigned position) { + SendMessage(hwnd, TBM_SETPOS, (WPARAM)true, (LPARAM)position); +} + +void pHorizontalSlider::constructor() { + hwnd = CreateWindow( + TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_HORZ, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&horizontalSlider); + unsigned position = horizontalSlider.state.position; + setLength(horizontalSlider.state.length); + horizontalSlider.setPosition(position); + synchronize(); +} + +void pHorizontalSlider::destructor() { + DestroyWindow(hwnd); +} + +void pHorizontalSlider::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/label.cpp b/ananke/phoenix/windows/widget/label.cpp new file mode 100644 index 00000000..56f0d433 --- /dev/null +++ b/ananke/phoenix/windows/widget/label.cpp @@ -0,0 +1,64 @@ +Geometry pLabel::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, label.state.text); + return { 0, 0, geometry.width, geometry.height }; +} + +void pLabel::setText(const string &text) { + SetWindowText(hwnd, utf16_t(text)); + InvalidateRect(hwnd, 0, false); +} + +void pLabel::constructor() { + hwnd = CreateWindow(L"phoenix_label", L"", WS_CHILD, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&label); + setDefaultFont(); + setText(label.state.text); + synchronize(); +} + +void pLabel::destructor() { + DestroyWindow(hwnd); +} + +void pLabel::orphan() { + destructor(); + constructor(); +} + +static LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Window *window = (Window*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); + Label *label = (Label*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!window || !label) return DefWindowProc(hwnd, msg, wparam, lparam); + + if(msg == WM_GETDLGCODE) { + return DLGC_STATIC | DLGC_WANTCHARS; + } + + if(msg == WM_ERASEBKGND) { + //background is erased during WM_PAINT to prevent flickering + return TRUE; + } + + if(msg == WM_PAINT) { + PAINTSTRUCT ps; + RECT rc; + BeginPaint(hwnd, &ps); + GetClientRect(hwnd, &rc); + FillRect(ps.hdc, &rc, window->p.brush ? window->p.brush : GetSysColorBrush(COLOR_3DFACE)); + SetBkColor(ps.hdc, window->p.brush ? window->p.brushColor : GetSysColor(COLOR_3DFACE)); + SelectObject(ps.hdc, ((Widget*)label)->p.hfont); + 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/ananke/phoenix/windows/widget/line-edit.cpp b/ananke/phoenix/windows/widget/line-edit.cpp new file mode 100644 index 00000000..eb6a8fb7 --- /dev/null +++ b/ananke/phoenix/windows/widget/line-edit.cpp @@ -0,0 +1,45 @@ +Geometry pLineEdit::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, lineEdit.state.text); + return { 0, 0, geometry.width + 12, geometry.height + 10 }; +} + +void pLineEdit::setEditable(bool editable) { + SendMessage(hwnd, EM_SETREADONLY, editable == false, 0); +} + +void pLineEdit::setText(const string &text) { + locked = true; + SetWindowText(hwnd, utf16_t(text)); + locked = false; +} + +string pLineEdit::text() { + unsigned length = GetWindowTextLength(hwnd); + wchar_t text[length + 1]; + GetWindowText(hwnd, text, length + 1); + text[length] = 0; + return (const char*)utf8_t(text); +} + +void pLineEdit::constructor() { + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_AUTOVSCROLL, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&lineEdit); + setDefaultFont(); + setEditable(lineEdit.state.editable); + setText(lineEdit.state.text); + synchronize(); +} + +void pLineEdit::destructor() { + lineEdit.state.text = text(); + DestroyWindow(hwnd); +} + +void pLineEdit::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/list-view.cpp b/ananke/phoenix/windows/widget/list-view.cpp new file mode 100644 index 00000000..675691e6 --- /dev/null +++ b/ananke/phoenix/windows/widget/list-view.cpp @@ -0,0 +1,243 @@ +unsigned ListView_GetColumnCount(HWND hwnd) { + unsigned count = 0; + LVCOLUMN column; + column.mask = LVCF_WIDTH; + while(ListView_GetColumn(hwnd, count++, &column)); + return --count; +} + +void ListView_SetImage(HWND hwnd, HIMAGELIST imageList, unsigned row, unsigned column, unsigned imageID) { + //if this is the first image assigned, set image list now + //do not set sooner, or image blocks will appear in a list with no images + if(ListView_GetImageList(hwnd, LVSIL_SMALL) != imageList) { + ListView_SetImageList(hwnd, imageList, LVSIL_SMALL); + } + + LVITEM item; + item.mask = LVIF_IMAGE; + item.iItem = row; + item.iSubItem = column; + item.iImage = imageID; + ListView_SetItem(hwnd, &item); +} + +void ImageList_Append(HIMAGELIST imageList, const nall::image &source) { + auto image = source; + if(image.empty()) { + image.allocate(15, 15); + image.clear(GetSysColor(COLOR_WINDOW)); + } + image.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + image.scale(15, 15, Interpolation::Linear); + HBITMAP bitmap = CreateBitmap(image); + ImageList_Add(imageList, bitmap, NULL); + DeleteObject(bitmap); +} + +void pListView::append(const lstring &list) { + wchar_t empty[] = L""; + unsigned row = ListView_GetItemCount(hwnd); + LVITEM item; + item.mask = LVIF_TEXT; + item.iItem = row; + item.iSubItem = 0; + item.pszText = empty; + locked = true; + ListView_InsertItem(hwnd, &item); + locked = false; + for(unsigned column = 0; column < list.size(); column++) { + utf16_t wtext(list(column, "")); + ListView_SetItemText(hwnd, row, column, wtext); + } +} + +void pListView::autoSizeColumns() { + unsigned columns = ListView_GetColumnCount(hwnd); + for(unsigned n = 0; n < columns; n++) { + ListView_SetColumnWidth(hwnd, n, LVSCW_AUTOSIZE_USEHEADER); + } +} + +bool pListView::checked(unsigned row) { + return ListView_GetCheckState(hwnd, row); +} + +void pListView::modify(unsigned row, const lstring &list) { + for(unsigned n = 0; n < list.size(); n++) { + utf16_t wtext(list(n, "")); + ListView_SetItemText(hwnd, row, n, wtext); + } +} + +void pListView::remove(unsigned row) { + ListView_DeleteItem(hwnd, row); +} + +void pListView::reset() { + ListView_DeleteAllItems(hwnd); + buildImageList(); //free previously allocated images +} + +bool pListView::selected() { + unsigned count = ListView_GetItemCount(hwnd); + for(unsigned n = 0; n < count; n++) { + if(ListView_GetItemState(hwnd, n, LVIS_SELECTED)) return true; + } + return false; +} + +unsigned pListView::selection() { + unsigned count = ListView_GetItemCount(hwnd); + for(unsigned n = 0; n < count; n++) { + if(ListView_GetItemState(hwnd, n, LVIS_SELECTED)) return n; + } + return listView.state.selection; +} + +void pListView::setCheckable(bool checkable) { + ListView_SetExtendedListViewStyle(hwnd, LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES | (checkable ? LVS_EX_CHECKBOXES : 0)); +} + +void pListView::setChecked(unsigned row, bool checked) { + locked = true; + ListView_SetCheckState(hwnd, row, checked); + locked = false; +} + +void pListView::setHeaderText(const lstring &list) { + while(ListView_DeleteColumn(hwnd, 0)); + + lstring headers = list; + if(headers.size() == 0) headers.append(""); //must have at least one column + + for(unsigned n = 0; n < headers.size(); n++) { + LVCOLUMN column; + column.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM; + column.fmt = LVCFMT_LEFT; + column.iSubItem = n; + utf16_t headerText(headers[n]); + column.pszText = headerText; + ListView_InsertColumn(hwnd, n, &column); + } + autoSizeColumns(); +} + +void pListView::setHeaderVisible(bool visible) { + SetWindowLong( + hwnd, GWL_STYLE, + (GetWindowLong(hwnd, GWL_STYLE) & ~LVS_NOCOLUMNHEADER) | + (visible ? 0 : LVS_NOCOLUMNHEADER) + ); +} + +void pListView::setImage(unsigned row, unsigned column, const image &image) { + //assign existing image + for(unsigned n = 0; n < images.size(); n++) { + if(images[n] == image) { + imageMap(row)(column) = n; + return ListView_SetImage(hwnd, imageList, row, column, n); + } + } + + //append and assign new image + imageMap(row)(column) = images.size(); + images.append(image); + ImageList_Append(imageList, image); + ListView_SetImage(hwnd, imageList, row, column, imageMap(row)(column)); +} + +void pListView::setSelected(bool selected) { + locked = true; + lostFocus = false; + if(selected == false) { + ListView_SetItemState(hwnd, -1, 0, LVIS_FOCUSED | LVIS_SELECTED); + } else { + setSelection(listView.state.selection); + } + locked = false; +} + +void pListView::setSelection(unsigned row) { + locked = true; + lostFocus = false; + ListView_SetItemState(hwnd, row, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); + locked = false; +} + +void pListView::constructor() { + lostFocus = false; + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", + WS_CHILD | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_NOCOLUMNHEADER, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&listView); + setDefaultFont(); + setHeaderText(listView.state.headerText); + setHeaderVisible(listView.state.headerVisible); + setCheckable(listView.state.checkable); + for(auto &text : listView.state.text) append(text); + for(unsigned n = 0; n < listView.state.checked.size(); n++) setChecked(n, listView.state.checked[n]); + buildImageList(); + if(listView.state.selected) setSelection(listView.state.selection); + autoSizeColumns(); + synchronize(); +} + +void pListView::destructor() { + DestroyWindow(hwnd); +} + +void pListView::orphan() { + destructor(); + constructor(); +} + +void pListView::setGeometry(const Geometry &geometry) { + pWidget::setGeometry(geometry); + autoSizeColumns(); +} + +void pListView::buildImageList() { + auto &list = listView.state.image; + unsigned columns = listView.state.text.size(); + unsigned rows = max(1u, listView.state.headerText.size()); + + ListView_SetImageList(hwnd, NULL, LVSIL_SMALL); + if(imageList) ImageList_Destroy(imageList); + imageList = ImageList_Create(15, 15, ILC_COLOR32, 1, 0); + + imageMap.reset(); + images.reset(); + images.append(nall::image()); //empty icon for cells without an image assigned (I_IMAGENONE does not work) + + //create a vector of unique images from all images used (many cells may use the same image) + for(unsigned y = 0; y < list.size(); y++) { + for(unsigned x = 0; x < list[y].size(); x++) { + bool found = false; + for(unsigned z = 0; z < images.size(); z++) { + if(list[y][x] == images[z]) { + found = true; + imageMap(y)(x) = z; + break; + } + } + + if(found == false) { + imageMap(y)(x) = images.size(); + images.append(list[y][x]); + } + } + } + + //build image list + for(auto &imageItem : images) ImageList_Append(imageList, imageItem); + if(images.size() <= 1) return; + + //set images for all cells + for(unsigned y = 0; y < columns; y++) { + for(unsigned x = 0; x < rows; x++) { + ListView_SetImage(hwnd, imageList, y, x, imageMap(y)(x)); + } + } +} diff --git a/ananke/phoenix/windows/widget/progress-bar.cpp b/ananke/phoenix/windows/widget/progress-bar.cpp new file mode 100644 index 00000000..f4703f1e --- /dev/null +++ b/ananke/phoenix/windows/widget/progress-bar.cpp @@ -0,0 +1,25 @@ +Geometry pProgressBar::minimumGeometry() { + return { 0, 0, 0, 23 }; +} + +void pProgressBar::setPosition(unsigned position) { + SendMessage(hwnd, PBM_SETPOS, (WPARAM)position, 0); +} + +void pProgressBar::constructor() { + hwnd = CreateWindow(PROGRESS_CLASS, L"", WS_CHILD | PBS_SMOOTH, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&progressBar); + SendMessage(hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); + SendMessage(hwnd, PBM_SETSTEP, MAKEWPARAM(1, 0), 0); + setPosition(progressBar.state.position); + synchronize(); +} + +void pProgressBar::destructor() { + DestroyWindow(hwnd); +} + +void pProgressBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/radio-box.cpp b/ananke/phoenix/windows/widget/radio-box.cpp new file mode 100644 index 00000000..ebcb1edc --- /dev/null +++ b/ananke/phoenix/windows/widget/radio-box.cpp @@ -0,0 +1,43 @@ +bool pRadioBox::checked() { + return SendMessage(hwnd, BM_GETCHECK, 0, 0); +} + +Geometry pRadioBox::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, radioBox.state.text); + return { 0, 0, geometry.width + 20, geometry.height + 4 }; +} + +void pRadioBox::setChecked() { + for(auto &item : radioBox.state.group) { + SendMessage(item.p.hwnd, BM_SETCHECK, (WPARAM)(&item == &radioBox), 0); + } +} + +void pRadioBox::setGroup(const set &group) { +} + +void pRadioBox::setText(const string &text) { + SetWindowText(hwnd, utf16_t(text)); +} + +void pRadioBox::constructor() { + hwnd = CreateWindow( + L"BUTTON", L"", + WS_CHILD | WS_TABSTOP | BS_RADIOBUTTON, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&radioBox); + setDefaultFont(); + if(radioBox.state.checked) setChecked(); + setText(radioBox.state.text); + synchronize(); +} + +void pRadioBox::destructor() { + DestroyWindow(hwnd); +} + +void pRadioBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/text-edit.cpp b/ananke/phoenix/windows/widget/text-edit.cpp new file mode 100644 index 00000000..8e1df517 --- /dev/null +++ b/ananke/phoenix/windows/widget/text-edit.cpp @@ -0,0 +1,58 @@ +void pTextEdit::setCursorPosition(unsigned position) { + if(position == ~0) position >>= 1; //Edit_SetSel takes signed type + Edit_SetSel(hwnd, position, position); + Edit_ScrollCaret(hwnd); +} + +void pTextEdit::setEditable(bool editable) { + SendMessage(hwnd, EM_SETREADONLY, editable == false, (LPARAM)0); +} + +void pTextEdit::setText(const string &text) { + locked = true; + string output = text; + output.replace("\r", ""); + output.replace("\n", "\r\n"); + SetWindowText(hwnd, utf16_t(output)); + locked = false; +} + +void pTextEdit::setWordWrap(bool wordWrap) { + //ES_AUTOHSCROLL cannot be changed after widget creation. + //As a result, we must destroy and re-create widget to change this setting. + orphan(); +} + +string pTextEdit::text() { + unsigned length = GetWindowTextLength(hwnd); + wchar_t buffer[length + 1]; + GetWindowText(hwnd, buffer, length + 1); + buffer[length] = 0; + string text = (const char*)utf8_t(buffer); + text.replace("\r", ""); + return text; +} + +void pTextEdit::constructor() { + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_TABSTOP | WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | (textEdit.state.wordWrap == false ? WS_HSCROLL | ES_AUTOHSCROLL : 0), + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&textEdit); + setDefaultFont(); + setCursorPosition(textEdit.state.cursorPosition); + setEditable(textEdit.state.editable); + setText(textEdit.state.text); + synchronize(); +} + +void pTextEdit::destructor() { + textEdit.state.text = text(); + DestroyWindow(hwnd); +} + +void pTextEdit::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/vertical-scroll-bar.cpp b/ananke/phoenix/windows/widget/vertical-scroll-bar.cpp new file mode 100644 index 00000000..dcc281f2 --- /dev/null +++ b/ananke/phoenix/windows/widget/vertical-scroll-bar.cpp @@ -0,0 +1,38 @@ +Geometry pVerticalScrollBar::minimumGeometry() { + return { 0, 0, 18, 0 }; +} + +unsigned pVerticalScrollBar::position() { + return GetScrollPos(hwnd, SB_CTL); +} + +void pVerticalScrollBar::setLength(unsigned length) { + length += (length == 0); + SetScrollRange(hwnd, SB_CTL, 0, length - 1, TRUE); + verticalScrollBar.setPosition(0); +} + +void pVerticalScrollBar::setPosition(unsigned position) { + SetScrollPos(hwnd, SB_CTL, position, TRUE); +} + +void pVerticalScrollBar::constructor() { + hwnd = CreateWindow( + L"SCROLLBAR", L"", WS_CHILD | SBS_VERT, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&verticalScrollBar); + unsigned position = verticalScrollBar.state.position; + setLength(verticalScrollBar.state.length); + verticalScrollBar.setPosition(position); + synchronize(); +} + +void pVerticalScrollBar::destructor() { + DestroyWindow(hwnd); +} + +void pVerticalScrollBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/vertical-slider.cpp b/ananke/phoenix/windows/widget/vertical-slider.cpp new file mode 100644 index 00000000..ac5cb1ce --- /dev/null +++ b/ananke/phoenix/windows/widget/vertical-slider.cpp @@ -0,0 +1,39 @@ +Geometry pVerticalSlider::minimumGeometry() { + return { 0, 0, 0, 25 }; +} + +unsigned pVerticalSlider::position() { + return SendMessage(hwnd, TBM_GETPOS, 0, 0); +} + +void pVerticalSlider::setLength(unsigned length) { + length += (length == 0); + SendMessage(hwnd, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(hwnd, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); + verticalSlider.setPosition(0); +} + +void pVerticalSlider::setPosition(unsigned position) { + SendMessage(hwnd, TBM_SETPOS, (WPARAM)true, (LPARAM)position); +} + +void pVerticalSlider::constructor() { + hwnd = CreateWindow( + TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_VERT, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&verticalSlider); + unsigned position = verticalSlider.state.position; + setLength(verticalSlider.state.length); + verticalSlider.setPosition(position); + synchronize(); +} + +void pVerticalSlider::destructor() { + DestroyWindow(hwnd); +} + +void pVerticalSlider::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/viewport.cpp b/ananke/phoenix/windows/widget/viewport.cpp new file mode 100644 index 00000000..c0b13b69 --- /dev/null +++ b/ananke/phoenix/windows/widget/viewport.cpp @@ -0,0 +1,57 @@ +static LRESULT CALLBACK Viewport_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Object *object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); + if(!dynamic_cast(object)) return DefWindowProc(hwnd, msg, wparam, lparam); + Viewport &viewport = (Viewport&)*object; + + if(msg == WM_GETDLGCODE) { + return DLGC_STATIC | DLGC_WANTCHARS; + } + + if(msg == WM_MOUSEMOVE) { + TRACKMOUSEEVENT tracker = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, hwnd }; + TrackMouseEvent(&tracker); + if(viewport.onMouseMove) viewport.onMouseMove({ (int16_t)LOWORD(lparam), (int16_t)HIWORD(lparam) }); + } + + if(msg == WM_MOUSELEAVE) { + if(viewport.onMouseLeave) viewport.onMouseLeave(); + } + + if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { + if(viewport.onMousePress) switch(msg) { + case WM_LBUTTONDOWN: viewport.onMousePress(Mouse::Button::Left); break; + case WM_MBUTTONDOWN: viewport.onMousePress(Mouse::Button::Middle); break; + case WM_RBUTTONDOWN: viewport.onMousePress(Mouse::Button::Right); break; + } + } + + if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) { + if(viewport.onMouseRelease) switch(msg) { + case WM_LBUTTONUP: viewport.onMouseRelease(Mouse::Button::Left); break; + case WM_MBUTTONUP: viewport.onMouseRelease(Mouse::Button::Middle); break; + case WM_RBUTTONUP: viewport.onMouseRelease(Mouse::Button::Right); break; + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +uintptr_t pViewport::handle() { + return (uintptr_t)hwnd; +} + +void pViewport::constructor() { + hwnd = CreateWindow(L"phoenix_viewport", L"", WS_CHILD | WS_DISABLED, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&viewport); + synchronize(); +} + +void pViewport::destructor() { + DestroyWindow(hwnd); +} + +void pViewport::orphan() { + destructor(); + constructor(); +} \ No newline at end of file diff --git a/ananke/phoenix/windows/widget/widget.cpp b/ananke/phoenix/windows/widget/widget.cpp new file mode 100644 index 00000000..b0e60c7d --- /dev/null +++ b/ananke/phoenix/windows/widget/widget.cpp @@ -0,0 +1,66 @@ +bool pWidget::enabled() { + return IsWindowEnabled(hwnd); +} + +Geometry pWidget::minimumGeometry() { + return { 0, 0, 0, 0 }; +} + +void pWidget::setEnabled(bool enabled) { + if(widget.state.abstract) enabled = false; + if(sizable.state.layout && sizable.state.layout->enabled() == false) enabled = false; + EnableWindow(hwnd, enabled); +} + +void pWidget::setFocused() { + SetFocus(hwnd); +} + +void pWidget::setFont(const string &font) { + if(hfont) DeleteObject(hfont); + hfont = pFont::create(font); + SendMessage(hwnd, WM_SETFONT, (WPARAM)hfont, 0); +} + +void pWidget::setGeometry(const Geometry &geometry) { + SetWindowPos(hwnd, NULL, geometry.x, geometry.y, geometry.width, geometry.height, SWP_NOZORDER); +} + +void pWidget::setVisible(bool visible) { + if(widget.state.abstract) visible = false; + if(sizable.state.layout && sizable.state.layout->visible() == false) visible = false; + ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); +} + +void pWidget::constructor() { + hfont = pFont::create("Tahoma, 8"); + if(widget.state.abstract) { + hwnd = CreateWindow(L"phoenix_label", L"", WS_CHILD, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&widget); + } +} + +void pWidget::destructor() { + if(widget.state.abstract) { + DestroyWindow(hwnd); + } +} + +void pWidget::orphan() { + destructor(); + constructor(); +} + +void pWidget::setDefaultFont() { + string description = widget.state.font; + if(description == "") description = "Tahoma, 8"; + hfont = pFont::create(description); + SendMessage(hwnd, WM_SETFONT, (WPARAM)hfont, 0); +} + +//calling Widget::setParent destroys widget and re-creates it: +//need to re-apply visiblity and enabled status; called by each subclassed setParent() function +void pWidget::synchronize() { + widget.setEnabled(widget.enabled()); + widget.setVisible(widget.visible()); +} diff --git a/ananke/phoenix/windows/window.cpp b/ananke/phoenix/windows/window.cpp new file mode 100644 index 00000000..dc8aea28 --- /dev/null +++ b/ananke/phoenix/windows/window.cpp @@ -0,0 +1,229 @@ +vector pWindow::modal; + +void pWindow::updateModality() { + for(auto &object : pObject::objects) { + if(dynamic_cast(object) == nullptr) continue; + pWindow *p = (pWindow*)object; + if(modal.size() == 0) EnableWindow(p->hwnd, true); + else EnableWindow(p->hwnd, modal.find(p)); + } +} + +static const unsigned FixedStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER; +static const unsigned ResizableStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME; + +Window& pWindow::none() { + static Window *window = nullptr; + if(window == nullptr) window = new Window; + return *window; +} + +void pWindow::append(Layout &layout) { + Geometry geom = window.state.geometry; + geom.x = geom.y = 0; + layout.setGeometry(geom); +} + +void pWindow::append(Menu &menu) { + menu.p.parentWindow = &window; + updateMenu(); +} + +void pWindow::append(Widget &widget) { + widget.p.parentWindow = &window; + widget.p.orphan(); + if(widget.state.font != "") widget.p.setFont(widget.state.font); + else if(window.state.widgetFont != "") widget.p.setFont(window.state.widgetFont); + else widget.p.setFont("Tahoma, 8"); +} + +Color pWindow::backgroundColor() { + if(window.state.backgroundColorOverride) return window.state.backgroundColor; + DWORD color = GetSysColor(COLOR_3DFACE); + return { (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color >> 0), 255 }; +} + +bool pWindow::focused() { + return (GetForegroundWindow() == hwnd); +} + +Geometry pWindow::frameMargin() { + unsigned style = window.state.resizable ? ResizableStyle : FixedStyle; + if(window.state.fullScreen) style = 0; + RECT rc = { 0, 0, 640, 480 }; + AdjustWindowRect(&rc, style, window.state.menuVisible); + unsigned statusHeight = 0; + if(window.state.statusVisible) { + RECT src; + GetClientRect(hstatus, &src); + statusHeight = src.bottom - src.top; + } + return { abs(rc.left), abs(rc.top), (rc.right - rc.left) - 640, (rc.bottom - rc.top) + statusHeight - 480 }; +} + +Geometry pWindow::geometry() { + Geometry margin = frameMargin(); + + RECT rc; + if(IsIconic(hwnd)) { + //GetWindowRect returns -32000(x),-32000(y) when window is minimized + WINDOWPLACEMENT wp; + GetWindowPlacement(hwnd, &wp); + rc = wp.rcNormalPosition; + } else { + GetWindowRect(hwnd, &rc); + } + + signed x = rc.left + margin.x; + signed y = rc.top + margin.y; + unsigned width = (rc.right - rc.left) - margin.width; + unsigned height = (rc.bottom - rc.top) - margin.height; + + return { x, y, width, height }; +} + +void pWindow::remove(Layout &layout) { +} + +void pWindow::remove(Menu &menu) { + updateMenu(); +} + +void pWindow::remove(Widget &widget) { + widget.p.orphan(); +} + +void pWindow::setBackgroundColor(const Color &color) { + if(brush) DeleteObject(brush); + brushColor = RGB(color.red, color.green, color.blue); + brush = CreateSolidBrush(brushColor); +} + +void pWindow::setFocused() { + if(window.state.visible == false) setVisible(true); + SetFocus(hwnd); +} + +void pWindow::setFullScreen(bool fullScreen) { + locked = true; + if(fullScreen == false) { + SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | (window.state.resizable ? ResizableStyle : FixedStyle)); + setGeometry(window.state.geometry); + } else { + SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP); + Geometry margin = frameMargin(); + setGeometry({ margin.x, margin.y, GetSystemMetrics(SM_CXSCREEN) - margin.width, GetSystemMetrics(SM_CYSCREEN) - margin.height }); + } + locked = false; +} + +void pWindow::setGeometry(const Geometry &geometry) { + locked = true; + Geometry margin = frameMargin(); + SetWindowPos( + hwnd, NULL, + geometry.x - margin.x, geometry.y - margin.y, + geometry.width + margin.width, geometry.height + margin.height, + SWP_NOZORDER | SWP_FRAMECHANGED + ); + SetWindowPos(hstatus, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); + for(auto &layout : window.state.layout) { + Geometry geom = this->geometry(); + geom.x = geom.y = 0; + layout.setGeometry(geom); + } + locked = false; +} + +void pWindow::setMenuFont(const string &font) { +} + +void pWindow::setMenuVisible(bool visible) { + locked = true; + SetMenu(hwnd, visible ? hmenu : 0); + setGeometry(window.state.geometry); + locked = false; +} + +void pWindow::setModal(bool modality) { + if(modality == false) { + if(auto position = modal.find(this)) modal.remove(position()); + } else { + modal.appendonce(this); + } + updateModality(); +} + +void pWindow::setResizable(bool resizable) { + SetWindowLongPtr(hwnd, GWL_STYLE, window.state.resizable ? ResizableStyle : FixedStyle); + setGeometry(window.state.geometry); +} + +void pWindow::setStatusFont(const string &font) { + if(hstatusfont) DeleteObject(hstatusfont); + hstatusfont = pFont::create(font); + SendMessage(hstatus, WM_SETFONT, (WPARAM)hstatusfont, 0); +} + +void pWindow::setStatusText(const string &text) { + SendMessage(hstatus, SB_SETTEXT, 0, (LPARAM)(wchar_t*)utf16_t(text)); +} + +void pWindow::setStatusVisible(bool visible) { + locked = true; + ShowWindow(hstatus, visible ? SW_SHOWNORMAL : SW_HIDE); + setGeometry(window.state.geometry); + locked = false; +} + +void pWindow::setTitle(const string &text) { + SetWindowText(hwnd, utf16_t(text)); +} + +void pWindow::setVisible(bool visible) { + ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); + if(visible == false) setModal(false); +} + +void pWindow::setWidgetFont(const string &font) { + for(auto &widget : window.state.widget) { + if(widget.state.font == "") widget.setFont(font); + } +} + +void pWindow::constructor() { + brush = 0; + + hwnd = CreateWindow(L"phoenix_window", L"", ResizableStyle, 128, 128, 256, 256, 0, 0, GetModuleHandle(0), 0); + hmenu = CreateMenu(); + hstatus = CreateWindow(STATUSCLASSNAME, L"", WS_CHILD, 0, 0, 0, 0, hwnd, 0, GetModuleHandle(0), 0); + hstatusfont = 0; + setStatusFont("Tahoma, 8"); + + //status bar will be capable of receiving tab focus if it is not disabled + SetWindowLongPtr(hstatus, GWL_STYLE, GetWindowLong(hstatus, GWL_STYLE) | WS_DISABLED); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&window); + setGeometry({ 128, 128, 256, 256 }); +} + +void pWindow::destructor() { + DeleteObject(hstatusfont); + DestroyWindow(hstatus); + DestroyMenu(hmenu); + DestroyWindow(hwnd); +} + +void pWindow::updateMenu() { + if(hmenu) DestroyMenu(hmenu); + hmenu = CreateMenu(); + + for(auto &menu : window.state.menu) { + menu.p.update(window); + if(menu.visible()) { + AppendMenu(hmenu, MF_STRING | MF_POPUP, (UINT_PTR)menu.p.hmenu, utf16_t(menu.state.text)); + } + } + + SetMenu(hwnd, window.state.menuVisible ? hmenu : 0); +} diff --git a/higan/Makefile b/higan/Makefile index ff1c8fd0..73b1a562 100755 --- a/higan/Makefile +++ b/higan/Makefile @@ -34,8 +34,7 @@ endif # platform ifeq ($(platform),x) flags += -march=native - link += -ldl -lX11 -lXext -else ifeq ($(platform),osx) + link += -Wl,-export-dynamic -ldl -lX11 -lXext else ifeq ($(platform),win) ifeq ($(arch),win32) flags += -m32 @@ -82,13 +81,14 @@ clean: -@$(call delete,obj/*.dylib) -@$(call delete,obj/*.dll) -@$(call delete,*.res) - -@$(call delete,*.pgd) - -@$(call delete,*.pgc) - -@$(call delete,*.ilk) - -@$(call delete,*.pdb) -@$(call delete,*.manifest) +archive: + if [ -f higan.tar.xz ]; then rm higan.tar.xz; fi + tar -cJf higan.tar.xz `ls` + sync: +ifeq ($(shell id -un),byuu) if [ -d ./libco ]; then rm -r ./libco; fi if [ -d ./nall ]; then rm -r ./nall; fi if [ -d ./ruby ]; then rm -r ./ruby; fi @@ -103,9 +103,6 @@ sync: rm -r ruby/_test rm -r phoenix/nall rm -r phoenix/test - -archive: - if [ -f higan.tar.xz ]; then rm higan.tar.xz; fi - tar -cJf higan.tar.xz `ls` +endif help:; diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index a096703f..26c15ccf 100755 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { static const char Name[] = "higan"; - static const char Version[] = "091.10"; + static const char Version[] = "091.11"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; } diff --git a/higan/emulator/interface.hpp b/higan/emulator/interface.hpp index efef6592..1fce96f3 100755 --- a/higan/emulator/interface.hpp +++ b/higan/emulator/interface.hpp @@ -56,8 +56,8 @@ struct Interface { virtual void audioSample(int16_t, int16_t) {} virtual int16_t inputPoll(unsigned, unsigned, unsigned) { return 0; } virtual unsigned dipSettings(const Markup::Node&) { return 0; } - virtual string scoreServer() { return ""; } virtual string path(unsigned) { return ""; } + virtual string server() { return ""; } virtual void notify(const string &text) { print(text, "\n"); } } *bind; @@ -70,8 +70,8 @@ struct Interface { void audioSample(int16_t lsample, int16_t rsample) { return bind->audioSample(lsample, rsample); } int16_t inputPoll(unsigned port, unsigned device, unsigned input) { return bind->inputPoll(port, device, input); } unsigned dipSettings(const Markup::Node &node) { return bind->dipSettings(node); } - string scoreServer() { return bind->scoreServer(); } string path(unsigned group) { return bind->path(group); } + string server() { return bind->server(); } template void notify(Args&... args) { return bind->notify({std::forward(args)...}); } //information diff --git a/higan/nall/beat/archive.hpp b/higan/nall/beat/archive.hpp new file mode 100644 index 00000000..ef7294cf --- /dev/null +++ b/higan/nall/beat/archive.hpp @@ -0,0 +1,84 @@ +#ifndef NALL_BEAT_ARCHIVE_HPP +#define NALL_BEAT_ARCHIVE_HPP + +#include + +namespace nall { + +struct beatArchive : beatBase { + bool create(const string &beatname, string pathname, const string &metadata = "") { + if(fp.open(beatname, file::mode::write) == false) return false; + if(pathname.endswith("/") == false) pathname.append("/"); + + checksum = ~0; + writeString("BPA1"); + writeNumber(metadata.length()); + writeString(metadata); + + lstring list; + ls(list, pathname, pathname); + for(auto &name : list) { + if(name.endswith("/")) { + name.rtrim<1>("/"); + writeNumber(0 | ((name.length() - 1) << 1)); + writeString(name); + } else { + file stream; + if(stream.open({pathname, name}, file::mode::read) == false) return false; + writeNumber(1 | ((name.length() - 1) << 1)); + writeString(name); + unsigned size = stream.size(); + writeNumber(size); + uint32_t checksum = ~0; + while(size--) { + uint8_t data = stream.read(); + write(data); + checksum = crc32_adjust(checksum, data); + } + writeChecksum(~checksum); + } + } + + writeChecksum(~checksum); + fp.close(); + return true; + } + + bool unpack(const string &beatname, string pathname) { + if(fp.open(beatname, file::mode::read) == false) return false; + if(pathname.endswith("/") == false) pathname.append("/"); + + checksum = ~0; + if(readString(4) != "BPA1") return false; + unsigned length = readNumber(); + while(length--) read(); + + directory::create(pathname); + while(fp.offset() < fp.size() - 4) { + unsigned data = readNumber(); + string name = readString((data >> 1) + 1); + if(name.position("\\") || name.position("../")) return false; //block path exploits + + if((data & 1) == 0) { + directory::create({pathname, name}); + } else { + file stream; + if(stream.open({pathname, name}, file::mode::write) == false) return false; + unsigned size = readNumber(); + uint32_t checksum = ~0; + while(size--) { + uint8_t data = read(); + stream.write(data); + checksum = crc32_adjust(checksum, data); + } + if(readChecksum(~checksum) == false) return false; + } + } + + return readChecksum(~checksum); + } +}; + +} + +#endif diff --git a/higan/nall/beat/base.hpp b/higan/nall/beat/base.hpp new file mode 100644 index 00000000..8e0001be --- /dev/null +++ b/higan/nall/beat/base.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_BEAT_BASE_HPP +#define NALL_BEAT_BASE_HPP + +namespace nall { + +struct beatBase { +protected: + file fp; + uint32_t checksum; + + void ls(lstring &list, const string &path, const string &basepath) { + lstring paths = directory::folders(path); + for(auto &pathname : paths) { + list.append(string{path, pathname}.ltrim<1>(basepath)); + ls(list, {path, pathname}, basepath); + } + + lstring files = directory::files(path); + for(auto &filename : files) { + list.append(string{path, filename}.ltrim<1>(basepath)); + } + } + + void write(uint8_t data) { + fp.write(data); + checksum = crc32_adjust(checksum, data); + } + + void writeNumber(uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) return write(0x80 | x); + write(x); + data--; + } + } + + void writeString(const string &text) { + unsigned length = text.length(); + for(unsigned n = 0; n < length; n++) write(text[n]); + } + + void writeChecksum(uint32_t checksum) { + write(checksum >> 0); + write(checksum >> 8); + write(checksum >> 16); + write(checksum >> 24); + } + + uint8_t read() { + uint8_t data = fp.read(); + checksum = crc32_adjust(checksum, data); + return data; + } + + uint64_t readNumber() { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + } + + string readString(unsigned length) { + string text; + text.reserve(length + 1); + for(unsigned n = 0; n < length; n++) { + text[n] = fp.read(); + checksum = crc32_adjust(checksum, text[n]); + } + text[length] = 0; + return text; + } + + bool readChecksum(uint32_t source) { + uint32_t checksum = 0; + checksum |= read() << 0; + checksum |= read() << 8; + checksum |= read() << 16; + checksum |= read() << 24; + return checksum == source; + } +}; + +} + +#endif diff --git a/higan/nall/beat/delta.hpp b/higan/nall/beat/delta.hpp new file mode 100644 index 00000000..ce120537 --- /dev/null +++ b/higan/nall/beat/delta.hpp @@ -0,0 +1,214 @@ +#ifndef NALL_BEAT_DELTA_HPP +#define NALL_BEAT_DELTA_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpsdelta { + inline void source(const uint8_t *data, unsigned size); + inline void target(const uint8_t *data, unsigned size); + + inline bool source(const string &filename); + inline bool target(const string &filename); + inline bool create(const string &filename, const string &metadata = ""); + +protected: + enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy }; + enum : unsigned { Granularity = 1 }; + + struct Node { + unsigned offset; + Node *next; + inline Node() : offset(0), next(nullptr) {} + inline ~Node() { if(next) delete next; } + }; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + + filemap targetFile; + const uint8_t *targetData; + unsigned targetSize; +}; + +void bpsdelta::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +void bpsdelta::target(const uint8_t *data, unsigned size) { + targetData = data; + targetSize = size; +} + +bool bpsdelta::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + source(sourceFile.data(), sourceFile.size()); + return true; +} + +bool bpsdelta::target(const string &filename) { + if(targetFile.open(filename, filemap::mode::read) == false) return false; + target(targetFile.data(), targetFile.size()); + return true; +} + +bool bpsdelta::create(const string &filename, const string &metadata) { + file modifyFile; + if(modifyFile.open(filename, file::mode::write) == false) return false; + + uint32_t sourceChecksum = ~0, modifyChecksum = ~0; + unsigned sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0; + + auto write = [&](uint8_t data) { + modifyFile.write(data); + modifyChecksum = crc32_adjust(modifyChecksum, data); + }; + + auto encode = [&](uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + }; + + write('B'); + write('P'); + write('S'); + write('1'); + + encode(sourceSize); + encode(targetSize); + + unsigned markupSize = metadata.length(); + encode(markupSize); + for(unsigned n = 0; n < markupSize; n++) write(metadata[n]); + + Node *sourceTree[65536], *targetTree[65536]; + for(unsigned n = 0; n < 65536; n++) sourceTree[n] = 0, targetTree[n] = 0; + + //source tree creation + for(unsigned offset = 0; offset < sourceSize; offset++) { + uint16_t symbol = sourceData[offset + 0]; + sourceChecksum = crc32_adjust(sourceChecksum, symbol); + if(offset < sourceSize - 1) symbol |= sourceData[offset + 1] << 8; + Node *node = new Node; + node->offset = offset; + node->next = sourceTree[symbol]; + sourceTree[symbol] = node; + } + + unsigned targetReadLength = 0; + + auto targetReadFlush = [&]() { + if(targetReadLength) { + encode(TargetRead | ((targetReadLength - 1) << 2)); + unsigned offset = outputOffset - targetReadLength; + while(targetReadLength) write(targetData[offset++]), targetReadLength--; + } + }; + + while(outputOffset < targetSize) { + unsigned maxLength = 0, maxOffset = 0, mode = TargetRead; + + uint16_t symbol = targetData[outputOffset + 0]; + if(outputOffset < targetSize - 1) symbol |= targetData[outputOffset + 1] << 8; + + { //source read + unsigned length = 0, offset = outputOffset; + while(offset < sourceSize && offset < targetSize && sourceData[offset] == targetData[offset]) { + length++; + offset++; + } + if(length > maxLength) maxLength = length, mode = SourceRead; + } + + { //source copy + Node *node = sourceTree[symbol]; + while(node) { + unsigned length = 0, x = node->offset, y = outputOffset; + while(x < sourceSize && y < targetSize && sourceData[x++] == targetData[y++]) length++; + if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = SourceCopy; + node = node->next; + } + } + + { //target copy + Node *node = targetTree[symbol]; + while(node) { + unsigned length = 0, x = node->offset, y = outputOffset; + while(y < targetSize && targetData[x++] == targetData[y++]) length++; + if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = TargetCopy; + node = node->next; + } + + //target tree append + node = new Node; + node->offset = outputOffset; + node->next = targetTree[symbol]; + targetTree[symbol] = node; + } + + { //target read + if(maxLength < 4) { + maxLength = min((unsigned)Granularity, targetSize - outputOffset); + mode = TargetRead; + } + } + + if(mode != TargetRead) targetReadFlush(); + + switch(mode) { + case SourceRead: + encode(SourceRead | ((maxLength - 1) << 2)); + break; + case TargetRead: + //delay write to group sequential TargetRead commands into one + targetReadLength += maxLength; + break; + case SourceCopy: + case TargetCopy: + encode(mode | ((maxLength - 1) << 2)); + signed relativeOffset; + if(mode == SourceCopy) { + relativeOffset = maxOffset - sourceRelativeOffset; + sourceRelativeOffset = maxOffset + maxLength; + } else { + relativeOffset = maxOffset - targetRelativeOffset; + targetRelativeOffset = maxOffset + maxLength; + } + encode((relativeOffset < 0) | (abs(relativeOffset) << 1)); + break; + } + + outputOffset += maxLength; + } + + targetReadFlush(); + + sourceChecksum = ~sourceChecksum; + for(unsigned n = 0; n < 32; n += 8) write(sourceChecksum >> n); + uint32_t targetChecksum = crc32_calculate(targetData, targetSize); + for(unsigned n = 0; n < 32; n += 8) write(targetChecksum >> n); + uint32_t outputChecksum = ~modifyChecksum; + for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n); + + modifyFile.close(); + return true; +} + +} + +#endif diff --git a/higan/nall/beat/linear.hpp b/higan/nall/beat/linear.hpp new file mode 100644 index 00000000..078aae34 --- /dev/null +++ b/higan/nall/beat/linear.hpp @@ -0,0 +1,152 @@ +#ifndef NALL_BEAT_LINEAR_HPP +#define NALL_BEAT_LINEAR_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpslinear { + inline void source(const uint8_t *data, unsigned size); + inline void target(const uint8_t *data, unsigned size); + + inline bool source(const string &filename); + inline bool target(const string &filename); + inline bool create(const string &filename, const string &metadata = ""); + +protected: + enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy }; + enum : unsigned { Granularity = 1 }; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + + filemap targetFile; + const uint8_t *targetData; + unsigned targetSize; +}; + +void bpslinear::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +void bpslinear::target(const uint8_t *data, unsigned size) { + targetData = data; + targetSize = size; +} + +bool bpslinear::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + source(sourceFile.data(), sourceFile.size()); + return true; +} + +bool bpslinear::target(const string &filename) { + if(targetFile.open(filename, filemap::mode::read) == false) return false; + target(targetFile.data(), targetFile.size()); + return true; +} + +bool bpslinear::create(const string &filename, const string &metadata) { + file modifyFile; + if(modifyFile.open(filename, file::mode::write) == false) return false; + + uint32_t modifyChecksum = ~0; + unsigned targetRelativeOffset = 0, outputOffset = 0; + + auto write = [&](uint8_t data) { + modifyFile.write(data); + modifyChecksum = crc32_adjust(modifyChecksum, data); + }; + + auto encode = [&](uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + }; + + unsigned targetReadLength = 0; + + auto targetReadFlush = [&]() { + if(targetReadLength) { + encode(TargetRead | ((targetReadLength - 1) << 2)); + unsigned offset = outputOffset - targetReadLength; + while(targetReadLength) write(targetData[offset++]), targetReadLength--; + } + }; + + write('B'); + write('P'); + write('S'); + write('1'); + + encode(sourceSize); + encode(targetSize); + + unsigned markupSize = metadata.length(); + encode(markupSize); + for(unsigned n = 0; n < markupSize; n++) write(metadata[n]); + + while(outputOffset < targetSize) { + unsigned sourceLength = 0; + for(unsigned n = 0; outputOffset + n < min(sourceSize, targetSize); n++) { + if(sourceData[outputOffset + n] != targetData[outputOffset + n]) break; + sourceLength++; + } + + unsigned rleLength = 0; + for(unsigned n = 1; outputOffset + n < targetSize; n++) { + if(targetData[outputOffset] != targetData[outputOffset + n]) break; + rleLength++; + } + + if(rleLength >= 4) { + //write byte to repeat + targetReadLength++; + outputOffset++; + targetReadFlush(); + + //copy starting from repetition byte + encode(TargetCopy | ((rleLength - 1) << 2)); + unsigned relativeOffset = (outputOffset - 1) - targetRelativeOffset; + encode(relativeOffset << 1); + outputOffset += rleLength; + targetRelativeOffset = outputOffset - 1; + } else if(sourceLength >= 4) { + targetReadFlush(); + encode(SourceRead | ((sourceLength - 1) << 2)); + outputOffset += sourceLength; + } else { + targetReadLength += Granularity; + outputOffset += Granularity; + } + } + + targetReadFlush(); + + uint32_t sourceChecksum = crc32_calculate(sourceData, sourceSize); + for(unsigned n = 0; n < 32; n += 8) write(sourceChecksum >> n); + uint32_t targetChecksum = crc32_calculate(targetData, targetSize); + for(unsigned n = 0; n < 32; n += 8) write(targetChecksum >> n); + uint32_t outputChecksum = ~modifyChecksum; + for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n); + + modifyFile.close(); + return true; +} + +} + +#endif diff --git a/higan/nall/beat/metadata.hpp b/higan/nall/beat/metadata.hpp new file mode 100644 index 00000000..58e6ab0a --- /dev/null +++ b/higan/nall/beat/metadata.hpp @@ -0,0 +1,121 @@ +#ifndef NALL_BEAT_METADATA_HPP +#define NALL_BEAT_METADATA_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpsmetadata { + inline bool load(const string &filename); + inline bool save(const string &filename, const string &metadata); + inline string metadata() const; + +protected: + file sourceFile; + string metadataString; +}; + +bool bpsmetadata::load(const string &filename) { + if(sourceFile.open(filename, file::mode::read) == false) return false; + + auto read = [&]() -> uint8_t { + return sourceFile.read(); + }; + + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + if(read() != 'B') return false; + if(read() != 'P') return false; + if(read() != 'S') return false; + if(read() != '1') return false; + decode(); + decode(); + unsigned metadataSize = decode(); + char data[metadataSize + 1]; + for(unsigned n = 0; n < metadataSize; n++) data[n] = read(); + data[metadataSize] = 0; + metadataString = (const char*)data; + + return true; +} + +bool bpsmetadata::save(const string &filename, const string &metadata) { + file targetFile; + if(targetFile.open(filename, file::mode::write) == false) return false; + if(sourceFile.open() == false) return false; + sourceFile.seek(0); + + auto read = [&]() -> uint8_t { + return sourceFile.read(); + }; + + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + uint32_t checksum = ~0; + + auto write = [&](uint8_t data) { + targetFile.write(data); + checksum = crc32_adjust(checksum, data); + }; + + auto encode = [&](uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + }; + + for(unsigned n = 0; n < 4; n++) write(read()); + encode(decode()); + encode(decode()); + unsigned sourceLength = decode(); + unsigned targetLength = metadata.length(); + encode(targetLength); + sourceFile.seek(sourceLength, file::index::relative); + for(unsigned n = 0; n < targetLength; n++) write(metadata[n]); + unsigned length = sourceFile.size() - sourceFile.offset() - 4; + for(unsigned n = 0; n < length; n++) write(read()); + uint32_t outputChecksum = ~checksum; + for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n); + + targetFile.close(); + return true; +} + +string bpsmetadata::metadata() const { + return metadataString; +} + +} + +#endif diff --git a/higan/nall/beat/multi.hpp b/higan/nall/beat/multi.hpp new file mode 100644 index 00000000..cddf5d6b --- /dev/null +++ b/higan/nall/beat/multi.hpp @@ -0,0 +1,242 @@ +#ifndef NALL_BEAT_MULTI_HPP +#define NALL_BEAT_MULTI_HPP + +#include +#include +#include + +namespace nall { + +struct bpsmulti { + enum : unsigned { + CreatePath = 0, + CreateFile = 1, + ModifyFile = 2, + MirrorFile = 3, + }; + + enum : unsigned { + OriginSource = 0, + OriginTarget = 1, + }; + + bool create(const string &patchName, const string &sourcePath, const string &targetPath, bool delta = false, const string &metadata = "") { + if(fp.open()) fp.close(); + fp.open(patchName, file::mode::write); + checksum = ~0; + + writeString("BPM1"); //signature + writeNumber(metadata.length()); + writeString(metadata); + + lstring sourceList, targetList; + ls(sourceList, sourcePath, sourcePath); + ls(targetList, targetPath, targetPath); + + for(auto &targetName : targetList) { + if(targetName.endswith("/")) { + targetName.rtrim<1>("/"); + writeNumber(CreatePath | ((targetName.length() - 1) << 2)); + writeString(targetName); + } else if(auto position = sourceList.find(targetName)) { //if sourceName == targetName + file sp, dp; + sp.open({sourcePath, targetName}, file::mode::read); + dp.open({targetPath, targetName}, file::mode::read); + + bool identical = sp.size() == dp.size(); + uint32_t cksum = ~0; + + for(unsigned n = 0; n < sp.size(); n++) { + uint8_t byte = sp.read(); + if(identical && byte != dp.read()) identical = false; + cksum = crc32_adjust(cksum, byte); + } + + if(identical) { + writeNumber(MirrorFile | ((targetName.length() - 1) << 2)); + writeString(targetName); + writeNumber(OriginSource); + writeChecksum(~cksum); + } else { + writeNumber(ModifyFile | ((targetName.length() - 1) << 2)); + writeString(targetName); + writeNumber(OriginSource); + + if(delta == false) { + bpslinear patch; + patch.source({sourcePath, targetName}); + patch.target({targetPath, targetName}); + patch.create({temppath(), "temp.bps"}); + } else { + bpsdelta patch; + patch.source({sourcePath, targetName}); + patch.target({targetPath, targetName}); + patch.create({temppath(), "temp.bps"}); + } + + auto buffer = file::read({temppath(), "temp.bps"}); + writeNumber(buffer.size()); + for(auto &byte : buffer) write(byte); + } + } else { + writeNumber(CreateFile | ((targetName.length() - 1) << 2)); + writeString(targetName); + auto buffer = file::read({targetPath, targetName}); + writeNumber(buffer.size()); + for(auto &byte : buffer) write(byte); + writeChecksum(crc32_calculate(buffer.data(), buffer.size())); + } + } + + //checksum + writeChecksum(~checksum); + fp.close(); + return true; + } + + bool apply(const string &patchName, const string &sourcePath, const string &targetPath) { + directory::remove(targetPath); //start with a clean directory + directory::create(targetPath); + + if(fp.open()) fp.close(); + fp.open(patchName, file::mode::read); + checksum = ~0; + + if(readString(4) != "BPM1") return false; + auto metadataLength = readNumber(); + while(metadataLength--) read(); + + while(fp.offset() < fp.size() - 4) { + auto encoding = readNumber(); + unsigned action = encoding & 3; + unsigned targetLength = (encoding >> 2) + 1; + string targetName = readString(targetLength); + + if(action == CreatePath) { + directory::create({targetPath, targetName, "/"}); + } else if(action == CreateFile) { + file fp; + fp.open({targetPath, targetName}, file::mode::write); + auto fileSize = readNumber(); + while(fileSize--) fp.write(read()); + uint32_t cksum = readChecksum(); + } else if(action == ModifyFile) { + auto encoding = readNumber(); + string originPath = encoding & 1 ? targetPath : sourcePath; + string sourceName = (encoding >> 1) == 0 ? targetName : readString(encoding >> 1); + auto patchSize = readNumber(); + vector buffer; + buffer.resize(patchSize); + for(unsigned n = 0; n < patchSize; n++) buffer[n] = read(); + bpspatch patch; + patch.modify(buffer.data(), buffer.size()); + patch.source({originPath, sourceName}); + patch.target({targetPath, targetName}); + if(patch.apply() != bpspatch::result::success) return false; + } else if(action == MirrorFile) { + auto encoding = readNumber(); + string originPath = encoding & 1 ? targetPath : sourcePath; + string sourceName = (encoding >> 1) == 0 ? targetName : readString(encoding >> 1); + file::copy({originPath, sourceName}, {targetPath, targetName}); + uint32_t cksum = readChecksum(); + } + } + + uint32_t cksum = ~checksum; + if(read() != (uint8_t)(cksum >> 0)) return false; + if(read() != (uint8_t)(cksum >> 8)) return false; + if(read() != (uint8_t)(cksum >> 16)) return false; + if(read() != (uint8_t)(cksum >> 24)) return false; + + fp.close(); + return true; + } + +protected: + file fp; + uint32_t checksum; + + //create() functions + void ls(lstring &list, const string &path, const string &basepath) { + lstring paths = directory::folders(path); + for(auto &pathname : paths) { + list.append(string{path, pathname}.ltrim<1>(basepath)); + ls(list, {path, pathname}, basepath); + } + + lstring files = directory::files(path); + for(auto &filename : files) { + list.append(string{path, filename}.ltrim<1>(basepath)); + } + } + + void write(uint8_t data) { + fp.write(data); + checksum = crc32_adjust(checksum, data); + } + + void writeNumber(uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + } + + void writeString(const string &text) { + unsigned length = text.length(); + for(unsigned n = 0; n < length; n++) write(text[n]); + } + + void writeChecksum(uint32_t cksum) { + write(cksum >> 0); + write(cksum >> 8); + write(cksum >> 16); + write(cksum >> 24); + } + + //apply() functions + uint8_t read() { + uint8_t data = fp.read(); + checksum = crc32_adjust(checksum, data); + return data; + } + + uint64_t readNumber() { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + } + + string readString(unsigned length) { + string text; + text.reserve(length + 1); + for(unsigned n = 0; n < length; n++) text[n] = read(); + text[length] = 0; + return text; + } + + uint32_t readChecksum() { + uint32_t checksum = 0; + checksum |= read() << 0; + checksum |= read() << 8; + checksum |= read() << 16; + checksum |= read() << 24; + return checksum; + } +}; + +} + +#endif diff --git a/higan/nall/beat/patch.hpp b/higan/nall/beat/patch.hpp new file mode 100644 index 00000000..8c6de75b --- /dev/null +++ b/higan/nall/beat/patch.hpp @@ -0,0 +1,219 @@ +#ifndef NALL_BEAT_PATCH_HPP +#define NALL_BEAT_PATCH_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpspatch { + inline bool modify(const uint8_t *data, unsigned size); + inline void source(const uint8_t *data, unsigned size); + inline void target(uint8_t *data, unsigned size); + + inline bool modify(const string &filename); + inline bool source(const string &filename); + inline bool target(const string &filename); + + inline string metadata() const; + inline unsigned size() const; + + enum result : unsigned { + unknown, + success, + patch_too_small, + patch_invalid_header, + source_too_small, + target_too_small, + source_checksum_invalid, + target_checksum_invalid, + patch_checksum_invalid, + }; + + inline result apply(); + +protected: + enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy }; + + filemap modifyFile; + const uint8_t *modifyData; + unsigned modifySize; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + + filemap targetFile; + uint8_t *targetData; + unsigned targetSize; + + unsigned modifySourceSize; + unsigned modifyTargetSize; + unsigned modifyMarkupSize; + string metadataString; +}; + +bool bpspatch::modify(const uint8_t *data, unsigned size) { + if(size < 19) return false; + modifyData = data; + modifySize = size; + + unsigned offset = 4; + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = modifyData[offset++]; + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + modifySourceSize = decode(); + modifyTargetSize = decode(); + modifyMarkupSize = decode(); + + char buffer[modifyMarkupSize + 1]; + for(unsigned n = 0; n < modifyMarkupSize; n++) buffer[n] = modifyData[offset++]; + buffer[modifyMarkupSize] = 0; + metadataString = (const char*)buffer; + + return true; +} + +void bpspatch::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +void bpspatch::target(uint8_t *data, unsigned size) { + targetData = data; + targetSize = size; +} + +bool bpspatch::modify(const string &filename) { + if(modifyFile.open(filename, filemap::mode::read) == false) return false; + return modify(modifyFile.data(), modifyFile.size()); +} + +bool bpspatch::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + source(sourceFile.data(), sourceFile.size()); + return true; +} + +bool bpspatch::target(const string &filename) { + file fp; + if(fp.open(filename, file::mode::write) == false) return false; + fp.truncate(modifyTargetSize); + fp.close(); + + if(targetFile.open(filename, filemap::mode::readwrite) == false) return false; + target(targetFile.data(), targetFile.size()); + return true; +} + +string bpspatch::metadata() const { + return metadataString; +} + +unsigned bpspatch::size() const { + return modifyTargetSize; +} + +bpspatch::result bpspatch::apply() { + if(modifySize < 19) return result::patch_too_small; + + uint32_t modifyChecksum = ~0, targetChecksum = ~0; + unsigned modifyOffset = 0, sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0; + + auto read = [&]() -> uint8_t { + uint8_t data = modifyData[modifyOffset++]; + modifyChecksum = crc32_adjust(modifyChecksum, data); + return data; + }; + + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + auto write = [&](uint8_t data) { + targetData[outputOffset++] = data; + targetChecksum = crc32_adjust(targetChecksum, data); + }; + + if(read() != 'B') return result::patch_invalid_header; + if(read() != 'P') return result::patch_invalid_header; + if(read() != 'S') return result::patch_invalid_header; + if(read() != '1') return result::patch_invalid_header; + + modifySourceSize = decode(); + modifyTargetSize = decode(); + modifyMarkupSize = decode(); + for(unsigned n = 0; n < modifyMarkupSize; n++) read(); + + if(modifySourceSize > sourceSize) return result::source_too_small; + if(modifyTargetSize > targetSize) return result::target_too_small; + + while(modifyOffset < modifySize - 12) { + unsigned length = decode(); + unsigned mode = length & 3; + length = (length >> 2) + 1; + + switch(mode) { + case SourceRead: + while(length--) write(sourceData[outputOffset]); + break; + case TargetRead: + while(length--) write(read()); + break; + case SourceCopy: + case TargetCopy: + signed offset = decode(); + bool negative = offset & 1; + offset >>= 1; + if(negative) offset = -offset; + + if(mode == SourceCopy) { + sourceRelativeOffset += offset; + while(length--) write(sourceData[sourceRelativeOffset++]); + } else { + targetRelativeOffset += offset; + while(length--) write(targetData[targetRelativeOffset++]); + } + break; + } + } + + uint32_t modifySourceChecksum = 0, modifyTargetChecksum = 0, modifyModifyChecksum = 0; + for(unsigned n = 0; n < 32; n += 8) modifySourceChecksum |= read() << n; + for(unsigned n = 0; n < 32; n += 8) modifyTargetChecksum |= read() << n; + uint32_t checksum = ~modifyChecksum; + for(unsigned n = 0; n < 32; n += 8) modifyModifyChecksum |= read() << n; + + uint32_t sourceChecksum = crc32_calculate(sourceData, modifySourceSize); + targetChecksum = ~targetChecksum; + + if(sourceChecksum != modifySourceChecksum) return result::source_checksum_invalid; + if(targetChecksum != modifyTargetChecksum) return result::target_checksum_invalid; + if(checksum != modifyModifyChecksum) return result::patch_checksum_invalid; + + return result::success; +} + +} + +#endif diff --git a/higan/sfc/Makefile b/higan/sfc/Makefile index e0625b21..7bd58516 100755 --- a/higan/sfc/Makefile +++ b/higan/sfc/Makefile @@ -46,19 +46,19 @@ obj/sfc-sufamiturbo.o: $(sfc)/chip/sufamiturbo/sufamiturbo.cpp $(sfc)/chip/sufam obj/sfc-nss.o : $(sfc)/chip/nss/nss.cpp $(call rwildcard,$(sfc)/chip/nss/) obj/sfc-event.o : $(sfc)/chip/event/event.cpp $(call rwildcard,$(sfc)/chip/event/) -obj/sfc-sa1.o : $(sfc)/chip/sa1/sa1.cpp $(call rwildcard,$(sfc)/chip/sa1/) -obj/sfc-superfx.o : $(sfc)/chip/superfx/superfx.cpp $(call rwildcard,$(sfc)/chip/superfx/) +obj/sfc-sa1.o : $(sfc)/chip/sa1/sa1.cpp $(call rwildcard,$(sfc)/chip/sa1/) +obj/sfc-superfx.o: $(sfc)/chip/superfx/superfx.cpp $(call rwildcard,$(sfc)/chip/superfx/) -obj/sfc-armdsp.o : $(sfc)/chip/armdsp/armdsp.cpp $(call rwildcard,$(sfc)/chip/armdsp/) -obj/sfc-hitachidsp.o : $(sfc)/chip/hitachidsp/hitachidsp.cpp $(call rwildcard,$(sfc)/chip/hitachidsp/) -obj/sfc-necdsp.o : $(sfc)/chip/necdsp/necdsp.cpp $(call rwildcard,$(sfc)/chip/necdsp/) +obj/sfc-armdsp.o : $(sfc)/chip/armdsp/armdsp.cpp $(call rwildcard,$(sfc)/chip/armdsp/) +obj/sfc-hitachidsp.o: $(sfc)/chip/hitachidsp/hitachidsp.cpp $(call rwildcard,$(sfc)/chip/hitachidsp/) +obj/sfc-necdsp.o : $(sfc)/chip/necdsp/necdsp.cpp $(call rwildcard,$(sfc)/chip/necdsp/) -obj/sfc-epsonrtc.o : $(sfc)/chip/epsonrtc/epsonrtc.cpp $(call rwildcard,$(sfc)/chip/epsonrtc/) -obj/sfc-sharprtc.o : $(sfc)/chip/sharprtc/sharprtc.cpp $(call rwildcard,$(sfc)/chip/sharprtc/) +obj/sfc-epsonrtc.o: $(sfc)/chip/epsonrtc/epsonrtc.cpp $(call rwildcard,$(sfc)/chip/epsonrtc/) +obj/sfc-sharprtc.o: $(sfc)/chip/sharprtc/sharprtc.cpp $(call rwildcard,$(sfc)/chip/sharprtc/) -obj/sfc-spc7110.o : $(sfc)/chip/spc7110/spc7110.cpp $(sfc)/chip/spc7110/* -obj/sfc-sdd1.o : $(sfc)/chip/sdd1/sdd1.cpp $(sfc)/chip/sdd1/* -obj/sfc-obc1.o : $(sfc)/chip/obc1/obc1.cpp $(sfc)/chip/obc1/* +obj/sfc-spc7110.o: $(sfc)/chip/spc7110/spc7110.cpp $(sfc)/chip/spc7110/* +obj/sfc-sdd1.o : $(sfc)/chip/sdd1/sdd1.cpp $(sfc)/chip/sdd1/* +obj/sfc-obc1.o : $(sfc)/chip/obc1/obc1.cpp $(sfc)/chip/obc1/* -obj/sfc-hsu1.o : $(sfc)/chip/hsu1/hsu1.cpp $(sfc)/chip/hsu1/* -obj/sfc-msu1.o : $(sfc)/chip/msu1/msu1.cpp $(sfc)/chip/msu1/* +obj/sfc-hsu1.o: $(sfc)/chip/hsu1/hsu1.cpp $(sfc)/chip/hsu1/* +obj/sfc-msu1.o: $(sfc)/chip/msu1/msu1.cpp $(sfc)/chip/msu1/* diff --git a/higan/sfc/cartridge/cartridge.cpp b/higan/sfc/cartridge/cartridge.cpp index 75f58edf..73e0ceb9 100755 --- a/higan/sfc/cartridge/cartridge.cpp +++ b/higan/sfc/cartridge/cartridge.cpp @@ -52,6 +52,7 @@ void Cartridge::load(const string &manifest) { else { sha256_ctx sha; uint8_t hash[32]; + vector buffer; sha256_init(&sha); //hash each ROM image that exists; any with size() == 0 is ignored by sha256_chunk() sha256_chunk(&sha, rom.data(), rom.size()); @@ -62,6 +63,14 @@ void Cartridge::load(const string &manifest) { sha256_chunk(&sha, spc7110.prom.data(), spc7110.prom.size()); sha256_chunk(&sha, spc7110.drom.data(), spc7110.drom.size()); sha256_chunk(&sha, sdd1.rom.data(), sdd1.rom.size()); + //hash all firmware that exists + buffer = armdsp.firmware(); + sha256_chunk(&sha, buffer.data(), buffer.size()); + buffer = hitachidsp.firmware(); + sha256_chunk(&sha, buffer.data(), buffer.size()); + buffer = necdsp.firmware(); + sha256_chunk(&sha, buffer.data(), buffer.size()); + //finalize hash sha256_final(&sha); sha256_hash(&sha, hash); string result; @@ -78,8 +87,8 @@ void Cartridge::load(const string &manifest) { void Cartridge::load_super_game_boy(const string &manifest) { XML::Document document(manifest); - auto rom = document["cartridge"]["rom"]; - auto ram = document["cartridge"]["ram"]; + auto rom = document["release/cartridge/rom"]; + auto ram = document["release/cartridge/ram"]; GameBoy::cartridge.load(GameBoy::System::Revision::SuperGameBoy, manifest); @@ -90,7 +99,7 @@ void Cartridge::load_super_game_boy(const string &manifest) { void Cartridge::load_satellaview(const string &manifest) { XML::Document document(manifest); - auto rom = document["cartridge"]["rom"]; + auto rom = document["release/cartridge/rom"]; if(rom["name"].exists()) { unsigned size = numeral(rom["size"].data); @@ -101,8 +110,8 @@ void Cartridge::load_satellaview(const string &manifest) { void Cartridge::load_sufami_turbo_a(const string &manifest) { XML::Document document(manifest); - auto rom = document["cartridge"]["rom"]; - auto ram = document["cartridge"]["ram"]; + auto rom = document["release/cartridge/rom"]; + auto ram = document["release/cartridge/ram"]; if(rom["name"].exists()) { unsigned size = numeral(rom["size"].data); @@ -124,8 +133,8 @@ void Cartridge::load_sufami_turbo_a(const string &manifest) { void Cartridge::load_sufami_turbo_b(const string &manifest) { XML::Document document(manifest); - auto rom = document["cartridge"]["rom"]; - auto ram = document["cartridge"]["ram"]; + auto rom = document["release/cartridge/rom"]; + auto ram = document["release/cartridge/ram"]; if(rom["name"].exists()) { unsigned size = numeral(rom["size"].data); diff --git a/higan/sfc/cartridge/cartridge.hpp b/higan/sfc/cartridge/cartridge.hpp index 042a00ed..b7916dc3 100755 --- a/higan/sfc/cartridge/cartridge.hpp +++ b/higan/sfc/cartridge/cartridge.hpp @@ -77,7 +77,7 @@ private: void parse_markup_map(Mapping&, Markup::Node); void parse_markup_memory(MappedRAM&, Markup::Node, unsigned id, bool writable); - void parse_markup_board(Markup::Node); + void parse_markup_cartridge(Markup::Node); void parse_markup_icd2(Markup::Node); void parse_markup_bsx(Markup::Node); void parse_markup_bsxslot(Markup::Node); diff --git a/higan/sfc/cartridge/markup.cpp b/higan/sfc/cartridge/markup.cpp index 8e5eae02..076c166c 100755 --- a/higan/sfc/cartridge/markup.cpp +++ b/higan/sfc/cartridge/markup.cpp @@ -1,32 +1,29 @@ #ifdef CARTRIDGE_CPP void Cartridge::parse_markup(const char *markup) { - mapping.reset(); - - auto document = Markup::Document(markup); - auto cartridge = document["cartridge"]; - auto board = cartridge["board"]; + auto cartridge = Markup::Document(markup)["release/cartridge"]; region = cartridge["region"].data != "PAL" ? Region::NTSC : Region::PAL; - parse_markup_board(board); - parse_markup_icd2(board["icd2"]); - parse_markup_bsx(board["bsx"]); - parse_markup_bsxslot(board["bsxslot"]); - parse_markup_sufamiturbo(board["sufamiturbo"]); - parse_markup_nss(board["nss"]); - parse_markup_event(board["event"]); - parse_markup_sa1(board["sa1"]); - parse_markup_superfx(board["superfx"]); - parse_markup_armdsp(board["armdsp"]); - parse_markup_hitachidsp(board["hitachidsp"]); - parse_markup_necdsp(board["necdsp"]); - parse_markup_epsonrtc(board["epsonrtc"]); - parse_markup_sharprtc(board["sharprtc"]); - parse_markup_spc7110(board["spc7110"]); - parse_markup_sdd1(board["sdd1"]); - parse_markup_obc1(board["obc1"]); - parse_markup_hsu1(board["hsu1"]); - parse_markup_msu1(board["msu1"]); + mapping.reset(); + parse_markup_cartridge(cartridge); + parse_markup_icd2(cartridge["icd2"]); + parse_markup_bsx(cartridge["bsx"]); + parse_markup_bsxslot(cartridge["bsxslot"]); + parse_markup_sufamiturbo(cartridge["sufamiturbo"]); + parse_markup_nss(cartridge["nss"]); + parse_markup_event(cartridge["event"]); + parse_markup_sa1(cartridge["sa1"]); + parse_markup_superfx(cartridge["superfx"]); + parse_markup_armdsp(cartridge["armdsp"]); + parse_markup_hitachidsp(cartridge["hitachidsp"]); + parse_markup_necdsp(cartridge["necdsp"]); + parse_markup_epsonrtc(cartridge["epsonrtc"]); + parse_markup_sharprtc(cartridge["sharprtc"]); + parse_markup_spc7110(cartridge["spc7110"]); + parse_markup_sdd1(cartridge["sdd1"]); + parse_markup_obc1(cartridge["obc1"]); + parse_markup_hsu1(cartridge["hsu1"]); + parse_markup_msu1(cartridge["msu1"]); } // @@ -50,7 +47,7 @@ void Cartridge::parse_markup_memory(MappedRAM &ram, Markup::Node node, unsigned // -void Cartridge::parse_markup_board(Markup::Node root) { +void Cartridge::parse_markup_cartridge(Markup::Node root) { if(root.exists() == false) return; parse_markup_memory(rom, root["rom"], ID::ROM, false); @@ -218,14 +215,6 @@ void Cartridge::parse_markup_event(Markup::Node root) { if(part.size() == 1) event.timer = decimal(part(0)); if(part.size() == 2) event.timer = decimal(part(0)) * 60 + decimal(part(1)); - part = string{root["server"]["address"].data}.ltrim<1>("http://").split<1>("/"); - event.path = {"/", part(1)}; - part = part(0).split<1>(":"); - event.host = part(0); - event.port = decimal(part(1)) ? decimal(part(1)) : 80; - event.username = root["server"]["username"].data; - event.password = root["server"]["password"].data; - for(auto &node : root) { if(node.name != "map") continue; diff --git a/higan/sfc/chip/armdsp/armdsp.hpp b/higan/sfc/chip/armdsp/armdsp.hpp index 22c2df0f..ce229853 100755 --- a/higan/sfc/chip/armdsp/armdsp.hpp +++ b/higan/sfc/chip/armdsp/armdsp.hpp @@ -25,7 +25,9 @@ struct ArmDSP : Processor::ARM, Coprocessor { void reset(); void arm_reset(); + nall::vector firmware(); void serialize(serializer&); + ArmDSP(); ~ArmDSP(); }; diff --git a/higan/sfc/chip/armdsp/serialization.cpp b/higan/sfc/chip/armdsp/serialization.cpp index f9c63c0b..3a659464 100755 --- a/higan/sfc/chip/armdsp/serialization.cpp +++ b/higan/sfc/chip/armdsp/serialization.cpp @@ -1,5 +1,14 @@ #ifdef ARMDSP_CPP +nall::vector ArmDSP::firmware() { + nall::vector buffer; + if(cartridge.has_armdsp() == false) return buffer; + buffer.reserve(128 * 1024 + 32 * 1024); + for(unsigned n = 0; n < 128 * 1024; n++) buffer.append(programROM[n]); + for(unsigned n = 0; n < 32 * 1024; n++) buffer.append(dataROM[n]); + return buffer; +} + void ArmDSP::serialize(serializer &s) { ARM::serialize(s); Thread::serialize(s); diff --git a/higan/sfc/chip/hitachidsp/hitachidsp.hpp b/higan/sfc/chip/hitachidsp/hitachidsp.hpp index 3379810f..408f1a32 100755 --- a/higan/sfc/chip/hitachidsp/hitachidsp.hpp +++ b/higan/sfc/chip/hitachidsp/hitachidsp.hpp @@ -25,6 +25,7 @@ struct HitachiDSP : Processor::HG51B, Coprocessor { uint8 dsp_read(unsigned addr); void dsp_write(unsigned addr, uint8 data); + vector firmware(); void serialize(serializer&); }; diff --git a/higan/sfc/chip/hitachidsp/serialization.cpp b/higan/sfc/chip/hitachidsp/serialization.cpp index 0ea9b242..01de7a0c 100755 --- a/higan/sfc/chip/hitachidsp/serialization.cpp +++ b/higan/sfc/chip/hitachidsp/serialization.cpp @@ -1,5 +1,17 @@ #ifdef HITACHIDSP_CPP +vector HitachiDSP::firmware() { + vector buffer; + if(cartridge.has_hitachidsp() == false) return buffer; + buffer.reserve(1024 * 3); + for(unsigned n = 0; n < 1024; n++) { + buffer.append(dataROM[n] >> 0); + buffer.append(dataROM[n] >> 8); + buffer.append(dataROM[n] >> 16); + } + return buffer; +} + void HitachiDSP::serialize(serializer &s) { HG51B::serialize(s); Thread::serialize(s); diff --git a/higan/sfc/chip/hsu1/hsu1.cpp b/higan/sfc/chip/hsu1/hsu1.cpp index 0ccb9cf7..c8f100dc 100644 --- a/higan/sfc/chip/hsu1/hsu1.cpp +++ b/higan/sfc/chip/hsu1/hsu1.cpp @@ -19,47 +19,93 @@ void HSU1::power() { } void HSU1::reset() { - packetlength = 0; + txbusy = 0; + rxbusy = 1; + txlatch = 0; + txbuffer.reset(); + rxbuffer.reset(); } uint8 HSU1::read(unsigned addr) { + addr &= 1; + + if(addr == 0) { + return (txbusy << 7) | (rxbusy << 6) | (1 << 0); + } + + if(addr == 1) { + if(rxbusy) return 0x00; + uint8 data = rxbuffer.take(0); + if(rxbuffer.size() == 0) rxbusy = 1; + return data; + } + + //unreachable return cpu.regs.mdr; } void HSU1::write(unsigned addr, uint8 data) { - packet[packetlength] = data; - if(packetlength < sizeof(packet)) packetlength++; - if(data) return; + addr &= 1; - lstring side = interface->scoreServer().split<1>("@"); - string username = side(0).split<1>(":")(0); - string password = side(0).split<1>(":")(1); - string servername = side(1).ltrim<1>("http://"); - side = servername.split<1>("/"); - string host = side(0).split<1>(":")(0); - string port = side(0).split<1>(":")(1); - string path = side(1); - if(port.empty()) port = "80"; + if(addr == 0) { + if(txbusy) return; + bool latch = data & 0x01; + if(txlatch == 1 && latch == 0) { + //username:password@http://server:port/path + lstring side = interface->server().split<1>("@"); + string username = side(0).split<1>(":")(0); + string password = side(0).split<1>(":")(1); + side(1).ltrim<1>("http://"); + string hostname = side(1).split<1>("/")(0); + string hostpath = side(1).split<1>("/")(1); + side = hostname.split<1>(":"); + hostname = side(0); + string hostport = side(1); + if(hostport.empty()) hostport = "80"; - http server; - if(server.connect(host, decimal(port))) { - string content = { - "username:", username, "\r\n", - "password:", password, "\r\n", - "sha256:", interface->sha256(), "\r\n", - "data:", (const char*)packet, "\r\n", - "emulator:bsnes\r\n" - }; - string packet = { - "POST ", path, " HTTP/1.0\r\n", - "Host: ", host, "\r\n", - "Connection: close\r\n", - "Content-Type: text/plain; charset=utf-8\r\n", - "Content-Length: ", content.length(), "\r\n", - "\r\n", - content - }; - server.send(packet); + http server; + if(server.connect(hostname, decimal(hostport))) { + string header { + "username:", username, "\n", + "password:", password, "\n", + "emulator:bsnes\n", + "sha256:", interface->sha256(), "\n", + "\n" + }; + + string packet { + "POST /", hostpath, " HTTP/1.0\r\n", + "Host: ", hostname, "\r\n", + "Connection: close\r\n", + "Content-Type: application/octet-stream\r\n", + "Content-Length: ", header.length() + txbuffer.size(), "\r\n", + "\r\n", + }; + + server.send(packet); + server.send(header); + server.send(txbuffer.data(), txbuffer.size()); + txbuffer.reset(); + + server.header = server.downloadHeader(); + uint8_t *data = nullptr; + unsigned size = 0; + server.downloadContent(data, size); + rxbuffer.resize(size); + memcpy(rxbuffer.data(), data, size); + rxbusy = rxbuffer.size() == 0; + free(data); + + server.disconnect(); + } + } + txlatch = latch; + } + + if(addr == 1) { + if(txbusy) return; + if(txlatch == 0) return; + txbuffer.append(data); } } diff --git a/higan/sfc/chip/hsu1/hsu1.hpp b/higan/sfc/chip/hsu1/hsu1.hpp index 13e79cb8..f0e2e676 100644 --- a/higan/sfc/chip/hsu1/hsu1.hpp +++ b/higan/sfc/chip/hsu1/hsu1.hpp @@ -11,8 +11,11 @@ struct HSU1 { void serialize(serializer&); private: - uint8 packet[512]; - unsigned packetlength; + bool txbusy; + bool rxbusy; + bool txlatch; + vector txbuffer; + vector rxbuffer; }; extern HSU1 hsu1; diff --git a/higan/sfc/chip/hsu1/serialization.cpp b/higan/sfc/chip/hsu1/serialization.cpp index 257a3f38..2543aaf3 100644 --- a/higan/sfc/chip/hsu1/serialization.cpp +++ b/higan/sfc/chip/hsu1/serialization.cpp @@ -1,8 +1,21 @@ #ifdef HSU1_CPP void HSU1::serialize(serializer &s) { - s.array(packet); - s.integer(packetlength); + s.integer(txbusy); + s.integer(rxbusy); + s.integer(txlatch); + + unsigned size; + + size = txbuffer.size(); + s.integer(size); + txbuffer.resize(size); + s.array(txbuffer.data(), txbuffer.size()); + + size = rxbuffer.size(); + s.integer(size); + rxbuffer.resize(size); + s.array(rxbuffer.data(), rxbuffer.size()); } #endif diff --git a/higan/sfc/chip/necdsp/necdsp.hpp b/higan/sfc/chip/necdsp/necdsp.hpp index fcdd2573..f2fff900 100755 --- a/higan/sfc/chip/necdsp/necdsp.hpp +++ b/higan/sfc/chip/necdsp/necdsp.hpp @@ -17,6 +17,7 @@ struct NECDSP : Processor::uPD96050, Coprocessor { void power(); void reset(); + vector firmware(); void serialize(serializer&); }; diff --git a/higan/sfc/chip/necdsp/serialization.cpp b/higan/sfc/chip/necdsp/serialization.cpp index 2d28c754..42df005e 100755 --- a/higan/sfc/chip/necdsp/serialization.cpp +++ b/higan/sfc/chip/necdsp/serialization.cpp @@ -1,5 +1,26 @@ #ifdef NECDSP_CPP +vector NECDSP::firmware() { + vector buffer; + if(cartridge.has_necdsp() == false) return buffer; + unsigned plength = 2048, dlength = 1024; + if(revision == Revision::uPD96050) plength = 16384, dlength = 2048; + buffer.reserve(plength * 3 + dlength * 2); + + for(unsigned n = 0; n < plength; n++) { + buffer.append(programROM[n] >> 0); + buffer.append(programROM[n] >> 8); + buffer.append(programROM[n] >> 16); + } + + for(unsigned n = 0; n < dlength; n++) { + buffer.append(dataROM[n] >> 0); + buffer.append(dataROM[n] >> 8); + } + + return buffer; +} + void NECDSP::serialize(serializer &s) { uPD96050::serialize(s); Thread::serialize(s); diff --git a/higan/target-ethos/configuration/configuration.cpp b/higan/target-ethos/configuration/configuration.cpp index c4bae5fd..5024772e 100755 --- a/higan/target-ethos/configuration/configuration.cpp +++ b/higan/target-ethos/configuration/configuration.cpp @@ -26,10 +26,9 @@ Configuration::Configuration() { append(input.focusAllow = false, "Input::Focus::AllowInput"); append(timing.video = 60.0, "Timing::Video"); append(timing.audio = 48000.0, "Timing::Audio"); - append(highScores.hostname = "", "HighScores::Hostname"); - append(highScores.username = "", "HighScores::Username"); - append(highScores.password = "", "HighScores::Password"); - append(path.game = {userpath(), "Emulation/"}, "Path::Game"); + append(server.hostname = "", "Server::Hostname"); + append(server.username = "", "Server::Username"); + append(server.password = "", "Server::Password"); load(); } diff --git a/higan/target-ethos/configuration/configuration.hpp b/higan/target-ethos/configuration/configuration.hpp index af18b9af..79c01ced 100755 --- a/higan/target-ethos/configuration/configuration.hpp +++ b/higan/target-ethos/configuration/configuration.hpp @@ -35,15 +35,11 @@ struct Configuration : configuration { double audio; } timing; - struct HighScores { + struct Server { string hostname; string username; string password; - } highScores; - - struct Path { - string game; - } path; + } server; void load(); void save(); diff --git a/higan/target-ethos/ethos.cpp b/higan/target-ethos/ethos.cpp index 0e27a373..3d1da610 100755 --- a/higan/target-ethos/ethos.cpp +++ b/higan/target-ethos/ethos.cpp @@ -21,27 +21,6 @@ string Application::path(const string &filename) { return {userpath, filename}; } -void Application::commandLineLoad(string pathname) { - pathname.transform("\\", "/"); - string type = extension(string{pathname}.rtrim<1>("/")); - if(!directory::exists(pathname) && !file::exists(pathname)) return; - - for(auto &emulator : this->emulator) { - for(auto &media : emulator->media) { - if(!media.load.empty()) continue; - if(type != media.type) continue; - - if(directory::exists(pathname)) { - return utility->loadMedia(emulator, media, pathname); - } - - if(file::exists(pathname)) { - return utility->loadMediaFromDatabase(emulator, media, pathname); - } - } - } -} - void Application::run() { inputManager->poll(); utility->updateStatus(); @@ -57,6 +36,8 @@ void Application::run() { } Application::Application(int argc, char **argv) { + ananke.open("ananke"); + application = this; quit = false; pause = false; @@ -97,7 +78,7 @@ Application::Application(int argc, char **argv) { inputSettings = new InputSettings; hotkeySettings = new HotkeySettings; timingSettings = new TimingSettings; - scoreSettings = new ScoreSettings; + serverSettings = new ServerSettings; driverSettings = new DriverSettings; settings = new Settings; cheatDatabase = new CheatDatabase; @@ -128,7 +109,7 @@ Application::Application(int argc, char **argv) { if(config->video.startFullScreen) utility->toggleFullScreen(); OS::processEvents(); - if(argc >= 2) commandLineLoad(argv[1]); + if(argc >= 2) utility->loadMedia(argv[1]); while(quit == false) { OS::processEvents(); @@ -140,6 +121,8 @@ Application::Application(int argc, char **argv) { browser->saveConfiguration(); inputManager->saveConfiguration(); windowManager->saveGeometry(); + + ananke.close(); } Application::~Application() { diff --git a/higan/target-ethos/ethos.hpp b/higan/target-ethos/ethos.hpp index 93937c5d..91aa7a64 100755 --- a/higan/target-ethos/ethos.hpp +++ b/higan/target-ethos/ethos.hpp @@ -33,6 +33,7 @@ Emulator::Interface& system(); struct Application { vector emulator; Emulator::Interface *active; + library ananke; bool quit; bool pause; @@ -49,7 +50,6 @@ struct Application { bool focused(); string path(const string &filename); - void commandLineLoad(string pathname); void run(); void bootstrap(); Application(int argc, char **argv); diff --git a/higan/target-ethos/general/browser.cpp b/higan/target-ethos/general/browser.cpp index f749ed6e..45b95505 100755 --- a/higan/target-ethos/general/browser.cpp +++ b/higan/target-ethos/general/browser.cpp @@ -165,7 +165,7 @@ void Browser::setPath(const string &path, unsigned selection) { string name = filename; name.rtrim<1>("/"); fileList.append(name); - fileList.setImage(filenameList.size(), 0, image(resource::cabinet, sizeof resource::cabinet)); + fileList.setImage(filenameList.size(), 0, image(resource::folder, sizeof resource::folder)); filenameList.append(filename); } } @@ -176,11 +176,12 @@ void Browser::setPath(const string &path, unsigned selection) { string name = filename; name.rtrim<1>(suffix); fileList.append(name); - fileList.setImage(filenameList.size(), 0, image(resource::folder, sizeof resource::folder)); + fileList.setImage(filenameList.size(), 0, image(resource::file, sizeof resource::file)); filenameList.append(filename); } } + /* file loading: deprecated for ananke for(auto &filename : contents) { string suffix = {".", this->extension}; if(filename.endswith(suffix)) { @@ -190,7 +191,7 @@ void Browser::setPath(const string &path, unsigned selection) { fileList.setImage(filenameList.size(), 0, image(resource::file, sizeof resource::file)); filenameList.append(filename); } - } + }*/ fileList.setSelection(selection); fileList.setFocused(); diff --git a/higan/target-ethos/general/presentation.cpp b/higan/target-ethos/general/presentation.cpp index 5c5adab7..df0b4c12 100755 --- a/higan/target-ethos/general/presentation.cpp +++ b/higan/target-ethos/general/presentation.cpp @@ -58,7 +58,9 @@ Presentation::Presentation() : active(nullptr) { setMenuVisible(); setStatusVisible(); - loadMenu.setText("Load"); + fileMenu.setText("File"); + fileLoad.setText("Load Game ..."); + loadMenu.setText("Library"); settingsMenu.setText("Settings"); videoMenu.setText("Video"); centerVideo.setText("Center"); @@ -84,6 +86,8 @@ Presentation::Presentation() : active(nullptr) { cheatEditor.setText("Cheat Editor"); synchronizeTime.setText("Synchronize Time"); + if(application->ananke.opened()) append(fileMenu); + fileMenu.append(fileLoad); append(loadMenu); for(auto &item : loadListSystem) loadMenu.append(*item); if(loadListSubsystem.size() > 0) { @@ -116,6 +120,15 @@ Presentation::Presentation() : active(nullptr) { onSize = [&] { utility->resize(); }; onClose = [&] { application->quit = true; }; + fileLoad.onActivate = [&] { + if(application->ananke.opened() == false) return; + function browse = application->ananke.sym("ananke_browse"); + if(browse == false) return; + string pathname = browse(); + if(pathname.empty()) return; + utility->loadMedia(pathname); + }; + shaderNone.onActivate = [&] { config->video.shader = "None"; utility->updateShader(); }; shaderBlur.onActivate = [&] { config->video.shader = "Blur"; utility->updateShader(); }; centerVideo.onActivate = [&] { config->video.scaleMode = 0; utility->resize(); }; diff --git a/higan/target-ethos/general/presentation.hpp b/higan/target-ethos/general/presentation.hpp index 1a44aa32..365dc97f 100755 --- a/higan/target-ethos/general/presentation.hpp +++ b/higan/target-ethos/general/presentation.hpp @@ -20,6 +20,8 @@ struct Presentation : Window { }; vector emulatorList; + Menu fileMenu; + Item fileLoad; Menu loadMenu; vector loadListSystem; vector loadListSubsystem; diff --git a/higan/target-ethos/interface/interface.cpp b/higan/target-ethos/interface/interface.cpp index 56941a63..b6d4e940 100755 --- a/higan/target-ethos/interface/interface.cpp +++ b/higan/target-ethos/interface/interface.cpp @@ -111,18 +111,18 @@ unsigned Interface::dipSettings(const Markup::Node &node) { return dipSwitches->run(node); } -string Interface::scoreServer() { - return string { - config->highScores.username, ":", - config->highScores.password, "@", - config->highScores.hostname - }; -} - string Interface::path(unsigned group) { return utility->path(group); } +string Interface::server() { + return { + config->server.username, ":", + config->server.password, "@", + config->server.hostname + }; +} + void Interface::notify(const string &text) { MessageWindow::information(*presentation, text); } diff --git a/higan/target-ethos/interface/interface.hpp b/higan/target-ethos/interface/interface.hpp index d5c0af55..683b2871 100755 --- a/higan/target-ethos/interface/interface.hpp +++ b/higan/target-ethos/interface/interface.hpp @@ -7,8 +7,8 @@ struct Interface : Emulator::Interface::Bind { void audioSample(int16_t lsample, int16_t rsample); int16_t inputPoll(unsigned port, unsigned device, unsigned input); unsigned dipSettings(const Markup::Node &node); - string scoreServer(); string path(unsigned group); + string server(); void notify(const string &text); }; diff --git a/higan/target-ethos/settings/scores.cpp b/higan/target-ethos/settings/scores.cpp deleted file mode 100644 index 56c26f0b..00000000 --- a/higan/target-ethos/settings/scores.cpp +++ /dev/null @@ -1,32 +0,0 @@ -ScoreSettings *scoreSettings = nullptr; - -ScoreSettings::ScoreSettings() { - title.setFont(application->titleFont); - title.setText("High Score Server Configuration"); - hostLabel.setText("Hostname:"); - userLabel.setText("Username:"); - passLabel.setText("Password:"); - - unsigned width = max( - Font(application->normalFont).geometry("Hostname:").width, - Font(application->normalFont).geometry("Username:").width - ); - - append(title, {~0, 0}, 5); - append(serverLayout, {~0, 0}, 5); - serverLayout.append(hostLabel, {width, 0}, 5); - serverLayout.append(hostEdit, {~0, 0}); - append(loginLayout, {~0, 0}); - loginLayout.append(userLabel, {width, 0}, 5); - loginLayout.append(userEdit, {~0, 0}, 5); - loginLayout.append(passLabel, {0, 0}, 5); - loginLayout.append(passEdit, {~0, 0}); - - hostEdit.setText(config->highScores.hostname); - userEdit.setText(config->highScores.username); - passEdit.setText(config->highScores.password); - - hostEdit.onChange = [&] { config->highScores.hostname = hostEdit.text(); }; - userEdit.onChange = [&] { config->highScores.username = userEdit.text(); }; - passEdit.onChange = [&] { config->highScores.password = passEdit.text(); }; -} diff --git a/higan/target-ethos/settings/scores.hpp b/higan/target-ethos/settings/scores.hpp deleted file mode 100644 index 43f46324..00000000 --- a/higan/target-ethos/settings/scores.hpp +++ /dev/null @@ -1,15 +0,0 @@ -struct ScoreSettings : SettingsLayout { - Label title; - HorizontalLayout serverLayout; - Label hostLabel; - LineEdit hostEdit; - HorizontalLayout loginLayout; - Label userLabel; - LineEdit userEdit; - Label passLabel; - LineEdit passEdit; - - ScoreSettings(); -}; - -extern ScoreSettings *scoreSettings; diff --git a/higan/target-ethos/settings/server.cpp b/higan/target-ethos/settings/server.cpp new file mode 100644 index 00000000..4728f7f5 --- /dev/null +++ b/higan/target-ethos/settings/server.cpp @@ -0,0 +1,32 @@ +ServerSettings *serverSettings = nullptr; + +ServerSettings::ServerSettings() { + title.setFont(application->titleFont); + title.setText("Server Settings"); + hostLabel.setText("Hostname:"); + userLabel.setText("Username:"); + passLabel.setText("Password:"); + + unsigned width = min( + Font(application->normalFont).geometry("Hostname:").width, + Font(application->normalFont).geometry("Username:").width + ); + + append(title, {~0, 0}, 5); + append(hostLayout, {~0, 0}, 5); + hostLayout.append(hostLabel, {width, 0}, 5); + hostLayout.append(hostEdit, {~0, 0}); + append(userLayout, {~0, 0}); + userLayout.append(userLabel, {width, 0}, 5); + userLayout.append(userEdit, {~0, 0}, 5); + userLayout.append(passLabel, {0, 0}, 5); + userLayout.append(passEdit, {~0, 0}); + + hostEdit.setText(config->server.hostname); + userEdit.setText(config->server.username); + passEdit.setText(config->server.password); + + hostEdit.onChange = [&] { config->server.hostname = hostEdit.text(); }; + userEdit.onChange = [&] { config->server.username = userEdit.text(); }; + passEdit.onChange = [&] { config->server.password = passEdit.text(); }; +} diff --git a/higan/target-ethos/settings/server.hpp b/higan/target-ethos/settings/server.hpp new file mode 100644 index 00000000..d079cafd --- /dev/null +++ b/higan/target-ethos/settings/server.hpp @@ -0,0 +1,15 @@ +struct ServerSettings : SettingsLayout { + Label title; + HorizontalLayout hostLayout; + Label hostLabel; + LineEdit hostEdit; + HorizontalLayout userLayout; + Label userLabel; + LineEdit userEdit; + Label passLabel; + LineEdit passEdit; + + ServerSettings(); +}; + +extern ServerSettings *serverSettings; diff --git a/higan/target-ethos/settings/settings.cpp b/higan/target-ethos/settings/settings.cpp index b9f6cd2f..ec06f9cf 100755 --- a/higan/target-ethos/settings/settings.cpp +++ b/higan/target-ethos/settings/settings.cpp @@ -4,7 +4,7 @@ #include "input.cpp" #include "hotkey.cpp" #include "timing.cpp" -#include "scores.cpp" +#include "server.cpp" #include "driver.cpp" Settings *settings = nullptr; @@ -32,7 +32,7 @@ Settings::Settings() { panelList.append("Input"); panelList.append("Hotkeys"); panelList.append("Timing"); - panelList.append("Scores"); + panelList.append("Server"); panelList.append("Driver"); append(layout); @@ -42,7 +42,7 @@ Settings::Settings() { append(*inputSettings); append(*hotkeySettings); append(*timingSettings); - append(*scoreSettings); + append(*serverSettings); append(*driverSettings); onClose = [&] { @@ -62,7 +62,7 @@ void Settings::panelChanged() { inputSettings->setVisible(false); hotkeySettings->setVisible(false); timingSettings->setVisible(false); - scoreSettings->setVisible(false); + serverSettings->setVisible(false); driverSettings->setVisible(false); if(panelList.selected() == false) return; @@ -72,7 +72,7 @@ void Settings::panelChanged() { case 2: return inputSettings->setVisible(); case 3: return hotkeySettings->setVisible(); case 4: return timingSettings->setVisible(); - case 5: return scoreSettings->setVisible(); + case 5: return serverSettings->setVisible(); case 6: return driverSettings->setVisible(); } } diff --git a/higan/target-ethos/settings/settings.hpp b/higan/target-ethos/settings/settings.hpp index 3f6ceee1..40a24481 100755 --- a/higan/target-ethos/settings/settings.hpp +++ b/higan/target-ethos/settings/settings.hpp @@ -11,7 +11,7 @@ struct SettingsLayout : HorizontalLayout { #include "input.hpp" #include "hotkey.hpp" #include "timing.hpp" -#include "scores.hpp" +#include "server.hpp" #include "driver.hpp" struct Settings : Window { diff --git a/higan/target-ethos/utility/database.cpp b/higan/target-ethos/utility/database.cpp deleted file mode 100644 index ea4a664c..00000000 --- a/higan/target-ethos/utility/database.cpp +++ /dev/null @@ -1,48 +0,0 @@ -void Utility::loadMediaFromDatabase(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &filename) { - string sha256 = file::sha256(filename); - string systemPath = {configpath(), "higan/", emulator->information.name, ".sys/"}; - string outputPath = {config->path.game, emulator->information.name, "/"}; - - string databaseText = string::read({systemPath(), "database.bml"}).rtrim("\n"); - lstring databaseItem = databaseText.split("\n\n"); - - for(auto &item : databaseItem) { - item.append("\n"); - auto document = Markup::Document(item); - - if(document["cartridge"]["information"]["sha256"].text() == sha256) { - string folderPath = { - outputPath, - document["cartridge"]["information"]["name"].text(), - " (", document["cartridge"]["information"]["region"].text(), ")", - " (", document["cartridge"]["information"]["revision"].text(), ")", - ".", extension(filename), "/" - }; - directory::create(folderPath); - file::write({folderPath, "sha256"}, (const uint8_t*)(const char*)sha256, sha256.length()); - file::write({folderPath, "manifest.bml"}, (const uint8_t*)(const char*)item, item.length()); - - auto buffer = file::read(filename); - unsigned offset = 0; - - for(auto &node : document["cartridge"]["information"]["configuration"]) { - if(node.name != "rom") continue; - string name = node["name"].text(); - unsigned size = node["size"].decimal(); - - if(file::exists({folderPath, name}) == false) { - file::write({folderPath, name}, buffer.data() + offset, size); - } - - offset += size; - } - - return loadMedia(emulator, media, folderPath); - } - } - - MessageWindow::warning(*presentation, - "Game not found in database; mapping information not available.\n\n" - "Unable to proceed with emulation." - ); -} diff --git a/higan/target-ethos/utility/utility.cpp b/higan/target-ethos/utility/utility.cpp index 1a3c0ed6..521a6ffe 100755 --- a/higan/target-ethos/utility/utility.cpp +++ b/higan/target-ethos/utility/utility.cpp @@ -1,5 +1,4 @@ #include "../ethos.hpp" -#include "database.cpp" Utility *utility = nullptr; @@ -8,19 +7,44 @@ void Utility::setInterface(Emulator::Interface *emulator) { presentation->synchronize(); } +//load from command-line, etc +void Utility::loadMedia(string pathname) { + pathname.transform("\\", "/"); + if(pathname.endswith("/")) pathname.rtrim("/"); + + //if a filename was provided: convert to game folder and then load + if(!directory::exists(pathname) && file::exists(pathname)) { + if(application->ananke.opened() == false) return; + function open = application->ananke.sym("ananke_open"); + if(open == false) return; + string name = open(pathname); + if(name.empty()) return; + return loadMedia(name); + } + + if(!directory::exists(pathname)) return; + string type = extension(pathname); + + //determine type by comparing extension against all emulation cores + for(auto &emulator : application->emulator) { + for(auto &media : emulator->media) { + if(!media.load.empty()) continue; + if(type != media.type) continue; + return utility->loadMedia(emulator, media, {pathname, "/"}); + } + } + + MessageWindow::warning(Window::none(), "Unable to determine media type."); +} + //load menu option selected void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media) { string pathname; if(!media.load.empty()) pathname = application->path({media.load, "/"}); if(!directory::exists(pathname)) pathname = browser->select("Load Media", media.type); - - if(directory::exists(pathname) && file::exists({pathname, "manifest.bml"})) { - return loadMedia(emulator, media, pathname); - } - - if(file::exists(pathname)) { - return loadMediaFromDatabase(emulator, media, pathname); - } + if(!directory::exists(pathname)) return; + if(!file::exists({pathname, "manifest.bml"})) return; + return loadMedia(emulator, media, pathname); } //load menu cartridge selected or command-line load @@ -38,7 +62,7 @@ void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Medi if(this->pathname.size() == 0) this->pathname.append(pathname); presentation->setSystemName(media.name); - load(Markup::Document(manifest)["cartridge"]["information"]["title"].text()); + load(Markup::Document(manifest)["release/information/title"].text()); } //request from emulation core to load non-volatile media folder diff --git a/higan/target-ethos/utility/utility.hpp b/higan/target-ethos/utility/utility.hpp index b41ce612..d804b2ed 100755 --- a/higan/target-ethos/utility/utility.hpp +++ b/higan/target-ethos/utility/utility.hpp @@ -1,9 +1,9 @@ struct Utility { void setInterface(Emulator::Interface *emulator); + void loadMedia(string pathname); void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media); void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname); - void loadMediaFromDatabase(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &filename); void loadRequest(unsigned id, const string &name, const string &type); void loadRequest(unsigned id, const string &path);