From 92307f31a3368135e46d9da832922dcaf47d5ff8 Mon Sep 17 00:00:00 2001 From: Marcos Medeiros <1381933+zxmarcos@users.noreply.github.com> Date: Thu, 3 Jul 2014 02:59:57 +0000 Subject: [PATCH] Initial Qt port. Deps: nall and ruby are included. --- projectfiles/qtcreator/fba_qt.pro | 940 ++++++++++++++++++ src/burner/burner.h | 10 +- src/burner/qt/aboutdialog.cpp | 21 + src/burner/qt/aboutdialog.h | 22 + src/burner/qt/aboutdialog.ui | 118 +++ src/burner/qt/burner_qt.h | 58 ++ src/burner/qt/bzip.cpp | 518 ++++++++++ src/burner/qt/dipswitchdialog.cpp | 188 ++++ src/burner/qt/dipswitchdialog.h | 34 + src/burner/qt/dipswitchdialog.ui | 96 ++ src/burner/qt/driver.cpp | 122 +++ src/burner/qt/emuworker.cpp | 128 +++ src/burner/qt/emuworker.h | 26 + src/burner/qt/main.cpp | 63 ++ src/burner/qt/mainwindow.cpp | 240 +++++ src/burner/qt/mainwindow.h | 78 ++ src/burner/qt/neocdlist.cpp | 500 ++++++++++ src/burner/qt/progress.cpp | 42 + src/burner/qt/qaudiointerface.cpp | 375 +++++++ src/burner/qt/qaudiointerface.h | 64 ++ src/burner/qt/qinputinterface.cpp | 282 ++++++ src/burner/qt/qinputinterface.h | 30 + src/burner/qt/qrubyviewport.cpp | 144 +++ src/burner/qt/qrubyviewport.h | 18 + src/burner/qt/qutil.cpp | 54 + src/burner/qt/qutil.h | 30 + src/burner/qt/resource/about.bmp | Bin 0 -> 196730 bytes src/burner/qt/resource/license.txt | 48 + src/burner/qt/resource/misc.bmp | Bin 0 -> 14456 bytes src/burner/qt/resource/splash.bmp | Bin 0 -> 204344 bytes .../resource/tv-not-found-non-essential.ico | Bin 0 -> 4286 bytes src/burner/qt/resource/tv-not-found.ico | Bin 0 -> 4286 bytes src/burner/qt/resource/tv-not-working.ico | Bin 0 -> 4286 bytes src/burner/qt/romdirsdialog.cpp | 48 + src/burner/qt/romdirsdialog.h | 32 + src/burner/qt/romdirsdialog.ui | 188 ++++ src/burner/qt/rominfodialog.cpp | 74 ++ src/burner/qt/rominfodialog.h | 25 + src/burner/qt/rominfodialog.ui | 121 +++ src/burner/qt/romscandialog.cpp | 102 ++ src/burner/qt/romscandialog.h | 47 + src/burner/qt/romscandialog.ui | 87 ++ src/burner/qt/rscr.qrc | 11 + src/burner/qt/ruby/Makefile | 37 + src/burner/qt/ruby/audio.hpp | 18 + src/burner/qt/ruby/audio/alsa.cpp | 240 +++++ src/burner/qt/ruby/audio/ao.cpp | 94 ++ src/burner/qt/ruby/audio/directsound.cpp | 209 ++++ src/burner/qt/ruby/audio/openal.cpp | 210 ++++ src/burner/qt/ruby/audio/oss.cpp | 113 +++ src/burner/qt/ruby/audio/pulseaudio.cpp | 177 ++++ src/burner/qt/ruby/audio/pulseaudiosimple.cpp | 115 +++ src/burner/qt/ruby/audio/xaudio2.cpp | 198 ++++ src/burner/qt/ruby/audio/xaudio2.hpp | 340 +++++++ src/burner/qt/ruby/implementation.cpp | 172 ++++ src/burner/qt/ruby/input.hpp | 23 + src/burner/qt/ruby/input/carbon.cpp | 182 ++++ .../qt/ruby/input/joypad/directinput.cpp | 211 ++++ src/burner/qt/ruby/input/joypad/sdl.cpp | 81 ++ src/burner/qt/ruby/input/joypad/udev.cpp | 280 ++++++ src/burner/qt/ruby/input/joypad/xinput.cpp | 162 +++ .../qt/ruby/input/keyboard/rawinput.cpp | 178 ++++ src/burner/qt/ruby/input/keyboard/xlib.cpp | 174 ++++ src/burner/qt/ruby/input/mouse/rawinput.cpp | 123 +++ src/burner/qt/ruby/input/mouse/xlib.cpp | 154 +++ src/burner/qt/ruby/input/sdl.cpp | 82 ++ src/burner/qt/ruby/input/shared/rawinput.cpp | 162 +++ src/burner/qt/ruby/input/udev.cpp | 88 ++ src/burner/qt/ruby/input/windows.cpp | 109 ++ src/burner/qt/ruby/input/xlib.cpp | 77 ++ src/burner/qt/ruby/ruby.cpp | 496 +++++++++ src/burner/qt/ruby/ruby.hpp | 98 ++ src/burner/qt/ruby/video.hpp | 25 + src/burner/qt/ruby/video/cgl.cpp | 190 ++++ src/burner/qt/ruby/video/direct3d.cpp | 463 +++++++++ src/burner/qt/ruby/video/directdraw.cpp | 186 ++++ src/burner/qt/ruby/video/gdi.cpp | 100 ++ src/burner/qt/ruby/video/glx.cpp | 251 +++++ src/burner/qt/ruby/video/opengl/bind.hpp | 99 ++ src/burner/qt/ruby/video/opengl/main.hpp | 210 ++++ src/burner/qt/ruby/video/opengl/opengl.hpp | 97 ++ src/burner/qt/ruby/video/opengl/program.hpp | 108 ++ src/burner/qt/ruby/video/opengl/shaders.hpp | 91 ++ src/burner/qt/ruby/video/opengl/surface.hpp | 114 +++ src/burner/qt/ruby/video/opengl/texture.hpp | 12 + src/burner/qt/ruby/video/opengl/utility.hpp | 106 ++ src/burner/qt/ruby/video/sdl.cpp | 141 +++ src/burner/qt/ruby/video/wgl.cpp | 160 +++ src/burner/qt/ruby/video/xshm.cpp | 228 +++++ src/burner/qt/ruby/video/xv.cpp | 499 ++++++++++ src/burner/qt/selectdialog.cpp | 307 ++++++ src/burner/qt/selectdialog.h | 84 ++ src/burner/qt/selectdialog.ui | 653 ++++++++++++ src/burner/qt/stringset.cpp | 52 + src/burner/qt/supportdirsdialog.cpp | 54 + src/burner/qt/supportdirsdialog.h | 40 + src/burner/qt/supportdirsdialog.ui | 199 ++++ src/burner/qt/tchar.h | 65 ++ src/dep/libs/nall/Makefile | 122 +++ src/dep/libs/nall/algorithm.hpp | 19 + src/dep/libs/nall/any.hpp | 98 ++ src/dep/libs/nall/atoi.hpp | 103 ++ src/dep/libs/nall/base64.hpp | 137 +++ src/dep/libs/nall/beat/archive.hpp | 84 ++ src/dep/libs/nall/beat/base.hpp | 92 ++ src/dep/libs/nall/beat/delta.hpp | 215 ++++ src/dep/libs/nall/beat/linear.hpp | 152 +++ src/dep/libs/nall/beat/metadata.hpp | 121 +++ src/dep/libs/nall/beat/multi.hpp | 242 +++++ src/dep/libs/nall/beat/patch.hpp | 219 ++++ src/dep/libs/nall/bit.hpp | 88 ++ src/dep/libs/nall/bmp.hpp | 100 ++ src/dep/libs/nall/compositor.hpp | 152 +++ src/dep/libs/nall/config.hpp | 103 ++ src/dep/libs/nall/crc16.hpp | 27 + src/dep/libs/nall/crc32.hpp | 68 ++ src/dep/libs/nall/directory.hpp | 230 +++++ src/dep/libs/nall/dl.hpp | 119 +++ src/dep/libs/nall/dsp.hpp | 15 + src/dep/libs/nall/dsp/buffer.hpp | 52 + src/dep/libs/nall/dsp/core.hpp | 168 ++++ src/dep/libs/nall/dsp/resample/average.hpp | 72 ++ src/dep/libs/nall/dsp/resample/cosine.hpp | 44 + src/dep/libs/nall/dsp/resample/cubic.hpp | 50 + src/dep/libs/nall/dsp/resample/hermite.hpp | 62 ++ src/dep/libs/nall/dsp/resample/linear.hpp | 43 + src/dep/libs/nall/dsp/resample/nearest.hpp | 43 + src/dep/libs/nall/dsp/resample/sinc.hpp | 54 + src/dep/libs/nall/dsp/settings.hpp | 50 + .../nall/emulation/super-famicom-usart.hpp | 103 ++ src/dep/libs/nall/endian.hpp | 42 + src/dep/libs/nall/file.hpp | 362 +++++++ src/dep/libs/nall/filemap.hpp | 215 ++++ src/dep/libs/nall/function.hpp | 64 ++ src/dep/libs/nall/group.hpp | 66 ++ src/dep/libs/nall/gzip.hpp | 85 ++ src/dep/libs/nall/hashset.hpp | 137 +++ src/dep/libs/nall/hid.hpp | 120 +++ src/dep/libs/nall/http.hpp | 176 ++++ src/dep/libs/nall/image.hpp | 22 + src/dep/libs/nall/image/base.hpp | 124 +++ src/dep/libs/nall/image/blend.hpp | 74 ++ src/dep/libs/nall/image/core.hpp | 164 +++ src/dep/libs/nall/image/fill.hpp | 88 ++ src/dep/libs/nall/image/interpolation.hpp | 65 ++ src/dep/libs/nall/image/load.hpp | 98 ++ src/dep/libs/nall/image/scale.hpp | 190 ++++ src/dep/libs/nall/image/static.hpp | 31 + src/dep/libs/nall/image/utility.hpp | 95 ++ src/dep/libs/nall/inflate.hpp | 349 +++++++ src/dep/libs/nall/interpolation.hpp | 59 ++ src/dep/libs/nall/intrinsics.hpp | 82 ++ src/dep/libs/nall/invoke.hpp | 58 ++ src/dep/libs/nall/ips.hpp | 100 ++ src/dep/libs/nall/map.hpp | 59 ++ src/dep/libs/nall/matrix.hpp | 33 + src/dep/libs/nall/mosaic.hpp | 10 + src/dep/libs/nall/mosaic/bitstream.hpp | 55 + src/dep/libs/nall/mosaic/context.hpp | 227 +++++ src/dep/libs/nall/mosaic/parser.hpp | 126 +++ src/dep/libs/nall/nall.hpp | 63 ++ src/dep/libs/nall/odbc.hpp | 153 +++ src/dep/libs/nall/platform.hpp | 96 ++ src/dep/libs/nall/png.hpp | 337 +++++++ src/dep/libs/nall/priority-queue.hpp | 110 ++ src/dep/libs/nall/property.hpp | 44 + src/dep/libs/nall/public-cast.hpp | 34 + src/dep/libs/nall/random.hpp | 43 + src/dep/libs/nall/serial.hpp | 118 +++ src/dep/libs/nall/serializer.hpp | 150 +++ src/dep/libs/nall/set.hpp | 267 +++++ src/dep/libs/nall/sha256.hpp | 147 +++ src/dep/libs/nall/smtp.hpp | 318 ++++++ src/dep/libs/nall/sort.hpp | 77 ++ src/dep/libs/nall/stdint.hpp | 44 + src/dep/libs/nall/stream.hpp | 26 + src/dep/libs/nall/stream/auto.hpp | 25 + src/dep/libs/nall/stream/file.hpp | 42 + src/dep/libs/nall/stream/gzip.hpp | 34 + src/dep/libs/nall/stream/http.hpp | 49 + src/dep/libs/nall/stream/memory.hpp | 47 + src/dep/libs/nall/stream/mmap.hpp | 42 + src/dep/libs/nall/stream/stream.hpp | 101 ++ src/dep/libs/nall/stream/vector.hpp | 39 + src/dep/libs/nall/stream/zip.hpp | 38 + src/dep/libs/nall/string.hpp | 53 + .../nall/string/allocator/copy-on-write.hpp | 104 ++ .../allocator/small-string-optimization.hpp | 111 +++ src/dep/libs/nall/string/allocator/vector.hpp | 94 ++ src/dep/libs/nall/string/base.hpp | 227 +++++ src/dep/libs/nall/string/cast.hpp | 199 ++++ src/dep/libs/nall/string/char.hpp | 13 + src/dep/libs/nall/string/char/base.hpp | 69 ++ src/dep/libs/nall/string/char/compare.hpp | 81 ++ src/dep/libs/nall/string/char/convert.hpp | 64 ++ src/dep/libs/nall/string/char/match.hpp | 61 ++ src/dep/libs/nall/string/char/strm.hpp | 41 + src/dep/libs/nall/string/char/strpos.hpp | 33 + src/dep/libs/nall/string/char/trim.hpp | 62 ++ src/dep/libs/nall/string/char/utf8.hpp | 37 + src/dep/libs/nall/string/char/utility.hpp | 50 + src/dep/libs/nall/string/core.hpp | 89 ++ src/dep/libs/nall/string/datetime.hpp | 31 + src/dep/libs/nall/string/eval/evaluator.hpp | 157 +++ src/dep/libs/nall/string/eval/literal.hpp | 103 ++ src/dep/libs/nall/string/eval/node.hpp | 41 + src/dep/libs/nall/string/eval/parser.hpp | 168 ++++ src/dep/libs/nall/string/file.hpp | 31 + src/dep/libs/nall/string/filename.hpp | 84 ++ src/dep/libs/nall/string/format.hpp | 72 ++ src/dep/libs/nall/string/list.hpp | 86 ++ src/dep/libs/nall/string/markup/bml.hpp | 157 +++ src/dep/libs/nall/string/markup/document.hpp | 14 + src/dep/libs/nall/string/markup/node.hpp | 147 +++ src/dep/libs/nall/string/markup/xml.hpp | 219 ++++ src/dep/libs/nall/string/platform.hpp | 98 ++ src/dep/libs/nall/string/ref.hpp | 45 + src/dep/libs/nall/string/replace.hpp | 55 + src/dep/libs/nall/string/split.hpp | 37 + src/dep/libs/nall/string/utility.hpp | 117 +++ src/dep/libs/nall/string/variadic.hpp | 20 + src/dep/libs/nall/string/wrapper.hpp | 124 +++ src/dep/libs/nall/thread.hpp | 127 +++ src/dep/libs/nall/traits.hpp | 33 + src/dep/libs/nall/unzip.hpp | 126 +++ src/dep/libs/nall/ups.hpp | 225 +++++ src/dep/libs/nall/utility.hpp | 138 +++ src/dep/libs/nall/varint.hpp | 220 ++++ src/dep/libs/nall/vector.hpp | 275 +++++ src/dep/libs/nall/windows/detour.hpp | 192 ++++ src/dep/libs/nall/windows/guid.hpp | 30 + src/dep/libs/nall/windows/launcher.hpp | 94 ++ src/dep/libs/nall/windows/registry.hpp | 121 +++ src/dep/libs/nall/windows/utf8.hpp | 89 ++ src/dep/libs/nall/xorg/guard.hpp | 31 + src/dep/libs/nall/xorg/xorg.hpp | 12 + src/dep/libs/nall/zip.hpp | 95 ++ src/intf/audio/aud_interface.cpp | 4 + src/intf/input/inp_interface.cpp | 4 + src/intf/video/vid_interface.cpp | 4 + 240 files changed, 28200 insertions(+), 2 deletions(-) create mode 100644 projectfiles/qtcreator/fba_qt.pro create mode 100644 src/burner/qt/aboutdialog.cpp create mode 100644 src/burner/qt/aboutdialog.h create mode 100644 src/burner/qt/aboutdialog.ui create mode 100644 src/burner/qt/burner_qt.h create mode 100644 src/burner/qt/bzip.cpp create mode 100644 src/burner/qt/dipswitchdialog.cpp create mode 100644 src/burner/qt/dipswitchdialog.h create mode 100644 src/burner/qt/dipswitchdialog.ui create mode 100644 src/burner/qt/driver.cpp create mode 100644 src/burner/qt/emuworker.cpp create mode 100644 src/burner/qt/emuworker.h create mode 100644 src/burner/qt/main.cpp create mode 100644 src/burner/qt/mainwindow.cpp create mode 100644 src/burner/qt/mainwindow.h create mode 100644 src/burner/qt/neocdlist.cpp create mode 100644 src/burner/qt/progress.cpp create mode 100644 src/burner/qt/qaudiointerface.cpp create mode 100644 src/burner/qt/qaudiointerface.h create mode 100644 src/burner/qt/qinputinterface.cpp create mode 100644 src/burner/qt/qinputinterface.h create mode 100644 src/burner/qt/qrubyviewport.cpp create mode 100644 src/burner/qt/qrubyviewport.h create mode 100644 src/burner/qt/qutil.cpp create mode 100644 src/burner/qt/qutil.h create mode 100644 src/burner/qt/resource/about.bmp create mode 100644 src/burner/qt/resource/license.txt create mode 100644 src/burner/qt/resource/misc.bmp create mode 100644 src/burner/qt/resource/splash.bmp create mode 100644 src/burner/qt/resource/tv-not-found-non-essential.ico create mode 100644 src/burner/qt/resource/tv-not-found.ico create mode 100644 src/burner/qt/resource/tv-not-working.ico create mode 100644 src/burner/qt/romdirsdialog.cpp create mode 100644 src/burner/qt/romdirsdialog.h create mode 100644 src/burner/qt/romdirsdialog.ui create mode 100644 src/burner/qt/rominfodialog.cpp create mode 100644 src/burner/qt/rominfodialog.h create mode 100644 src/burner/qt/rominfodialog.ui create mode 100644 src/burner/qt/romscandialog.cpp create mode 100644 src/burner/qt/romscandialog.h create mode 100644 src/burner/qt/romscandialog.ui create mode 100644 src/burner/qt/rscr.qrc create mode 100755 src/burner/qt/ruby/Makefile create mode 100755 src/burner/qt/ruby/audio.hpp create mode 100755 src/burner/qt/ruby/audio/alsa.cpp create mode 100755 src/burner/qt/ruby/audio/ao.cpp create mode 100755 src/burner/qt/ruby/audio/directsound.cpp create mode 100755 src/burner/qt/ruby/audio/openal.cpp create mode 100755 src/burner/qt/ruby/audio/oss.cpp create mode 100755 src/burner/qt/ruby/audio/pulseaudio.cpp create mode 100755 src/burner/qt/ruby/audio/pulseaudiosimple.cpp create mode 100755 src/burner/qt/ruby/audio/xaudio2.cpp create mode 100755 src/burner/qt/ruby/audio/xaudio2.hpp create mode 100755 src/burner/qt/ruby/implementation.cpp create mode 100755 src/burner/qt/ruby/input.hpp create mode 100755 src/burner/qt/ruby/input/carbon.cpp create mode 100755 src/burner/qt/ruby/input/joypad/directinput.cpp create mode 100755 src/burner/qt/ruby/input/joypad/sdl.cpp create mode 100755 src/burner/qt/ruby/input/joypad/udev.cpp create mode 100755 src/burner/qt/ruby/input/joypad/xinput.cpp create mode 100755 src/burner/qt/ruby/input/keyboard/rawinput.cpp create mode 100755 src/burner/qt/ruby/input/keyboard/xlib.cpp create mode 100755 src/burner/qt/ruby/input/mouse/rawinput.cpp create mode 100755 src/burner/qt/ruby/input/mouse/xlib.cpp create mode 100755 src/burner/qt/ruby/input/sdl.cpp create mode 100755 src/burner/qt/ruby/input/shared/rawinput.cpp create mode 100755 src/burner/qt/ruby/input/udev.cpp create mode 100755 src/burner/qt/ruby/input/windows.cpp create mode 100755 src/burner/qt/ruby/input/xlib.cpp create mode 100755 src/burner/qt/ruby/ruby.cpp create mode 100755 src/burner/qt/ruby/ruby.hpp create mode 100755 src/burner/qt/ruby/video.hpp create mode 100755 src/burner/qt/ruby/video/cgl.cpp create mode 100755 src/burner/qt/ruby/video/direct3d.cpp create mode 100755 src/burner/qt/ruby/video/directdraw.cpp create mode 100755 src/burner/qt/ruby/video/gdi.cpp create mode 100755 src/burner/qt/ruby/video/glx.cpp create mode 100755 src/burner/qt/ruby/video/opengl/bind.hpp create mode 100755 src/burner/qt/ruby/video/opengl/main.hpp create mode 100755 src/burner/qt/ruby/video/opengl/opengl.hpp create mode 100755 src/burner/qt/ruby/video/opengl/program.hpp create mode 100755 src/burner/qt/ruby/video/opengl/shaders.hpp create mode 100755 src/burner/qt/ruby/video/opengl/surface.hpp create mode 100755 src/burner/qt/ruby/video/opengl/texture.hpp create mode 100755 src/burner/qt/ruby/video/opengl/utility.hpp create mode 100755 src/burner/qt/ruby/video/sdl.cpp create mode 100755 src/burner/qt/ruby/video/wgl.cpp create mode 100755 src/burner/qt/ruby/video/xshm.cpp create mode 100755 src/burner/qt/ruby/video/xv.cpp create mode 100644 src/burner/qt/selectdialog.cpp create mode 100644 src/burner/qt/selectdialog.h create mode 100644 src/burner/qt/selectdialog.ui create mode 100644 src/burner/qt/stringset.cpp create mode 100644 src/burner/qt/supportdirsdialog.cpp create mode 100644 src/burner/qt/supportdirsdialog.h create mode 100644 src/burner/qt/supportdirsdialog.ui create mode 100644 src/burner/qt/tchar.h create mode 100755 src/dep/libs/nall/Makefile create mode 100755 src/dep/libs/nall/algorithm.hpp create mode 100755 src/dep/libs/nall/any.hpp create mode 100755 src/dep/libs/nall/atoi.hpp create mode 100755 src/dep/libs/nall/base64.hpp create mode 100755 src/dep/libs/nall/beat/archive.hpp create mode 100755 src/dep/libs/nall/beat/base.hpp create mode 100755 src/dep/libs/nall/beat/delta.hpp create mode 100755 src/dep/libs/nall/beat/linear.hpp create mode 100755 src/dep/libs/nall/beat/metadata.hpp create mode 100755 src/dep/libs/nall/beat/multi.hpp create mode 100755 src/dep/libs/nall/beat/patch.hpp create mode 100755 src/dep/libs/nall/bit.hpp create mode 100755 src/dep/libs/nall/bmp.hpp create mode 100755 src/dep/libs/nall/compositor.hpp create mode 100755 src/dep/libs/nall/config.hpp create mode 100755 src/dep/libs/nall/crc16.hpp create mode 100755 src/dep/libs/nall/crc32.hpp create mode 100755 src/dep/libs/nall/directory.hpp create mode 100755 src/dep/libs/nall/dl.hpp create mode 100755 src/dep/libs/nall/dsp.hpp create mode 100755 src/dep/libs/nall/dsp/buffer.hpp create mode 100755 src/dep/libs/nall/dsp/core.hpp create mode 100755 src/dep/libs/nall/dsp/resample/average.hpp create mode 100755 src/dep/libs/nall/dsp/resample/cosine.hpp create mode 100755 src/dep/libs/nall/dsp/resample/cubic.hpp create mode 100755 src/dep/libs/nall/dsp/resample/hermite.hpp create mode 100755 src/dep/libs/nall/dsp/resample/linear.hpp create mode 100755 src/dep/libs/nall/dsp/resample/nearest.hpp create mode 100755 src/dep/libs/nall/dsp/resample/sinc.hpp create mode 100755 src/dep/libs/nall/dsp/settings.hpp create mode 100755 src/dep/libs/nall/emulation/super-famicom-usart.hpp create mode 100755 src/dep/libs/nall/endian.hpp create mode 100755 src/dep/libs/nall/file.hpp create mode 100755 src/dep/libs/nall/filemap.hpp create mode 100755 src/dep/libs/nall/function.hpp create mode 100755 src/dep/libs/nall/group.hpp create mode 100755 src/dep/libs/nall/gzip.hpp create mode 100755 src/dep/libs/nall/hashset.hpp create mode 100755 src/dep/libs/nall/hid.hpp create mode 100755 src/dep/libs/nall/http.hpp create mode 100755 src/dep/libs/nall/image.hpp create mode 100755 src/dep/libs/nall/image/base.hpp create mode 100755 src/dep/libs/nall/image/blend.hpp create mode 100755 src/dep/libs/nall/image/core.hpp create mode 100755 src/dep/libs/nall/image/fill.hpp create mode 100755 src/dep/libs/nall/image/interpolation.hpp create mode 100755 src/dep/libs/nall/image/load.hpp create mode 100755 src/dep/libs/nall/image/scale.hpp create mode 100755 src/dep/libs/nall/image/static.hpp create mode 100755 src/dep/libs/nall/image/utility.hpp create mode 100755 src/dep/libs/nall/inflate.hpp create mode 100755 src/dep/libs/nall/interpolation.hpp create mode 100755 src/dep/libs/nall/intrinsics.hpp create mode 100755 src/dep/libs/nall/invoke.hpp create mode 100755 src/dep/libs/nall/ips.hpp create mode 100755 src/dep/libs/nall/map.hpp create mode 100755 src/dep/libs/nall/matrix.hpp create mode 100755 src/dep/libs/nall/mosaic.hpp create mode 100755 src/dep/libs/nall/mosaic/bitstream.hpp create mode 100755 src/dep/libs/nall/mosaic/context.hpp create mode 100755 src/dep/libs/nall/mosaic/parser.hpp create mode 100755 src/dep/libs/nall/nall.hpp create mode 100755 src/dep/libs/nall/odbc.hpp create mode 100755 src/dep/libs/nall/platform.hpp create mode 100755 src/dep/libs/nall/png.hpp create mode 100755 src/dep/libs/nall/priority-queue.hpp create mode 100755 src/dep/libs/nall/property.hpp create mode 100755 src/dep/libs/nall/public-cast.hpp create mode 100755 src/dep/libs/nall/random.hpp create mode 100755 src/dep/libs/nall/serial.hpp create mode 100755 src/dep/libs/nall/serializer.hpp create mode 100755 src/dep/libs/nall/set.hpp create mode 100755 src/dep/libs/nall/sha256.hpp create mode 100755 src/dep/libs/nall/smtp.hpp create mode 100755 src/dep/libs/nall/sort.hpp create mode 100755 src/dep/libs/nall/stdint.hpp create mode 100755 src/dep/libs/nall/stream.hpp create mode 100755 src/dep/libs/nall/stream/auto.hpp create mode 100755 src/dep/libs/nall/stream/file.hpp create mode 100755 src/dep/libs/nall/stream/gzip.hpp create mode 100755 src/dep/libs/nall/stream/http.hpp create mode 100755 src/dep/libs/nall/stream/memory.hpp create mode 100755 src/dep/libs/nall/stream/mmap.hpp create mode 100755 src/dep/libs/nall/stream/stream.hpp create mode 100755 src/dep/libs/nall/stream/vector.hpp create mode 100755 src/dep/libs/nall/stream/zip.hpp create mode 100755 src/dep/libs/nall/string.hpp create mode 100755 src/dep/libs/nall/string/allocator/copy-on-write.hpp create mode 100755 src/dep/libs/nall/string/allocator/small-string-optimization.hpp create mode 100755 src/dep/libs/nall/string/allocator/vector.hpp create mode 100755 src/dep/libs/nall/string/base.hpp create mode 100755 src/dep/libs/nall/string/cast.hpp create mode 100755 src/dep/libs/nall/string/char.hpp create mode 100755 src/dep/libs/nall/string/char/base.hpp create mode 100755 src/dep/libs/nall/string/char/compare.hpp create mode 100755 src/dep/libs/nall/string/char/convert.hpp create mode 100755 src/dep/libs/nall/string/char/match.hpp create mode 100755 src/dep/libs/nall/string/char/strm.hpp create mode 100755 src/dep/libs/nall/string/char/strpos.hpp create mode 100755 src/dep/libs/nall/string/char/trim.hpp create mode 100755 src/dep/libs/nall/string/char/utf8.hpp create mode 100755 src/dep/libs/nall/string/char/utility.hpp create mode 100755 src/dep/libs/nall/string/core.hpp create mode 100755 src/dep/libs/nall/string/datetime.hpp create mode 100755 src/dep/libs/nall/string/eval/evaluator.hpp create mode 100755 src/dep/libs/nall/string/eval/literal.hpp create mode 100755 src/dep/libs/nall/string/eval/node.hpp create mode 100755 src/dep/libs/nall/string/eval/parser.hpp create mode 100755 src/dep/libs/nall/string/file.hpp create mode 100755 src/dep/libs/nall/string/filename.hpp create mode 100755 src/dep/libs/nall/string/format.hpp create mode 100755 src/dep/libs/nall/string/list.hpp create mode 100755 src/dep/libs/nall/string/markup/bml.hpp create mode 100755 src/dep/libs/nall/string/markup/document.hpp create mode 100755 src/dep/libs/nall/string/markup/node.hpp create mode 100755 src/dep/libs/nall/string/markup/xml.hpp create mode 100755 src/dep/libs/nall/string/platform.hpp create mode 100755 src/dep/libs/nall/string/ref.hpp create mode 100755 src/dep/libs/nall/string/replace.hpp create mode 100755 src/dep/libs/nall/string/split.hpp create mode 100755 src/dep/libs/nall/string/utility.hpp create mode 100755 src/dep/libs/nall/string/variadic.hpp create mode 100755 src/dep/libs/nall/string/wrapper.hpp create mode 100755 src/dep/libs/nall/thread.hpp create mode 100755 src/dep/libs/nall/traits.hpp create mode 100755 src/dep/libs/nall/unzip.hpp create mode 100755 src/dep/libs/nall/ups.hpp create mode 100755 src/dep/libs/nall/utility.hpp create mode 100755 src/dep/libs/nall/varint.hpp create mode 100755 src/dep/libs/nall/vector.hpp create mode 100755 src/dep/libs/nall/windows/detour.hpp create mode 100755 src/dep/libs/nall/windows/guid.hpp create mode 100755 src/dep/libs/nall/windows/launcher.hpp create mode 100755 src/dep/libs/nall/windows/registry.hpp create mode 100755 src/dep/libs/nall/windows/utf8.hpp create mode 100755 src/dep/libs/nall/xorg/guard.hpp create mode 100755 src/dep/libs/nall/xorg/xorg.hpp create mode 100755 src/dep/libs/nall/zip.hpp diff --git a/projectfiles/qtcreator/fba_qt.pro b/projectfiles/qtcreator/fba_qt.pro new file mode 100644 index 000000000..1a21b8416 --- /dev/null +++ b/projectfiles/qtcreator/fba_qt.pro @@ -0,0 +1,940 @@ +#=============================================================================== +# FINAL BURN ALPHA QT +#=============================================================================== +QT += widgets multimedia +TARGET = fbaqt + +linux:QT += x11extras + +#=============================================================================== +# DRIVERS +#=============================================================================== +DRV_CAPCOM = true +DRV_CAVE = true +DRV_CPS3 = true +DRV_DATAEAST = true +DRV_GALAXIAN = true +DRV_IREM = true +DRV_KONAMI = true +DRV_SMS = true +DRV_MEGADRIVE = true +DRV_NEOGEO = true +DRV_PCE = true +DRV_PGM = true +DRV_PRE90S = true +DRV_PST90S = true +DRV_SEGA = true +DRV_SNES = true +DRV_TAITO = true +DRV_TOAPLAN = true +DRV_PSIKYO = true + +#=============================================================================== +# DEPENDENCIES +#=============================================================================== + +#------------------------------------------------------------------------------- +# for Linux, absolute paths +#------------------------------------------------------------------------------- +SRC = $$system(readlink -m $$PWD/../../src) +SCRIPTS = $$SRC/dep/scripts +GEN = $$SRC/dep/generated + +# We need ld +FBA_LD = ld + +#------------------------------------------------------------------------------- +# Additional include paths +#------------------------------------------------------------------------------- +INCLUDEPATH += \ + $$GEN \ + $$SRC/dep/qtcreator \ + $$SRC/burn \ + $$SRC/burn/snd \ + $$SRC/burn/devices \ + $$SRC/burn/drv/capcom \ + $$SRC/burn/drv/cave \ + $$SRC/burn/drv/konami \ + $$SRC/burn/drv/neogeo \ + $$SRC/burn/drv/pgm \ + $$SRC/burn/drv/pre90s \ + $$SRC/burn/drv/psikyo \ + $$SRC/burn/drv/pst90s \ + $$SRC/burn/drv/sega \ + $$SRC/burn/drv/taito \ + $$SRC/burn/drv/toaplan \ + $$SRC/burner \ + $$SRC/burner/qt \ + $$SRC/cpu \ + $$SRC/cpu/i8039 \ + $$SRC/cpu/konami \ + $$SRC/cpu/m68k \ + $$SRC/intf \ + $$SRC/intf/video \ + $$SRC/intf/video/scalers \ + $$SRC/intf/input \ + $$SRC/intf/audio \ + $$SRC/intf/cd \ + $$SRC/dep/libs \ + $$SRC/dep/libs/libpng \ + $$SRC/dep/libs/zlib \ + +DEFINES += BUILD_QT \ + LSB_FIRST \ + "__fastcall=" \ + "_fastcall=" \ + WITH_QTCREATOR \ + INCLUDE_LIB_PNGH + +# no warnings... +QMAKE_CXXFLAGS += -w +QMAKE_CFLAGS += -w + + +#------------------------------------------------------------------------------- +# C++11 +#------------------------------------------------------------------------------- +CONFIG += c++11 + +#------------------------------------------------------------------------------- +# src/dep/generated +#------------------------------------------------------------------------------- +GENERATED.target = $$GEN/empty +GENERATED.commands = \ + @echo "Creating generated directory..."; \ + cd $$SRC/dep; \ + mkdir generated; \ + touch generated/empty + +#------------------------------------------------------------------------------- +# src/dep/generated/ctv.h +#------------------------------------------------------------------------------- +CTV_HEADER.depends = GENERATED +CTV_HEADER.target = $$GEN/ctv.h + +CTV_HEADER.commands = \ + @echo "Building ctv..."; \ + cd $$SRC/burn/drv/capcom; \ + $$QMAKE_CXX ctv_make.cpp -o ctv_make; \ + ./ctv_make > $$CTV_HEADER.target; \ + rm ctv_make + +#------------------------------------------------------------------------------- +# perl scripts +#------------------------------------------------------------------------------- + +# cave_sprite_func.h +CAVE_SPRFUNC_HEADER.depends = GENERATED +CAVE_SPRFUNC_HEADER.target = $$GEN/cave_sprite_func.h +CAVE_SPRFUNC_HEADER.commands = \ + @echo "Generating cave_sprite_func.h"; \ + perl $$SCRIPTS/cave_sprite_func.pl -o $$CAVE_SPRFUNC_HEADER.target; + +# cave_tile_func.h +CAVE_TILEFUNC_HEADER.depends = GENERATED +CAVE_TILEFUNC_HEADER.target = $$GEN/cave_tile_func.h +CAVE_TILEFUNC_HEADER.commands = \ + @echo "Generating cave_tile_func.h"; \ + perl $$SCRIPTS/cave_tile_func.pl -o $$CAVE_TILEFUNC_HEADER.target; + +# neo_sprite_func.h +NEO_SPRFUNC_HEADER.depends = GENERATED +NEO_SPRFUNC_HEADER.target = $$GEN/neo_sprite_func.h +NEO_SPRFUNC_HEADER.commands = \ + @echo "Generating neo_sprite_func.h"; \ + perl $$SCRIPTS/neo_sprite_func.pl -o $$NEO_SPRFUNC_HEADER.target; + +# psikyo_tile_func.h +PSIKYO_TILEFUNC_HEADER.depends = GENERATED +PSIKYO_TILEFUNC_HEADER.target = $$GEN/psikyo_tile_func.h +PSIKYO_TILEFUNC_HEADER.commands = \ + @echo "Generating psikyo_tile_func.h"; \ + perl $$SCRIPTS/psikyo_tile_func.pl -o $$PSIKYO_TILEFUNC_HEADER.target; + +# toa_gp9001_func +TOA_GP9001_FUNC_HEADER.depends = GENERATED +TOA_GP9001_FUNC_HEADER.target = $$GEN/toa_gp9001_func.h +TOA_GP9001_FUNC_HEADER.commands = \ + @echo "Generating toa_gp9001_func.h"; \ + perl $$SCRIPTS/toa_gp9001_func.pl -o $$TOA_GP9001_FUNC_HEADER.target; + +#------------------------------------------------------------------------------- +# pgm_sprite.h +#------------------------------------------------------------------------------- +PGM_SPRITE_CREATE.depends = GENERATED +PGM_SPRITE_CREATE.target = $$GEN/pgm_sprite_create +PGM_SPRITE_CREATE.commands =\ + @echo "Compiling pgm_sprite_create.cpp"; \ + $$QMAKE_CXX $$SRC/burn/drv/pgm/pgm_sprite_create.cpp -o $$PGM_SPRITE_CREATE.target; + +PGM_SPRITE_HEADER.depends = PGM_SPRITE_CREATE +PGM_SPRITE_HEADER.target = $$GEN/pgm_sprite.h +PGM_SPRITE_HEADER.commands =\ + @echo "Generating pgm_sprite.h"; \ + $$PGM_SPRITE_CREATE.target > $$PGM_SPRITE_HEADER.target + + + +#------------------------------------------------------------------------------- +# Musashi68k +#------------------------------------------------------------------------------- +M68K_MAKE.target = $$GEN/m68kmake +M68K_MAKE.depends = $$SRC/cpu/m68k/m68kmake.c GENERATED +M68K_MAKE.commands = \ + @echo "Compiling Musashi MC680x0 core: m68kmake.c..."; \ + $$QMAKE_CC $$SRC/cpu/m68k/m68kmake.c -o $$M68K_MAKE.target; \ + $$M68K_MAKE.target $$GEN/ $$SRC/cpu/m68k/m68k_in.c; + +# objects +M68K_OPAC.target = $$GEN/m68kopac.o +M68K_OPAC.depends = M68K_MAKE +M68K_OPAC.commands = \ + @echo "Compiling Musashi MC680x0 core: m68kopac.c..."; \ + $$QMAKE_CC $$QMAKE_CFLAGS -I$$SRC/cpu/m68k $$GEN/m68kopac.c -c -o $$M68K_OPAC.target; + +M68K_OPDM.target = $$GEN/m68kopdm.o +M68K_OPDM.depends = M68K_MAKE +M68K_OPDM.commands = \ + @echo "Compiling Musashi MC680x0 core: m68kopdm.c..."; \ + $$QMAKE_CC $$QMAKE_CFLAGS -I$$SRC/cpu/m68k $$GEN/m68kopdm.c -c -o $$M68K_OPDM.target; + +M68K_OPNZ.target = $$GEN/m68kopnz.o +M68K_OPNZ.depends = M68K_MAKE +M68K_OPNZ.commands = \ + @echo "Compiling Musashi MC680x0 core: m68kopnz.c..."; \ + $$QMAKE_CC $$QMAKE_CFLAGS -I$$SRC/cpu/m68k $$GEN/m68kopnz.c -c -o $$M68K_OPNZ.target; + +M68K_OPS.target = $$GEN/m68kops.o +M68K_OPS.depends = M68K_MAKE +M68K_OPS.commands = \ + @echo "Compiling Musashi MC680x0 core: m68kops.c..."; \ + $$QMAKE_CC $$QMAKE_CFLAGS -I$$SRC/cpu/m68k $$GEN/m68kops.c -c -o $$M68K_OPS.target; + +M68K_OPS_HEADER.target = $$GEN/m68kops.h +M68K_OPS_HEADER.depends = M68K_MAKE +M68K_OPS_HEADER.commands = \ + $$M68K_MAKE.target $$GEN/ $$SRC/cpu/m68k/m68k_in.c; + +M68K_LIB.target = $$GEN/libm68kops.o +M68K_LIB.depends = M68K_MAKE M68K_OPAC M68K_OPDM M68K_OPNZ M68K_OPS M68K_OPS_HEADER +M68K_LIB.commands = \ + @echo "Partially linking Musashi MC680x0 core: libm68kops.o..."; \ + $$FBA_LD -r $$M68K_OPAC.target $$M68K_OPDM.target \ + $$M68K_OPNZ.target $$M68K_OPS.target -o $$M68K_LIB.target + + +OBJECTS += $$M68K_LIB.target + +#------------------------------------------------------------------------------- +# Gamelist +#------------------------------------------------------------------------------- +DRIVERLIST.depends = GENERATED +DRIVERLIST.target = $$GEN/driverlist.h + +DRIVERLIST_PATHS = +$$DRV_SMS:DRIVERLIST_PATHS += $$SRC/burn/drv/sms +$$DRV_PCE:DRIVERLIST_PATHS += $$SRC/burn/drv/pce +$$DRV_PGM:DRIVERLIST_PATHS += $$SRC/burn/drv/pgm +$$DRV_CAVE:DRIVERLIST_PATHS += $$SRC/burn/drv/cave +$$DRV_CPS3:DRIVERLIST_PATHS += $$SRC/burn/drv/cps3 +$$DRV_IREM:DRIVERLIST_PATHS += $$SRC/burn/drv/irem +$$DRV_SEGA:DRIVERLIST_PATHS += $$SRC/burn/drv/sega +$$DRV_SNES:DRIVERLIST_PATHS += $$SRC/burn/drv/snes +$$DRV_TAITO:DRIVERLIST_PATHS += $$SRC/burn/drv/taito +$$DRV_CAPCOM:DRIVERLIST_PATHS += $$SRC/burn/drv/capcom +$$DRV_NEOGEO:DRIVERLIST_PATHS += $$SRC/burn/drv/neogeo +$$DRV_KONAMI:DRIVERLIST_PATHS += $$SRC/burn/drv/konami +$$DRV_PRE90S:DRIVERLIST_PATHS += $$SRC/burn/drv/pre90s +$$DRV_PST90S:DRIVERLIST_PATHS += $$SRC/burn/drv/pst90s +$$DRV_PSIKYO:DRIVERLIST_PATHS += $$SRC/burn/drv/psikyo +$$DRV_TOAPLAN:DRIVERLIST_PATHS += $$SRC/burn/drv/toaplan +$$DRV_DATAEAST:DRIVERLIST_PATHS += $$SRC/burn/drv/dataeast +$$DRV_GALAXIAN:DRIVERLIST_PATHS += $$SRC/burn/drv/galaxian +$$DRV_MEGADRIVE:DRIVERLIST_PATHS += $$SRC/burn/drv/megadrive + +DRIVERLIST.commands = \ + @echo "Generating driverlist.h"; \ + perl $$SCRIPTS/gamelist.pl -o $$DRIVERLIST.target $$DRIVERLIST_PATHS; + +#=============================================================================== +# RUBY CONFIG +#=============================================================================== +LINUX_VIDEO_XV = true +LINUX_VIDEO_SDL = true +LINUX_VIDEO_XSHM = true +LINUX_VIDEO_GLX = true + +MACX_VIDEO_CGL = true +MACX_VIDEO_SDL = true + +linux { + DEFINES += PLATFORM_X + + $${LINUX_VIDEO_XV} { + DEFINES += VIDEO_XV + SOURCES += $$SRC/burner/qt/ruby/video/xv.cpp + LIBS += -lX11 -lXext -lXv + } + + $${LINUX_VIDEO_SDL} { + DEFINES += VIDEO_SDL + SOURCES += $$SRC/burner/qt/ruby/video/sdl.cpp + LIBS *= -lSDL + } + + $${LINUX_VIDEO_XSHM} { + DEFINES += VIDEO_XSHM + SOURCES += $$SRC/burner/qt/ruby/video/xshm.cpp + LIBS += -lX11 -lXext + } + + $${LINUX_VIDEO_GLX} { + DEFINES += VIDEO_GLX + SOURCES += $$SRC/burner/qt/ruby/video/glx.cpp + LIBS += -lX11 -lXext -lGL + } +} + +macx { + DEFINES += PLATFORM_MACOSX + $${MACX_VIDEO_CGL} { + SOURCES += $$SRC/burner/qt/ruby/video/cgl.cpp + # LIBS += + } + + $${MACX_VIDEO_SDL} { + DEFINES += VIDEO_SDL + SOURCES += $$SRC/burner/qt/ruby/video/sdl.cpp + } +} + +$${LINUX_VIDEO_GLX} | $${MACX_VIDEO_CGL} { + HEADERS += $$SRC/burner/qt/ruby/video/opengl/bind.hpp \ + $$SRC/burner/qt/ruby/video/opengl/main.hpp \ + $$SRC/burner/qt/ruby/video/opengl/opengl.hpp \ + $$SRC/burner/qt/ruby/video/opengl/program.hpp \ + $$SRC/burner/qt/ruby/video/opengl/shaders.hpp \ + $$SRC/burner/qt/ruby/video/opengl/surface.hpp \ + $$SRC/burner/qt/ruby/video/opengl/texture.hpp \ + $$SRC/burner/qt/ruby/video/opengl/utility.hpp +} + +#------------------------------------------------------------------------------- +#------------------------------------------------------------------------------- +QMAKE_EXTRA_TARGETS += \ + GENERATED \ + CTV_HEADER \ + CAVE_SPRFUNC_HEADER \ + CAVE_TILEFUNC_HEADER \ + NEO_SPRFUNC_HEADER \ + PSIKYO_TILEFUNC_HEADER \ + PGM_SPRITE_CREATE \ + PGM_SPRITE_HEADER \ + TOA_GP9001_FUNC_HEADER \ + DRIVERLIST \ + M68K_MAKE \ + M68K_OPAC \ + M68K_OPDM \ + M68K_OPNZ \ + M68K_OPS \ + M68K_OPS_HEADER \ + M68K_LIB + +PRE_TARGETDEPS += \ + $$CTV_HEADER.target \ + $$CAVE_SPRFUNC_HEADER.target \ + $$CAVE_TILEFUNC_HEADER.target \ + $$NEO_SPRFUNC_HEADER.target \ + $$PSIKYO_TILEFUNC_HEADER.target \ + $$PGM_SPRITE_HEADER.target \ + $$TOA_GP9001_FUNC_HEADER.target \ + $$DRIVERLIST.target \ + $$M68K_LIB.target \ + +LIBS += -lSDL + +QMAKE_CLEAN += $$GEN/* +#=============================================================================== +# CAPCOM DRIVERS +#=============================================================================== +$$DRV_CAPCOM { + message("Capcom drivers enabled") + + HEADERS += $$files(../../src/burn/drv/capcom/*.h) + SOURCES += $$files(../../src/burn/drv/capcom/*.cpp) + SOURCES -= ../../src/burn/drv/capcom/ctv_make.cpp +} + +#=============================================================================== +# CAVE DRIVERS +#=============================================================================== +$$DRV_CAVE { + message("Cave drivers enabled") + HEADERS += $$files(../../src/burn/drv/cave/*.h) + SOURCES += $$files(../../src/burn/drv/cave/*.cpp) +} + +#=============================================================================== +# CPS3 DRIVERS +#=============================================================================== +$$DRV_CPS3 { + message("CPS3 drivers enabled") + + HEADERS += $$files(../../src/burn/drv/cps3/*.h) + SOURCES += $$files(../../src/burn/drv/cps3/*.cpp) +} + +#=============================================================================== +# DATAEAST DRIVERS +#=============================================================================== +$$DRV_DATAEAST { + message("Dataeast drivers enabled") + + HEADERS += $$files(../../src/burn/drv/dataeast/*.h) + SOURCES += $$files(../../src/burn/drv/dataeast/*.cpp) +} + +#=============================================================================== +# GALAXIAN DRIVERS +#=============================================================================== +$$DRV_GALAXIAN { + message("Galaxian drivers enabled") + + HEADERS += $$files(../../src/burn/drv/galaxian/*.h) + SOURCES += $$files(../../src/burn/drv/galaxian/*.cpp) +} + +#=============================================================================== +# IREM DRIVERS +#=============================================================================== +$$DRV_IREM { + message("Irem drivers enabled") + + HEADERS += $$files(../../src/burn/drv/irem/*.h) + SOURCES += $$files(../../src/burn/drv/irem/*.cpp) +} + +#=============================================================================== +# KONAMI DRIVERS +#=============================================================================== +$$DRV_KONAMI { + message("Konami drivers enabled") + + HEADERS += $$files(../../src/burn/drv/konami/*.h) + SOURCES += $$files(../../src/burn/drv/konami/*.cpp) +} + +#=============================================================================== +# MEGADRIVE DRIVERS +#=============================================================================== +$$DRV_MEGADRIVE { + message("Megadrive drivers enabled") + + HEADERS += $$files(../../src/burn/drv/megadrive/*.h) + SOURCES += $$files(../../src/burn/drv/megadrive/*.cpp) +} + +#=============================================================================== +# NEOGEO DRIVERS +#=============================================================================== +$$DRV_NEOGEO { + message("Neogeo drivers enabled") + + HEADERS += $$files(../../src/burn/drv/neogeo/*.h) + SOURCES += $$files(../../src/burn/drv/neogeo/*.cpp) + SOURCES *= ../../src/burner/qt/neocdlist.cpp +} + +#=============================================================================== +# PC-ENGINE DRIVERS +#=============================================================================== +$$DRV_PCE { + message("PC-Engine drivers enabled") + + HEADERS += $$files(../../src/burn/drv/pce/*.h) + SOURCES += $$files(../../src/burn/drv/pce/*.cpp) +} + +#=============================================================================== +# PGM DRIVERS +#=============================================================================== +$$DRV_PGM { + message("PGM drivers enabled") + + HEADERS += $$files(../../src/burn/drv/pgm/*.h) + SOURCES += $$files(../../src/burn/drv/pgm/*.cpp) + SOURCES -= ../../src/burn/drv/pgm/pgm_sprite_create.cpp +} + +#=============================================================================== +# PRE90S DRIVERS +#=============================================================================== +$$DRV_PRE90S { + message("Pre90s drivers enabled") + + HEADERS += $$files(../../src/burn/drv/pre90s/*.h) + SOURCES += $$files(../../src/burn/drv/pre90s/*.cpp) + + HEADERS *= ../../src/burn/drv/sega/mc8123.h + SOURCES *= ../../src/burn/drv/sega/mc8123.cpp + # CAPCOM deps... + HEADERS *= $$files(../../src/burn/drv/capcom/*.h) + SOURCES *= $$files(../../src/burn/drv/capcom/*.cpp) + SOURCES -= ../../src/burn/drv/capcom/ctv_make.cpp + # KONAMI deps... + HEADERS *= $$files(../../src/burn/drv/konami/*.h) + SOURCES *= $$files(../../src/burn/drv/konami/k*.cpp) + +} + + +#=============================================================================== +# PSIKYO DRIVERS +#=============================================================================== +$$DRV_PSIKYO { + message("Psikyo drivers enabled") + + HEADERS += $$files(../../src/burn/drv/psikyo/*.h) + SOURCES += $$files(../../src/burn/drv/psikyo/*.cpp) +} + + +#=============================================================================== +# PST90S DRIVERS +#=============================================================================== +$$DRV_PST90S { + message("Pst90s drivers enabled") + HEADERS += $$files(../../src/burn/drv/pst90s/*.h) + SOURCES += $$files(../../src/burn/drv/pst90s/*.cpp) +} + +#=============================================================================== +# SEGA DRIVERS +#=============================================================================== +$$DRV_SEGA { + message("Sega drivers enabled") + + HEADERS *= $$files(../../src/burn/drv/sega/*.h) + SOURCES *= $$files(../../src/burn/drv/sega/*.cpp) +} + +#=============================================================================== +# MASTERSYSTEM DRIVERS +#=============================================================================== +$$DRV_SMS { + message("SMS drivers enabled") + + HEADERS += $$files(../../src/burn/drv/sms/*.h) + SOURCES += $$files(../../src/burn/drv/sms/*.cpp) +} + +#=============================================================================== +# SNES DRIVERS +#=============================================================================== +$$DRV_SNES { + message("SNES drivers enabled") + + HEADERS += $$files(../../src/burn/drv/snes/*.h) + SOURCES += $$files(../../src/burn/drv/snes/*.cpp) +} + + +#=============================================================================== +# TAITO DRIVERS +#=============================================================================== +$$DRV_TAITO { + message("Taito drivers enabled") + + HEADERS += $$files(../../src/burn/drv/taito/*.h) + SOURCES += $$files(../../src/burn/drv/taito/*.cpp) +} + + +#=============================================================================== +# TOAPLAN DRIVERS +#=============================================================================== +$$DRV_TOAPLAN { + message("Toaplan drivers enabled") + + HEADERS += $$files(../../src/burn/drv/toaplan/*.h) + SOURCES += $$files(../../src/burn/drv/toaplan/*.cpp) +} + +SOURCES += \ + ../../src/burn/devices/8255ppi.cpp \ + ../../src/burn/devices/8257dma.cpp \ + ../../src/burn/devices/eeprom.cpp \ + ../../src/burn/devices/pandora.cpp \ + ../../src/burn/devices/seibusnd.cpp \ + ../../src/burn/devices/sknsspr.cpp \ + ../../src/burn/devices/slapstic.cpp \ + ../../src/burn/devices/t5182.cpp \ + ../../src/burn/devices/timekpr.cpp \ + ../../src/burn/devices/tms34061.cpp \ + ../../src/burn/devices/v3021.cpp \ + ../../src/burn/devices/vdc.cpp \ + ../../src/burn/snd/burn_y8950.cpp \ + ../../src/burn/snd/burn_ym2151.cpp \ + ../../src/burn/snd/burn_ym2203.cpp \ + ../../src/burn/snd/burn_ym2413.cpp \ + ../../src/burn/snd/burn_ym2608.cpp \ + ../../src/burn/snd/burn_ym2610.cpp \ + ../../src/burn/snd/burn_ym2612.cpp \ + ../../src/burn/snd/burn_ym3526.cpp \ + ../../src/burn/snd/burn_ym3812.cpp \ + ../../src/burn/snd/burn_ymf278b.cpp \ + ../../src/burn/snd/c6280.cpp \ + ../../src/burn/snd/dac.cpp \ + ../../src/burn/snd/es5506.cpp \ + ../../src/burn/snd/es8712.cpp \ + ../../src/burn/snd/flt_rc.cpp \ + ../../src/burn/snd/ics2115.cpp \ + ../../src/burn/snd/iremga20.cpp \ + ../../src/burn/snd/k005289.cpp \ + ../../src/burn/snd/k007232.cpp \ + ../../src/burn/snd/k051649.cpp \ + ../../src/burn/snd/k053260.cpp \ + ../../src/burn/snd/k054539.cpp \ + ../../src/burn/snd/msm5205.cpp \ + ../../src/burn/snd/msm5232.cpp \ + ../../src/burn/snd/msm6295.cpp \ + ../../src/burn/snd/namco_snd.cpp \ + ../../src/burn/snd/nes_apu.cpp \ + ../../src/burn/snd/rf5c68.cpp \ + ../../src/burn/snd/saa1099.cpp \ + ../../src/burn/snd/samples.cpp \ + ../../src/burn/snd/segapcm.cpp \ + ../../src/burn/snd/sn76496.cpp \ + ../../src/burn/snd/upd7759.cpp \ + ../../src/burn/snd/vlm5030.cpp \ + ../../src/burn/snd/x1010.cpp \ + ../../src/burn/snd/ymz280b.cpp \ + ../../src/burn/snd/ay8910.c \ + ../../src/burn/snd/fm.c \ + ../../src/burn/snd/fmopl.c \ + ../../src/burn/snd/ym2151.c \ + ../../src/burn/snd/ym2413.c \ + ../../src/burn/snd/ymdeltat.c \ + ../../src/burn/snd/ymf278b.c \ + ../../src/burn/burn_sound.cpp \ + ../../src/burn/burn.cpp \ + ../../src/burn/cheat.cpp \ + ../../src/burn/debug_track.cpp \ + ../../src/burn/hiscore.cpp \ + ../../src/burn/load.cpp \ + ../../src/burn/tiles_generic.cpp \ + ../../src/burn/timer.cpp \ + ../../src/burn/vector.cpp \ + ../../src/burn/burn_sound_c.cpp \ + ../../src/burn/burn_memory.cpp \ + ../../src/burn/burn_led.cpp \ + ../../src/burn/burn_gun.cpp \ + ../../src/cpu/hd6309_intf.cpp \ + ../../src/cpu/konami_intf.cpp \ + ../../src/cpu/m6502_intf.cpp \ + ../../src/cpu/m6800_intf.cpp \ + ../../src/cpu/m6805_intf.cpp \ + ../../src/cpu/m6809_intf.cpp \ + ../../src/cpu/m68000_intf.cpp \ + ../../src/cpu/nec_intf.cpp \ + ../../src/cpu/pic16c5x_intf.cpp \ + ../../src/cpu/s2650_intf.cpp \ + ../../src/cpu/h6280_intf.cpp \ + ../../src/cpu/arm7_intf.cpp \ + ../../src/cpu/arm_intf.cpp \ + ../../src/cpu/arm/arm.cpp \ + ../../src/cpu/arm7/arm7.cpp \ + ../../src/cpu/arm7/arm7core.c \ + ../../src/cpu/arm7/arm7exec.c \ + ../../src/cpu/h6280/h6280.cpp \ + ../../src/cpu/h6280/tblh6280.c \ + ../../src/cpu/hd6309/hd6309.cpp \ + ../../src/cpu/hd6309/6309ops.c \ + ../../src/cpu/hd6309/6309tbl.c \ + ../../src/cpu/i8039/i8039.cpp \ + ../../src/cpu/konami/konami.cpp \ + ../../src/cpu/konami/konamops.c \ + ../../src/cpu/konami/konamtbl.c \ + ../../src/cpu/m6502/m6502.cpp \ + ../../src/cpu/m6502/t65c02.c \ + ../../src/cpu/m6502/t65sc02.c \ + ../../src/cpu/m6502/t6502.c \ + ../../src/cpu/m6502/tdeco16.c \ + ../../src/cpu/m6502/tn2a03.c \ + ../../src/cpu/m6800/m6800.cpp \ + ../../src/cpu/m6800/6800ops.c \ + ../../src/cpu/m6800/6800tbl.c \ + ../../src/cpu/m6805/m6805.cpp \ + ../../src/cpu/m6805/6805ops.c \ + ../../src/cpu/m6809/m6809.cpp \ + ../../src/cpu/m6809/6809ops.c \ + ../../src/cpu/m6809/6809tbl.c \ + ../../src/cpu/nec/nec.cpp \ + ../../src/cpu/nec/v25.cpp \ + ../../src/cpu/nec/necinstr.c \ + ../../src/cpu/nec/v25instr.c \ + ../../src/cpu/nec/v25sfr.c \ + ../../src/cpu/pic16c5x/pic16c5x.cpp \ + ../../src/cpu/s2650/s2650.cpp \ + ../../src/cpu/sh2/sh2.cpp \ + ../../src/cpu/z80/z80.cpp \ + ../../src/cpu/z80/z80daisy.cpp \ + ../../src/cpu/m68k/m68kcpu.c \ + ../../src/burner/conc.cpp \ + ../../src/burner/cong.cpp \ + ../../src/burner/dat.cpp \ + ../../src/burner/gamc.cpp \ + ../../src/burner/gami.cpp \ + ../../src/burner/image.cpp \ + ../../src/burner/misc.cpp \ + ../../src/burner/sshot.cpp \ + ../../src/burner/state.cpp \ + ../../src/burner/statec.cpp \ + ../../src/burner/zipfn.cpp \ + ../../src/burner/ioapi.c \ + ../../src/burner/unzip.c \ + ../../src/dep/libs/libpng/png.c \ + ../../src/dep/libs/libpng/pngerror.c \ + ../../src/dep/libs/libpng/pngget.c \ + ../../src/dep/libs/libpng/pngmem.c \ + ../../src/dep/libs/libpng/pngpread.c \ + ../../src/dep/libs/libpng/pngread.c \ + ../../src/dep/libs/libpng/pngrio.c \ + ../../src/dep/libs/libpng/pngrtran.c \ + ../../src/dep/libs/libpng/pngrutil.c \ + ../../src/dep/libs/libpng/pngset.c \ + ../../src/dep/libs/libpng/pngtrans.c \ + ../../src/dep/libs/libpng/pngwio.c \ + ../../src/dep/libs/libpng/pngwrite.c \ + ../../src/dep/libs/libpng/pngwtran.c \ + ../../src/dep/libs/libpng/pngwutil.c \ + ../../src/intf/interface.cpp \ + ../../src/intf/audio/aud_dsp.cpp \ + ../../src/intf/audio/aud_interface.cpp \ + ../../src/intf/audio/lowpass2.cpp \ + ../../src/intf/cd/cd_interface.cpp \ + ../../src/intf/input/inp_interface.cpp \ + ../../src/intf/video/vid_interface.cpp \ + ../../src/intf/video/vid_support.cpp \ + ../../src/cpu/z80_intf.cpp \ + ../../src/burner/qt/main.cpp \ + ../../src/burner/qt/aboutdialog.cpp \ + ../../src/burner/qt/bzip.cpp \ + ../../src/burner/qt/dipswitchdialog.cpp \ + ../../src/burner/qt/driver.cpp \ + ../../src/burner/qt/emuworker.cpp \ + ../../src/burner/qt/mainwindow.cpp \ + ../../src/burner/qt/progress.cpp \ + ../../src/burner/qt/qaudiointerface.cpp \ + ../../src/burner/qt/qinputinterface.cpp \ + ../../src/burner/qt/qrubyviewport.cpp \ + ../../src/burner/qt/qutil.cpp \ + ../../src/burner/qt/romdirsdialog.cpp \ + ../../src/burner/qt/rominfodialog.cpp \ + ../../src/burner/qt/romscandialog.cpp \ + ../../src/burner/qt/selectdialog.cpp \ + ../../src/burner/qt/stringset.cpp \ + ../../src/burner/qt/supportdirsdialog.cpp \ + ../../src/burner/qt/ruby/implementation.cpp \ + ../../src/burner/qt/ruby/ruby.cpp \ + ../../src/dep/libs/zlib/adler32.c \ + ../../src/dep/libs/zlib/compress.c \ + ../../src/dep/libs/zlib/crc32.c \ + ../../src/dep/libs/zlib/deflate.c \ + ../../src/dep/libs/zlib/gzclose.c \ + ../../src/dep/libs/zlib/gzlib.c \ + ../../src/dep/libs/zlib/gzread.c \ + ../../src/dep/libs/zlib/gzwrite.c \ + ../../src/dep/libs/zlib/infback.c \ + ../../src/dep/libs/zlib/inffast.c \ + ../../src/dep/libs/zlib/inflate.c \ + ../../src/dep/libs/zlib/inftrees.c \ + ../../src/dep/libs/zlib/trees.c \ + ../../src/dep/libs/zlib/uncompr.c \ + ../../src/dep/libs/zlib/zutil.c \ + ../../src/burn/devices/nmk004.cpp \ + ../../src/cpu/tlcs90/tlcs90.cpp \ + ../../src/cpu/tlcs90_intf.cpp + +HEADERS += \ + ../../src/burn/devices/8255ppi.h \ + ../../src/burn/devices/8257dma.h \ + ../../src/burn/devices/eeprom.h \ + ../../src/burn/devices/pandora.h \ + ../../src/burn/devices/seibusnd.h \ + ../../src/burn/devices/sknsspr.h \ + ../../src/burn/devices/slapstic.h \ + ../../src/burn/devices/t5182.h \ + ../../src/burn/devices/timekpr.h \ + ../../src/burn/devices/tms34061.h \ + ../../src/burn/devices/v3021.h \ + ../../src/burn/devices/vdc.h \ + ../../src/burn/snd/ay8910.h \ + ../../src/burn/snd/burn_y8950.h \ + ../../src/burn/snd/burn_ym2151.h \ + ../../src/burn/snd/burn_ym2203.h \ + ../../src/burn/snd/burn_ym2413.h \ + ../../src/burn/snd/burn_ym2608.h \ + ../../src/burn/snd/burn_ym2610.h \ + ../../src/burn/snd/burn_ym2612.h \ + ../../src/burn/snd/burn_ym3526.h \ + ../../src/burn/snd/burn_ym3812.h \ + ../../src/burn/snd/burn_ymf278b.h \ + ../../src/burn/snd/c6280.h \ + ../../src/burn/snd/dac.h \ + ../../src/burn/snd/es5506.h \ + ../../src/burn/snd/es8712.h \ + ../../src/burn/snd/flt_rc.h \ + ../../src/burn/snd/fm.h \ + ../../src/burn/snd/fmopl.h \ + ../../src/burn/snd/ics2115.h \ + ../../src/burn/snd/iremga20.h \ + ../../src/burn/snd/k005289.h \ + ../../src/burn/snd/k007232.h \ + ../../src/burn/snd/k051649.h \ + ../../src/burn/snd/k053260.h \ + ../../src/burn/snd/k054539.h \ + ../../src/burn/snd/msm5205.h \ + ../../src/burn/snd/msm5232.h \ + ../../src/burn/snd/msm6295.h \ + ../../src/burn/snd/namco_snd.h \ + ../../src/burn/snd/nes_apu.h \ + ../../src/burn/snd/nes_defs.h \ + ../../src/burn/snd/rescap.h \ + ../../src/burn/snd/rf5c68.h \ + ../../src/burn/snd/saa1099.h \ + ../../src/burn/snd/samples.h \ + ../../src/burn/snd/segapcm.h \ + ../../src/burn/snd/sn76496.h \ + ../../src/burn/snd/upd7759.h \ + ../../src/burn/snd/vlm5030.h \ + ../../src/burn/snd/x1010.h \ + ../../src/burn/snd/ym2151.h \ + ../../src/burn/snd/ym2413.h \ + ../../src/burn/snd/ymdeltat.h \ + ../../src/burn/snd/ymf278b.h \ + ../../src/burn/snd/ymz280b.h \ + ../../src/burn/burn_sound.h \ + ../../src/burn/burn.h \ + ../../src/burn/burnint.h \ + ../../src/burn/cheat.h \ + ../../src/burn/driver.h \ + ../../src/burn/hiscore.h \ + ../../src/burn/state.h \ + ../../src/burn/stdfunc.h \ + ../../src/burn/tiles_generic.h \ + ../../src/burn/timer.h \ + ../../src/burn/vector.h \ + ../../src/burn/version.h \ + ../../src/burn/burn_led.h \ + ../../src/burn/burn_gun.h \ + ../../src/burn/bitswap.h \ + ../../src/cpu/h6280_intf.h \ + ../../src/cpu/hd6309_intf.h \ + ../../src/cpu/konami_intf.h \ + ../../src/cpu/m6502_intf.h \ + ../../src/cpu/m6800_intf.h \ + ../../src/cpu/m6805_intf.h \ + ../../src/cpu/m6809_intf.h \ + ../../src/cpu/m68000_debug.h \ + ../../src/cpu/m68000_intf.h \ + ../../src/cpu/nec_intf.h \ + ../../src/cpu/pic16c5x_intf.h \ + ../../src/cpu/s2650_intf.h \ + ../../src/cpu/arm7_intf.h \ + ../../src/cpu/arm_intf.h \ + ../../src/cpu/arm7/arm7core.h \ + ../../src/cpu/h6280/h6280.h \ + ../../src/cpu/h6280/h6280ops.h \ + ../../src/cpu/hd6309/hd6309.h \ + ../../src/cpu/i8039/i8039.h \ + ../../src/cpu/konami/konami.h \ + ../../src/cpu/m6502/ill02.h \ + ../../src/cpu/m6502/m6502.h \ + ../../src/cpu/m6502/ops02.h \ + ../../src/cpu/m6502/opsc02.h \ + ../../src/cpu/m6502/opsn2a03.h \ + ../../src/cpu/m6800/m6800.h \ + ../../src/cpu/m6805/m6805.h \ + ../../src/cpu/m6809/m6809.h \ + ../../src/cpu/nec/nec.h \ + ../../src/cpu/nec/necea.h \ + ../../src/cpu/nec/necinstr.h \ + ../../src/cpu/nec/necmacro.h \ + ../../src/cpu/nec/necmodrm.h \ + ../../src/cpu/nec/necpriv.h \ + ../../src/cpu/nec/v25instr.h \ + ../../src/cpu/nec/v25priv.h \ + ../../src/cpu/pic16c5x/pic16c5x.h \ + ../../src/cpu/s2650/s2650.h \ + ../../src/cpu/z80/z80.h \ + ../../src/cpu/z80/z80daisy.h \ + ../../src/cpu/m68k/m68kcpu.h \ + ../../src/cpu/m68k/m68kconf.h \ + ../../src/burner/burner.h \ + ../../src/burner/gameinp.h \ + ../../src/burner/ioapi.h \ + ../../src/burner/neocdlist.h \ + ../../src/burner/title.h \ + ../../src/burner/unzip.h \ + ../../src/dep/libs/libpng/png.h \ + ../../src/dep/libs/libpng/pngconf.h \ + ../../src/dep/libs/libpng/pngdebug.h \ + ../../src/dep/libs/libpng/pnginfo.h \ + ../../src/dep/libs/libpng/pnglibconf.h \ + ../../src/dep/libs/libpng/pngpriv.h \ + ../../src/dep/libs/libpng/pngstruct.h \ + ../../src/intf/interface.h \ + ../../src/intf/audio/aud_dsp.h \ + ../../src/intf/audio/lowpass2.h \ + ../../src/intf/cd/cd_interface.h \ + ../../src/intf/input/inp_keys.h \ + ../../src/intf/video/vid_support.h \ + ../../src/cpu/sh2_intf.h \ + ../../src/cpu/z80_intf.h \ + ../../src/burner/qt/aboutdialog.h \ + ../../src/burner/qt/burner_qt.h \ + ../../src/burner/qt/dipswitchdialog.h \ + ../../src/burner/qt/emuworker.h \ + ../../src/burner/qt/mainwindow.h \ + ../../src/burner/qt/qaudiointerface.h \ + ../../src/burner/qt/qinputinterface.h \ + ../../src/burner/qt/qrubyviewport.h \ + ../../src/burner/qt/qutil.h \ + ../../src/burner/qt/romdirsdialog.h \ + ../../src/burner/qt/rominfodialog.h \ + ../../src/burner/qt/romscandialog.h \ + ../../src/burner/qt/selectdialog.h \ + ../../src/burner/qt/supportdirsdialog.h \ + ../../src/burner/qt/tchar.h \ + ../../src/burner/qt/ruby/audio.hpp \ + ../../src/burner/qt/ruby/input.hpp \ + ../../src/burner/qt/ruby/ruby.hpp \ + ../../src/burner/qt/ruby/video.hpp \ + ../../src/dep/libs/zlib/crc32.h \ + ../../src/dep/libs/zlib/deflate.h \ + ../../src/dep/libs/zlib/gzguts.h \ + ../../src/dep/libs/zlib/inffast.h \ + ../../src/dep/libs/zlib/inffixed.h \ + ../../src/dep/libs/zlib/inflate.h \ + ../../src/dep/libs/zlib/inftrees.h \ + ../../src/dep/libs/zlib/trees.h \ + ../../src/dep/libs/zlib/zconf.h \ + ../../src/dep/libs/zlib/zconf.h.in \ + ../../src/dep/libs/zlib/zlib.h \ + ../../src/dep/libs/zlib/zutil.h \ + ../../src/burn/devices/nmk004.h + +OTHER_FILES += + +RESOURCES += \ + ../../src/burner/qt/rscr.qrc + +FORMS += \ + ../../src/burner/qt/aboutdialog.ui \ + ../../src/burner/qt/dipswitchdialog.ui \ + ../../src/burner/qt/romdirsdialog.ui \ + ../../src/burner/qt/rominfodialog.ui \ + ../../src/burner/qt/romscandialog.ui \ + ../../src/burner/qt/selectdialog.ui \ + ../../src/burner/qt/supportdirsdialog.ui diff --git a/src/burner/burner.h b/src/burner/burner.h index 5c2d93f37..b476e5576 100644 --- a/src/burner/burner.h +++ b/src/burner/burner.h @@ -1,6 +1,6 @@ // FB Alpha - Emulator for MC68000/Z80 based arcade games // Refer to the "license.txt" file for more info - +#pragma once #include #include #include @@ -17,7 +17,11 @@ #define MAKE_STRING(s) MAKE_STRING_2(s) #define BZIP_MAX (20) // Maximum zip files to search through -#define DIRS_MAX (20) // Maximum number of directories to search +#if defined (BUILD_QT) + #define DIRS_MAX (4) // Maximum number of directories to search +#else + #define DIRS_MAX (20) // Maximum number of directories to search +#endif #include "title.h" #include "burn.h" @@ -42,6 +46,8 @@ typedef struct tagIMAGE { #include "burner_xbox.h" #elif defined(__LIBRETRO__) #include "burner_libretro.h" +#elif defined(BUILD_QT) + #include "burner_qt.h" #endif #if defined (INCLUDE_LIB_PNGH) diff --git a/src/burner/qt/aboutdialog.cpp b/src/burner/qt/aboutdialog.cpp new file mode 100644 index 000000000..0e1f8c735 --- /dev/null +++ b/src/burner/qt/aboutdialog.cpp @@ -0,0 +1,21 @@ +#include +#include "aboutdialog.h" +#include "ui_aboutdialog.h" +#include "qutil.h" + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::AboutDialog) +{ + ui->setupUi(this); + ui->teLicense->hide(); + layout()->setContentsMargins(0, 0, 0, 0); + layout()->setSizeConstraint(QLayout::SetFixedSize); + + ui->teLicense->setText(util::loadText(tr(":/resource/license.txt"))); +} + +AboutDialog::~AboutDialog() +{ + delete ui; +} diff --git a/src/burner/qt/aboutdialog.h b/src/burner/qt/aboutdialog.h new file mode 100644 index 000000000..d462de289 --- /dev/null +++ b/src/burner/qt/aboutdialog.h @@ -0,0 +1,22 @@ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include + +namespace Ui { +class AboutDialog; +} + +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AboutDialog(QWidget *parent = 0); + ~AboutDialog(); + +private: + Ui::AboutDialog *ui; +}; + +#endif // ABOUTDIALOG_H diff --git a/src/burner/qt/aboutdialog.ui b/src/burner/qt/aboutdialog.ui new file mode 100644 index 000000000..075e9c0a7 --- /dev/null +++ b/src/burner/qt/aboutdialog.ui @@ -0,0 +1,118 @@ + + + AboutDialog + + + + 0 + 0 + 645 + 350 + + + + About + + + + + + QFrame::StyledPanel + + + + + + :/resource/about.bmp + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + License + + + true + + + + + + + Ok + + + + + + + + + + 8 + + + + true + + + + + + + + + + + btnOk + clicked() + AboutDialog + accept() + + + 293 + 178 + + + 488 + 177 + + + + + btnLicense + toggled(bool) + teLicense + setVisible(bool) + + + 167 + 182 + + + 154 + 259 + + + + + diff --git a/src/burner/qt/burner_qt.h b/src/burner/qt/burner_qt.h new file mode 100644 index 000000000..90051c858 --- /dev/null +++ b/src/burner/qt/burner_qt.h @@ -0,0 +1,58 @@ +#ifndef _BURNER_QT_H +#define _BURNER_QT_H + +typedef unsigned char BYTE; +/* +typedef unsigned short WORD; +*/ +typedef unsigned long DWORD; + +extern int bDrvOkay; +extern int bRunPause; +extern bool bAlwaysProcessKeyboardInput; +extern int nAppVirtualFps; + +// main.cpp +extern bool AppProcessKeyboardInput(); +extern void InpDIPSWResetDIPs (void); +extern void IpsApplyPatches(UINT8 *, char *); +extern void Reinitialise(void); +extern TCHAR *GetIsoPath(); +extern int VidRecalcPal(); +extern void wav_pause(bool bResume); + +extern TCHAR szAppRomPaths[DIRS_MAX][MAX_PATH]; +// drv.cpp +int DrvInit(int nDrvNum, bool bRestore); +int DrvInitCallback(); +int DrvExit(); + +// progress.cpp +int ProgressUpdateBurner(double dProgress, const TCHAR* pszText, bool bAbs); +void ProgressCreate(); +void ProgressDestroy(); +bool ProgressIsRunning(); + +#ifdef _MSC_VER +#define snprintf _snprintf +#define ANSIToTCHAR(str, foo, bar) (str) +#endif + +extern char *TCHARToANSI(const TCHAR* pszInString, char* pszOutString, int nOutSize); + + +class StringSet { +public: + TCHAR* szText; + int nLen; + // printf function to add text to the Bzip string + int __cdecl Add(TCHAR* szFormat, ...); + int Reset(); + StringSet(); + ~StringSet(); +}; + +extern StringSet BzipText; +extern StringSet BzipDetail; + +#endif diff --git a/src/burner/qt/bzip.cpp b/src/burner/qt/bzip.cpp new file mode 100644 index 000000000..d512d7628 --- /dev/null +++ b/src/burner/qt/bzip.cpp @@ -0,0 +1,518 @@ +// Burner Zip module +#include "burner.h" + +#include + +int nBzipError = 0; // non-zero if there is a problem with the opened romset + +static TCHAR* szBzipName[BZIP_MAX] = { NULL, }; // Zip files to search through + +struct RomFind { int nState; int nZip; int nPos; }; // State is non-zero if found. 1 = found totally okay. +static struct RomFind* RomFind = NULL; +static int nRomCount = 0; static int nTotalSize = 0; +static struct ZipEntry* List = NULL; static int nListCount = 0; // List of entries for current zip file +static int nCurrentZip = -1; // Zip which is currently open +static int nZipsFound = 0; + +StringSet BzipText; // Text which describes any problems with loading the zip +StringSet BzipDetail; // Text which describes in detail any problems with loading the zip + +int BzipStatus() +{ + if (!(nBzipError & 0x0F0F)) { + return BZIP_STATUS_OK; + } + if (nBzipError & 1) { + return BZIP_STATUS_ERROR; + } + + return BZIP_STATUS_BADDATA; +} + +void BzipListFree() +{ + if (List) { + for (int i = 0; i < nListCount; i++) { + if (List[i].szName) { + free(List[i].szName); + List[i].szName = NULL; + } + } + free(List); + } + + List = NULL; + nListCount = 0; +} + +static char* GetFilenameA(char* szFull) +{ + int nLen = strlen(szFull); + + if (nLen <= 0) { + return szFull; + } + for (int i = nLen - 1; i >= 0; i--) { + if (szFull[i] == '\\' || szFull[i] == '/') { + return szFull + i + 1; + } + } + + return szFull; +} + +static TCHAR* GetFilenameW(TCHAR* szFull) +{ + int nLen = _tcslen(szFull); + + if (nLen <= 0) { + return szFull; + } + for (int i = nLen - 1; i >= 0; i--) { + if (szFull[i] == _T('\\') || szFull[i] == _T('/')) { + return szFull + i + 1; + } + } + + return szFull; +} + +static int FindRomByName(TCHAR* szName) +{ + struct ZipEntry* pl; + int i; + + // Find the rom named szName in the List + for (i = 0, pl = List; i < nListCount; i++, pl++) { + TCHAR szCurrentName[MAX_PATH]; + if (_tcsicmp(szName, GetFilenameW(ANSIToTCHAR(pl->szName, szCurrentName, MAX_PATH))) == 0) { + return i; + } + } + return -1; // couldn't find the rom +} + +static int FindRomByCrc(unsigned int nCrc) +{ + struct ZipEntry* pl; + int i; + + // Find the rom named szName in the List + for (i = 0, pl = List; i< nListCount; i++, pl++) { + if (nCrc == pl->nCrc) { + return i; + } + } + + return -1; // couldn't find the rom +} + +// Find rom number i from the pBzipDriver game +static int FindRom(int i) +{ + struct BurnRomInfo ri; + int nRet; + + memset(&ri, 0, sizeof(ri)); + + nRet = BurnDrvGetRomInfo(&ri, i); + if (nRet != 0) { // Failure: no such rom + return -2; + } + + if (ri.nCrc) { // Search by crc first + nRet = FindRomByCrc(ri.nCrc); + if (nRet >= 0) { + return nRet; + } + } + + for (int nAka = 0; nAka < 0x10000; nAka++) { // Failing that, search for possible names + char *szPossibleName = NULL; + + nRet = BurnDrvGetRomName(&szPossibleName, i, nAka); + if (nRet) { // No more rom names + break; + } + nRet = FindRomByName(ANSIToTCHAR(szPossibleName, NULL, 0)); + if (nRet >= 0) { + return nRet; + } + } + + return -1; // Couldn't find the rom +} + +static int RomDescribe(StringSet* pss, struct BurnRomInfo* pri) +{ + pss->Add(_T("The ")); + if (pri->nType & 0x10) { + pss->Add(_T("essential ")); + } + if (pri->nType & 0x80) { + pss->Add(_T("BIOS ")); + } + if (pri->nType & 0x01) { + pss->Add(_T("graphics ")); + } + if (pri->nType & 0x02) { + pss->Add(_T("sound ")); + } + pss->Add(_T("ROM ")); + + return 0; +} + +static int CheckRomsBoot() +{ + for (int i = 0; i < nRomCount; i++) { + struct BurnRomInfo ri; + int nState; + + memset(&ri, 0, sizeof(ri)); + BurnDrvGetRomInfo(&ri, i); // Find information about the wanted rom + nState = RomFind[i].nState; // Get the state of the rom in the zip file + + if (nState != 1 && ri.nType && ri.nCrc) { + if (ri.nType & 0x80) { + return 2; + } + return 1; + } + } + + return 0; +} + +static int GetBZipError(int nState) +{ + switch (nState) { + case 1: // OK + return 0x00; + case 0: // Not present + return 0x01; + case 3: // Incomplete + return 0x01; + default: // CRC wrong or too large + return 0x10; + } + + return 0x10; +} + +// Check the roms to see if they code, graphics etc are complete +static int CheckRoms() +{ + nBzipError = 0; // Assume romset is fine + + for (int i = 0; i < nRomCount; i++) { + struct BurnRomInfo ri; + + memset(&ri, 0, sizeof(ri)); + BurnDrvGetRomInfo(&ri, i); // Find information about the wanted rom + if (ri.nCrc && (ri.nType & 0x80) == 0) { + int nState = RomFind[i].nState; // Get the state of the rom in the zip file + + if (nState == 0 && ri.nType) { // (A type of 0 means empty slot - no rom) + char* szName = "Unknown"; + RomDescribe(&BzipDetail, &ri); + BurnDrvGetRomName(&szName, i, 0); + BzipDetail.Add(_T("%hs was not found.\n"), szName); + } + + if (ri.nType & 0x90) { // essential rom - without it the game may not run at all + nBzipError |= GetBZipError(nState) << 0; + } + if (ri.nType & 0x01) { // rom which contains graphics information + nBzipError |= GetBZipError(nState) << 1; + } + if (ri.nType & 0x02) { // rom which contains sound information + nBzipError |= GetBZipError(nState) << 2; + } + } + } + + if (!nZipsFound) { + nBzipError |= 0x08; // No data at all! + } + + return 0; +} + +static int __cdecl BzipBurnLoadRom(unsigned char* Dest, int* pnWrote, int i) +{ +#if defined (BUILD_WIN32) + MSG Msg; +#endif + + struct BurnRomInfo ri; + int nWantZip = 0; + TCHAR szText[128]; + char* pszRomName = NULL; + int nRet = 0; + + if (i < 0 || i >= nRomCount) { + return 1; + } + + ri.nLen = 0; + BurnDrvGetRomInfo(&ri, i); // Get info + + // show what we're doing + BurnDrvGetRomName(&pszRomName, i, 0); + if (pszRomName == NULL) { + pszRomName = "unknown"; + } + _stprintf(szText, _T("Loading")); + if (ri.nType & 0x83) { + if (ri.nType & 0x80) { + _stprintf (szText + _tcslen(szText), _T(" %s"), _T("BIOS ")); + } + if (ri.nType & 0x10) { + _stprintf (szText + _tcslen(szText), _T(" %s"), _T("program ")); + } + if (ri.nType & 0x01) { + _stprintf (szText + _tcslen(szText), _T(" %s"), _T("graphics ")); + } + if (ri.nType & 0x02) { + _stprintf (szText + _tcslen(szText), _T(" %s"), _T("sound ")); + } + _stprintf(szText + _tcslen(szText), _T("(%hs)..."), pszRomName); + } else { + _stprintf(szText + _tcslen(szText), _T(" %hs..."), pszRomName); + } + + ProgressUpdateBurner(ri.nLen ? 1.0 / ((double)nTotalSize / ri.nLen) : 0, szText, 0); + +#if defined (BUILD_WIN32) + // Check for messages: + while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) { + DispatchMessage(&Msg); + } +#endif + + if (RomFind[i].nState == 0) { // Rom not found in zip at all + return 1; + } + + nWantZip = RomFind[i].nZip; // Which zip file it is in + if (nCurrentZip != nWantZip) { // If we haven't got the right zip file currently open + ZipClose(); + nCurrentZip = -1; + if (ZipOpen(TCHARToANSI(szBzipName[nWantZip], NULL, 0))) { + return 1; + } + nCurrentZip = nWantZip; + } + + // Read in file and return how many bytes we read + if (ZipLoadFile(Dest, ri.nLen, pnWrote, RomFind[i].nPos)) { + // Error loading from the zip file + TCHAR szTemp[128] = _T(""); + _stprintf(szTemp, _T("%s reading %.30hs from %.30s"), nRet == 2 ? _T("CRC error") : _T("Error"), pszRomName, GetFilenameW(szBzipName[nCurrentZip])); +// AppError(szTemp, 1); + return 1; + } + + return 0; +} + +int BzipOpen(bool bootApp) +{ + int nMemLen; // Zip name number + + nZipsFound = 0; // Haven't found zips yet + nTotalSize = 0; + + if (szBzipName == NULL) { + return 1; + } + + BzipClose(); // Make sure nothing is open + + if(!bootApp) { // reset information strings + BzipText.Reset(); + BzipDetail.Reset(); + } + + // Count the number of roms needed + for (nRomCount = 0; ; nRomCount++) { + if (BurnDrvGetRomInfo(NULL, nRomCount)) { + break; + } + } + if (nRomCount <= 0) { + return 1; + } + + // Create an array for holding lookups for each rom -> zip entries + nMemLen = nRomCount * sizeof(struct RomFind); + RomFind = (struct RomFind*)malloc(nMemLen); + if (RomFind == NULL) { + return 1; + } + memset(RomFind, 0, nMemLen); + + for (int z = 0; z < BZIP_MAX; z++) { + char* szName = NULL; + + if (BurnDrvGetZipName(&szName, z)) { + break; + } + + for (int d = 0; d < DIRS_MAX; d++) { + free(szBzipName[z]); + szBzipName[z] = (TCHAR*)malloc(MAX_PATH * sizeof(TCHAR)); + + _stprintf(szBzipName[z], _T("%s%hs"), szAppRomPaths[d], szName); + + if (ZipOpen(TCHARToANSI(szBzipName[z], NULL, 0)) == 0) { // Open the rom zip file + nZipsFound++; + nCurrentZip = z; + break; + } + } + + if (nCurrentZip >= 0) { + if (!bootApp) { + BzipText.Add(_T("Found %s;\n"), szBzipName[z]); + } + ZipGetList(&List, &nListCount); // Get the list of entries + + for (int i = 0; i < nRomCount; i++) { + struct BurnRomInfo ri; + int nFind; + + if (RomFind[i].nState == 1) { // Already found this and it's okay + continue; + } + + memset(&ri, 0, sizeof(ri)); + + nFind = FindRom(i); + + if (nFind < 0) { // Couldn't find this rom at all + continue; + } + + RomFind[i].nZip = z; // Remember which zip file it is in + RomFind[i].nPos = nFind; + RomFind[i].nState = 1; // Set to found okay + + BurnDrvGetRomInfo(&ri, i); // Get info about the rom + + if ((ri.nType & 0x80) == 0) { + nTotalSize += ri.nLen; + } + + if (List[nFind].nLen == ri.nLen) { + if (ri.nCrc) { // If we know the CRC + if (List[nFind].nCrc != ri.nCrc) { // Length okay, but CRC wrong + RomFind[i].nState = 2; + } + } + } else { + if (List[nFind].nLen < ri.nLen) { + RomFind[i].nState = 3; // Too small + } else { + RomFind[i].nState = 4; // Too big + } + } + + if (!bootApp) { + if (RomFind[i].nState != 1) { + RomDescribe(&BzipDetail, &ri); + + if (RomFind[i].nState == 2) { + BzipDetail.Add(_T("%hs has a CRC of %.8X. (It should be %.8X.)\n"), GetFilenameA(List[nFind].szName), List[nFind].nCrc, ri.nCrc); + } + if (RomFind[i].nState == 3) { + BzipDetail.Add(_T("%hs is %dk which is incomplete. (It should be %dkB.)\n"), GetFilenameA(List[nFind].szName), List[nFind].nLen >> 10, ri.nLen >> 10); + } + if (RomFind[i].nState == 4) { + BzipDetail.Add(_T("%hs is %dk which is too big. (It should be %dkB.)\n"), GetFilenameA(List[nFind].szName), List[nFind].nLen >> 10, ri.nLen >> 10); + } + } + } + } + + BzipListFree(); + + } else { + if (!bootApp) { + BzipText.Add(_T("Couldn't find %hs;\n"), szName); + } + } + + ZipClose(); // Close the last zip file if open + nCurrentZip = -1; + } + + if (!bootApp) { + // Check the roms to see if they code, graphics etc are complete + CheckRoms(); + + if (nZipsFound) { + if (nBzipError == 0) { + BzipText.Add(_T("The romset is fine.\n")); + } + + if (nBzipError & 0x07) { + BzipText.Add(_T("However the romset is INCOMPLETE.\n")); + } + + if (nBzipError & 0x01) { + BzipText.Add(_T("Essential rom data is missing; the game probably won't run.\n")); + } else { + if (nBzipError & 0x10) { + BzipText.Add(_T("Some essential roms are different. ")); + } + } + if (nBzipError & 0x02) { + BzipText.Add(_T("Graphical data is missing. ")); + } else { + if (nBzipError & 0x20) { + BzipText.Add(_T("Some graphics roms are different. ")); + } + } + if (nBzipError & 0x04) { + BzipText.Add(_T("Sound data is missing. ")); + } else { + if (nBzipError & 0x40) { + BzipText.Add(_T("Some sound roms are different. ")); + } + } + + if (nBzipError & 0x76) { + BzipText.Add(_T("\n")); + } + } + + BurnExtLoadRom = BzipBurnLoadRom; // Okay to call our function to load each rom + + } else { + return CheckRomsBoot(); + } + + return 0; +} + +int BzipClose() +{ + ZipClose(); + nCurrentZip = -1; // Close the last zip file if open + + BurnExtLoadRom = NULL; // Can't call our function to load each rom anymore + nBzipError = 0; // reset romset errors + + free(RomFind); + RomFind = NULL; + nRomCount = 0; + + for (int z = 0; z < BZIP_MAX; z++) { + free(szBzipName[z]); + szBzipName[z] = NULL; + } + + return 0; +} diff --git a/src/burner/qt/dipswitchdialog.cpp b/src/burner/qt/dipswitchdialog.cpp new file mode 100644 index 000000000..a362b0719 --- /dev/null +++ b/src/burner/qt/dipswitchdialog.cpp @@ -0,0 +1,188 @@ +#include +#include "dipswitchdialog.h" +#include "ui_dipswitchdialog.h" +#include "burner.h" + +DipswitchDialog::DipswitchDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::DipswitchDialog) +{ + ui->setupUi(this); + setWindowTitle("DIPSwitches"); + connect(ui->tvSettings, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + this, SLOT(dipChange(QTreeWidgetItem*,QTreeWidgetItem*))); + connect(ui->cbValues, SIGNAL(currentIndexChanged(int)), this, SLOT(dipValueChange(int))); + m_dipGroup = 0; +} + +DipswitchDialog::~DipswitchDialog() +{ + delete ui; +} + +int DipswitchDialog::exec() +{ + getDipOffset(); + reset(); + makeList(); + int ret = QDialog::exec(); + return ret; +} + +void DipswitchDialog::reset() +{ + int i = 0; + BurnDIPInfo bdi; + struct GameInp* pgi; + + getDipOffset(); + + while (BurnDrvGetDIPInfo(&bdi, i) == 0) { + if (bdi.nFlags == 0xFF) { + pgi = GameInp + bdi.nInput + m_dipOffset; + pgi->Input.Constant.nConst = (pgi->Input.Constant.nConst & ~bdi.nMask) | (bdi.nSetting & bdi.nMask); + } + i++; + } +} + +void DipswitchDialog::dipChange(QTreeWidgetItem *item, QTreeWidgetItem *prev) +{ + ui->cbValues->clear(); + if (item == nullptr) + return; + + qDebug() << "DIP Change"; + m_dipGroup = item->data(0, Qt::UserRole).toInt(); + + BurnDIPInfo bdiGroup; + BurnDrvGetDIPInfo(&bdiGroup, m_dipGroup); + + int nCurrentSetting = 0; + for (int i = 0, j = 0; i < bdiGroup.nSetting; i++) { + TCHAR szText[256]; + BurnDIPInfo bdi; + + do { + BurnDrvGetDIPInfo(&bdi, m_dipGroup + 1 + j++); + } while (bdi.nFlags == 0); + + if (bdiGroup.szText) { + _stprintf(szText, _T("%hs: %hs"), bdiGroup.szText, bdi.szText); + } else { + _stprintf(szText, _T("%hs"), bdi.szText); + } + ui->cbValues->insertItem(i, szText); + + if (checkSetting(m_dipGroup + j)) { + nCurrentSetting = i; + } + } + ui->cbValues->setCurrentIndex(nCurrentSetting); +} + +void DipswitchDialog::dipValueChange(int index) +{ + if (ui->cbValues->count() <= 0) + return; + + qDebug() <<"Value changed"; + BurnDIPInfo bdi = {0, 0, 0, 0, NULL}; + struct GameInp *pgi; + int j = 0; + for (int i = 0; i <= index; i++) { + do { + BurnDrvGetDIPInfo(&bdi, m_dipGroup + 1 + j++); + } while (bdi.nFlags == 0); + } + pgi = GameInp + bdi.nInput + m_dipOffset; + pgi->Input.Constant.nConst = (pgi->Input.Constant.nConst & ~bdi.nMask) | (bdi.nSetting & bdi.nMask); + if (bdi.nFlags & 0x40) { + while (BurnDrvGetDIPInfo(&bdi, m_dipGroup + 1 + j++) == 0) { + if (bdi.nFlags == 0) { + pgi = GameInp + bdi.nInput + m_dipOffset; + pgi->Input.Constant.nConst = (pgi->Input.Constant.nConst & ~bdi.nMask) | (bdi.nSetting & bdi.nMask); + } else { + break; + } + } + } +} + +bool DipswitchDialog::checkSetting(int i) +{ + BurnDIPInfo bdi; + BurnDrvGetDIPInfo(&bdi, i); + struct GameInp* pgi = GameInp + bdi.nInput + m_dipOffset; + + if ((pgi->Input.Constant.nConst & bdi.nMask) == bdi.nSetting) { + unsigned char nFlags = bdi.nFlags; + if ((nFlags & 0x0F) <= 1) { + return true; + } else { + for (int j = 1; j < (nFlags & 0x0F); j++) { + BurnDrvGetDIPInfo(&bdi, i + j); + pgi = GameInp + bdi.nInput + m_dipOffset; + if (nFlags & 0x80) { + if ((pgi->Input.Constant.nConst & bdi.nMask) == bdi.nSetting) { + return false; + } + } else { + if ((pgi->Input.Constant.nConst & bdi.nMask) != bdi.nSetting) { + return false; + } + } + } + return true; + } + } + return false; +} + +void DipswitchDialog::getDipOffset() +{ + BurnDIPInfo bdi; + m_dipOffset = 0; + for (int i = 0; BurnDrvGetDIPInfo(&bdi, i) == 0; i++) { + if (bdi.nFlags == 0xF0) { + m_dipOffset = bdi.nInput; + break; + } + } +} + +void DipswitchDialog::makeList() +{ + clearList(); + BurnDIPInfo bdi; + unsigned int i = 0, j = 0, k = 0; + char* pDIPGroup = NULL; + while (BurnDrvGetDIPInfo(&bdi, i) == 0) { + if ((bdi.nFlags & 0xF0) == 0xF0) { + if (bdi.nFlags == 0xFE || bdi.nFlags == 0xFD) { + pDIPGroup = bdi.szText; + k = i; + } + i++; + } else { + if (checkSetting(i)) { + QTreeWidgetItem *item = new QTreeWidgetItem(); + + item->setText(0, pDIPGroup); + item->setText(1, bdi.szText); + item->setData(0, Qt::UserRole, k); + item->setData(1, Qt::UserRole, k); + ui->tvSettings->addTopLevelItem(item); + j++; + } + i += (bdi.nFlags & 0x0F); + } + } + +} + +void DipswitchDialog::clearList() +{ + while (ui->tvSettings->topLevelItemCount() > 0) + ui->tvSettings->takeTopLevelItem(0); +} diff --git a/src/burner/qt/dipswitchdialog.h b/src/burner/qt/dipswitchdialog.h new file mode 100644 index 000000000..9a595b4de --- /dev/null +++ b/src/burner/qt/dipswitchdialog.h @@ -0,0 +1,34 @@ +#ifndef DIPSWITCHDIALOG_H +#define DIPSWITCHDIALOG_H + +#include +#include + +namespace Ui { +class DipswitchDialog; +} + +class DipswitchDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DipswitchDialog(QWidget *parent = 0); + ~DipswitchDialog(); + +public slots: + int exec(); + void reset(); + void dipChange(QTreeWidgetItem * item, QTreeWidgetItem * prev); + void dipValueChange(int index); +private: + bool checkSetting(int i); + void getDipOffset(); + void makeList(); + void clearList(); + Ui::DipswitchDialog *ui; + unsigned m_dipOffset; + unsigned m_dipGroup; +}; + +#endif // DIPSWITCHDIALOG_H diff --git a/src/burner/qt/dipswitchdialog.ui b/src/burner/qt/dipswitchdialog.ui new file mode 100644 index 000000000..7eaac4cc0 --- /dev/null +++ b/src/burner/qt/dipswitchdialog.ui @@ -0,0 +1,96 @@ + + + DipswitchDialog + + + + 0 + 0 + 555 + 374 + + + + Dialog + + + + + + QAbstractScrollArea::AdjustToContents + + + true + + + + DIPSwitch + + + + + Setting + + + + + + + + + + + + + Default + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Ok + + + + + + + + + + + btnOk + clicked() + DipswitchDialog + accept() + + + 477 + 355 + + + 290 + 359 + + + + + diff --git a/src/burner/qt/driver.cpp b/src/burner/qt/driver.cpp new file mode 100644 index 000000000..e5506823b --- /dev/null +++ b/src/burner/qt/driver.cpp @@ -0,0 +1,122 @@ +// Driver Init module +#include "burner.h" +#include + +int bDrvOkay = 0; // 1 if the Driver has been initted okay, and it's okay to use the BurnDrv functions + +static int DrvBzipOpen() +{ + BzipOpen(false); + + if (BzipStatus()) + return 1; + return 0; +} + +static int DoLibInit() // Do Init of Burn library driver +{ + int nRet = 0; + + if (DrvBzipOpen()) { + return 1; + } + ProgressCreate(); + nRet = BurnDrvInit(); + + BzipClose(); + + ProgressDestroy(); + + if (nRet) { + return 3; + } else { + return 0; + } +} + +// Catch calls to BurnLoadRom() once the emulation has started; +// Intialise the zip module before forwarding the call, and exit cleanly. +static int __cdecl DrvLoadRom(unsigned char* Dest, int* pnWrote, int i) +{ + int nRet; + + BzipOpen(false); + + if ((nRet = BurnExtLoadRom(Dest, pnWrote, i)) != 0) { + char* pszFilename; + + BurnDrvGetRomName(&pszFilename, i, 0); + } + + BzipClose(); + return nRet; +} + +int DrvInit(int nDrvNum, bool bRestore) +{ + int nStatus; + bDrvOkay = 0; + + DrvExit(); // Make sure exitted + nBurnDrvActive = nDrvNum; // Set the driver number + + nMaxPlayers = BurnDrvGetMaxPlayers(); + GameInpInit(); // Init game input + if (ConfigGameLoad(true)) { + ConfigGameLoadHardwareDefaults(); + } + InputMake(true); + GameInpDefault(); + + nStatus = DoLibInit(); // Init the Burn library's driver + if (nStatus) { + if (nStatus & 2) { + BurnDrvExit(); // Exit the driver + } + return 1; + } + + + + BurnExtLoadRom = DrvLoadRom; + + bDrvOkay = 1; // Okay to use all BurnDrv functions + + if (BurnDrvGetFlags() & BDF_ORIENTATION_VERTICAL) { + bVidArcaderes = bVidArcaderesVer; + nVidWidth = nVidVerWidth; + nVidHeight = nVidVerHeight; + } else { + bVidArcaderes = bVidArcaderesHor; + nVidWidth = nVidHorWidth; + nVidHeight = nVidHorHeight; + } + + nBurnLayer = 0xFF; // show all layers + + // Reset the speed throttling code, so we don't 'jump' after the load + //RunReset(); + VidExit(); + return 0; +} + +int DrvInitCallback() +{ + return DrvInit(nBurnDrvActive, false); +} + +int DrvExit() +{ + if (bDrvOkay) { + ConfigGameSave(bSaveInputs); + GameInpExit(); + BurnDrvExit(); // Exit the driver + } + + BurnExtLoadRom = NULL; + bDrvOkay = 0; // Stop using the BurnDrv functions + bRunPause = 0; // Don't pause when exitted + nBurnDrvActive = ~0U; // no driver selected + return 0; +} + diff --git a/src/burner/qt/emuworker.cpp b/src/burner/qt/emuworker.cpp new file mode 100644 index 000000000..d7eb4e095 --- /dev/null +++ b/src/burner/qt/emuworker.cpp @@ -0,0 +1,128 @@ +#include +#include "emuworker.h" +#include "burner.h" + +static int GetInput(bool bCopy); +static int RunFrame(int bDraw, int bPause); +static int RunGetNextSound(int bDraw); + +EmuWorker::EmuWorker(QObject *parent) : + QObject(parent) +{ + m_isRunning = false; +} + +bool EmuWorker::init() +{ + AudSoundStop(); + + if (bDrvOkay) + DrvExit(); + + // we need to initialize sound first + AudSoundInit(); + AudSetCallback(RunGetNextSound); + bAudOkay = 1; + + DrvInit(m_game, false); + if (!bDrvOkay) + return false; + + VidInit(); + + return true; +} + +void EmuWorker::resume() +{ + AudSoundPlay(); + if (!bDrvOkay) { + m_isRunning = false; + return; + } + m_isRunning = true; +} + + +void EmuWorker::close() +{ + AudSoundStop(); + DrvExit(); +} + +void EmuWorker::pause() +{ + m_isRunning = false; +} + +void EmuWorker::setGame(int no) +{ + m_game = no; +} + +void EmuWorker::run() +{ + if (m_isRunning && bDrvOkay) { + AudSoundCheck(); + } +} + +int GetInput(bool bCopy) +{ + InputMake(bCopy); // get input + return 0; +} + +int RunFrame(int bDraw, int bPause) +{ + static int bPrevPause = 0; + static int bPrevDraw = 0; + + if (bPrevDraw && !bPause) { + VidPaint(0); // paint the screen (no need to validate) + } + + if (!bDrvOkay) { + return 1; + } + + if (bPause) + { + GetInput(false); // Update burner inputs, but not game inputs + if (bPause != bPrevPause) + { + VidPaint(2); // Redraw the screen (to ensure mode indicators are updated) + } + } + else + { + nFramesEmulated++; + nCurrentFrame++; + GetInput(true); // Update inputs + } + if (bDraw) { + nFramesRendered++; + if (VidFrame()) { // Do one frame + AudBlankSound(); + } + } + else { // frame skipping + pBurnDraw = NULL; // Make sure no image is drawn + BurnDrvFrame(); + } + bPrevPause = bPause; + bPrevDraw = bDraw; + + return 0; +} + +int RunGetNextSound(int bDraw) +{ + if (nAudNextSound == NULL) { + return 1; + } + // Render frame with sound + pBurnSoundOut = nAudNextSound; + RunFrame(bDraw, 0); + return 0; +} diff --git a/src/burner/qt/emuworker.h b/src/burner/qt/emuworker.h new file mode 100644 index 000000000..9f44f21ca --- /dev/null +++ b/src/burner/qt/emuworker.h @@ -0,0 +1,26 @@ +#ifndef EMUWORKER_H +#define EMUWORKER_H + +#include + +class EmuWorker : public QObject +{ + Q_OBJECT +public: + explicit EmuWorker(QObject *parent = 0); + +signals: + +public slots: + bool init(); + void resume(); + void close(); + void pause(); + void setGame(int no); + void run(); +private: + int m_game; + bool m_isRunning; +}; + +#endif // EMUWORKER_H diff --git a/src/burner/qt/main.cpp b/src/burner/qt/main.cpp new file mode 100644 index 000000000..7fbdf3a44 --- /dev/null +++ b/src/burner/qt/main.cpp @@ -0,0 +1,63 @@ +#include +#include "burner.h" +#include "mainwindow.h" + +int bRunPause = 0; +bool bAlwaysProcessKeyboardInput; +bool bDoIpsPatch; + +TCHAR szAppBurnVer[16]; + +int nAppVirtualFps = 6000; + +TCHAR *GetIsoPath() +{ + return NULL; +} + + +void IpsApplyPatches(UINT8 *, char *) +{ + +} + +void InpDIPSWResetDIPs() +{ +} + + +void Reinitialise(void) +{ + +} + +void wav_pause(bool bResume) +{ + +} + +bool AppProcessKeyboardInput() +{ + return true; +} + +char *TCHARToANSI(const TCHAR* pszInString, char* pszOutString, int nOutSize) +{ + if (pszOutString) { + strcpy(pszOutString, pszInString); + return pszOutString; + } + + return (char*)pszInString; +} + + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + MainWindow mw; + mw.show(); + + return mw.main(app); +} diff --git a/src/burner/qt/mainwindow.cpp b/src/burner/qt/mainwindow.cpp new file mode 100644 index 000000000..ae4e1fa05 --- /dev/null +++ b/src/burner/qt/mainwindow.cpp @@ -0,0 +1,240 @@ +#include +#ifdef Q_OS_MACX +#include +#else +#include +#endif +#include "mainwindow.h" +#include "burner.h" +#include "selectdialog.h" +#include "ruby/ruby.hpp" + + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent) +{ + + createDefaultDirs(); + + BurnLibInit(); + createActions(); + createMenus(); + createControls(); + + setWindowTitle("FB Alpha Q"); + + connectActions(); + + m_isRunning = false; + InputInit(); + + show(); + + ruby::video.driver("X-Video"); + ruby::video.set(ruby::Video::Handle, m_viewport->id()); + ruby::video.set(ruby::Video::Depth, 24u); + + if (ruby::video.init()) { + qDebug() << "Ruby Succefully initialized"; + } +} + +MainWindow::~MainWindow() +{ + BurnLibExit(); + AudSoundStop(); + AudSoundExit(); + ruby::video.term(); +} + +int MainWindow::main(QApplication &app) +{ + m_closeApp = false; + while (!m_closeApp) { + app.processEvents(); + m_emulation->run(); + } + return 0; +} + +void MainWindow::loadGame() +{ + int ret = m_selectDlg->exec(); + if (ret != QDialog::Accepted) { + return; + } + m_emulation->pause(); + m_emulation->setGame(m_selectDlg->selectedDriver()); + bool okay = m_emulation->init(); + + if (!okay) { + QStringList str; + str << BzipText.szText << BzipDetail.szText; + QMessageBox::critical(this, "Error", str.join("\n")); + return; + } + m_isRunning = true; + enableInGame(); + m_emulation->resume(); +} + + +void MainWindow::exitEmulator() +{ + close(); +} + +void MainWindow::closeGame() +{ + if (bDrvOkay) { + m_emulation->close(); + disableInGame(); + ruby::video.clear(); + ruby::video.refresh(); + } +} + +void MainWindow::setupInputInterface(QAction *action) +{ + InputExit(); + nInputSelect = action->data().toInt(); + InputInit(); +} + +void MainWindow::toogleMenu() +{ + if (menuBar()->isHidden()) { + menuBar()->show(); + } + else menuBar()->hide(); +} + +void MainWindow::toogleFullscreen() +{ + if (isFullScreen()) + showNormal(); + else showFullScreen(); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + m_closeApp = true; + event->accept(); +} + +void MainWindow::createDefaultDirs() +{ + QStringList dirs; + dirs << "config" << "config/games" << "config/ips" << "config/localisation" + << "config/presets" << "recordings" << "roms" << "savestates" + << "screenshots" << "support" << "support/previews" + << "support/titles" << "support/icons" << "support/cheats" + << "support/hiscores" << "support/samples" << "support/ips" + << "support/neocdz" << "neocdiso"; + QDir current = QDir::currentPath(); + foreach (QString dirName, dirs) + current.mkpath(dirName); +} + +void MainWindow::createMenus() +{ + m_menuGame = menuBar()->addMenu(tr("Game")); + m_menuGame->addAction(m_actionLoadGame); + m_menuGame->addAction(m_actionCloseGame); + m_menuGame->addSeparator(); + m_menuGame->addAction(m_actionExitEmulator); + + m_menuInput = menuBar()->addMenu(tr("Input")); + m_menuInputPlugin = new QMenu(tr("Plugin"), this); + m_menuInput->addMenu(m_menuInputPlugin); + setupInputInterfaces(); + + m_menuInput->addAction(m_actionDipswitch); + + m_menuMisc = menuBar()->addMenu(tr("Misc")); + m_menuMisc->addAction(m_actionConfigureRomPaths); + m_menuMisc->addAction(m_actionConfigureSupportPaths); + m_menuMisc->addSeparator(); + m_menuMisc->addAction(m_actionToogleMenu); + + m_menuHelp = menuBar()->addMenu(tr("Help")); + m_menuHelp->addAction(m_actionAbout); +} + +void MainWindow::createControls() +{ + m_audio = QAudioInterface::get(this); + m_viewport = QRubyViewport::get(); + setCentralWidget(m_viewport); + resize(640, 480); + + m_emulation = new EmuWorker(); + m_selectDlg = new SelectDialog(this); + m_supportPathDlg = new SupportDirsDialog(this); + m_aboutDlg = new AboutDialog(this); + m_dipSwitchDlg = new DipswitchDialog(this); +} + +void MainWindow::createActions() +{ + m_actionLoadGame = new QAction(tr("Load Game"), this); + m_actionLoadGame->setShortcut(QKeySequence("F6")); + m_actionCloseGame = new QAction(tr("Close Game"), this); + m_actionCloseGame->setEnabled(false); + m_actionConfigureRomPaths = new QAction(tr("Configure ROM paths..."), this); + m_actionConfigureSupportPaths = new QAction(tr("Configure support paths..."), this); + m_actionExitEmulator = new QAction(tr("Exit emulator"), this); + m_actionToogleMenu = new QAction(tr("Toogle Menu"), this); + m_scutToogleMenu = new QShortcut(QKeySequence(tr("F12")), this); + m_scutToogleFullscreen = new QShortcut(QKeySequence(tr("Alt+Return")), this); + m_scutToogleMenu->setContext(Qt::ApplicationShortcut); + m_actionAbout = new QAction(tr("About FBA"), this); + m_actionDipswitch = new QAction(tr("Configure DIPs"), this); + m_actionDipswitch->setEnabled(false); +} + +void MainWindow::connectActions() +{ + connect(m_actionLoadGame, SIGNAL(triggered()), this, SLOT(loadGame())); + connect(m_actionCloseGame, SIGNAL(triggered()), this, SLOT(closeGame())); + connect(m_actionConfigureRomPaths, SIGNAL(triggered()), m_selectDlg, SLOT(editRomPaths())); + connect(m_actionExitEmulator, SIGNAL(triggered()), this, SLOT(exitEmulator())); + connect(m_actionConfigureSupportPaths, SIGNAL(triggered()), m_supportPathDlg, SLOT(exec())); + connect(m_actionAbout, SIGNAL(triggered()), m_aboutDlg, SLOT(exec())); + connect(m_actionToogleMenu, SIGNAL(triggered()), this, SLOT(toogleMenu())); + connect(m_scutToogleMenu, SIGNAL(activated()), this, SLOT(toogleMenu())); + connect(m_scutToogleFullscreen, SIGNAL(activated()), this, SLOT(toogleFullscreen())); + connect(m_actionDipswitch, SIGNAL(triggered()), m_dipSwitchDlg, SLOT(exec())); +} + +void MainWindow::enableInGame() +{ + m_actionCloseGame->setEnabled(true); + m_actionDipswitch->setEnabled(true); +} + +void MainWindow::disableInGame() +{ + m_actionCloseGame->setEnabled(false); + m_actionDipswitch->setEnabled(false); +} + +void MainWindow::setupInputInterfaces() +{ + m_actionInputPlugins = new QActionGroup(this); + m_actionInputPlugins->setExclusive(true); + m_inputInterfaces = QVector::fromStdVector(InputGetInterfaces()); + + for (int i = 0; i < m_inputInterfaces.size(); i++) { + const InputInOut *intf = m_inputInterfaces[i]; + QAction *action = new QAction(QString(intf->szModuleName), this); + action->setCheckable(true); + action->setChecked(i ? false : true); + action->setData(QVariant(i)); + m_actionInputPlugins->addAction(action); + } + + m_menuInputPlugin->addActions(m_actionInputPlugins->actions()); + connect(m_actionInputPlugins, SIGNAL(triggered(QAction*)), + this, SLOT(setupInputInterface(QAction*))); +} diff --git a/src/burner/qt/mainwindow.h b/src/burner/qt/mainwindow.h new file mode 100644 index 000000000..fb392b92b --- /dev/null +++ b/src/burner/qt/mainwindow.h @@ -0,0 +1,78 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "selectdialog.h" +#include "qrubyviewport.h" +#include "qaudiointerface.h" +#include "supportdirsdialog.h" +#include "aboutdialog.h" +#include "dipswitchdialog.h" +#include "emuworker.h" +#include "burner.h" + + +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + + int main(QApplication &app); +signals: +public slots: + void loadGame(); + void exitEmulator(); + void closeGame(); + void setupInputInterface(QAction *action); + void toogleMenu(); + void toogleFullscreen(); + void closeEvent(QCloseEvent *event); +private: + void createDefaultDirs(); + void createMenus(); + void createControls(); + void createActions(); + void connectActions(); + void enableInGame(); + void disableInGame(); + int m_game; + bool m_isRunning; + QRubyViewport *m_viewport; + QAudioInterface *m_audio; + EmuWorker *m_emulation; + SelectDialog *m_selectDlg; + SupportDirsDialog *m_supportPathDlg; + AboutDialog *m_aboutDlg; + DipswitchDialog *m_dipSwitchDlg; + QMenu *m_menuGame; + QMenu *m_menuMisc; + QMenu *m_menuHelp; + QMenu *m_menuInput; + QMenu *m_menuInputPlugin; + QAction *m_actionConfigureRomPaths; + QAction *m_actionConfigureSupportPaths; + QAction *m_actionAbout; + QAction *m_actionLoadGame; + QAction *m_actionExitEmulator; + QAction *m_actionCloseGame; + QAction *m_actionToogleMenu; + QAction *m_actionDipswitch; + QShortcut *m_scutToogleMenu; + QShortcut *m_scutToogleFullscreen; + + void setupInputInterfaces(); + QVector m_inputInterfaces; + QActionGroup *m_actionInputPlugins; + bool m_closeApp; +}; + +#endif // MAINWINDOW_H diff --git a/src/burner/qt/neocdlist.cpp b/src/burner/qt/neocdlist.cpp new file mode 100644 index 000000000..6899fc974 --- /dev/null +++ b/src/burner/qt/neocdlist.cpp @@ -0,0 +1,500 @@ +// --------------------------------------------------------------------------------------- +// NeoGeo CD Game Info Module (by CaptainCPS-X) +// --------------------------------------------------------------------------------------- +#include "burner.h" +#include "../neocdlist.h" + +struct NGCDGAME games[] = +{ +// ---------------------------------------------------------------------------------------------------------------------------------------------// +// * Name * Title * Year * Company * Game ID // +// ---------------------------------------------------------------------------------------------------------------------------------------------// + { _T("nam1975") , _T("NAM-1975") , _T("1990") , _T("SNK") , 0x0001 }, // + { _T("bstars") , _T("Baseball Stars Professional") , _T("1991") , _T("SNK") , 0x0002 }, // + { _T("tpgolf") , _T("Top Player's Golf") , _T("1990") , _T("SNK") , 0x0003 }, // + { _T("mahretsu") , _T("Mahjong Kyo Retsuden - Nishi Nihon Hen") , _T("1990") , _T("SNK") , 0x0004 }, // + { _T("maglord") , _T("Magician Lord") , _T("1990") , _T("ADK") , 0x0005 }, // + { _T("ridhero") , _T("Riding Hero") , _T("1991") , _T("SNK") , 0x0006 }, // + { _T("alpham2") , _T("Alpha Mission II / ASO II - Last Guardian") , _T("1994") , _T("SNK") , 0x0007 }, // + { _T("ncombat") , _T("Ninja Combat") , _T("1990") , _T("SNK/ADK") , 0x0009 }, // + { _T("cyberlip") , _T("Cyber-Lip") , _T("1991") , _T("SNK") , 0x0010 }, // + { _T("superspy") , _T("The Super Spy") , _T("1990") , _T("SNK") , 0x0011 }, // + { _T("mutnat") , _T("Mutation Nation") , _T("1995") , _T("SNK") , 0x0014 }, // + { _T("sengoku") , _T("Sengoku / Sengoku Denshou") , _T("1994") , _T("SNK") , 0x0017 }, // + { _T("burningf") , _T("Burning Fight") , _T("1994") , _T("SNK") , 0x0018 }, // + { _T("lbowling") , _T("League Bowling") , _T("1994") , _T("SNK") , 0x0019 }, // + { _T("gpilots") , _T("Ghost Pilots") , _T("1991") , _T("SNK") , 0x0020 }, // + { _T("joyjoy") , _T("Puzzled / Joy Joy Kid") , _T("1990") , _T("SNK") , 0x0021 }, // + { _T("bjourney") , _T("Blue's Journey / Raguy") , _T("1990") , _T("SNK/ADK") , 0x0022 }, // + { _T("lresort") , _T("Last Resort") , _T("1994") , _T("SNK") , 0x0024 }, // + { _T("2020bb") , _T("2020 Super Baseball") , _T("1994") , _T("SNK") , 0x0030 }, // + { _T("socbrawl") , _T("Soccer Brawl") , _T("1991") , _T("SNK") , 0x0031 }, // + { _T("roboarmy") , _T("Robo Army") , _T("1991") , _T("SNK") , 0x0032 }, // + { _T("fatfury") , _T("Fatal Fury - The Battle of Fury") , _T("1994") , _T("SNK") , 0x0033 }, // + { _T("fbfrenzy") , _T("Football Frenzy") , _T("1994") , _T("SNK") , 0x0034 }, // + { _T("crswords") , _T("Crossed Swords") , _T("1994") , _T("SNK/ADK") , 0x0037 }, // + { _T("rallych") , _T("Rally Chase") , _T("1991") , _T("SNK/ADK") , 0x0038 }, // + { _T("kotm2") , _T("King of the Monsters 2") , _T("1992") , _T("SNK") , 0x0039 }, // + { _T("sengoku2") , _T("Sengoku 2 / Sengoku Denshou 2") , _T("1994") , _T("SNK") , 0x0040 }, // + { _T("bstars2") , _T("Baseball Stars 2") , _T("1992") , _T("SNK") , 0x0041 }, // + { _T("3countb") , _T("3 Count Bout / Fire Suplex") , _T("1995") , _T("SNK") , 0x0043 }, // + { _T("aof") , _T("Art of Fighting / Ryuuko no Ken") , _T("1994") , _T("SNK") , 0x0044 }, // + { _T("samsho") , _T("Samurai Shodown / Samurai Spirits") , _T("1993") , _T("SNK") , 0x0045 }, // + { _T("tophuntr") , _T("Top Hunter - Roddy & Cathy") , _T("1994") , _T("SNK") , 0x0046 }, // + { _T("fatfury2") , _T("Fatal Fury 2 / Garou Densetsu 2 - Aratanaru Tatakai") , _T("1994") , _T("SNK") , 0x0047 }, // + { _T("janshin") , _T("Janshin Densetsu - Quest of the Jongmaster") , _T("1995") , _T("Yubis") , 0x0048 }, // + { _T("ncommand") , _T("Ninja Commando") , _T("1992") , _T("SNK") , 0x0050 }, // + { _T("viewpoin") , _T("Viewpoint") , _T("1992") , _T("Sammy/Aicom") , 0x0051 }, // + { _T("ssideki") , _T("Super Sidekicks / Tokuten Oh") , _T("1993") , _T("SNK") , 0x0052 }, // + { _T("wh1") , _T("World Heroes") , _T("1992") , _T("ADK") , 0x0053 }, // + { _T("crsword2") , _T("Crossed Swords II") , _T("1995") , _T("SNK/ADK") , 0x0054 }, // + { _T("kof94") , _T("The King of Fighters '94 (JP)") , _T("1994") , _T("SNK") , 0x0055 }, // + { _T("kof94ju") , _T("The King of Fighters '94 (JP-US)") , _T("1994") , _T("SNK") , 0x1055 }, // custom id + { _T("aof2") , _T("Art of Fighting 2 / Ryuuko no Ken 2") , _T("1994") , _T("SNK") , 0x0056 }, // + { _T("wh2") , _T("World Heroes 2") , _T("1995") , _T("SNK/ADK") , 0x0057 }, // + { _T("fatfursp") , _T("Fatal Fury Special / Garou Densetsu Special") , _T("1994") , _T("SNK") , 0x0058 }, // + { _T("savagere") , _T("Savage Reign / Fu'un Mokujiroku - Kakutou Sousei") , _T("1995") , _T("SNK") , 0x0059 }, // + { _T("ssideki2") , _T("Super Sidekicks 2 / Tokuten Oh 2") , _T("1994") , _T("SNK") , 0x0061 }, // + { _T("samsho2") , _T("Samurai Shodown 2 / Shin Samurai Spirits") , _T("1994") , _T("SNK") , 0x0063 }, // + { _T("wh2j") , _T("World Heroes 2 Jet") , _T("1995") , _T("SNK/ADK") , 0x0064 }, // + { _T("wjammers") , _T("Windjammers / Flying Power Disc") , _T("1994") , _T("Data East") , 0x0065 }, // + { _T("karnovr") , _T("Karnov's Revenge / Fighters History Dynamite") , _T("1994") , _T("Data East") , 0x0066 }, // + { _T("pspikes2") , _T("Power Spikes II") , _T("1994") , _T("Video System") , 0x0068 }, // + { _T("aodk") , _T("Aggressors of Dark Kombat / Tsuukai GanGan Koushinkyoku") , _T("1994"), _T("SNK/ADK") , 0x0074 }, // + { _T("sonicwi2") , _T("Aero Fighters 2 / Sonic Wings 2") , _T("1994") , _T("Video System") , 0x0075 }, // + { _T("galaxyfg") , _T("Galaxy Fight - Universal Warriors") , _T("1995") , _T("Sunsoft") , 0x0078 }, // + { _T("strhoop") , _T("Street Hoop / Dunk Dream") , _T("1994") , _T("Data East") , 0x0079 }, // + { _T("quizkof") , _T("Quiz King of Fighters") , _T("1993") , _T("SNK/Saurus") , 0x0080 }, // + { _T("ssideki3") , _T("Super Sidekicks 3 - The Next Glory / Tokuten Oh 3 - Eikoue No Chousen"), _T("1995") , _T("SNK") , 0x0081 }, // + { _T("doubledr") , _T("Double Dragon") , _T("1995") , _T("Technos") , 0x0082 }, // + { _T("pbobblen") , _T("Puzzle Bobble / Bust-A-Move") , _T("1994") , _T("SNK") , 0x0083 }, // + { _T("kof95") , _T("The King of Fighters '95 (JP-US)") , _T("1995") , _T("SNK") , 0x0084 }, // + { _T("kof95r1") , _T("The King of Fighters '95 (JP-US)(Rev 1)") , _T("1995") , _T("SNK") , 0x1084 }, // + { _T("ssrpg") , _T("Shinsetsu Samurai Spirits - Bushidohretsuden") , _T("1997") , _T("SNK") , 0x0085 }, // + { _T("samsho3") , _T("Samurai Shodown 3 / Samurai Spirits 3") , _T("1995") , _T("SNK") , 0x0087 }, // + { _T("stakwin") , _T("Stakes Winner - GI Kanzen Seiha Heno Machi") , _T("1995") , _T("Saurus") , 0x0088 }, // + { _T("pulstar") , _T("Pulstar") , _T("1995") , _T("Aicom") , 0x0089 }, // + { _T("whp") , _T("World Heroes Perfect") , _T("1995") , _T("ADK") , 0x0090 }, // + { _T("kabukikl") , _T("Kabuki Klash / Tengai Makyou Shinden - Far East of Eden") , _T("1995"), _T("Hudson") , 0x0092 }, // + { _T("gowcaizr") , _T("Voltage Fighter Gowcaizer / Choujin Gakuen Gowcaizer"), _T("1995") , _T("Technos") , 0x0094 }, // + { _T("rbff1") , _T("Real Bout Fatal Fury") , _T("1995") , _T("SNK") , 0x0095 }, // + { _T("aof3") , _T("Art of Fighting 3: Path of the Warrior") , _T("1996") , _T("SNK") , 0x0096 }, // + { _T("sonicwi3") , _T("Aero Fighters 3 / Sonic Wings 3") , _T("1995") , _T("SNK") , 0x0097 }, // + { _T("fromanc2") , _T("Idol Mahjong Final Romance 2") , _T("1995") , _T("Video Systems") , 0x0098 }, // + { _T("turfmast") , _T("Neo Turf Masters / Big Tournament Golf") , _T("1996") , _T("Nazca") , 0x0200 }, // + { _T("mslug") , _T("Metal Slug - Super Vehicle-001") , _T("1996") , _T("Nazca") , 0x0201 }, // + { _T("mosyougi") , _T("Shougi no Tatsujin - Master of Syougi") , _T("1995") , _T("ADK") , 0x0203 }, // + { _T("adkworld") , _T("ADK World / ADK Special") , _T("1995") , _T("ADK") , 0x0204 }, // + { _T("ngcdsp") , _T("Neo Geo CD Special") , _T("1995") , _T("SNK") , 0x0205 }, // + { _T("zintrick") , _T("Zintrick / Oshidashi Zintrick") , _T("1996") , _T("ADK") , 0x0211 }, // + { _T("overtop") , _T("OverTop") , _T("1996") , _T("ADK") , 0x0212 }, // + { _T("neodrift") , _T("Neo DriftOut") , _T("1996") , _T("Visco") , 0x0213 }, // + { _T("kof96") , _T("The King of Fighters '96") , _T("1996") , _T("SNK") , 0x0214 }, // + { _T("ninjamas") , _T("Ninja Master's - Haou Ninpou-Chou") , _T("1996") , _T("ADK/SNK") , 0x0217 }, // + { _T("ragnagrd") , _T("Ragnagard / Shinouken") , _T("1996") , _T("Saurus") , 0x0218 }, // + { _T("pgoal") , _T("Pleasuregoal - 5 on 5 Mini Soccer / Futsal") , _T("1996") , _T("Saurus") , 0x0219 }, // + { _T("ironclad") , _T("Ironclad / Choutetsu Brikin'ger") , _T("1996") , _T("Saurus") , 0x0220 }, // + { _T("magdrop2") , _T("Magical Drop 2") , _T("1996") , _T("Data East") , 0x0221 }, // + { _T("samsho4") , _T("Samurai Shodown IV - Amakusa's Revenge") , _T("1996") , _T("SNK") , 0x0222 }, // + { _T("rbffspec") , _T("Real Bout Fatal Fury Special") , _T("1996") , _T("SNK") , 0x0223 }, // + { _T("twinspri") , _T("Twinkle Star Sprites") , _T("1996") , _T("ADK") , 0x0224 }, // + { _T("kof96ngc") , _T("The King of Fighters '96 NEOGEO Collection") , _T("1996") , _T("SNK") , 0x0229 }, // + { _T("breakers") , _T("Breakers") , _T("1996") , _T("Visco") , 0x0230 }, // + { _T("kof97") , _T("The King of Fighters '97") , _T("1997") , _T("SNK") , 0x0232 }, // + { _T("lastblad") , _T("The Last Blade / Bakumatsu Roman - Gekka no Kenshi") , _T("1997") , _T("SNK") , 0x0234 }, // + { _T("rbff2") , _T("Real Bout Fatal Fury 2 / Garou Densetsu 2 - Aratanaru Tatakai"), _T("1998"), _T("SNK") , 0x0240 }, // + { _T("mslug2") , _T("Metal Slug 2 - Super Vehicle-001/II") , _T("1998") , _T("SNK") , 0x0241 }, // + { _T("kof98") , _T("The King of Fighters '98 - The Slugfest") , _T("1998") , _T("SNK") , 0x0242 }, // + { _T("lastbld2") , _T("The Last Blade 2") , _T("1998") , _T("SNK") , 0x0243 }, // + { _T("kof99") , _T("The King of Fighters '99 - Millennium Battle") , _T("1999") , _T("SNK") , 0x0251 }, // + { _T("fatfury3") , _T("Fatal Fury 3 - Road to the Final Victory / Garou Densetsu 3 - Harukanaru Tatakai"), _T("1995"), _T("SNK"), 0x069c }, // +}; + +NGCDGAME* GetNeoGeoCDInfo(unsigned int nID) +{ + for(unsigned int nGame = 0; nGame < (sizeof(games) / sizeof(NGCDGAME)); nGame++) { + if(nID == games[nGame].id ) { + return &games[nGame]; + } + } + + return NULL; +} + +NGCDGAME* game; + +// Get the title +int GetNeoCDTitle(unsigned int nGameID) +{ + game = (NGCDGAME*)malloc(sizeof(NGCDGAME)); + memset(game, 0, sizeof(NGCDGAME)); + + if(GetNeoGeoCDInfo(nGameID)) + { + memcpy(game, GetNeoGeoCDInfo(nGameID), sizeof(NGCDGAME)); + return 1; + } + + game = NULL; + return 0; +} + +void iso9660_ReadOffset(unsigned char *Dest, FILE* fp, unsigned int lOffset, unsigned int lSize, unsigned int lLength) +{ + if(fp == NULL) return; + if (Dest == NULL) return; + + fseek(fp, lOffset, SEEK_SET); + fread(Dest, lLength, lSize, fp); +} + +static void NeoCDList_iso9660_CheckDirRecord(FILE* fp, int nSector) +{ + int nSectorLength = 2048; + //int nFile = 0; + unsigned int lBytesRead = 0; + //int nLen = 0; + unsigned int lOffset = 0; + bool bNewSector = false; + bool bRevisionQueve = false; + int nRevisionQueveID = 0; + + lOffset = (nSector * nSectorLength); + + unsigned char* nLenDR = (unsigned char*)malloc(1 * sizeof(unsigned char)); + unsigned char* Flags = (unsigned char*)malloc(1 * sizeof(unsigned char)); + unsigned char* ExtentLoc = (unsigned char*)malloc(8 * sizeof(unsigned char)); + unsigned char* Data = (unsigned char*)malloc(0x10b * sizeof(unsigned char)); + unsigned char* LEN_FI = (unsigned char*)malloc(1 * sizeof(unsigned char)); + char *File = (char*)malloc(32 * sizeof(char)); + + while(1) + { + iso9660_ReadOffset(nLenDR, fp, lOffset, 1, sizeof(unsigned char)); + + if(nLenDR[0] == 0x22) { + lOffset += nLenDR[0]; + lBytesRead += nLenDR[0]; + continue; + } + + if(nLenDR[0] < 0x22) + { + if(bNewSector) + { + if(bRevisionQueve) { + bRevisionQueve = false; + + GetNeoCDTitle(nRevisionQueveID); + } + return; + } + + nLenDR[0] = 0; + iso9660_ReadOffset(nLenDR, fp, lOffset + 1, 1, sizeof(unsigned char)); + + if(nLenDR[0] < 0x22) { + lOffset += (2048 - lBytesRead); + lBytesRead = 0; + bNewSector = true; + continue; + } + } + + bNewSector = false; + + iso9660_ReadOffset(Flags, fp, lOffset + 25, 1, sizeof(unsigned char)); + + if(!(Flags[0] & (1 << 1))) + { + iso9660_ReadOffset(ExtentLoc, fp, lOffset + 2, 8, sizeof(unsigned char)); + + char szValue[9]; + sprintf(szValue, "%02x%02x%02x%02x", ExtentLoc[4], ExtentLoc[5], ExtentLoc[6], ExtentLoc[7]); + + unsigned int nValue = 0; + sscanf(szValue, "%x", &nValue); + + iso9660_ReadOffset(Data, fp, nValue * 2048, 0x10a, sizeof(unsigned char)); + + char szData[8]; + sprintf(szData, "%c%c%c%c%c%c%c", Data[0x100], Data[0x101], Data[0x102], Data[0x103], Data[0x104], Data[0x105], Data[0x106]); + + if(!strncmp(szData, "NEO-GEO", 7)) + { + char id[] = "0000"; + sprintf(id, "%02X%02X", Data[0x108], Data[0x109]); + + unsigned int nID = 0; + sscanf(id, "%x", &nID); + + iso9660_ReadOffset(LEN_FI, fp, lOffset + 32, 1, sizeof(unsigned char)); + + iso9660_ReadOffset((unsigned char*)File, fp, lOffset + 33, LEN_FI[0], sizeof(char)); + strncpy(File, File, LEN_FI[0]); + File[LEN_FI[0]] = 0; + + // King of Fighters '94, The (1994)(SNK)(JP) + // 10-6-1994 (P1.PRG) + if(nID == 0x0055 && Data[0x67] == 0xDE) { + // ...continue + } + + // King of Fighters '94, The (1994)(SNK)(JP-US) + // 11-21-1994 (P1.PRG) + if(nID == 0x0055 && Data[0x67] == 0xE6) { + // Change to custom revision id + nID = 0x1055; + } + + // King of Fighters '95, The (1995)(SNK)(JP-US)[!][NGCD-084 MT B01, B03-B06, NGCD-084E MT B01] + // 9-11-1995 (P1.PRG) + if(nID == 0x0084 && Data[0x6C] == 0xC0) { + // ... continue + } + + // King of Fighters '95, The (1995)(SNK)(JP-US)[!][NGCD-084 MT B10, NGCD-084E MT B03] + // 10-5-1995 (P1.PRG) + if(nID == 0x0084 && Data[0x6C] == 0xFF) { + // Change to custom revision id + nID = 0x1084; + } + + // King of Fighters '96 NEOGEO Collection, The + if(nID == 0x0229) { + bRevisionQueve = false; + + GetNeoCDTitle(nID); + break; + } + + // King of Fighters '96, The + if(nID == 0x0214) { + bRevisionQueve = true; + nRevisionQueveID = nID; + lOffset += nLenDR[0]; + lBytesRead += nLenDR[0]; + continue; + } + + GetNeoCDTitle(nID); + + break; + } + } + + lOffset += nLenDR[0]; + lBytesRead += nLenDR[0]; + } + + if (nLenDR) { + free(nLenDR); + nLenDR = NULL; + } + + if (Flags) { + free(Flags); + Flags = NULL; + } + + if (ExtentLoc) { + free(ExtentLoc); + ExtentLoc = NULL; + } + + if (Data) { + free(Data); + Data = NULL; + } + + if (LEN_FI) { + free(LEN_FI); + LEN_FI = NULL; + } + + if (File) { + free(File); + File = NULL; + } +} + +// Check the specified ISO, and proceed accordingly +static int NeoCDList_CheckISO(TCHAR* pszFile) +{ + if(!pszFile) { + // error + return 0; + } + + // Make sure we have a valid ISO file extension... + if(_tcsstr(pszFile, _T(".iso")) || _tcsstr(pszFile, _T(".ISO")) ) + { + FILE* fp = _tfopen(pszFile, _T("rb")); + if(fp) + { + // Read ISO and look for 68K ROM standard program header, ID is always there + // Note: This function works very quick, doesn't compromise performance :) + // it just read each sector first 264 bytes aproximately only. + + // Get ISO Size (bytes) + fseek(fp, 0, SEEK_END); + unsigned int lSize = 0; + lSize = ftell(fp); + //rewind(fp); + fseek(fp, 0, SEEK_SET); + + // If it has at least 16 sectors proceed + if(lSize > (2048 * 16)) + { + // Check for Standard ISO9660 Identifier + unsigned char IsoHeader[2048 * 16 + 1]; + unsigned char IsoCheck[6]; + + fread(IsoHeader, 1, 2048 * 16 + 1, fp); // advance to sector 16 and PVD Field 2 + fread(IsoCheck, 1, 5, fp); // get Standard Identifier Field from PVD + + // Verify that we have indeed a valid ISO9660 MODE1/2048 + if(!memcmp(IsoCheck, "CD001", 5)) + { + //bprintf(PRINT_NORMAL, _T(" Standard ISO9660 Identifier Found. \n")); + iso9660_VDH vdh; + + // Get Volume Descriptor Header + memset(&vdh, 0, sizeof(vdh)); + //memcpy(&vdh, iso9660_ReadOffset(fp, (2048 * 16), sizeof(vdh)), sizeof(vdh)); + iso9660_ReadOffset((unsigned char*)&vdh, fp, 2048 * 16, 1, sizeof(vdh)); + + // Check for a valid Volume Descriptor Type + if(vdh.vdtype == 0x01) + { +#if 0 +// This will fail on 64-bit due to differing variable sizes in the pvd struct + // Get Primary Volume Descriptor + iso9660_PVD pvd; + memset(&pvd, 0, sizeof(pvd)); + //memcpy(&pvd, iso9660_ReadOffset(fp, (2048 * 16), sizeof(pvd)), sizeof(pvd)); + iso9660_ReadOffset((unsigned char*)&pvd, fp, 2048 * 16, 1, sizeof(pvd)); + + // ROOT DIRECTORY RECORD + + // Handle Path Table Location + char szRootSector[32]; + unsigned int nRootSector = 0; + + sprintf(szRootSector, "%02X%02X%02X%02X", + pvd.root_directory_record.location_of_extent[4], + pvd.root_directory_record.location_of_extent[5], + pvd.root_directory_record.location_of_extent[6], + pvd.root_directory_record.location_of_extent[7]); + + // Convert HEX string to Decimal + sscanf(szRootSector, "%X", &nRootSector); +#else +// Just read from the file directly at the correct offset (0x8000 + 0x9e for the start of the root directory record) + unsigned char buffer[8]; + char szRootSector[4]; + unsigned int nRootSector = 0; + + fseek(fp, 0x809e, SEEK_SET); + fread(buffer, 1, 8, fp); + + sprintf(szRootSector, "%02x%02x%02x%02x", buffer[4], buffer[5], buffer[6], buffer[7]); + + sscanf(szRootSector, "%x", &nRootSector); +#endif + + // Process the Root Directory Records + NeoCDList_iso9660_CheckDirRecord(fp, nRootSector); + + // Path Table Records are not processed, since NeoGeo CD should not have subdirectories + // ... + } + } else { + + //bprintf(PRINT_NORMAL, _T(" Standard ISO9660 Identifier Not Found, cannot continue. \n")); + return 0; + } + } + } else { + + //bprintf(PRINT_NORMAL, _T(" Couldn't open %s \n"), GetIsoPath()); + return 0; + } + + if(fp) fclose(fp); + + } else { + + //bprintf(PRINT_NORMAL, _T(" File doesn't have a valid ISO extension [ .iso / .ISO ] \n")); + return 0; + } + + return 1; +} + +// This will do everything +int GetNeoGeoCD_Identifier() +{ + if(!GetIsoPath() || !IsNeoGeoCD()) { + return 0; + } + + // Make sure we have a valid ISO file extension... + if(_tcsstr(GetIsoPath(), _T(".iso")) || _tcsstr(GetIsoPath(), _T(".ISO")) ) + { + if(_tfopen(GetIsoPath(), _T("rb"))) + { + // Read ISO and look for 68K ROM standard program header, ID is always there + // Note: This function works very quick, doesn't compromise performance :) + // it just read each sector first 264 bytes aproximately only. + NeoCDList_CheckISO(GetIsoPath()); + + } else { + + bprintf(PRINT_NORMAL, _T(" Couldn't open %s \n"), GetIsoPath()); + return 0; + } + + } else { + + bprintf(PRINT_NORMAL, _T(" File doesn't have a valid ISO extension [ .iso / .ISO ] \n")); + return 0; + } + + return 1; +} + +int NeoCDInfo_Init() +{ + NeoCDInfo_Exit(); + return GetNeoGeoCD_Identifier(); +} + +TCHAR* NeoCDInfo_Text(int nText) +{ + if(!game || !IsNeoGeoCD() || !bDrvOkay) return NULL; + + switch(nText) + { + case DRV_NAME: return game->pszName; + case DRV_FULLNAME: return game->pszTitle; + case DRV_MANUFACTURER: return game->pszCompany; + case DRV_DATE: return game->pszYear; + } + + return NULL; +} + +int NeoCDInfo_ID() +{ + if(!game || !IsNeoGeoCD() || !bDrvOkay) return 0; + return game->id; +} + +void NeoCDInfo_Exit() +{ + if(game) { + free(game); + game = NULL; + } +} diff --git a/src/burner/qt/progress.cpp b/src/burner/qt/progress.cpp new file mode 100644 index 000000000..00c4f0b08 --- /dev/null +++ b/src/burner/qt/progress.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include "burner.h" + +QProgressDialog *dlgProgress = nullptr; + +void ProgressCreate() +{ + if (dlgProgress == nullptr) { + dlgProgress = new QProgressDialog(); + dlgProgress->setModal(true); + dlgProgress->setRange(0, 100); + dlgProgress->setWindowTitle(QObject::tr("Working...")); + } + dlgProgress->setValue(0); + dlgProgress->show(); + QApplication::processEvents(); +} + +void ProgressDestroy() +{ + dlgProgress->close(); +} + +int ProgressUpdateBurner(double dProgress, const TCHAR* pszText, bool bAbs) +{ + if (dlgProgress != nullptr && dlgProgress->isVisible()) { + dlgProgress->setValue(dlgProgress->value() + dProgress * 100); + dlgProgress->setLabelText(pszText); + QApplication::processEvents(); + } +} + +bool ProgressIsRunning() +{ + if (dlgProgress != nullptr) { + if (dlgProgress->isVisible()) + return true; + } + return false; +} diff --git a/src/burner/qt/qaudiointerface.cpp b/src/burner/qt/qaudiointerface.cpp new file mode 100644 index 000000000..6af861a71 --- /dev/null +++ b/src/burner/qt/qaudiointerface.cpp @@ -0,0 +1,375 @@ +#include +#include +#include "burner.h" +#include "qaudiointerface.h" + +#define QT_DEBUG_SOUNDBACKEND 0 + +QAudioInterface *qAudio = nullptr; +int (*QtSoundGetNextSound)(int); + +static int nQtAudioFps = 0; +static int cbLoopLen = 0; + +static QAudioInterfaceBuffer *qSoundBuffer = nullptr; + +static int QtSoundGetNextSoundFiller(int) +{ + qDebug() << __func__; + if (nAudNextSound == nullptr) + return 1; + memset(nAudNextSound, 0, nAudSegLen * 4); + return 0; +} + +static int QtSoundBlankSound() +{ + qDebug() << __func__; + if (nAudNextSound != nullptr) + AudWriteSilence(); + return 0; +} + +static int QtSoundCheck() +{ + int avail = 0; + while ((avail = qSoundBuffer->bytesAvailable()) > (nAudAllocSegLen * 3)) { + return 0; + } + + QtSoundGetNextSound(1); + qSoundBuffer->writeData((const char *)nAudNextSound, nAudSegLen << 2); + return 0; +} + +static int QtSoundExit() +{ + qDebug() << __func__; + return 0; +} + +static int QtSoundSetCallback(int (*pCallback)(int)) +{ + qDebug() << __func__; + if (pCallback == nullptr) + QtSoundGetNextSound = QtSoundGetNextSoundFiller; + else + QtSoundGetNextSound = pCallback; + return 0; +} + +static int QtSoundInit() +{ + qDebug() << __func__; + nQtAudioFps = nAppVirtualFps; + nAudSegLen = (nAudSampleRate[0] * 100 + (nQtAudioFps / 2)) / nQtAudioFps; + + // seglen * 2 channels * 2 bytes per sample (16bits) + nAudAllocSegLen = nAudSegLen * 4; + + // seg * nsegs * 2 channels + if (qSoundBuffer != nullptr) + delete qSoundBuffer; + + qSoundBuffer = new QAudioInterfaceBuffer(); + qSoundBuffer->setBufferSize(nAudAllocSegLen * nAudSegCount); + nAudNextSound = new short[nAudAllocSegLen / sizeof(short)]; + + QtSoundSetCallback(nullptr); + QtSoundGetNextSoundFiller(0); + + //qSoundBuffer->writeData((const char *) nAudNextSound, nAudAllocSegLen); + //qSoundBuffer->writeData((const char *) nAudNextSound, nAudAllocSegLen); + + pBurnSoundOut = nAudNextSound; + nBurnSoundRate = nAudSampleRate[0]; + nBurnSoundLen = nAudAllocSegLen; + + qAudio = QAudioInterface::get(); + qAudio->setBuffer(qSoundBuffer); + QAudioFormat format; + format.setChannelCount(2); + format.setSampleRate(nAudSampleRate[0]); + format.setSampleType(QAudioFormat::SignedInt); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setSampleSize(16); + qAudio->setFormat(format); + + // we need to invoke this method on sound's thread, so we can't call this + // method from our object + QMetaObject::invokeMethod(qAudio, "init", Qt::QueuedConnection); + + bAudOkay = 1; + return 0; +} + +static int QtSoundPlay() +{ + qDebug() << __func__; + QMetaObject::invokeMethod(qAudio, "play", Qt::QueuedConnection); + bAudPlaying = 1; + return 0; +} + +static int QtSoundStop() +{ + qDebug() << __func__; + if (qAudio) + QMetaObject::invokeMethod(qAudio, "stop", Qt::QueuedConnection); + bAudPlaying = 0; + return 0; +} + +static int QtSoundSetVolume() +{ + qDebug() << __func__; + return 1; +} + +static int QtSoundGetSettings(InterfaceInfo *info) +{ + Q_UNUSED(info); + return 0; +} + +struct AudOut AudOutQtSound = { QtSoundBlankSound, QtSoundCheck, + QtSoundInit, QtSoundSetCallback, QtSoundPlay, + QtSoundStop, QtSoundExit, QtSoundSetVolume, + QtSoundGetSettings, ("qt-audio") }; + + + +QAudioInterface *QAudioInterface::m_onlyInstance = nullptr; +QAudioInterface::QAudioInterface(QObject *parent) : + QThread() +{ + // start thread + start(); + m_audioOutput = nullptr; + QObject::moveToThread(this); +} + +QAudioInterface::~QAudioInterface() +{ + if (m_audioOutput != nullptr) { + delete m_audioOutput; + m_audioOutput = nullptr; + } +} + +void QAudioInterface::run() +{ + exec(); +} + +QAudioInterface *QAudioInterface::get(QObject *parent) +{ + if (m_onlyInstance != nullptr) + return m_onlyInstance; + m_onlyInstance = new QAudioInterface(parent); + + return m_onlyInstance; +} + +void QAudioInterface::init() +{ + if (m_audioOutput != nullptr) { + stop(); + delete m_audioOutput; + m_audioOutput = nullptr; + } + m_audioOutput = new QAudioOutput(m_format, this); + m_audioOutput->setBufferSize(nAudAllocSegLen); +} + +void QAudioInterface::play() +{ + if (m_buffer != nullptr) + m_buffer->start(); + m_audioOutput->start(m_buffer); +} + +void QAudioInterface::stop() +{ + m_audioOutput->stop(); + if (m_buffer != nullptr) + m_buffer->stop(); +} + +void QAudioInterface::setBuffer(QAudioInterfaceBuffer *buffer) +{ + m_buffer = buffer; +} + +void QAudioInterface::setFormat(QAudioFormat &format) +{ + m_format = format; +} + +void QAudioInterface::writeMoreData() +{ +} + + +QAudioInterfaceBuffer::QAudioInterfaceBuffer(int size) : + QIODevice() +{ + + m_buffer = nullptr; + if (size > 0) { + m_buffer = new char[size]; + } + m_size = size; + m_readPos = 0; + m_writePos = 0; + m_readWrap = 0; + m_writeWrap = 0; +} + +QAudioInterfaceBuffer::~QAudioInterfaceBuffer() +{ + delete[] m_buffer; + m_buffer = nullptr; + m_size = 0; +} + +void QAudioInterfaceBuffer::start() +{ + open(QIODevice::ReadWrite | QIODevice::Unbuffered); +} + +void QAudioInterfaceBuffer::stop() +{ + close(); +} + + +qint64 QAudioInterfaceBuffer::readData(char *data, qint64 maxlen) +{ + + if (m_size <= 0 || maxlen <= 0) { + return 0; + } + + int avail = this->bytesAvailable(); + +#if QT_DEBUG_SOUNDBACKEND + qDebug() << QThread::currentThreadId() << (quint64)m_elapsed.elapsed() << + "rd:" << maxlen << "/" << avail; +#endif + if (avail <= 0) { + memset(data, 0, maxlen); + return maxlen; + } + + m_mutex.lock(); + + int count = maxlen; + char *pdata = data; + int increment = 0; + + while (count > 0) { + bool wrap = (m_readPos + count) >= m_size; + if (wrap) { + int block = m_size - m_readPos; + memcpy(pdata, m_buffer + m_readPos, block); + count -= block; + pdata += block; + increment = block; + m_readWrap++; + } else { + memcpy(pdata, m_buffer + m_readPos, count); + increment = count; + count -= count; + pdata += count; + } + m_readPos = (m_readPos + increment) % m_size; + } + + int retval = avail; + if (avail >= maxlen) + retval = maxlen; + + m_mutex.unlock(); + return retval; +} + +qint64 QAudioInterfaceBuffer::writeData(const char *data, qint64 len) +{ + if (m_size <= 0 || len <= 0) { + return 0; + } + + m_mutex.lock(); + int count = len; + const char *pdata = data; + int increment = 0; + +#if QT_DEBUG_SOUNDBACKEND + qDebug() << QThread::currentThreadId() << (quint64)m_elapsed.elapsed() << + "wr:" << len << "+" << (writeCounter() - readCounter()); +#endif + while (count > 0) { + bool wrap = (m_writePos + count) >= m_size; + if (wrap) { + int block = m_size - m_writePos; + memcpy(m_buffer + m_writePos, pdata, block); + increment = block; + count -= block; + pdata += block; + m_writeWrap++; + m_writePos = 0; + } else { + memcpy(m_buffer + m_writePos, pdata, count); + increment = count; + count -= count; + pdata += count; + m_writePos = (m_writePos + increment) % m_size; + } + } + + m_mutex.unlock(); + return len; +} + +qint64 QAudioInterfaceBuffer::bytesAvailable() +{ + m_mutex.lock(); + int count = writeCounter() - readCounter(); + m_mutex.unlock(); + if (count >= 0) + return count; + return 0; +} + +void QAudioInterfaceBuffer::setBufferSize(int len) +{ + if (m_buffer != nullptr) + delete[] m_buffer; +#if QT_DEBUG_SOUNDBACKEND + qDebug() << "buffer size" << len; +#endif + m_buffer = new char[len]; + m_size = len; + m_readPos = 0; + m_writePos = 0; + m_readWrap = 0; + m_writeWrap = 0; + zero(); +} + +void QAudioInterfaceBuffer::zero() +{ + if (m_buffer != nullptr) + memset(m_buffer, 0, m_size); +} + +int QAudioInterfaceBuffer::readCounter() const +{ + return (m_readWrap * m_size) + m_readPos; +} + +int QAudioInterfaceBuffer::writeCounter() const +{ + return (m_writeWrap * m_size) + m_writePos; +} diff --git a/src/burner/qt/qaudiointerface.h b/src/burner/qt/qaudiointerface.h new file mode 100644 index 000000000..ddefe5e3c --- /dev/null +++ b/src/burner/qt/qaudiointerface.h @@ -0,0 +1,64 @@ +#ifndef QAUDIOINTERFACE_H +#define QAUDIOINTERFACE_H + +#include +#include +#include +#include +#include +#include + +class QAudioInterfaceBuffer : public QIODevice +{ + Q_OBJECT + +public: + QAudioInterfaceBuffer(int size = 0); + ~QAudioInterfaceBuffer(); + void start(); + void stop(); + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + qint64 bytesAvailable(); + void setBufferSize(int len); + +private: + QElapsedTimer m_elapsed; + QMutex m_mutex; + void zero(); + int readCounter() const; + int writeCounter() const; + int m_writeWrap; + int m_readWrap; + int m_size; + char *m_buffer; + int m_readPos; + int m_writePos; +}; + +class QAudioInterface : public QThread +{ + Q_OBJECT + + QAudioInterface(QObject *parent=0); + static QAudioInterface *m_onlyInstance; +public: + ~QAudioInterface(); + + void run(); + static QAudioInterface *get(QObject *parent=0); + void setBuffer(QAudioInterfaceBuffer *buffer); + void setFormat(QAudioFormat &format); +signals: +public slots: + void play(); + void stop(); + void init(); + void writeMoreData(); +private: + QAudioOutput *m_audioOutput; + QAudioFormat m_format; + QAudioInterfaceBuffer *m_buffer; +}; + +#endif // QAUDIOINTERFACE_H diff --git a/src/burner/qt/qinputinterface.cpp b/src/burner/qt/qinputinterface.cpp new file mode 100644 index 000000000..04200b5eb --- /dev/null +++ b/src/burner/qt/qinputinterface.cpp @@ -0,0 +1,282 @@ +#include +#include "qinputinterface.h" +#include "burner.h" + +QInputInterface *qInput = nullptr; + +int QtKToFBAK(int key); +static char qKeyboardState[QINPUT_MAX_KEYS] = { 0 }; +static bool bKeyboardRead = false; + +int QtInputSetCooperativeLevel(bool bExclusive, bool) +{ + qDebug() << __func__; + return 0; +} + +int QtInputExit() +{ + qDebug() << __func__; + if (qInput) { + delete qInput; + qInput = nullptr; + } + return 0; +} + +int QtInputInit() +{ + qDebug() << __func__; + qInput = QInputInterface::get(); + qInput->install(); + memset(qKeyboardState, 0, QINPUT_MAX_KEYS); + bKeyboardRead = false; + return 0; +} + +int QtInputStart() +{ + bKeyboardRead = false; + return 0; +} + +static int ReadJoystick() +{ + return 0; +} + +int QtInputJoyAxis(int i, int nAxis) +{ + return 0; +} + +static int ReadKeyboard() +{ + if (bKeyboardRead) + return 0; + qInput->snapshot(qKeyboardState); + bKeyboardRead = true; + return 0; +} + +static int ReadMouse() +{ + return 0; +} + +int QtInputMouseAxis(int i, int nAxis) +{ + return 0; +} + +static int JoystickState(int i, int nSubCode) +{ + return 0; +} + +static int CheckMouseState(unsigned int nSubCode) +{ + return 0; +} + +int QtInputState(int nCode) +{ + if (nCode < 0) + return 0; + + if (nCode < 256) { +#if 1 + if (!bKeyboardRead) + ReadKeyboard(); + return qKeyboardState[nCode & 0xFF]; +#else + return qInput->state(nCode); +#endif + } + return 0; +} + +int QtInputFind(bool CreateBaseline) +{ + return -1; +} + +int QtInputGetControlName(int nCode, TCHAR* pszDeviceName, TCHAR* pszControlName) +{ + return 0; +} + +struct InputInOut InputInOutQt = { QtInputInit, QtInputExit, + QtInputSetCooperativeLevel, QtInputStart, + QtInputState, QtInputJoyAxis, QtInputMouseAxis, + QtInputFind, QtInputGetControlName, NULL, + ("Qt") }; + + +QInputInterface *QInputInterface::m_onlyInstance = nullptr; +QInputInterface::QInputInterface(QObject *parent) : + QObject(parent) +{ + memset(m_keys, 0, QINPUT_MAX_KEYS); + m_isInitialized = false; +} + +QInputInterface::~QInputInterface() +{ + if (m_isInitialized) + uninstall(); + m_onlyInstance = nullptr; +} + +QInputInterface *QInputInterface::get(QObject *parent) +{ + if (m_onlyInstance != nullptr) + return m_onlyInstance; + m_onlyInstance = new QInputInterface(parent); + return m_onlyInstance; +} + +void QInputInterface::install() +{ + qApp->installEventFilter(this); + m_isInitialized = true; +} + +void QInputInterface::uninstall() +{ + qApp->removeEventFilter(this); + m_isInitialized = false; +} + +void QInputInterface::snapshot(char *buffer, int keys) +{ + if (keys >= QINPUT_MAX_KEYS) + keys = QINPUT_MAX_KEYS; + memcpy(buffer, m_keys, keys); +} + +int QInputInterface::state(int key) +{ + if (key >= 0 && key < 256) + return m_keys[key]; + return 0; +} + +bool QInputInterface::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + //qDebug() << m_timer.elapsed() << "pressed"; + int fbak = QtKToFBAK(keyEvent->key()); + if (fbak < QINPUT_MAX_KEYS) + m_keys[fbak] = 1; + } + if (event->type() == QEvent::KeyRelease) { + //qDebug() << m_timer.elapsed() << "released"; + QKeyEvent *keyEvent = static_cast(event); + int fbak = QtKToFBAK(keyEvent->key()); + if (fbak < QINPUT_MAX_KEYS) + m_keys[fbak] = 0; + } + return false; +} + + +int QtKToFBAK(int key) +{ + /* + * TODO: + * FBK_CAPITAL 0x3A + * RIGHT-(SHIFT/CONTROL/ALT) + * NUMPADS + */ + switch (key) { + case Qt::Key_Escape: return FBK_ESCAPE; + case Qt::Key_0: return FBK_0; + case Qt::Key_1: return FBK_1; + case Qt::Key_2: return FBK_2; + case Qt::Key_3: return FBK_3; + case Qt::Key_4: return FBK_4; + case Qt::Key_5: return FBK_5; + case Qt::Key_6: return FBK_6; + case Qt::Key_7: return FBK_7; + case Qt::Key_8: return FBK_8; + case Qt::Key_9: return FBK_9; + case Qt::Key_Minus: return FBK_MINUS; + case Qt::Key_Equal: return FBK_EQUALS; + case Qt::Key_Back: return FBK_BACK; + case Qt::Key_Tab: return FBK_TAB; + case Qt::Key_Q: return FBK_Q; + case Qt::Key_W: return FBK_W; + case Qt::Key_E: return FBK_E; + case Qt::Key_R: return FBK_R; + case Qt::Key_T: return FBK_T; + case Qt::Key_Y: return FBK_Y; + case Qt::Key_U: return FBK_U; + case Qt::Key_I: return FBK_I; + case Qt::Key_O: return FBK_O; + case Qt::Key_P: return FBK_P; + case Qt::Key_BracketLeft: return FBK_LBRACKET; + case Qt::Key_BracketRight: return FBK_RBRACKET; + case Qt::Key_Return: return FBK_RETURN; + case Qt::Key_Control: return FBK_LCONTROL; + case Qt::Key_A: return FBK_A; + case Qt::Key_S: return FBK_S; + case Qt::Key_D: return FBK_D; + case Qt::Key_F: return FBK_F; + case Qt::Key_G: return FBK_G; + case Qt::Key_H: return FBK_H; + case Qt::Key_J: return FBK_J; + case Qt::Key_K: return FBK_K; + case Qt::Key_L: return FBK_L; + case Qt::Key_Semicolon: return FBK_SEMICOLON; + case Qt::Key_Apostrophe: return FBK_APOSTROPHE; + case Qt::Key_Dead_Grave: return FBK_GRAVE; + case Qt::Key_Shift: return FBK_LSHIFT; + case Qt::Key_Backslash: return FBK_BACKSLASH; + case Qt::Key_Z: return FBK_Z; + case Qt::Key_X: return FBK_X; + case Qt::Key_C: return FBK_C; + case Qt::Key_V: return FBK_V; + case Qt::Key_B: return FBK_B; + case Qt::Key_N: return FBK_N; + case Qt::Key_M: return FBK_M; + case Qt::Key_Comma: return FBK_COMMA; + case Qt::Key_Period: return FBK_PERIOD; + case Qt::Key_Slash: return FBK_SLASH; + case Qt::Key_multiply: return FBK_MULTIPLY; + case Qt::Key_Alt: return FBK_LALT; + case Qt::Key_Space: return FBK_SPACE; + case Qt::Key_F1: return FBK_F1; + case Qt::Key_F2: return FBK_F2; + case Qt::Key_F3: return FBK_F3; + case Qt::Key_F4: return FBK_F4; + case Qt::Key_F5: return FBK_F5; + case Qt::Key_F6: return FBK_F6; + case Qt::Key_F7: return FBK_F7; + case Qt::Key_F8: return FBK_F8; + case Qt::Key_F9: return FBK_F9; + case Qt::Key_F10: return FBK_F10; + case Qt::Key_NumLock: return FBK_NUMLOCK; + case Qt::Key_ScrollLock: return FBK_SCROLL; + case Qt::Key_Pause: return FBK_PAUSE; + case Qt::Key_Home: return FBK_HOME; + case Qt::Key_Up: return FBK_UPARROW; + case Qt::Key_PageUp: return FBK_PRIOR; + case Qt::Key_Left: return FBK_LEFTARROW; + case Qt::Key_Right: return FBK_RIGHTARROW; + case Qt::Key_End: return FBK_END; + case Qt::Key_Down: return FBK_DOWNARROW; + case Qt::Key_PageDown: return FBK_NEXT; + case Qt::Key_Insert: return FBK_INSERT; + case Qt::Key_Delete: return FBK_DELETE; + case Qt::Key_F11: return FBK_F11; + case Qt::Key_F12: return FBK_F12; + case Qt::Key_F13: return FBK_F13; + case Qt::Key_F14: return FBK_F14; + case Qt::Key_F15: return FBK_F15; + default: + return 0; + } + return 0; +} diff --git a/src/burner/qt/qinputinterface.h b/src/burner/qt/qinputinterface.h new file mode 100644 index 000000000..a9432fac9 --- /dev/null +++ b/src/burner/qt/qinputinterface.h @@ -0,0 +1,30 @@ +#ifndef QINPUTINTERFACE_H +#define QINPUTINTERFACE_H + +#include +#include + +#define QINPUT_MAX_KEYS 256 + +class QInputInterface : public QObject +{ + Q_OBJECT + explicit QInputInterface(QObject *parent = 0); + static QInputInterface *m_onlyInstance; + char m_keys[QINPUT_MAX_KEYS]; + QElapsedTimer m_timer; + bool m_isInitialized; +public: + ~QInputInterface(); + static QInputInterface *get(QObject *parent=nullptr); + void install(); + void uninstall(); + void snapshot(char *buffer, int keys=QINPUT_MAX_KEYS); + int state(int key); +signals: +public slots: +protected: + bool eventFilter(QObject *obj, QEvent *event); +}; + +#endif // QINPUTINTERFACE_H diff --git a/src/burner/qt/qrubyviewport.cpp b/src/burner/qt/qrubyviewport.cpp new file mode 100644 index 000000000..0b24f2ba9 --- /dev/null +++ b/src/burner/qt/qrubyviewport.cpp @@ -0,0 +1,144 @@ +#include +#include "qrubyviewport.h" +#include "burner.h" +#include "ruby/ruby.hpp" +#include "nall/traits.hpp" + +static QRubyViewport *viewport = nullptr; +static int VidMemLen = 0; +static int VidMemPitch = 0; +static int VidBpp = 0; +static int clipx = 0; +static int clipy = 0; +static int sizex = 0; +static int sizey = 0; +static unsigned char* VidMem = NULL; + +static inline unsigned c16to32(unsigned color) +{ + // convert from BGR565 to BGRA32 + unsigned b = color & 0x001F; + unsigned g = color & 0x07E0; + unsigned r = color & 0xF800; + return 0xFF000000 | (r << 8) | (g << 5) | (b << 3); +} + +void videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, + unsigned width, unsigned height) { + uint32_t *output; + unsigned outputPitch; + + if (ruby::video.lock(output, outputPitch, width, height)) { + pitch >>= 2, outputPitch >>= 2; + + for (unsigned y = 0; y < height; y++) { + const uint32_t *psrc = data + y * pitch; + uint32_t *pdst = output + y * outputPitch; + + for (unsigned x = 0; x < width; x++) { + *pdst++ = c16to32(*psrc); + psrc = (uint32_t*)((uint16_t*)psrc + 1); + } + } + ruby::video.unlock(); + ruby::video.refresh(); + } +} + + +static INT32 RubyVidInit() +{ + qDebug() << __func__; + viewport = QRubyViewport::get(); + + if (BurnDrvGetFlags() & BDF_ORIENTATION_VERTICAL) { + BurnDrvGetVisibleSize(&clipy, &clipx); + BurnDrvGetFullSize(&sizex, &sizey); + } else { + BurnDrvGetVisibleSize(&clipx, &clipy); + BurnDrvGetFullSize(&sizex, &sizey); + } + VidBpp = nBurnBpp = 2; + VidMemPitch = sizex * VidBpp; + VidMemLen = sizey * VidMemPitch; + VidMem = (unsigned char*)malloc(VidMemLen); + SetBurnHighCol(16); + + return 0; +} + +static INT32 RubyVidExit() +{ + qDebug() << __func__; + free(VidMem); + return 0; +} + +static INT32 RubyVidFrame(bool bRedraw) +{ + nBurnBpp=2; + + nBurnPitch = VidMemPitch; + pBurnDraw = VidMem; + + if (bDrvOkay) { + pBurnSoundOut = nAudNextSound; + nBurnSoundLen = nAudSegLen; + BurnDrvFrame(); // Run one frame and draw the screen + } + + nFramesRendered++; + + pBurnDraw = NULL; + nBurnPitch = 0; + return 0; +} + +static INT32 RubyVidPaint(INT32 bValidate) +{ + if (bValidate & 2) { + } + videoRefresh(nullptr, (const uint32_t*) VidMem, VidMemPitch, sizex, sizey); + return 0; +} + +static INT32 RubyVidImageSize(RECT* pRect, INT32 nGameWidth, INT32 nGameHeight) +{ + return 0; +} + +// Get plugin info +static INT32 RubyVidGetPluginSettings(InterfaceInfo* pInfo) +{ + return 0; +} + +struct VidOut VidRuby = { RubyVidInit, RubyVidExit, RubyVidFrame, RubyVidPaint, + RubyVidImageSize, RubyVidGetPluginSettings, + ("ruby-video") }; + + +QRubyViewport *QRubyViewport::m_onlyInstance = nullptr; +QRubyViewport::QRubyViewport(QWidget *parent) : + QWidget(parent) +{ + setAutoFillBackground(false); +} + +QRubyViewport::~QRubyViewport() +{ + m_onlyInstance = nullptr; +} + +QRubyViewport *QRubyViewport::get(QWidget *parent) +{ + if (m_onlyInstance != nullptr) + return m_onlyInstance; + m_onlyInstance = new QRubyViewport(parent); + return m_onlyInstance; +} + +uintptr_t QRubyViewport::id() +{ + return (uintptr_t)winId(); +} diff --git a/src/burner/qt/qrubyviewport.h b/src/burner/qt/qrubyviewport.h new file mode 100644 index 000000000..74a900fc9 --- /dev/null +++ b/src/burner/qt/qrubyviewport.h @@ -0,0 +1,18 @@ +#ifndef QRUBYVIEWPORT_H +#define QRUBYVIEWPORT_H + +#include +#include + +class QRubyViewport : public QWidget +{ + Q_OBJECT + explicit QRubyViewport(QWidget *parent = 0); + static QRubyViewport *m_onlyInstance; +public: + ~QRubyViewport(); + static QRubyViewport *get(QWidget *parent = nullptr); + uintptr_t id(); +}; + +#endif // QRUBYVIEWPORT_H diff --git a/src/burner/qt/qutil.cpp b/src/burner/qt/qutil.cpp new file mode 100644 index 000000000..89675f095 --- /dev/null +++ b/src/burner/qt/qutil.cpp @@ -0,0 +1,54 @@ +#include +#include +#include "qutil.h" + +namespace util { + +void fixPath(QString &path) +{ + if (!path.isEmpty()) { + QChar lastChar = path.at(path.size() - 1); + if (lastChar != QChar('\\') && lastChar != QChar('/')) + path.append('/'); + } +} + +void PathHandler::editorToString() +{ + if (str == nullptr || edit == nullptr) + return; + QByteArray bytes; + QString path = edit->text(); + fixPath(path); + bytes = path.toLocal8Bit(); + _sntprintf(str, MAX_PATH, bytes.data()); +} + +void PathHandler::stringToEditor() +{ + if (edit == nullptr) + return; + edit->setText(QString(str)); +} + +void PathHandler::browse(QWidget *parent) +{ + QString path = QFileDialog::getExistingDirectory(parent); + if (path.isEmpty()) + return; + if (edit == nullptr) + return; + edit->setText(path); +} + +QString loadText(const QString &fileName) +{ + QFile file(fileName); + if (file.exists()) { + file.open(QFile::ReadOnly | QFile::Text); + return QString(file.readAll()); + } + return QString(); +} + +} diff --git a/src/burner/qt/qutil.h b/src/burner/qt/qutil.h new file mode 100644 index 000000000..8e051be82 --- /dev/null +++ b/src/burner/qt/qutil.h @@ -0,0 +1,30 @@ +#ifndef QUTIL_H +#define QUTIL_H + +#include +#include +#include +#include "burner.h" + +namespace util { + +struct PathHandler { + TCHAR *str; + QLineEdit *edit; + int number; + PathHandler(TCHAR *s=nullptr, QLineEdit *e=nullptr, int n=-1) { + str = s; + edit = e; + number = n; + } + void editorToString(); + void stringToEditor(); + void browse(QWidget *parent=nullptr); +}; + +void fixPath(QString &path); +QString loadText(const QString &fileName); + +} + +#endif // QUTIL_H diff --git a/src/burner/qt/resource/about.bmp b/src/burner/qt/resource/about.bmp new file mode 100644 index 0000000000000000000000000000000000000000..938ae09a0a851396bd7c164626859e4195c03e70 GIT binary patch literal 196730 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%0AsR!NUUU39P{pq1iK>Zn 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

NmE({Twu(UIBU=df&Xta|OQ)19zT>-8Z)QNfe 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 literal 0 HcmV?d00001 diff --git a/src/burner/qt/resource/splash.bmp b/src/burner/qt/resource/splash.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1690a818d9ffc8857d5cd6a1c040bf9aec46419c GIT binary patch literal 204344 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±#qG9zkJ@&(wrMZ4WuI@$F*?H1<(_|n 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 literal 0 HcmV?d00001 diff --git a/src/burner/qt/resource/tv-not-found-non-essential.ico b/src/burner/qt/resource/tv-not-found-non-essential.ico new file mode 100644 index 0000000000000000000000000000000000000000..367cb47cfc254c672cc869fabf30141422d32e0b GIT binary patch literal 4286 zcmcIn3s93+7XBZ3hX4UWc!dx`62d!3K;8%{58ntVASynmW2X=C?Y6{L=~h?W>N>Nl zc4uqdy6x0fp}d6fNPzH2Kt+}+TB&Y#oZ8MZTW8m?j%Ux!7>Ck@wma@+4mY{?zxVs@ zx!*bG4@J@V7ZO5||I}YRDax0ksBknKqO#D)IB6!FdrnP?`)JYP&|=WkXcDylTN6|C zEF;6&wT>gGKfo1@p5h4az0Vc@;|y2)?QvG{x19`cXd%Nr8${8Yk%aDf5%Hy&xs^QO zg>yoW2ZJI{xFPg}F|->354gc|hnr}(xe^%1{tL7oo&*lDg1bpHdki@c-~3OF!E0Q~ z7yaQ=iTC%z9=(2F*NT!x9^=PP`7aL@v<(?W1? z#4v;$M$qmej~4VzD$V{8@rf*dvdI`W;TAoxVX}mZQl6pc6Sb{FN{i5eH_2 z@n7eY)5ktYpL6-6jO>w1saanS8&hE{SOdd|?_h*5i1?S$zCipT^hF-U{RF{fGZ7He zOvoW0zni^5q0kx-0xiV+lwiZ}-7&M^h{4!Wpw?_q%H`1lfxwGmXaU1v-DKY0szjYG ze|tvO?_1{OeRVlN^~?{%7h_L3q|ofUh(Fz3urGSa8oHDkXbx6GQ*anGE7V{Mi-0rH zhR;*{0}E&3?wZs1%JbV4>hA`hYJ&KIaJYpWTG0Y2dW-AX$^C4R+lCgE4w^!B(13k| zN()`uD0nL@yv3cz519Pybf4WQT6K^kvjGpWFqBks6FuT>pdk;k~ zU`0+nTtg1m&@Y%rj!s^i=I&Y99uW(5h*=vR1&#U`IH!x8kaM^{D}E(3qdwv(b2wxQ zr%o-NzR-iQF@!vt4T<-!qJ{_u*F9iU=aJgGXGN; zA1rz9^_W@b|LUs%`_tTT3w5xN<^74w#Y(;AcIM|9vr7jS%fWQro#2NWuMdt@Bc>7tx^ZNzx#(L}<|AmzK0 zvB^GxBDa{%@R)x)P>Xi-J&DC*9N`@-%z-O4+P!_tilA{`J~ZbpfVSKMNRi2RO@}XL z*w6zijolod=+sIrYPC)m2^Q4Oc4g=f&h6Sm!g7=s!=wH_i19#TlGLsDxZ?XXP3y|_ z`s)6YGHA|U0GAdnfwqFhkm%#P)3tVT?}pel8oj+eF%@bJh>tn37Jbken*dcI>QhtC z?OH2lIYFO<>h3y37?YSAuA>jma>d^;6dhg?5mjZcSPRXImO|^&?IVQZd0iM6Bq#J@?I!OIzvhXr;hULA|f*Y`3H@56Si<6Tel;&t@F zW}fH*ndfsj;t$tv9I>xj4Hm?xdHFK>U^bWcii_X2{DPySTV8_B(sHnrtpdX3 zP+G>9htspJ4Xs+^^h-P5OFQ1rz=~4XZJhmIb?(}4a-S>pe$9$C)q**x3-hA`^Uwt^ zzXzB%^{(f_6$*o6*Pch+rK_N;d^L0-eqY5JaG)*;AHu5xafwd%%ue}He7p9U+6M|n zX&-1J9@wqQYF~^!p*qf4e za=I9sx}QuaG#=IHzwE)h(1AJ9s@6k4;G{MPVT7{na;VMOe0IgkI7HIu>Id0laf7 z^5`mG)7%d3ZY{VQt<9o!k;FBg|wAxv}DlI$m z>GmDNecN_?e-*h5z3?K~H*5y`+I7&sx)N-tk-Fm2dx!HDH00|eE5!l<`HlQsn}Ej` zMTLdM7bGPWug=Kax*;R;^~%(=J%vW&#w49CNABsV{H1{KPzlh%0#Fn@rl`z^6s4S? zD35V04IRn}en;jwYpU*GO<`C?}7*8mw8n*)&_kjNZg(+2J literal 0 HcmV?d00001 diff --git a/src/burner/qt/resource/tv-not-found.ico b/src/burner/qt/resource/tv-not-found.ico new file mode 100644 index 0000000000000000000000000000000000000000..9cfa60df9574840139544cfba1ad8199976ac86b GIT binary patch literal 4286 zcmcIn3slqB8UH`>B7qPH5MCi6fdmp1fdUE`RG@(33kBq{Y zp0lalS*>e3Pwf=sWgsMkHwmC<)uNMXyW^?t8E5ru9_#Vjcf-=9SZKTBZqAqc&;LGt zzx&WEf~R=K}=ypNRm&h?M^R z2XD#ceGn4ThGv4GU=zsYy)b)r55&hGHbq1%u9C@=vjhTxFT+p*4u|!WO8vvN+N9j{ z^y=SbWtlz=2^n$o1M$U}lLNz~yovbJ?FB29Zfa60yFjUE#kI6RSXe7WMiT7PH-dvx z@@L}iYLm$#bHl>SU-J26{VqQc4!3bH+JL4*e(dV&UW-I?t5hlzsMRf?Qa6E0)dX5? z7vjHW;`7D9?tQ0wrfJV`F84sEhsPiYMW{`IOP2(H1br}$nymW-$A2R}X@)0}%a7gC zYTH4hxro*P(a{~q;n)2v%lDlQ&;313d#S|Y-`IV9FJr89qb^B|3?PRqi0SgfRpf9L z^^thQeXO^4TBTm!ftU@Ti>?Qqt{K<<-UOE``(E+OFkWgOpTmv-nZv15AB+>RxCeO* zqh?wk1O>S`Ozs2b2jT;zi$kQw*pirN1AWW|m^JGH?zai&S$4qHK2wd@DnGx2LqxMd zLG2I_(BX{LfPl*&_1$&c&o6vlN~-lsE|*{vO%NaSU`&Qql6kp2A9-@}+0PAzc8E`C zz}RZS^}jX2;fQrJ;gU9n@l?rVuRBz#E~i#WyaWcCF>Z#C$DWf6L$jn`Y}~8oWU>!2 zChUkmjQF>4PxAgq+I)RuGcz--uyA1u7z~XtcdiM-!?$!|6GPgkD;TTcp`mqlt=8() zYj}7wa=3(J`!P@KKE*JM6Pt_=4o)kHjJ$&QlX~Ry@8I*#?byT%3(MJB2lv{RWs_g2Xn=aH{G#Gf01X%?C5CgzXSpY zuATVcHsT+>OVd<{R9CF4aIk$Mcw;s?mt<^=B?kg%9fW^73ke6qI zB}+)YP1@la>t3_0r%I!#vnMBapswp7HueI<#MHx_Iac)hHr((3`rLgSha(DAt3S9W z@^s+2NzSXAh=1mDnr5Q(`oeAHacuFheYZ$XXJ zLt9NUJ9#5(X4Q-n6@*2RLK=Km7KlLg4U!m1z6mHls1S=7H^{RF# zEbK-7SGC|U4jD@dm+PfUNI2lg%CtE(nwru8$;l0nnrcDc6YNQyPxgNHTPW0J1qOCG zbAm{O=eG0i@T;qMF%t#`YT{mgx$ALZK__Bcpmw^JkGK-+23!9`f_EV?OHoPhPxpY(zPt@q<@e$KSD*Sm*euKQ zk;zVsOQpRak&ycXxxbL}ao%PZ-`w2oM}D&DI+PT5LTQN&2p7ZrgC83VhptwX55t-w z3;L!5_ooA&2O%kG*SG52HJ^L!>zln-sU(`}#JKLl`vl3+=O1F;jCS>t%cJ6U?i_kt zTGEZ!U1;4HAarmX*E!r%jti5wFw9p`v_}OIc}`lf$~TLy(rb=frd|Hg!G^kAOst=E%o- zeGh7s#0bG3KtGrtbGae&ruGjER7CFG+xtaDc|Vkul06`Pm^AV(Ep69@sbi-5Es^NU zqoaTOS?ru{%uD94@zv_9;N!Dy+T5C!mS0&@a|23?UHdSJUsl=+^Hb}_f`XFv^LWB! z#1&4(#u)VT@(N#}Qf;`HkZ=wXUa;Xg+J^Xer_ho-eHrgm#~lpAo$+7Ls#V+SYK&v3 z#U3|5^q?L_ps271G8UcsM_k<2LA84IyJ2D3#}$gDCp4P%jj^%&Z_l0gKBS~rF*leo zK3Wj}5=86UU{=f!^7!)|K412oXDV6O>ebt8_y6)HtXn&XJZw;g`#|D?a4IggBM%4W z$v#+`XM@~a8|H*=JhyBxe||gW#%7FWeV5rz?FjT#o znp(L#F){xotu{GBB9SS6C?Gsi09aT6ih?H;mHvpL6cZHXJx)=4i(-OrNgrp8`z)*x zjC2i9H2svSp`Xx3_7Q7jC-_Eo+}p@n6h_*ZZltMwH8>4zq|w-?tcCvsn;!AU@rley R<|Y_VDQX{12Qcmp{|2TdP*DH? literal 0 HcmV?d00001 diff --git a/src/burner/qt/resource/tv-not-working.ico b/src/burner/qt/resource/tv-not-working.ico new file mode 100644 index 0000000000000000000000000000000000000000..9149eeba31b10a3950dcdcfdfffa232df250fdf6 GIT binary patch literal 4286 zcmcInYfKeK6yD1s?+PN-gx3WWp=c3#h{PI*fQm*%6jV%&R1g&v1Q7&*MFABB6vR-) zT9I0Q0I^9mCjKG*Ys#%nsd>Z5gv(xlkLs<}iDAX3lpW-#IfX ziV6P$0u=sLx;zwRlAP2rii(Q#anq(vV&TGtcVTNYY+!8u(_=E3Ce4^JvrwhC3fuCArPm#h(|sQaDE#joSmH` z7A#nBjk+jC%-q~u{WtZ~f6g;5{7#`QT)%z$c2QhhEH-S|ApHIPKLb8#Ip%|SN2jHw z-C<0rS7LKyj4LWCL`g}>pLoB8HQ&S68|CHY4{K^_^f~TfOt_x$pdOA>r%vq!`1_+7 z0FDKKS3k);GT!`MR#rw|uEfU1X8ZW~eCXuleI^9v+^OnVH$9Y1)1I zE#p8t7$@pN98Lq@RxZ@|`1ni2q7Ph8*}Z!={rNF0EG%s_+vOZEQjnWJjB(>TIh2)^ zCBTO~xz;!a1_ov`r_3AiDfA;FBjbvTi;K5$t*v*&FA&_nDzPOm`J28lf8gXj?0>C3 z5kn8ummYVCPY$nMz51JRvqzVdl$5KO zvg5q1hg@5+Vnsi(BwrC-dOBlH}Qle8D-ML(t-aGvBII&?_K zudJ*Ts9zBo8QEeBm)|8e^D6mDZ2Ac7(?)ErpEz-1IM)6pb3+c`%({aqAJlw#Lqmg* z*!%bIXKYHjZ={DYU%7H+e`#r{-g_lyhp}JtTkc1%GQ-YaX(Rn7zoFw1@)I(D{`{(= zM~{l?>S{r3_EFSBiQH=(iH%(LGnULD^^mW$1%98h_FL}7K40+vt`R>yJ^eB8LlY7b zidtJ+b?myjI#FL=FJ{i1*&z4Ik=W=F7RHHjXAWrt`!Zr_6Px>Src8t1gXE_4AKbZ* zDRWFrOh#K86J3Bku($v(X=ccNvN-+M+*Ro~H z#>A#I@UelpqVE*)59ing&Y{1$!uK1;j~^H0A#rF2&XHp9daq3M+uKh--Ue7F5})tzyFl(iY@5!WJ*(?w{Amk(zKuR~{ltkAx;^9t zZ8&!97|(?fc`o%CN8F(*=r7|>A!l$7J_VmXkn4hif@YmNckUT|XZ$JjnY^SujLVTD zNA%xo*RFk|zNH@P(bLoOBlOy*xUan8EBS+(@3vkGyS_Mc=8TR@%P4!8|!*{dUP9P9Ei{OGY{x@zoU12J9qBfFL6$^&YnH{ zWKvSnMeyQh;;<*sZzDFpq4s}4PR2Y_X$!bt+uPgwihs-jv1w0pbF=WpFH*rUuW%P5bcl5PS&YwU3sI#+E=b`LRvJXg1o|n>Ra!q10-s~0N z>;?2+|FQ5!n#bKB6nX6E>gpOoed;wyeELeicos0vtSMsio!{9j5dWLVO%C*qdXE?h z*ZLdo-ciV3BVzvpYV +#include "romdirsdialog.h" +#include "ui_romdirsdialog.h" +#include "burner.h" + +TCHAR szAppRomPaths[DIRS_MAX][MAX_PATH] = { { _T("roms/") } }; + +RomDirsDialog::RomDirsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::RomDirsDialog) +{ + ui->setupUi(this); + setWindowTitle(tr("Edit ROM paths")); + + m_group = new QButtonGroup(this); + m_group->addButton(ui->btnEditPath1, 0); + m_group->addButton(ui->btnEditPath2, 1); + m_group->addButton(ui->btnEditPath3, 2); + m_group->addButton(ui->btnEditPath4, 3); + + m_handlers[0] = util::PathHandler(szAppRomPaths[0], ui->lePath1, 0); + m_handlers[1] = util::PathHandler(szAppRomPaths[1], ui->lePath2, 1); + m_handlers[2] = util::PathHandler(szAppRomPaths[2], ui->lePath3, 2); + m_handlers[3] = util::PathHandler(szAppRomPaths[3], ui->lePath4, 3); + + connect(m_group, SIGNAL(buttonClicked(int)), this, SLOT(editPath(int))); +} + +RomDirsDialog::~RomDirsDialog() +{ + delete ui; +} + +int RomDirsDialog::exec() +{ + for (int i = 0; i < DIRS_MAX; i++) + m_handlers[i].stringToEditor(); + + if (QDialog::exec() == QDialog::Accepted) { + for (int i = 0; i < DIRS_MAX; i++) + m_handlers[i].editorToString(); + } +} + +void RomDirsDialog::editPath(int no) +{ + m_handlers[no].browse(this); +} diff --git a/src/burner/qt/romdirsdialog.h b/src/burner/qt/romdirsdialog.h new file mode 100644 index 000000000..359c73965 --- /dev/null +++ b/src/burner/qt/romdirsdialog.h @@ -0,0 +1,32 @@ +#ifndef ROMDIRSDIALOG_H +#define ROMDIRSDIALOG_H + +#include +#include +#include "qutil.h" + +namespace Ui { +class RomDirsDialog; +} + +class RomDirsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit RomDirsDialog(QWidget *parent = 0); + ~RomDirsDialog(); + +public slots: + int exec(); + void editPath(int no); +private: + Ui::RomDirsDialog *ui; + int m_activePath; + QButtonGroup *m_group; + + util::PathHandler m_handlers[DIRS_MAX]; + +}; + +#endif // ROMDIRSDIALOG_H diff --git a/src/burner/qt/romdirsdialog.ui b/src/burner/qt/romdirsdialog.ui new file mode 100644 index 000000000..1f2b2a2f2 --- /dev/null +++ b/src/burner/qt/romdirsdialog.ui @@ -0,0 +1,188 @@ + + + RomDirsDialog + + + + 0 + 0 + 510 + 244 + + + + Dialog + + + true + + + + + + + + Path #1 + + + + + + + true + + + + + + + Browse + + + + + + + + + + + Path #2 + + + + + + + true + + + + + + + Browse + + + + + + + + + + + Path #3 + + + + + + + true + + + + + + + Browse + + + + + + + + + + + Path #4 + + + + + + + true + + + + + + + Browse + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + Ok + + + + + + + + + + + btnCancel + clicked() + RomDirsDialog + reject() + + + 239 + 192 + + + 160 + 198 + + + + + btnOk + clicked() + RomDirsDialog + accept() + + + 355 + 197 + + + 316 + 217 + + + + + diff --git a/src/burner/qt/rominfodialog.cpp b/src/burner/qt/rominfodialog.cpp new file mode 100644 index 000000000..6861a5e1c --- /dev/null +++ b/src/burner/qt/rominfodialog.cpp @@ -0,0 +1,74 @@ +#include +#include "rominfodialog.h" +#include "ui_rominfodialog.h" +#include "burner.h" + +RomInfoDialog::RomInfoDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::RomInfoDialog) +{ + ui->setupUi(this); +} + +RomInfoDialog::~RomInfoDialog() +{ + delete ui; +} + +void RomInfoDialog::setDriverNo(int no) +{ + clear(); + m_driverNo = no; + + int tmp = nBurnDrvActive; + + nBurnDrvActive = m_driverNo; + setWindowTitle(QString(BurnDrvGetText(DRV_FULLNAME))); + for (int i = 0; i < 0x100; i++) { + BurnRomInfo info; + char *romName = nullptr; + memset(&info, 0, sizeof(info)); + BurnDrvGetRomInfo(&info, i); + BurnDrvGetRomName(&romName, i, 0); + + if (info.nLen == 0 || info.nType & BRF_BIOS) + continue; + + QTreeWidgetItem *item = new QTreeWidgetItem(); + item->setText(0, QString(romName)); + item->setText(1, QString::number(info.nLen)); + + + item->setText(2, QString::number(info.nCrc, 16)); + + QStringList type; + if (info.nType & BRF_ESS) + type << "Essential"; + if (info.nType & BRF_OPT) + type << "Optional"; + if (info.nType & BRF_PRG) + type << "Program"; + if (info.nType & BRF_GRA) + type << "Graphics"; + if (info.nType & BRF_SND) + type << "Sound"; + if (info.nType & BRF_BIOS) + type << "BIOS"; + item->setText(3, type.join(", ")); + + if (info.nType & BRF_NODUMP) + item->setText(4, "No Dump"); + + item->setTextAlignment(0, Qt::AlignLeft); + item->setTextAlignment(1, Qt::AlignRight); + item->setTextAlignment(2, Qt::AlignRight); + ui->tvRoms->addTopLevelItem(item); + } + + nBurnDrvActive = tmp; +} + +void RomInfoDialog::clear() +{ + ui->tvRoms->clear(); +} diff --git a/src/burner/qt/rominfodialog.h b/src/burner/qt/rominfodialog.h new file mode 100644 index 000000000..1518ef3e5 --- /dev/null +++ b/src/burner/qt/rominfodialog.h @@ -0,0 +1,25 @@ +#ifndef ROMINFODIALOG_H +#define ROMINFODIALOG_H + +#include + +namespace Ui { +class RomInfoDialog; +} + +class RomInfoDialog : public QDialog +{ + Q_OBJECT + +public: + explicit RomInfoDialog(QWidget *parent = 0); + ~RomInfoDialog(); + + void setDriverNo(int no); +private: + void clear(); + int m_driverNo; + Ui::RomInfoDialog *ui; +}; + +#endif // ROMINFODIALOG_H diff --git a/src/burner/qt/rominfodialog.ui b/src/burner/qt/rominfodialog.ui new file mode 100644 index 000000000..1984b5a26 --- /dev/null +++ b/src/burner/qt/rominfodialog.ui @@ -0,0 +1,121 @@ + + + RomInfoDialog + + + + 0 + 0 + 650 + 358 + + + + Dialog + + + + + + 0 + + + + Rom Info + + + + + + + Name + + + + + Size (bytes) + + + + + CRC32 + + + + + Type + + + + + Flags + + + + + + + + + Sample Info + + + + + + + Name + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + + + + + + + + + btnClose + clicked() + RomInfoDialog + accept() + + + 604 + 332 + + + 514 + 334 + + + + + diff --git a/src/burner/qt/romscandialog.cpp b/src/burner/qt/romscandialog.cpp new file mode 100644 index 000000000..753ac68ce --- /dev/null +++ b/src/burner/qt/romscandialog.cpp @@ -0,0 +1,102 @@ +#include +#include "romscandialog.h" +#include "ui_romscandialog.h" +#include "burner.h" + +RomScanDialog::RomScanDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::RomScanDialog), + m_analyzer(this) +{ + ui->setupUi(this); + + setWindowTitle(tr("Wait...")); + setFixedSize(size()); + + m_status.resize(nBurnDrvCount); + for (int i = 0; i < nBurnDrvCount; i++) + m_status[i] = 0; + + connect(&m_analyzer, SIGNAL(finished()), this, SLOT(accept())); + connect(ui->btnCancel, SIGNAL(clicked()), &m_analyzer, SLOT(terminate())); + + connect(&m_analyzer, SIGNAL(setRange(int,int)), + ui->progressBar, SLOT(setRange(int,int))); + connect(&m_analyzer, SIGNAL(setValue(int)), + ui->progressBar, SLOT(setValue(int))); + connect(this, SIGNAL(rejected()), &m_analyzer, SLOT(terminate())); +} + +RomScanDialog::~RomScanDialog() +{ + delete ui; +} + +int RomScanDialog::status(int drvNo) +{ + if (drvNo < 0 && drvNo >= nBurnDrvCount) + return 0; + return m_status[drvNo]; +} + +void RomScanDialog::cancel() +{ + close(); +} + +void RomScanDialog::showEvent(QShowEvent *event) +{ + ui->progressBar->setValue(0); + m_analyzer.start(); +} + +bool RomScanDialog::load() +{ + +} + +bool RomScanDialog::save() +{ + +} + +RomAnalyzer::RomAnalyzer(RomScanDialog *parent) : + m_scanDlg(parent) +{ +} + +void RomAnalyzer::run() +{ + QVector &status = m_scanDlg->m_status; + + if (status.size() != nBurnDrvCount) + status.resize(nBurnDrvCount); + + int tmp = nBurnDrvActive; + + emit setRange(0, nBurnDrvCount - 1); + + for (int i = 0; i < nBurnDrvCount; i++) { + nBurnDrvActive = i; + + emit setValue(i); + + int stat = BzipOpen(1); + switch (stat) { + case 0: + status[i] = 3; + break; + case 2: + status[i] = 1; + break; + case 1: + status[i] = 0; + break; + default: + break; + } + BzipClose(); + } + msleep(100); + nBurnDrvActive = tmp; +} diff --git a/src/burner/qt/romscandialog.h b/src/burner/qt/romscandialog.h new file mode 100644 index 000000000..658c84c0d --- /dev/null +++ b/src/burner/qt/romscandialog.h @@ -0,0 +1,47 @@ +#ifndef ROMSCANDIALOG_H +#define ROMSCANDIALOG_H + +#include +#include +#include + +namespace Ui { +class RomScanDialog; +} + +class RomScanDialog; + +class RomAnalyzer : public QThread { + Q_OBJECT + RomScanDialog *m_scanDlg; +public: + RomAnalyzer(RomScanDialog *parent); +signals: + void setRange(int, int); + void setValue(int); +private: + void run(); +}; + +class RomScanDialog : public QDialog +{ + friend class RomAnalyzer; + Q_OBJECT + +public: + explicit RomScanDialog(QWidget *parent = 0); + ~RomScanDialog(); + int status(int drvNo); +public slots: + void cancel(); +protected: + virtual void showEvent(QShowEvent *event); +private: + bool load(); + bool save(); + Ui::RomScanDialog *ui; + QVector m_status; + RomAnalyzer m_analyzer; +}; + +#endif // ROMSCANDIALOG_H diff --git a/src/burner/qt/romscandialog.ui b/src/burner/qt/romscandialog.ui new file mode 100644 index 000000000..c89648e5c --- /dev/null +++ b/src/burner/qt/romscandialog.ui @@ -0,0 +1,87 @@ + + + RomScanDialog + + + Qt::NonModal + + + + 0 + 0 + 373 + 80 + + + + Dialog + + + false + + + + + + QFrame::Box + + + QFrame::Sunken + + + + + + :/resource/misc.bmp + + + + + + + + + 0 + + + + + + + + + Scanning ROMs... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + + + + + + + + diff --git a/src/burner/qt/rscr.qrc b/src/burner/qt/rscr.qrc new file mode 100644 index 000000000..3f0dc0019 --- /dev/null +++ b/src/burner/qt/rscr.qrc @@ -0,0 +1,11 @@ + + + resource/splash.bmp + resource/tv-not-working.ico + resource/tv-not-found.ico + resource/tv-not-found-non-essential.ico + resource/misc.bmp + resource/about.bmp + resource/license.txt + + diff --git a/src/burner/qt/ruby/Makefile b/src/burner/qt/ruby/Makefile new file mode 100755 index 000000000..8a68983d1 --- /dev/null +++ b/src/burner/qt/ruby/Makefile @@ -0,0 +1,37 @@ +ifeq ($(platform),macosx) + rubyflags = $(objcppflags) $(flags) +else + rubyflags = $(cppflags) $(flags) +endif + +rubyflags += $(foreach c,$(subst .,_,$(call strupper,$(ruby))),-D$c) +rubyflags += $(if $(findstring .sdl,$(ruby)),`sdl-config --cflags`) + +rubylink = + +rubylink += $(if $(findstring video.cgl,$(ruby)),-framework OpenGL) +rubylink += $(if $(findstring video.direct3d,$(ruby)),-ld3d9) +rubylink += $(if $(findstring video.directdraw,$(ruby)),-lddraw) +rubylink += $(if $(findstring video.glx,$(ruby)),-lGL) +rubylink += $(if $(findstring video.wgl,$(ruby)),-lopengl32) +rubylink += $(if $(findstring video.xv,$(ruby)),-lXv) + +rubylink += $(if $(findstring audio.alsa,$(ruby)),-lasound) +rubylink += $(if $(findstring audio.ao,$(ruby)),-lao) +rubylink += $(if $(findstring audio.directsound,$(ruby)),-ldsound) +rubylink += $(if $(findstring audio.pulseaudio,$(ruby)),-lpulse) +rubylink += $(if $(findstring audio.pulseaudiosimple,$(ruby)),-lpulse-simple) +rubylink += $(if $(findstring audio.xaudio2,$(ruby)),-lole32) + +rubylink += $(if $(findstring input.udev,$(ruby)),-ludev) +rubylink += $(if $(findstring input.windows,$(ruby)),-ldinput8 -ldxguid) + +rubylink += $(if $(findstring .sdl,$(ruby)),`sdl-config --libs`) + +ifeq ($(platform),windows) + rubylink += $(if $(findstring audio.openal,$(ruby)),-lopenal32) +else ifeq ($(platform),macosx) + rubylink += $(if $(findstring audio.openal,$(ruby)),-framework OpenAL) +else + rubylink += $(if $(findstring audio.openal,$(ruby)),-lopenal) +endif diff --git a/src/burner/qt/ruby/audio.hpp b/src/burner/qt/ruby/audio.hpp new file mode 100755 index 000000000..9d8603e35 --- /dev/null +++ b/src/burner/qt/ruby/audio.hpp @@ -0,0 +1,18 @@ +struct Audio { + static const char* Handle; + static const char* Synchronize; + static const char* Frequency; + static const char* Latency; + + virtual bool cap(const nall::string& name) { return false; } + virtual nall::any get(const nall::string& name) { return false; } + virtual bool set(const nall::string& name, const nall::any& value) { return false; } + + virtual void sample(uint16_t left, uint16_t right) {} + virtual void clear() {} + virtual bool init() { return true; } + virtual void term() {} + + Audio() {} + virtual ~Audio() {} +}; diff --git a/src/burner/qt/ruby/audio/alsa.cpp b/src/burner/qt/ruby/audio/alsa.cpp new file mode 100755 index 000000000..0c37a5fb7 --- /dev/null +++ b/src/burner/qt/ruby/audio/alsa.cpp @@ -0,0 +1,240 @@ +//audio.alsa (2009-11-30) +//authors: BearOso, byuu, Nach, RedDwarf + +#include + +namespace ruby { + +class pAudioALSA { +public: + struct { + snd_pcm_t* handle; + snd_pcm_format_t format; + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; + int channels; + const char* name; + } device; + + struct { + uint32_t* data; + unsigned length; + } buffer; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + if(device.handle) init(); + } + return true; + } + + if(name == Audio::Frequency) { + if(settings.frequency != any_cast(value)) { + settings.frequency = any_cast(value); + if(device.handle) init(); + } + return true; + } + + if(name == Audio::Latency) { + if(settings.latency != any_cast(value)) { + settings.latency = any_cast(value); + if(device.handle) init(); + } + return true; + } + + return false; + } + + void sample(uint16_t left, uint16_t right) { + if(!device.handle) return; + + buffer.data[buffer.length++] = left + (right << 16); + if(buffer.length < device.period_size) return; + + snd_pcm_sframes_t avail; + do { + avail = snd_pcm_avail_update(device.handle); + if(avail < 0) snd_pcm_recover(device.handle, avail, 1); + if(avail < buffer.length) { + if(settings.synchronize == false) { + buffer.length = 0; + return; + } + int error = snd_pcm_wait(device.handle, -1); + if(error < 0) snd_pcm_recover(device.handle, error, 1); + } + } while(avail < buffer.length); + + //below code has issues with PulseAudio sound server + #if 0 + if(settings.synchronize == false) { + snd_pcm_sframes_t avail = snd_pcm_avail_update(device.handle); + if(avail < device.period_size) { + buffer.length = 0; + return; + } + } + #endif + + uint32_t* buffer_ptr = buffer.data; + int i = 4; + + while((buffer.length > 0) && i--) { + snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length); + if(written < 0) { + //no samples written + snd_pcm_recover(device.handle, written, 1); + } else if(written <= buffer.length) { + buffer.length -= written; + buffer_ptr += written; + } + } + + if(i < 0) { + if(buffer.data == buffer_ptr) { + buffer.length--; + buffer_ptr++; + } + memmove(buffer.data, buffer_ptr, buffer.length * sizeof(uint32_t)); + } + } + + void clear() { + } + + bool init() { + term(); + + if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) { + term(); + return false; + } + + //below code will not work with 24khz frequency rate (ALSA library bug) + #if 0 + if(snd_pcm_set_params(device.handle, device.format, SND_PCM_ACCESS_RW_INTERLEAVED, + device.channels, settings.frequency, 1, settings.latency * 1000) < 0) { + //failed to set device parameters + term(); + return false; + } + + if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) { + device.period_size = settings.latency * 1000 * 1e-6 * settings.frequency / 4; + } + #endif + + snd_pcm_hw_params_t* hwparams; + snd_pcm_sw_params_t* swparams; + unsigned rate = settings.frequency; + unsigned buffer_time = settings.latency * 1000; + unsigned period_time = settings.latency * 1000 / 4; + + snd_pcm_hw_params_alloca(&hwparams); + if(snd_pcm_hw_params_any(device.handle, hwparams) < 0) { + term(); + return false; + } + + if(snd_pcm_hw_params_set_access(device.handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0 + || snd_pcm_hw_params_set_format(device.handle, hwparams, device.format) < 0 + || snd_pcm_hw_params_set_channels(device.handle, hwparams, device.channels) < 0 + || snd_pcm_hw_params_set_rate_near(device.handle, hwparams, &rate, 0) < 0 + || snd_pcm_hw_params_set_period_time_near(device.handle, hwparams, &period_time, 0) < 0 + || snd_pcm_hw_params_set_buffer_time_near(device.handle, hwparams, &buffer_time, 0) < 0 + ) { + term(); + return false; + } + + if(snd_pcm_hw_params(device.handle, hwparams) < 0) { + term(); + return false; + } + + if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) { + term(); + return false; + } + + snd_pcm_sw_params_alloca(&swparams); + if(snd_pcm_sw_params_current(device.handle, swparams) < 0) { + term(); + return false; + } + + if(snd_pcm_sw_params_set_start_threshold(device.handle, swparams, + (device.buffer_size / device.period_size) * device.period_size) < 0 + ) { + term(); + return false; + } + + if(snd_pcm_sw_params(device.handle, swparams) < 0) { + term(); + return false; + } + + buffer.data = new uint32_t[device.period_size]; + return true; + } + + void term() { + if(device.handle) { + //snd_pcm_drain(device.handle); //prevents popping noise; but causes multi-second lag + snd_pcm_close(device.handle); + device.handle = 0; + } + + if(buffer.data) { + delete[] buffer.data; + buffer.data = 0; + } + } + + pAudioALSA() { + device.handle = 0; + device.format = SND_PCM_FORMAT_S16_LE; + device.channels = 2; + device.name = "default"; + + buffer.data = 0; + buffer.length = 0; + + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 60; + } + + ~pAudioALSA() { + term(); + } +}; + +DeclareAudio(ALSA) + +}; diff --git a/src/burner/qt/ruby/audio/ao.cpp b/src/burner/qt/ruby/audio/ao.cpp new file mode 100755 index 000000000..32bee07d7 --- /dev/null +++ b/src/burner/qt/ruby/audio/ao.cpp @@ -0,0 +1,94 @@ +/* + audio.ao (2008-06-01) + authors: Nach, RedDwarf +*/ + +#include + +namespace ruby { + +class pAudioAO { +public: + int driver_id; + ao_sample_format driver_format; + ao_device* audio_device; + + struct { + unsigned frequency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Frequency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Frequency) return settings.frequency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(audio_device) init(); + return true; + } + + return false; + } + + void sample(uint16_t l_sample, uint16_t r_sample) { + uint32_t samp = (l_sample << 0) + (r_sample << 16); + ao_play(audio_device, (char*)&samp, 4); //This may need to be byte swapped for Big Endian + } + + void clear() { + } + + bool init() { + term(); + + driver_id = ao_default_driver_id(); //ao_driver_id((const char*)driver) + if(driver_id < 0) return false; + + driver_format.bits = 16; + driver_format.channels = 2; + driver_format.rate = settings.frequency; + driver_format.byte_format = AO_FMT_LITTLE; + + ao_option* options = nullptr; + ao_info *di = ao_driver_info(driver_id); + if(!di) return false; + if(!strcmp(di->short_name, "alsa")) { + ao_append_option(&options, "buffer_time", "100000"); //100ms latency (default was 500ms) + } + + audio_device = ao_open_live(driver_id, &driver_format, options); + if(!audio_device) return false; + + return true; + } + + void term() { + if(audio_device) { + ao_close(audio_device); + audio_device = 0; + } + } + + pAudioAO() { + audio_device = 0; + ao_initialize(); + + settings.frequency = 22050; + } + + ~pAudioAO() { + term(); + //ao_shutdown(); //FIXME: this is causing a segfault for some reason when called ... + } +}; + +DeclareAudio(AO) + +}; diff --git a/src/burner/qt/ruby/audio/directsound.cpp b/src/burner/qt/ruby/audio/directsound.cpp new file mode 100755 index 000000000..9da869bc4 --- /dev/null +++ b/src/burner/qt/ruby/audio/directsound.cpp @@ -0,0 +1,209 @@ +/* + audio.directsound (2007-12-26) + author: byuu +*/ + +#include + +namespace ruby { + +class pAudioDS { +public: + LPDIRECTSOUND ds; + LPDIRECTSOUNDBUFFER dsb_p, dsb_b; + DSBUFFERDESC dsbd; + WAVEFORMATEX wfx; + + struct { + unsigned rings; + unsigned latency; + + uint32_t* buffer; + unsigned bufferoffset; + + unsigned readring; + unsigned writering; + int distance; + } device; + + struct { + HWND handle; + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Handle) return true; + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Handle) return (uintptr_t)settings.handle; + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + if(ds) clear(); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(ds) init(); + return true; + } + + if(name == Audio::Latency) { + settings.latency = any_cast(value); + if(ds) init(); + return true; + } + + return false; + } + + void sample(uint16_t left, uint16_t right) { + device.buffer[device.bufferoffset++] = left + (right << 16); + if(device.bufferoffset < device.latency) return; + device.bufferoffset = 0; + + DWORD pos, size; + void* output; + + if(settings.synchronize) { + //wait until playback buffer has an empty ring to write new audio data to + while(device.distance >= device.rings - 1) { + dsb_b->GetCurrentPosition(&pos, 0); + unsigned activering = pos / (device.latency * 4); + if(activering == device.readring) continue; + + //subtract number of played rings from ring distance counter + device.distance -= (device.rings + activering - device.readring) % device.rings; + device.readring = activering; + + if(device.distance < 2) { + //buffer underflow; set max distance to recover quickly + device.distance = device.rings - 1; + device.writering = (device.rings + device.readring - 1) % device.rings; + break; + } + } + } + + device.writering = (device.writering + 1) % device.rings; + device.distance = (device.distance + 1) % device.rings; + + if(dsb_b->Lock(device.writering * device.latency * 4, device.latency * 4, &output, &size, 0, 0, 0) == DS_OK) { + memcpy(output, device.buffer, device.latency * 4); + dsb_b->Unlock(output, size, 0, 0); + } + } + + void clear() { + device.readring = 0; + device.writering = device.rings - 1; + device.distance = device.rings - 1; + + device.bufferoffset = 0; + if(device.buffer) memset(device.buffer, 0, device.latency * device.rings * 4); + + if(!dsb_b) return; + dsb_b->Stop(); + dsb_b->SetCurrentPosition(0); + + DWORD size; + void* output; + dsb_b->Lock(0, device.latency * device.rings * 4, &output, &size, 0, 0, 0); + memset(output, 0, size); + dsb_b->Unlock(output, size, 0, 0); + + dsb_b->Play(0, 0, DSBPLAY_LOOPING); + } + + bool init() { + term(); + + device.rings = 8; + device.latency = settings.frequency * settings.latency / device.rings / 1000.0 + 0.5; + device.buffer = new uint32_t[device.latency * device.rings]; + device.bufferoffset = 0; + + DirectSoundCreate(0, &ds, 0); + ds->SetCooperativeLevel((HWND)settings.handle, DSSCL_PRIORITY); + + memset(&dsbd, 0, sizeof(dsbd)); + dsbd.dwSize = sizeof(dsbd); + dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbd.dwBufferBytes = 0; + dsbd.lpwfxFormat = 0; + ds->CreateSoundBuffer(&dsbd, &dsb_p, 0); + + memset(&wfx, 0, sizeof(wfx)); + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = settings.frequency; + wfx.wBitsPerSample = 16; + wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + dsb_p->SetFormat(&wfx); + + memset(&dsbd, 0, sizeof(dsbd)); + dsbd.dwSize = sizeof(dsbd); + dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE; + dsbd.dwBufferBytes = device.latency * device.rings * sizeof(uint32_t); + dsbd.guid3DAlgorithm = GUID_NULL; + dsbd.lpwfxFormat = &wfx; + ds->CreateSoundBuffer(&dsbd, &dsb_b, 0); + dsb_b->SetFrequency(settings.frequency); + dsb_b->SetCurrentPosition(0); + + clear(); + return true; + } + + void term() { + if(device.buffer) { + delete[] device.buffer; + device.buffer = 0; + } + + if(dsb_b) { dsb_b->Stop(); dsb_b->Release(); dsb_b = 0; } + if(dsb_p) { dsb_p->Stop(); dsb_p->Release(); dsb_p = 0; } + if(ds) { ds->Release(); ds = 0; } + } + + pAudioDS() { + ds = 0; + dsb_p = 0; + dsb_b = 0; + + device.buffer = 0; + device.bufferoffset = 0; + device.readring = 0; + device.writering = 0; + device.distance = 0; + + settings.handle = GetDesktopWindow(); + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 120; + } +}; + +DeclareAudio(DS) + +}; diff --git a/src/burner/qt/ruby/audio/openal.cpp b/src/burner/qt/ruby/audio/openal.cpp new file mode 100755 index 000000000..af478be0e --- /dev/null +++ b/src/burner/qt/ruby/audio/openal.cpp @@ -0,0 +1,210 @@ +/* + audio.openal (2007-12-26) + author: Nach + contributors: byuu, wertigon, _willow_ +*/ + +#if defined(PLATFORM_MACOSX) + #include + #include +#else + #include + #include +#endif + +namespace ruby { + +class pAudioOpenAL { +public: + struct { + ALCdevice* handle; + ALCcontext* context; + ALuint source; + ALenum format; + unsigned latency; + unsigned queue_length; + } device; + + struct { + uint32_t* data; + unsigned length; + unsigned size; + } buffer; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + return true; + } + + if(name == Audio::Latency) { + if(settings.latency != any_cast(value)) { + settings.latency = any_cast(value); + update_latency(); + } + return true; + } + + return false; + } + + void sample(uint16_t sl, uint16_t sr) { + buffer.data[buffer.length++] = sl + (sr << 16); + if(buffer.length < buffer.size) return; + + ALuint albuffer = 0; + int processed = 0; + while(true) { + alGetSourcei(device.source, AL_BUFFERS_PROCESSED, &processed); + while(processed--) { + alSourceUnqueueBuffers(device.source, 1, &albuffer); + alDeleteBuffers(1, &albuffer); + device.queue_length--; + } + //wait for buffer playback to catch up to sample generation if not synchronizing + if(settings.synchronize == false || device.queue_length < 3) break; + } + + if(device.queue_length < 3) { + alGenBuffers(1, &albuffer); + alBufferData(albuffer, device.format, buffer.data, buffer.size * 4, settings.frequency); + alSourceQueueBuffers(device.source, 1, &albuffer); + device.queue_length++; + } + + ALint playing; + alGetSourcei(device.source, AL_SOURCE_STATE, &playing); + if(playing != AL_PLAYING) alSourcePlay(device.source); + buffer.length = 0; + } + + void clear() { + } + + void update_latency() { + if(buffer.data) delete[] buffer.data; + buffer.size = settings.frequency * settings.latency / 1000.0 + 0.5; + buffer.data = new uint32_t[buffer.size]; + } + + bool init() { + update_latency(); + device.queue_length = 0; + + bool success = false; + if(device.handle = alcOpenDevice(NULL)) { + if(device.context = alcCreateContext(device.handle, NULL)) { + alcMakeContextCurrent(device.context); + alGenSources(1, &device.source); + + //alSourcef (device.source, AL_PITCH, 1.0); + //alSourcef (device.source, AL_GAIN, 1.0); + //alSource3f(device.source, AL_POSITION, 0.0, 0.0, 0.0); + //alSource3f(device.source, AL_VELOCITY, 0.0, 0.0, 0.0); + //alSource3f(device.source, AL_DIRECTION, 0.0, 0.0, 0.0); + //alSourcef (device.source, AL_ROLLOFF_FACTOR, 0.0); + //alSourcei (device.source, AL_SOURCE_RELATIVE, AL_TRUE); + + alListener3f(AL_POSITION, 0.0, 0.0, 0.0); + alListener3f(AL_VELOCITY, 0.0, 0.0, 0.0); + ALfloat listener_orientation[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + alListenerfv(AL_ORIENTATION, listener_orientation); + + success = true; + } + } + + if(success == false) { + term(); + return false; + } + + return true; + } + + void term() { + if(alIsSource(device.source) == AL_TRUE) { + int playing = 0; + alGetSourcei(device.source, AL_SOURCE_STATE, &playing); + if(playing == AL_PLAYING) { + alSourceStop(device.source); + int queued = 0; + alGetSourcei(device.source, AL_BUFFERS_QUEUED, &queued); + while(queued--) { + ALuint albuffer = 0; + alSourceUnqueueBuffers(device.source, 1, &albuffer); + alDeleteBuffers(1, &albuffer); + device.queue_length--; + } + } + + alDeleteSources(1, &device.source); + device.source = 0; + } + + if(device.context) { + alcMakeContextCurrent(NULL); + alcDestroyContext(device.context); + device.context = 0; + } + + if(device.handle) { + alcCloseDevice(device.handle); + device.handle = 0; + } + + if(buffer.data) { + delete[] buffer.data; + buffer.data = 0; + } + } + + pAudioOpenAL() { + device.source = 0; + device.handle = 0; + device.context = 0; + device.format = AL_FORMAT_STEREO16; + device.queue_length = 0; + + buffer.data = 0; + buffer.length = 0; + buffer.size = 0; + + settings.synchronize = true; + settings.frequency = 22050; + settings.latency = 40; + } + + ~pAudioOpenAL() { + term(); + } +}; + +DeclareAudio(OpenAL) + +}; diff --git a/src/burner/qt/ruby/audio/oss.cpp b/src/burner/qt/ruby/audio/oss.cpp new file mode 100755 index 000000000..2c77b6aed --- /dev/null +++ b/src/burner/qt/ruby/audio/oss.cpp @@ -0,0 +1,113 @@ +/* + audio.oss (2007-12-26) + author: Nach +*/ + +#include +#include +#include +#include + +//OSS4 soundcard.h includes below SNDCTL defines, but OSS3 does not +//However, OSS4 soundcard.h does not reside in +//Therefore, attempt to manually define SNDCTL values if using OSS3 header +//Note that if the defines below fail to work on any specific platform, one can point soundcard.h +//above to the correct location for OSS4 (usually /usr/lib/oss/include/sys/soundcard.h) +//Failing that, one can disable OSS4 ioctl calls inside init() and remove the below defines + +#ifndef SNDCTL_DSP_COOKEDMODE + #define SNDCTL_DSP_COOKEDMODE _IOW('P', 30, int) +#endif + +#ifndef SNDCTL_DSP_POLICY + #define SNDCTL_DSP_POLICY _IOW('P', 45, int) +#endif + +namespace ruby { + +class pAudioOSS { +public: + struct { + int fd; + int format; + int channels; + const char* name; + } device; + + struct { + unsigned frequency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Frequency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Frequency) return settings.frequency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(device.fd > 0) init(); + return true; + } + + return false; + } + + void sample(uint16_t sl, uint16_t sr) { + uint32_t sample = sl + (sr << 16); + unsigned unused = write(device.fd, &sample, 4); + } + + void clear() { + } + + bool init() { + term(); + + device.fd = open(device.name, O_WRONLY, O_NONBLOCK); + if(device.fd < 0) return false; + + #if 1 //SOUND_VERSION >= 0x040000 + //attempt to enable OSS4-specific features regardless of version + //OSS3 ioctl calls will silently fail, but sound will still work + int cooked = 1, policy = 4; //policy should be 0 - 10, lower = less latency, more CPU usage + ioctl(device.fd, SNDCTL_DSP_COOKEDMODE, &cooked); + ioctl(device.fd, SNDCTL_DSP_POLICY, &policy); + #endif + int freq = settings.frequency; + ioctl(device.fd, SNDCTL_DSP_CHANNELS, &device.channels); + ioctl(device.fd, SNDCTL_DSP_SETFMT, &device.format); + ioctl(device.fd, SNDCTL_DSP_SPEED, &freq); + + return true; + } + + void term() { + if(device.fd > 0) { + close(device.fd); + device.fd = -1; + } + } + + pAudioOSS() { + device.fd = -1; + device.format = AFMT_S16_LE; + device.channels = 2; + device.name = "/dev/dsp"; + + settings.frequency = 22050; + } + + ~pAudioOSS() { + term(); + } +}; + +DeclareAudio(OSS) + +}; diff --git a/src/burner/qt/ruby/audio/pulseaudio.cpp b/src/burner/qt/ruby/audio/pulseaudio.cpp new file mode 100755 index 000000000..e248bccf7 --- /dev/null +++ b/src/burner/qt/ruby/audio/pulseaudio.cpp @@ -0,0 +1,177 @@ +//audio.pulseaudio (2010-01-05) +//author: RedDwarf + +#include + +namespace ruby { + +class pAudioPulseAudio { +public: + struct { + pa_mainloop* mainloop; + pa_context* context; + pa_stream* stream; + pa_sample_spec spec; + pa_buffer_attr buffer_attr; + bool first; + } device; + + struct { + uint32_t* data; + size_t size; + unsigned offset; + } buffer; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(device.stream) { + pa_operation_unref(pa_stream_update_sample_rate(device.stream, settings.frequency, NULL, NULL)); + } + return true; + } + + if(name == Audio::Latency) { + settings.latency = any_cast(value); + if(device.stream) { + device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec); + pa_stream_set_buffer_attr(device.stream, &device.buffer_attr, NULL, NULL); + } + return true; + } + } + + void sample(uint16_t left, uint16_t right) { + pa_stream_begin_write(device.stream, (void**)&buffer.data, &buffer.size); + buffer.data[buffer.offset++] = left + (right << 16); + if((buffer.offset + 1) * pa_frame_size(&device.spec) <= buffer.size) return; + + while(true) { + if(device.first) { + device.first = false; + pa_mainloop_iterate(device.mainloop, 0, NULL); + } else { + pa_mainloop_iterate(device.mainloop, 1, NULL); + } + unsigned length = pa_stream_writable_size(device.stream); + if(length >= buffer.offset * pa_frame_size(&device.spec)) break; + if(settings.synchronize == false) { + buffer.offset = 0; + return; + } + } + + pa_stream_write(device.stream, (const void*)buffer.data, buffer.offset * pa_frame_size(&device.spec), NULL, 0LL, PA_SEEK_RELATIVE); + buffer.data = 0; + buffer.offset = 0; + } + + void clear() { + } + + bool init() { + device.mainloop = pa_mainloop_new(); + + device.context = pa_context_new(pa_mainloop_get_api(device.mainloop), "ruby::pulseaudio"); + pa_context_connect(device.context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_context_state_t cstate; + do { + pa_mainloop_iterate(device.mainloop, 1, NULL); + cstate = pa_context_get_state(device.context); + if(!PA_CONTEXT_IS_GOOD(cstate)) return false; + } while(cstate != PA_CONTEXT_READY); + + device.spec.format = PA_SAMPLE_S16LE; + device.spec.channels = 2; + device.spec.rate = settings.frequency; + device.stream = pa_stream_new(device.context, "audio", &device.spec, NULL); + + device.buffer_attr.maxlength = -1; + device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec); + device.buffer_attr.prebuf = -1; + device.buffer_attr.minreq = -1; + device.buffer_attr.fragsize = -1; + + pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY | PA_STREAM_VARIABLE_RATE); + pa_stream_connect_playback(device.stream, NULL, &device.buffer_attr, flags, NULL, NULL); + + pa_stream_state_t sstate; + do { + pa_mainloop_iterate(device.mainloop, 1, NULL); + sstate = pa_stream_get_state(device.stream); + if(!PA_STREAM_IS_GOOD(sstate)) return false; + } while(sstate != PA_STREAM_READY); + + buffer.size = 960; + buffer.offset = 0; + device.first = true; + + return true; + } + + void term() { + if(buffer.data) { + pa_stream_cancel_write(device.stream); + buffer.data = 0; + } + + if(device.stream) { + pa_stream_disconnect(device.stream); + pa_stream_unref(device.stream); + device.stream = 0; + } + + if(device.context) { + pa_context_disconnect(device.context); + pa_context_unref(device.context); + device.context = 0; + } + + if(device.mainloop) { + pa_mainloop_free(device.mainloop); + device.mainloop = 0; + } + } + + pAudioPulseAudio() { + device.mainloop = 0; + device.context = 0; + device.stream = 0; + buffer.data = 0; + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 60; + } + + ~pAudioPulseAudio() { + term(); + } +}; + +DeclareAudio(PulseAudio) + +} diff --git a/src/burner/qt/ruby/audio/pulseaudiosimple.cpp b/src/burner/qt/ruby/audio/pulseaudiosimple.cpp new file mode 100755 index 000000000..5815c6d70 --- /dev/null +++ b/src/burner/qt/ruby/audio/pulseaudiosimple.cpp @@ -0,0 +1,115 @@ +//audio.pulseaudiosimple (2010-01-05) +//author: byuu + +#include +#include + +namespace ruby { + +class pAudioPulseAudioSimple { +public: + struct { + pa_simple* handle; + pa_sample_spec spec; + } device; + + struct { + uint32_t* data; + unsigned offset; + } buffer; + + struct { + unsigned frequency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Frequency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Frequency) return settings.frequency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(device.handle) init(); + return true; + } + + return false; + } + + void sample(uint16_t left, uint16_t right) { + if(!device.handle) return; + + buffer.data[buffer.offset++] = left + (right << 16); + if(buffer.offset >= 64) { + int error; + pa_simple_write(device.handle, (const void*)buffer.data, buffer.offset * sizeof(uint32_t), &error); + buffer.offset = 0; + } + } + + void clear() { + } + + bool init() { + term(); + + device.spec.format = PA_SAMPLE_S16LE; + device.spec.channels = 2; + device.spec.rate = settings.frequency; + + int error = 0; + device.handle = pa_simple_new( + 0, //default server + "ruby::pulseaudiosimple", //application name + PA_STREAM_PLAYBACK, //direction + 0, //default device + "audio", //stream description + &device.spec, //sample format + 0, //default channel map + 0, //default buffering attributes + &error //error code + ); + if(!device.handle) { + fprintf(stderr, "ruby::pulseaudiosimple failed to initialize - %s\n", pa_strerror(error)); + return false; + } + + buffer.data = new uint32_t[64]; + buffer.offset = 0; + return true; + } + + void term() { + if(device.handle) { + int error; + pa_simple_flush(device.handle, &error); + pa_simple_free(device.handle); + device.handle = nullptr; + } + + if(buffer.data) { + delete[] buffer.data; + buffer.data = nullptr; + } + } + + pAudioPulseAudioSimple() { + device.handle = nullptr; + buffer.data = nullptr; + settings.frequency = 22050; + } + + ~pAudioPulseAudioSimple() { + term(); + } +}; + +DeclareAudio(PulseAudioSimple) + +}; diff --git a/src/burner/qt/ruby/audio/xaudio2.cpp b/src/burner/qt/ruby/audio/xaudio2.cpp new file mode 100755 index 000000000..19add1876 --- /dev/null +++ b/src/burner/qt/ruby/audio/xaudio2.cpp @@ -0,0 +1,198 @@ +/* + audio.xaudio2 (2010-08-14) + author: OV2 +*/ + +#include "xaudio2.hpp" +#include + +namespace ruby { + +class pAudioXAudio2: public IXAudio2VoiceCallback { +public: + IXAudio2* pXAudio2; + IXAudio2MasteringVoice* pMasterVoice; + IXAudio2SourceVoice* pSourceVoice; + + //inherited from IXAudio2VoiceCallback + STDMETHODIMP_(void) OnBufferStart(void* pBufferContext){} + STDMETHODIMP_(void) OnLoopEnd(void* pBufferContext){} + STDMETHODIMP_(void) OnStreamEnd() {} + STDMETHODIMP_(void) OnVoiceError(void* pBufferContext, HRESULT Error) {} + STDMETHODIMP_(void) OnVoiceProcessingPassEnd() {} + STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32 BytesRequired) {} + + struct { + unsigned buffers; + unsigned latency; + + uint32_t* buffer; + unsigned bufferoffset; + + volatile long submitbuffers; + unsigned writebuffer; + } device; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + if(pXAudio2) clear(); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(pXAudio2) init(); + return true; + } + + if(name == Audio::Latency) { + settings.latency = any_cast(value); + if(pXAudio2) init(); + return true; + } + + return false; + } + + void pushbuffer(unsigned bytes, uint32_t* pAudioData) { + XAUDIO2_BUFFER xa2buffer = {0}; + xa2buffer.AudioBytes = bytes; + xa2buffer.pAudioData = reinterpret_cast(pAudioData); + xa2buffer.pContext = 0; + InterlockedIncrement(&device.submitbuffers); + pSourceVoice->SubmitSourceBuffer(&xa2buffer); + } + + void sample(uint16_t left, uint16_t right) { + device.buffer[device.writebuffer * device.latency + device.bufferoffset++] = left + (right << 16); + if(device.bufferoffset < device.latency) return; + device.bufferoffset = 0; + + if(device.submitbuffers == device.buffers - 1) { + if(settings.synchronize == true) { + //wait until there is at least one other free buffer for the next sample + while(device.submitbuffers == device.buffers - 1) { + //Sleep(0); + } + } else { //we need one free buffer for the next sample, so ignore the current contents + return; + } + } + + pushbuffer(device.latency * 4,device.buffer + device.writebuffer * device.latency); + + device.writebuffer = (device.writebuffer + 1) % device.buffers; + } + + void clear() { + if(!pSourceVoice) return; + pSourceVoice->Stop(0); + pSourceVoice->FlushSourceBuffers(); //calls OnBufferEnd for all currently submitted buffers + + device.writebuffer = 0; + + device.bufferoffset = 0; + if(device.buffer) memset(device.buffer, 0, device.latency * device.buffers * 4); + + pSourceVoice->Start(0); + } + + bool init() { + term(); + + device.buffers = 8; + device.latency = settings.frequency * settings.latency / device.buffers / 1000.0 + 0.5; + device.buffer = new uint32_t[device.latency * device.buffers]; + device.bufferoffset = 0; + device.submitbuffers = 0; + + HRESULT hr; + if(FAILED(hr = XAudio2Create(&pXAudio2, 0 , XAUDIO2_DEFAULT_PROCESSOR))) { + return false; + } + + if(FAILED(hr = pXAudio2->CreateMasteringVoice( &pMasterVoice, 2, settings.frequency, 0, 0 , NULL))) { + return false; + } + + WAVEFORMATEX wfx; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = settings.frequency; + wfx.nBlockAlign = 4; + wfx.wBitsPerSample = 16; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + wfx.cbSize = 0; + + if(FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&wfx, XAUDIO2_VOICE_NOSRC , XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL))) { + return false; + } + + clear(); + return true; + } + + void term() { + if(pSourceVoice) { + pSourceVoice->Stop(0); + pSourceVoice->DestroyVoice(); + pSourceVoice = nullptr; + } + if(pMasterVoice) { + pMasterVoice->DestroyVoice(); + pMasterVoice = nullptr; + } + if(pXAudio2) { + pXAudio2->Release(); + pXAudio2 = nullptr; + } + if(device.buffer) { + delete[] device.buffer; + device.buffer = nullptr; + } + } + + STDMETHODIMP_(void) OnBufferEnd(void* pBufferContext) { + InterlockedDecrement(&device.submitbuffers); + } + + pAudioXAudio2() { + pXAudio2 = nullptr; + pMasterVoice = nullptr; + pSourceVoice = nullptr; + + device.buffer = nullptr; + device.bufferoffset = 0; + device.submitbuffers = 0; + device.writebuffer = 0; + + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 120; + } +}; + +DeclareAudio(XAudio2) + +}; diff --git a/src/burner/qt/ruby/audio/xaudio2.hpp b/src/burner/qt/ruby/audio/xaudio2.hpp new file mode 100755 index 000000000..e283f5038 --- /dev/null +++ b/src/burner/qt/ruby/audio/xaudio2.hpp @@ -0,0 +1,340 @@ +/* + xaudio2.hpp (2010-08-14) + author: OV2 + + ruby-specific header to provide mingw-friendly xaudio2 interfaces +*/ + +#ifndef XAUDIO2_RUBY_H +#define XAUDIO2_RUBY_H + +//64-bit GCC fix +#define GUID_EXT EXTERN_C +#define GUID_SECT + +#include + +#define DEFINE_GUID_X(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) GUID_EXT const GUID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} +#define DEFINE_CLSID_X(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID_X(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) +#define DEFINE_IID_X(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID_X(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) +#define X2DEFAULT(x) =x + +DEFINE_CLSID_X(XAudio2, e21a7345, eb21, 468e, be, 50, 80, 4d, b9, 7c, f7, 08); +DEFINE_CLSID_X(XAudio2_Debug, f7a76c21, 53d4, 46bb, ac, 53, 8b, 45, 9c, ae, 46, bd); +DEFINE_IID_X(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb); + +DECLARE_INTERFACE(IXAudio2Voice); + +#define XAUDIO2_COMMIT_NOW 0 +#define XAUDIO2_DEFAULT_CHANNELS 0 +#define XAUDIO2_DEFAULT_SAMPLERATE 0 +#define XAUDIO2_DEFAULT_FREQ_RATIO 4.0f +#define XAUDIO2_DEBUG_ENGINE 0x0001 +#define XAUDIO2_VOICE_NOSRC 0x0004 + +typedef struct +{ + WAVEFORMATEX Format; + union + { + WORD wValidBitsPerSample; + WORD wSamplesPerBlock; + WORD wReserved; + } Samples; + DWORD dwChannelMask; + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE, *LPPWAVEFORMATEXTENSIBLE; +typedef const WAVEFORMATEXTENSIBLE* LPCWAVEFORMATEXTENSIBLE; + +typedef enum XAUDIO2_DEVICE_ROLE +{ + NotDefaultDevice = 0x0, + DefaultConsoleDevice = 0x1, + DefaultMultimediaDevice = 0x2, + DefaultCommunicationsDevice = 0x4, + DefaultGameDevice = 0x8, + GlobalDefaultDevice = 0xf, + InvalidDeviceRole = ~GlobalDefaultDevice +} XAUDIO2_DEVICE_ROLE; + +typedef struct XAUDIO2_DEVICE_DETAILS +{ + WCHAR DeviceID[256]; + WCHAR DisplayName[256]; + XAUDIO2_DEVICE_ROLE Role; + WAVEFORMATEXTENSIBLE OutputFormat; +} XAUDIO2_DEVICE_DETAILS; + +typedef struct XAUDIO2_VOICE_DETAILS +{ + UINT32 CreationFlags; + UINT32 InputChannels; + UINT32 InputSampleRate; +} XAUDIO2_VOICE_DETAILS; + +typedef enum XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER +{ + Processor1 = 0x00000001, + Processor2 = 0x00000002, + Processor3 = 0x00000004, + Processor4 = 0x00000008, + Processor5 = 0x00000010, + Processor6 = 0x00000020, + Processor7 = 0x00000040, + Processor8 = 0x00000080, + Processor9 = 0x00000100, + Processor10 = 0x00000200, + Processor11 = 0x00000400, + Processor12 = 0x00000800, + Processor13 = 0x00001000, + Processor14 = 0x00002000, + Processor15 = 0x00004000, + Processor16 = 0x00008000, + Processor17 = 0x00010000, + Processor18 = 0x00020000, + Processor19 = 0x00040000, + Processor20 = 0x00080000, + Processor21 = 0x00100000, + Processor22 = 0x00200000, + Processor23 = 0x00400000, + Processor24 = 0x00800000, + Processor25 = 0x01000000, + Processor26 = 0x02000000, + Processor27 = 0x04000000, + Processor28 = 0x08000000, + Processor29 = 0x10000000, + Processor30 = 0x20000000, + Processor31 = 0x40000000, + Processor32 = 0x80000000, + XAUDIO2_ANY_PROCESSOR = 0xffffffff, + XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR +} XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER, XAUDIO2_PROCESSOR; + +typedef struct XAUDIO2_VOICE_SENDS +{ + UINT32 OutputCount; + IXAudio2Voice** pOutputVoices; +} XAUDIO2_VOICE_SENDS; + +typedef struct XAUDIO2_EFFECT_DESCRIPTOR +{ + IUnknown* pEffect; + BOOL InitialState; + UINT32 OutputChannels; +} XAUDIO2_EFFECT_DESCRIPTOR; + +typedef struct XAUDIO2_EFFECT_CHAIN +{ + UINT32 EffectCount; + const XAUDIO2_EFFECT_DESCRIPTOR* pEffectDescriptors; +} XAUDIO2_EFFECT_CHAIN; + +typedef enum XAUDIO2_FILTER_TYPE +{ + LowPassFilter, + BandPassFilter, + HighPassFilter +} XAUDIO2_FILTER_TYPE; + +typedef struct XAUDIO2_FILTER_PARAMETERS +{ + XAUDIO2_FILTER_TYPE Type; + float Frequency; + float OneOverQ; + +} XAUDIO2_FILTER_PARAMETERS; + +typedef struct XAUDIO2_BUFFER +{ + UINT32 Flags; + UINT32 AudioBytes; + const BYTE* pAudioData; + UINT32 PlayBegin; + UINT32 PlayLength; + UINT32 LoopBegin; + UINT32 LoopLength; + UINT32 LoopCount; + void* pContext; +} XAUDIO2_BUFFER; + +typedef struct XAUDIO2_BUFFER_WMA +{ + const UINT32* pDecodedPacketCumulativeBytes; + UINT32 PacketCount; +} XAUDIO2_BUFFER_WMA; + +typedef struct XAUDIO2_VOICE_STATE +{ + void* pCurrentBufferContext; + UINT32 BuffersQueued; + UINT64 SamplesPlayed; +} XAUDIO2_VOICE_STATE; + +typedef struct XAUDIO2_PERFORMANCE_DATA +{ + UINT64 AudioCyclesSinceLastQuery; + UINT64 TotalCyclesSinceLastQuery; + UINT32 MinimumCyclesPerQuantum; + UINT32 MaximumCyclesPerQuantum; + UINT32 MemoryUsageInBytes; + UINT32 CurrentLatencyInSamples; + UINT32 GlitchesSinceEngineStarted; + UINT32 ActiveSourceVoiceCount; + UINT32 TotalSourceVoiceCount; + UINT32 ActiveSubmixVoiceCount; + UINT32 TotalSubmixVoiceCount; + UINT32 ActiveXmaSourceVoices; + UINT32 ActiveXmaStreams; +} XAUDIO2_PERFORMANCE_DATA; + +typedef struct XAUDIO2_DEBUG_CONFIGURATION +{ + UINT32 TraceMask; + UINT32 BreakMask; + BOOL LogThreadID; + BOOL LogFileline; + BOOL LogFunctionName; + BOOL LogTiming; +} XAUDIO2_DEBUG_CONFIGURATION; + +DECLARE_INTERFACE(IXAudio2EngineCallback) +{ + STDMETHOD_(void, OnProcessingPassStart) (THIS) PURE; + STDMETHOD_(void, OnProcessingPassEnd) (THIS) PURE; + STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT Error) PURE; +}; + +DECLARE_INTERFACE(IXAudio2VoiceCallback) +{ + STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired) PURE; + STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS) PURE; + STDMETHOD_(void, OnStreamEnd) (THIS) PURE; + STDMETHOD_(void, OnBufferStart) (THIS_ void* pBufferContext) PURE; + STDMETHOD_(void, OnBufferEnd) (THIS_ void* pBufferContext) PURE; + STDMETHOD_(void, OnLoopEnd) (THIS_ void* pBufferContext) PURE; + STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) PURE; +}; + +DECLARE_INTERFACE(IXAudio2Voice) +{ + #define Declare_IXAudio2Voice_Methods() \ + STDMETHOD_(void, GetVoiceDetails) (THIS_ XAUDIO2_VOICE_DETAILS* pVoiceDetails) PURE; \ + STDMETHOD(SetOutputVoices) (THIS_ const XAUDIO2_VOICE_SENDS* pSendList) PURE; \ + STDMETHOD(SetEffectChain) (THIS_ const XAUDIO2_EFFECT_CHAIN* pEffectChain) PURE; \ + STDMETHOD(EnableEffect) (THIS_ UINT32 EffectIndex, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD(DisableEffect) (THIS_ UINT32 EffectIndex, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetEffectState) (THIS_ UINT32 EffectIndex, BOOL* pEnabled) PURE; \ + STDMETHOD(SetEffectParameters) (THIS_ UINT32 EffectIndex, \ + const void* pParameters, \ + UINT32 ParametersByteSize, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD(GetEffectParameters) (THIS_ UINT32 EffectIndex, void* pParameters, \ + UINT32 ParametersByteSize) PURE; \ + STDMETHOD(SetFilterParameters) (THIS_ const XAUDIO2_FILTER_PARAMETERS* pParameters, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetFilterParameters) (THIS_ XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \ + STDMETHOD(SetVolume) (THIS_ float Volume, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetVolume) (THIS_ float* pVolume) PURE; \ + STDMETHOD(SetChannelVolumes) (THIS_ UINT32 Channels, const float* pVolumes, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetChannelVolumes) (THIS_ UINT32 Channels, float* pVolumes) PURE; \ + STDMETHOD(SetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \ + UINT32 SourceChannels, UINT32 DestinationChannels, \ + const float* pLevelMatrix, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \ + UINT32 SourceChannels, UINT32 DestinationChannels, \ + float* pLevelMatrix) PURE; \ + STDMETHOD_(void, DestroyVoice) (THIS) PURE + + Declare_IXAudio2Voice_Methods(); +}; + + +DECLARE_INTERFACE_(IXAudio2MasteringVoice, IXAudio2Voice) +{ + Declare_IXAudio2Voice_Methods(); +}; + +DECLARE_INTERFACE_(IXAudio2SubmixVoice, IXAudio2Voice) +{ + Declare_IXAudio2Voice_Methods(); +}; + +DECLARE_INTERFACE_(IXAudio2SourceVoice, IXAudio2Voice) +{ + Declare_IXAudio2Voice_Methods(); + STDMETHOD(Start) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD(Stop) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD(SubmitSourceBuffer) (THIS_ const XAUDIO2_BUFFER* pBuffer, const XAUDIO2_BUFFER_WMA* pBufferWMA X2DEFAULT(NULL)) PURE; + STDMETHOD(FlushSourceBuffers) (THIS) PURE; + STDMETHOD(Discontinuity) (THIS) PURE; + STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD_(void, GetState) (THIS_ XAUDIO2_VOICE_STATE* pVoiceState) PURE; + STDMETHOD(SetFrequencyRatio) (THIS_ float Ratio, + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD_(void, GetFrequencyRatio) (THIS_ float* pRatio) PURE; +}; + +DECLARE_INTERFACE_(IXAudio2, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvInterface) PURE; + STDMETHOD_(ULONG, AddRef) (THIS) PURE; + STDMETHOD_(ULONG, Release) (THIS) PURE; + STDMETHOD(GetDeviceCount) (THIS_ UINT32* pCount) PURE; + STDMETHOD(GetDeviceDetails) (THIS_ UINT32 Index, XAUDIO2_DEVICE_DETAILS* pDeviceDetails) PURE; + STDMETHOD(Initialize) (THIS_ UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) PURE; + STDMETHOD(RegisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE; + STDMETHOD_(void, UnregisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE; + STDMETHOD(CreateSourceVoice) (THIS_ IXAudio2SourceVoice** ppSourceVoice, + const WAVEFORMATEX* pSourceFormat, + UINT32 Flags X2DEFAULT(0), + float MaxFrequencyRatio X2DEFAULT(XAUDIO2_DEFAULT_FREQ_RATIO), + IXAudio2VoiceCallback* pCallback X2DEFAULT(NULL), + const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), + const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + STDMETHOD(CreateSubmixVoice) (THIS_ IXAudio2SubmixVoice** ppSubmixVoice, + UINT32 InputChannels, UINT32 InputSampleRate, + UINT32 Flags X2DEFAULT(0), UINT32 ProcessingStage X2DEFAULT(0), + const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), + const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + STDMETHOD(CreateMasteringVoice) (THIS_ IXAudio2MasteringVoice** ppMasteringVoice, + UINT32 InputChannels X2DEFAULT(XAUDIO2_DEFAULT_CHANNELS), + UINT32 InputSampleRate X2DEFAULT(XAUDIO2_DEFAULT_SAMPLERATE), + UINT32 Flags X2DEFAULT(0), UINT32 DeviceIndex X2DEFAULT(0), + const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + STDMETHOD(StartEngine) (THIS) PURE; + STDMETHOD_(void, StopEngine) (THIS) PURE; + STDMETHOD(CommitChanges) (THIS_ UINT32 OperationSet) PURE; + STDMETHOD_(void, GetPerformanceData) (THIS_ XAUDIO2_PERFORMANCE_DATA* pPerfData) PURE; + STDMETHOD_(void, SetDebugConfiguration) (THIS_ const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration, + void* pReserved X2DEFAULT(NULL)) PURE; +}; + +__inline HRESULT XAudio2Create(IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) +{ + IXAudio2* pXAudio2; + HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? CLSID_XAudio2_Debug : CLSID_XAudio2, + NULL, CLSCTX_INPROC_SERVER, IID_IXAudio2, (void**)&pXAudio2); + if (SUCCEEDED(hr)) + { + hr = pXAudio2->Initialize(Flags, XAudio2Processor); + if (SUCCEEDED(hr)) + { + *ppXAudio2 = pXAudio2; + } + else + { + pXAudio2->Release(); + } + } + return hr; +} +#endif diff --git a/src/burner/qt/ruby/implementation.cpp b/src/burner/qt/ruby/implementation.cpp new file mode 100755 index 000000000..f33cb33b6 --- /dev/null +++ b/src/burner/qt/ruby/implementation.cpp @@ -0,0 +1,172 @@ +/* Global Headers */ + +#if defined(PLATFORM_X) + #include + #include + #include +#elif defined(PLATFORM_MACOSX) + #define decimal CocoaDecimal + #include + #include + #undef decimal +#elif defined(PLATFORM_WINDOWS) + #define _WIN32_WINNT 0x0501 + #include +#endif + +using namespace nall; + +/* Video */ + +#define DeclareVideo(Name) \ + struct Video##Name : Video { \ + bool cap(const string& name) { return p.cap(name); } \ + any get(const string& name) { return p.get(name); } \ + bool set(const string& name, const any& value) { return p.set(name, value); } \ + \ + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { return p.lock(data, pitch, width, height); } \ + void unlock() { p.unlock(); } \ + \ + void clear() { p.clear(); } \ + void refresh() { p.refresh(); } \ + bool init() { return p.init(); } \ + void term() { p.term(); } \ + \ + Video##Name() : p(*new pVideo##Name) {} \ + ~Video##Name() { delete &p; } \ + \ + private: \ + pVideo##Name& p; \ + }; + +#ifdef VIDEO_CGL + #include +#endif + +#ifdef VIDEO_DIRECT3D + #include +#endif + +#ifdef VIDEO_DIRECTDRAW + #include +#endif + +#ifdef VIDEO_GDI + #include +#endif + +#ifdef VIDEO_GLX + #include +#endif + +#ifdef VIDEO_SDL + #include +#endif + +#ifdef VIDEO_WGL + #include +#endif + +#ifdef VIDEO_XSHM + #include +#endif + +#ifdef VIDEO_XV + #include +#endif + +/* Audio */ + +#define DeclareAudio(Name) \ + struct Audio##Name : Audio { \ + bool cap(const string& name) { return p.cap(name); } \ + any get(const string& name) { return p.get(name); } \ + bool set(const string& name, const any& value) { return p.set(name, value); } \ + \ + void sample(uint16_t left, uint16_t right) { p.sample(left, right); } \ + void clear() { p.clear(); } \ + bool init() { return p.init(); } \ + void term() { p.term(); } \ + \ + Audio##Name() : p(*new pAudio##Name) {} \ + ~Audio##Name() { delete &p; } \ + \ + private: \ + pAudio##Name& p; \ + }; + +#ifdef AUDIO_ALSA + #include +#endif + +#ifdef AUDIO_AO + #include +#endif + +#ifdef AUDIO_DIRECTSOUND + #include +#endif + +#ifdef AUDIO_OPENAL + #include +#endif + +#ifdef AUDIO_OSS + #include +#endif + +#ifdef AUDIO_PULSEAUDIO + #include +#endif + +#ifdef AUDIO_PULSEAUDIOSIMPLE + #include +#endif + +#ifdef AUDIO_XAUDIO2 + #include +#endif + +/* Input */ + +#define DeclareInput(Name) \ + struct Input##Name : Input { \ + bool cap(const string& name) { return p.cap(name); } \ + any get(const string& name) { return p.get(name); } \ + bool set(const string& name, const any& value) { return p.set(name, value); } \ + \ + bool acquire() { return p.acquire(); } \ + bool unacquire() { return p.unacquire(); } \ + bool acquired() { return p.acquired(); } \ + \ + vector poll() { return p.poll(); } \ + bool rumble(uint64_t id, bool enable) { return p.rumble(id, enable); } \ + bool init() { return p.init(); } \ + void term() { p.term(); } \ + \ + Input##Name() : p(*new pInput##Name) {} \ + ~Input##Name() { delete &p; } \ + \ + private: \ + pInput##Name& p; \ + }; + +#ifdef INPUT_CARBON + #include +#endif + +#ifdef INPUT_SDL + #include +#endif + +#ifdef INPUT_UDEV + #include +#endif + +#ifdef INPUT_WINDOWS + #include +#endif + +#ifdef INPUT_XLIB + #include +#endif diff --git a/src/burner/qt/ruby/input.hpp b/src/burner/qt/ruby/input.hpp new file mode 100755 index 000000000..2582ccd28 --- /dev/null +++ b/src/burner/qt/ruby/input.hpp @@ -0,0 +1,23 @@ +struct Input { + static const char* Handle; + static const char* KeyboardSupport; + static const char* MouseSupport; + static const char* JoypadSupport; + static const char* JoypadRumbleSupport; + + virtual bool cap(const nall::string& name) { return false; } + virtual nall::any get(const nall::string& name) { return false; } + virtual bool set(const nall::string& name, const nall::any& value) { return false; } + + virtual bool acquire() { return false; } + virtual bool unacquire() { return false; } + virtual bool acquired() { return false; } + + virtual nall::vector poll() { return {}; } + virtual bool rumble(uint64_t id, bool enable) {} + virtual bool init() { return true; } + virtual void term() {} + + Input() {} + virtual ~Input() {} +}; diff --git a/src/burner/qt/ruby/input/carbon.cpp b/src/burner/qt/ruby/input/carbon.cpp new file mode 100755 index 000000000..2ca5f55e4 --- /dev/null +++ b/src/burner/qt/ruby/input/carbon.cpp @@ -0,0 +1,182 @@ +namespace ruby { + +struct pInputCarbon { + struct Key { + uint8_t id; + string name; + }; + vector keys; + + struct Keyboard { + HID::Keyboard hid; + } kb; + + bool cap(const string& name) { + if(name == Input::KeyboardSupport) return true; + return false; + } + + any get(const string& name) { + return false; + } + + bool set(const string& name, const any& value) { + return false; + } + + bool acquire() { return false; } + bool unacquire() { return false; } + bool acquired() { return false; } + + void assign(HID::Device& hid, unsigned groupID, unsigned inputID, int16_t value) { + auto& group = hid.group[groupID]; + if(group.input[inputID].value == value) return; + if(input.onChange) input.onChange(hid, groupID, inputID, group.input[inputID].value, value); + group.input[inputID].value = value; + } + + void poll(vector& devices) { + KeyMap keymap; + GetKeys(keymap); + uint8_t* buffer = (uint8_t*)keymap; + + unsigned inputID = 0; + for(auto& key : keys) { + bool value = buffer[key.id >> 3] & (1 << (key.id & 7))); + assign(kb.hid, HID::Keyboard::GroupID::Button, inputID++, value); + } + + devices.append(&kb.hid); + } + + bool rumble(uint64_t id, bool enable) { + return false; + } + + bool init() { + keys.append({0x35, "Escape"}); + keys.append({0x7a, "F1"}); + keys.append({0x78, "F2"}); + keys.append({0x63, "F3"}); + keys.append({0x76, "F4"}); + keys.append({0x60, "F5"}); + keys.append({0x61, "F6"}); + keys.append({0x62, "F7"}); + keys.append({0x64, "F8"}); + keys.append({0x65, "F9"}); + keys.append({0x6d, "F10"}); + keys.append({0x67, "F11"}); + //keys.append({0x??, "F12"}); + + keys.append({0x69, "PrintScreen"}); + //keys.append({0x??, "ScrollLock"}); + keys.append({0x71, "Pause"}); + + keys.append({0x32, "Tilde"}); + keys.append({0x12, "Num1"}); + keys.append({0x13, "Num2"}); + keys.append({0x14, "Num3"}); + keys.append({0x15, "Num4"}); + keys.append({0x17, "Num5"}); + keys.append({0x16, "Num6"}); + keys.append({0x1a, "Num7"}); + keys.append({0x1c, "Num8"}); + keys.append({0x19, "Num9"}); + keys.append({0x1d, "Num0"}); + + keys.append({0x1b, "Dash"}); + keys.append({0x18, "Equal"}); + keys.append({0x33, "Backspace"}); + + keys.append({0x72, "Insert"}); + keys.append({0x75, "Delete"}); + keys.append({0x73, "Home"}); + keys.append({0x77, "End"}); + keys.append({0x74, "PageUp"}); + keys.append({0x79, "PageDown"}); + + keys.append({0x00, "A"}); + keys.append({0x0b, "B"}); + keys.append({0x08, "C"}); + keys.append({0x02, "D"}); + keys.append({0x0e, "E"}); + keys.append({0x03, "F"}); + keys.append({0x05, "G"}); + keys.append({0x04, "H"}); + keys.append({0x22, "I"}); + keys.append({0x26, "J"}); + keys.append({0x28, "K"}); + keys.append({0x25, "L"}); + keys.append({0x2e, "M"}); + keys.append({0x2d, "N"}); + keys.append({0x1f, "O"}); + keys.append({0x23, "P"}); + keys.append({0x0c, "Q"}); + keys.append({0x0f, "R"}); + keys.append({0x01, "S"}); + keys.append({0x11, "T"}); + keys.append({0x20, "U"}); + keys.append({0x09, "V"}); + keys.append({0x0d, "W"}); + keys.append({0x07, "X"}); + keys.append({0x10, "Y"}); + keys.append({0x06, "Z"}); + + keys.append({0x21, "LeftBracket"}); + keys.append({0x1e, "RightBracket"}); + keys.append({0x2a, "Backslash"}); + keys.append({0x29, "Semicolon"}); + keys.append({0x27, "Apostrophe"}); + keys.append({0x2b, "Comma"}); + keys.append({0x2f, "Period"}); + keys.append({0x2c, "Slash"}); + + keys.append({0x53, "Keypad1"}); + keys.append({0x54, "Keypad2"}); + keys.append({0x55, "Keypad3"}); + keys.append({0x56, "Keypad4"}); + keys.append({0x57, "Keypad5"}); + keys.append({0x58, "Keypad6"}); + keys.append({0x59, "Keypad7"}); + keys.append({0x5b, "Keypad8"}); + keys.append({0x5c, "Keypad9"}); + keys.append({0x52, "Keypad0"}); + + //keys.append({0x??, "Point"}); + keys.append({0x4c, "Enter"}); + keys.append({0x45, "Add"}); + keys.append({0x4e, "Subtract"}); + keys.append({0x43, "Multiply"}); + keys.append({0x4b, "Divide"}); + + keys.append({0x47, "NumLock"}); + //keys.append({0x39, "CapsLock"}); + + keys.append({0x7e, "Up"}); + keys.append({0x7d, "Down"}); + keys.append({0x7b, "Left"}); + keys.append({0x7c, "Right"}); + + keys.append({0x30, "Tab"}); + keys.append({0x24, "Return"}); + keys.append({0x31, "Spacebar"}); + //keys.append({0x??, "Menu"}); + + keys.append({0x38, "Shift"}); + keys.append({0x3b, "Control"}); + keys.append({0x3a, "Alt"}); + keys.append({0x37, "Super"}); + + kb.hid.id = 1; + for(auto& key : keys) kb.hid.button().append({key.name}); + + return true; + } + + void term() { + } +}; + +DeclareInput(Carbon) + +}; diff --git a/src/burner/qt/ruby/input/joypad/directinput.cpp b/src/burner/qt/ruby/input/joypad/directinput.cpp new file mode 100755 index 000000000..d88378952 --- /dev/null +++ b/src/burner/qt/ruby/input/joypad/directinput.cpp @@ -0,0 +1,211 @@ +#ifndef RUBY_INPUT_JOYPAD_DIRECTINPUT +#define RUBY_INPUT_JOYPAD_DIRECTINPUT + +namespace ruby { + +BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p); +BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p); +BOOL CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p); + +struct InputJoypadDirectInput { + struct Joypad { + HID::Joypad hid; + + LPDIRECTINPUTDEVICE8 device = nullptr; + LPDIRECTINPUTEFFECT effect = nullptr; + + uint32_t pathID = 0; + uint16_t vendorID = 0; + uint16_t productID = 0; + bool isXInputDevice = false; + }; + vector joypads; + + uintptr_t handle = 0; + LPDIRECTINPUT8 context = nullptr; + LPDIRECTINPUTDEVICE8 device = nullptr; + bool xinputAvailable = false; + unsigned effects = 0; + + void assign(HID::Joypad& hid, unsigned groupID, unsigned inputID, int16_t value) { + auto& group = hid.group[groupID]; + if(group.input[inputID].value == value) return; + if(input.onChange) input.onChange(hid, groupID, inputID, group.input[inputID].value, value); + group.input[inputID].value = value; + } + + void poll(vector& devices) { + for(auto& jp : joypads) { + if(FAILED(jp.device->Poll())) jp.device->Acquire(); + + DIJOYSTATE2 state; + if(FAILED(jp.device->GetDeviceState(sizeof(DIJOYSTATE2), &state))) continue; + + for(unsigned n = 0; n < 4; n++) { + assign(jp.hid, HID::Joypad::GroupID::Axis, 0, state.lX); + assign(jp.hid, HID::Joypad::GroupID::Axis, 1, state.lY); + assign(jp.hid, HID::Joypad::GroupID::Axis, 2, state.lZ); + assign(jp.hid, HID::Joypad::GroupID::Axis, 3, state.lRx); + assign(jp.hid, HID::Joypad::GroupID::Axis, 4, state.lRy); + assign(jp.hid, HID::Joypad::GroupID::Axis, 5, state.lRz); + + unsigned pov = state.rgdwPOV[n]; + int16_t xaxis = 0; + int16_t yaxis = 0; + + if(pov < 36000) { + if(pov >= 31500 || pov <= 4500) yaxis = -32768; + if(pov >= 4500 && pov <= 13500) xaxis = +32767; + if(pov >= 13500 && pov <= 22500) yaxis = +32767; + if(pov >= 22500 && pov <= 31500) xaxis = -32768; + } + + assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 0, xaxis); + assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 1, yaxis); + } + + for(unsigned n = 0; n < 128; n++) { + assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)state.rgbButtons[n]); + } + + devices.append(&jp.hid); + } + } + + bool rumble(uint64_t id, bool enable) { + for(auto& jp : joypads) { + if(jp.hid.id != id) continue; + if(jp.effect == nullptr) continue; + + if(enable) jp.effect->Start(1, 0); + else jp.effect->Stop(); + return true; + } + + return false; + } + + bool init(uintptr_t handle, LPDIRECTINPUT8 context, bool xinputAvailable) { + this->handle = handle; + this->context = context; + this->xinputAvailable = xinputAvailable; + context->EnumDevices(DI8DEVCLASS_GAMECTRL, DirectInput_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY); + return true; + } + + void term() { + for(auto& jp : joypads) { + jp.device->Unacquire(); + if(jp.effect) jp.effect->Release(); + jp.device->Release(); + } + joypads.reset(); + context = nullptr; + } + + bool initJoypad(const DIDEVICEINSTANCE* instance) { + Joypad jp; + jp.vendorID = instance->guidProduct.Data1 >> 0; + jp.productID = instance->guidProduct.Data1 >> 16; + if(auto device = rawinput.find(jp.vendorID, jp.productID)) { + jp.pathID = crc32_calculate((const uint8_t*)device().path.data(), device().path.size()); + jp.hid.id = (uint64_t)jp.pathID << 32 | jp.vendorID << 16 | jp.productID << 0; + jp.isXInputDevice = device().isXInputDevice; + } else { + //this should never occur + return DIENUM_CONTINUE; + } + + //Microsoft has intentionally imposed artificial restrictions on XInput devices when used with DirectInput + //a) the two triggers are merged into a single axis, making uniquely distinguishing them impossible + //b) rumble support is not exposed + //thus, it's always preferred to let the XInput driver handle these joypads + //but if the driver is not available (XInput 1.3 does not ship with stock Windows XP), fall back on DirectInput + if(jp.isXInputDevice && xinputAvailable) return DIENUM_CONTINUE; + + if(FAILED(context->CreateDevice(instance->guidInstance, &device, 0))) return DIENUM_CONTINUE; + jp.device = device; + device->SetDataFormat(&c_dfDIJoystick2); + device->SetCooperativeLevel((HWND)handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + + effects = 0; + device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS); + device->EnumObjects(DirectInput_EnumJoypadEffectsCallback, (void*)this, DIDFT_FFACTUATOR); + jp.hid.rumble = effects > 0; + + if(jp.hid.rumble) { + //disable auto-centering spring for rumble support + DIPROPDWORD property; + memset(&property, 0, sizeof(DIPROPDWORD)); + property.diph.dwSize = sizeof(DIPROPDWORD); + property.diph.dwHeaderSize = sizeof(DIPROPHEADER); + property.diph.dwObj = 0; + property.diph.dwHow = DIPH_DEVICE; + property.dwData = false; + device->SetProperty(DIPROP_AUTOCENTER, &property.diph); + + DWORD dwAxes[2] = {DIJOFS_X, DIJOFS_Y}; + LONG lDirection[2] = {0, 0}; + DICONSTANTFORCE force; + force.lMagnitude = DI_FFNOMINALMAX; //full force + DIEFFECT effect; + memset(&effect, 0, sizeof(DIEFFECT)); + effect.dwSize = sizeof(DIEFFECT); + effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + effect.dwDuration = INFINITE; + effect.dwSamplePeriod = 0; + effect.dwGain = DI_FFNOMINALMAX; + effect.dwTriggerButton = DIEB_NOTRIGGER; + effect.dwTriggerRepeatInterval = 0; + effect.cAxes = 2; + effect.rgdwAxes = dwAxes; + effect.rglDirection = lDirection; + effect.lpEnvelope = 0; + effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); + effect.lpvTypeSpecificParams = &force; + effect.dwStartDelay = 0; + device->CreateEffect(GUID_ConstantForce, &effect, &jp.effect, NULL); + } + + for(unsigned n = 0; n < 6; n++) jp.hid.axis().append({n}); + for(unsigned n = 0; n < 8; n++) jp.hid.hat().append({n}); + for(unsigned n = 0; n < 128; n++) jp.hid.button().append({n}); + joypads.append(jp); + + return DIENUM_CONTINUE; + } + + bool initAxis(const DIDEVICEOBJECTINSTANCE* instance) { + DIPROPRANGE range; + memset(&range, 0, sizeof(DIPROPRANGE)); + range.diph.dwSize = sizeof(DIPROPRANGE); + range.diph.dwHeaderSize = sizeof(DIPROPHEADER); + range.diph.dwHow = DIPH_BYID; + range.diph.dwObj = instance->dwType; + range.lMin = -32768; + range.lMax = +32767; + device->SetProperty(DIPROP_RANGE, &range.diph); + return DIENUM_CONTINUE; + } + + bool initEffect(const DIDEVICEOBJECTINSTANCE* instance) { + effects++; + return DIENUM_CONTINUE; + } +}; + +BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) { + return ((InputJoypadDirectInput*)p)->initJoypad(instance); +} + +BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) { + return ((InputJoypadDirectInput*)p)->initAxis(instance); +} + +BOOL CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) { + return ((InputJoypadDirectInput*)p)->initEffect(instance); +} + +} + +#endif diff --git a/src/burner/qt/ruby/input/joypad/sdl.cpp b/src/burner/qt/ruby/input/joypad/sdl.cpp new file mode 100755 index 000000000..fbe1d89f3 --- /dev/null +++ b/src/burner/qt/ruby/input/joypad/sdl.cpp @@ -0,0 +1,81 @@ +#ifndef RUBY_INPUT_JOYPAD_SDL +#define RUBY_INPUT_JOYPAD_SDL + +namespace ruby { + +struct InputJoypadSDL { + struct Joypad { + HID::Joypad hid; + + unsigned id = 0; + SDL_Joystick* handle = nullptr; + }; + vector joypads; + + void assign(HID::Joypad& hid, unsigned groupID, unsigned inputID, int16_t value) { + auto& group = hid.group[groupID]; + if(group.input[inputID].value == value) return; + if(input.onChange) input.onChange(hid, groupID, inputID, group.input[inputID].value, value); + group.input[inputID].value = value; + } + + void poll(vector& devices) { + SDL_JoystickUpdate(); + + for(auto& jp : joypads) { + for(unsigned n = 0; n < jp.hid.axis().input.size(); n++) { + assign(jp.hid, HID::Joypad::GroupID::Axis, n, (int16_t)SDL_JoystickGetAxis(jp.handle, n)); + } + + for(signed n = 0; n < (signed)jp.hid.hat().input.size() - 1; n += 2) { + uint8_t state = SDL_JoystickGetHat(jp.handle, n >> 1); + assign(jp.hid, HID::Joypad::GroupID::Hat, n + 0, state & SDL_HAT_LEFT ? -32768 : state & SDL_HAT_RIGHT ? +32767 : 0); + assign(jp.hid, HID::Joypad::GroupID::Hat, n + 1, state & SDL_HAT_UP ? -32768 : state & SDL_HAT_DOWN ? +32767 : 0); + } + + for(unsigned n = 0; n < jp.hid.button().input.size(); n++) { + assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)SDL_JoystickGetButton(jp.handle, n)); + } + + devices.append(&jp.hid); + } + } + + bool init() { + SDL_InitSubSystem(SDL_INIT_JOYSTICK); + SDL_JoystickEventState(SDL_IGNORE); + + unsigned joypadCount = SDL_NumJoysticks(); + for(unsigned id = 0; id < joypadCount; id++) { + Joypad jp; + jp.id = id; + jp.handle = SDL_JoystickOpen(id); + + unsigned axes = SDL_JoystickNumAxes(jp.handle); + unsigned hats = SDL_JoystickNumHats(jp.handle) * 2; + unsigned buttons = 32; //there is no SDL_JoystickNumButtons() + + jp.hid.id = 2 + jp.id; + for(unsigned n = 0; n < axes; n++) jp.hid.axis().append({n}); + for(unsigned n = 0; n < hats; n++) jp.hid.hat().append({n}); + for(unsigned n = 0; n < buttons; n++) jp.hid.button().append({n}); + jp.hid.rumble = false; + + joypads.append(jp); + } + + return true; + } + + void term() { + for(auto& jp : joypads) { + SDL_JoystickClose(jp.handle); + } + joypads.reset(); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +}; + +} + +#endif diff --git a/src/burner/qt/ruby/input/joypad/udev.cpp b/src/burner/qt/ruby/input/joypad/udev.cpp new file mode 100755 index 000000000..a0674d8e5 --- /dev/null +++ b/src/burner/qt/ruby/input/joypad/udev.cpp @@ -0,0 +1,280 @@ +#ifndef RUBY_INPUT_JOYPAD_UDEV +#define RUBY_INPUT_JOYPAD_UDEV + +namespace ruby { + +struct InputJoypadUdev { + udev* context = nullptr; + udev_monitor* monitor = nullptr; + udev_enumerate* enumerator = nullptr; + udev_list_entry* devices = nullptr; + udev_list_entry* item = nullptr; + + struct JoypadInput { + signed code = 0; + unsigned id = 0; + int16_t value = 0; + input_absinfo info; + + JoypadInput() {} + JoypadInput(signed code) : code(code) {} + JoypadInput(signed code, unsigned id) : code(code), id(id) {} + bool operator< (const JoypadInput& source) const { return code < source.code; } + bool operator==(const JoypadInput& source) const { return code == source.code; } + }; + + struct Joypad { + HID::Joypad hid; + + int fd = -1; + dev_t device = 0; + string deviceName; + string deviceNode; + + uint8_t evbit[(EV_MAX + 7) / 8] = {0}; + uint8_t keybit[(KEY_MAX + 7) / 8] = {0}; + uint8_t absbit[(ABS_MAX + 7) / 8] = {0}; + uint8_t ffbit[(FF_MAX + 7) / 8] = {0}; + unsigned effects = 0; + + string name; + string manufacturer; + string product; + string serial; + string vendorID; + string productID; + + set axes; + set hats; + set buttons; + bool rumble = false; + unsigned effectID = 0; + }; + vector joypads; + + void assign(HID::Joypad& hid, unsigned groupID, unsigned inputID, int16_t value) { + auto& group = hid.group[groupID]; + if(group.input[inputID].value == value) return; + if(input.onChange) input.onChange(hid, groupID, inputID, group.input[inputID].value, value); + group.input[inputID].value = value; + } + + void poll(vector& devices) { + while(hotplugDevicesAvailable()) hotplugDevice(); + + for(auto& jp : joypads) { + input_event events[32]; + signed length = 0; + while((length = read(jp.fd, events, sizeof(events))) > 0) { + length /= sizeof(input_event); + for(unsigned i = 0; i < length; i++) { + signed code = events[i].code; + signed type = events[i].type; + signed value = events[i].value; + + if(type == EV_ABS) { + if(auto input = jp.axes.find({code})) { + signed range = input().info.maximum - input().info.minimum; + value = (value - input().info.minimum) * 65535ll / range - 32767; + assign(jp.hid, HID::Joypad::GroupID::Axis, input().id, sclamp<16>(value)); + } else if(auto input = jp.hats.find({code})) { + signed range = input().info.maximum - input().info.minimum; + value = (value - input().info.minimum) * 65535ll / range - 32767; + assign(jp.hid, HID::Joypad::GroupID::Hat, input().id, sclamp<16>(value)); + } + } else if(type == EV_KEY) { + if(code >= BTN_MISC) { + if(auto input = jp.buttons.find({code})) { + assign(jp.hid, HID::Joypad::GroupID::Button, input().id, (bool)value); + } + } + } + } + } + + devices.append(&jp.hid); + } + } + + bool rumble(uint64_t id, bool enable) { + for(auto& jp : joypads) { + if(jp.hid.id != id) continue; + if(jp.hid.rumble == false) continue; + + input_event play; + memset(&play, 0, sizeof(input_event)); + play.type = EV_FF; + play.code = jp.effectID; + play.value = enable; + write(jp.fd, &play, sizeof(input_event)); + return true; + } + + return false; + } + + bool init() { + context = udev_new(); + if(context == nullptr) return false; + + monitor = udev_monitor_new_from_netlink(context, "udev"); + if(monitor) { + udev_monitor_filter_add_match_subsystem_devtype(monitor, "input", nullptr); + udev_monitor_enable_receiving(monitor); + } + + enumerator = udev_enumerate_new(context); + if(enumerator) { + udev_enumerate_add_match_property(enumerator, "ID_INPUT_JOYSTICK", "1"); + udev_enumerate_scan_devices(enumerator); + devices = udev_enumerate_get_list_entry(enumerator); + for(udev_list_entry* item = devices; item != nullptr; item = udev_list_entry_get_next(item)) { + string name = udev_list_entry_get_name(item); + udev_device* device = udev_device_new_from_syspath(context, name); + string deviceNode = udev_device_get_devnode(device); + if(deviceNode) createJoypad(device, deviceNode); + udev_device_unref(device); + } + } + + return true; + } + + void term() { + if(enumerator) { udev_enumerate_unref(enumerator); enumerator = nullptr; } + } + +private: + bool hotplugDevicesAvailable() { + pollfd fd = {0}; + fd.fd = udev_monitor_get_fd(monitor); + fd.events = POLLIN; + return (::poll(&fd, 1, 0) == 1) && (fd.revents & POLLIN); + } + + void hotplugDevice() { + udev_device* device = udev_monitor_receive_device(monitor); + if(device == nullptr) return; + + string value = udev_device_get_property_value(device, "ID_INPUT_JOYSTICK"); + string action = udev_device_get_action(device); + string deviceNode = udev_device_get_devnode(device); + if(value == "1") { + if(action == "add") { + createJoypad(device, deviceNode); + } + if(action == "remove") { + removeJoypad(device, deviceNode); + } + } + } + + void createJoypad(udev_device* device, const string& deviceNode) { + Joypad jp; + jp.deviceNode = deviceNode; + + struct stat st; + if(stat(deviceNode, &st) < 0) return; + jp.device = st.st_rdev; + + jp.fd = open(deviceNode, O_RDWR | O_NONBLOCK); + if(jp.fd < 0) return; + + uint8_t evbit[(EV_MAX + 7) / 8] = {0}; + uint8_t keybit[(KEY_MAX + 7) / 8] = {0}; + uint8_t absbit[(ABS_MAX + 7) / 8] = {0}; + + ioctl(jp.fd, EVIOCGBIT(0, sizeof(jp.evbit)), jp.evbit); + ioctl(jp.fd, EVIOCGBIT(EV_KEY, sizeof(jp.keybit)), jp.keybit); + ioctl(jp.fd, EVIOCGBIT(EV_ABS, sizeof(jp.absbit)), jp.absbit); + ioctl(jp.fd, EVIOCGBIT(EV_FF, sizeof(jp.ffbit)), jp.ffbit); + ioctl(jp.fd, EVIOCGEFFECTS, &jp.effects); + + #define testBit(buffer, bit) (buffer[(bit) >> 3] & 1 << ((bit) & 7)) + + if(testBit(jp.evbit, EV_KEY)) { + if(udev_device* parent = udev_device_get_parent_with_subsystem_devtype(device, "input", nullptr)) { + jp.name = udev_device_get_sysattr_value(parent, "name"); + jp.vendorID = udev_device_get_sysattr_value(parent, "id/vendor"); + jp.productID = udev_device_get_sysattr_value(parent, "id/product"); + if(udev_device* root = udev_device_get_parent_with_subsystem_devtype(parent, "usb", "usb_device")) { + if(jp.vendorID == udev_device_get_sysattr_value(root, "idVendor") + && jp.productID == udev_device_get_sysattr_value(root, "idProduct") + ) { + jp.deviceName = udev_device_get_devpath(root); + jp.manufacturer = udev_device_get_sysattr_value(root, "manufacturer"); + jp.product = udev_device_get_sysattr_value(root, "product"); + jp.serial = udev_device_get_sysattr_value(root, "serial"); + } + } + } + + unsigned axes = 0; + unsigned hats = 0; + unsigned buttons = 0; + for(signed i = 0; i < ABS_MISC; i++) { + if(testBit(jp.absbit, i)) { + if(i >= ABS_HAT0X && i <= ABS_HAT3Y) { + if(auto hat = jp.hats.insert({i, hats++})) { + ioctl(jp.fd, EVIOCGABS(i), &hat().info); + } + } else { + if(auto axis = jp.axes.insert({i, axes++})) { + ioctl(jp.fd, EVIOCGABS(i), &axis().info); + } + } + } + } + for(signed i = BTN_JOYSTICK; i < KEY_MAX; i++) { + if(testBit(jp.keybit, i)) { + jp.buttons.insert({i, buttons++}); + } + } + for(signed i = BTN_MISC; i < BTN_JOYSTICK; i++) { + if(testBit(jp.keybit, i)) { + jp.buttons.insert({i, buttons++}); + } + } + jp.rumble = jp.effects >= 2 && testBit(jp.ffbit, FF_RUMBLE); + if(jp.rumble) { + ff_effect effect; + memset(&effect, 0, sizeof(ff_effect)); + effect.type = FF_RUMBLE; + effect.id = -1; + effect.u.rumble.strong_magnitude = 65535; + effect.u.rumble.weak_magnitude = 65535; + ioctl(jp.fd, EVIOCSFF, &effect); + jp.effectID = effect.id; + } + + createJoypadHID(jp); + joypads.append(jp); + } + + #undef testBit + } + + void createJoypadHID(Joypad& jp) { + uint64_t pathID = crc32_calculate((const uint8_t*)jp.deviceName.data(), jp.deviceName.size()); + jp.hid.id = pathID << 32 | hex(jp.vendorID) << 16 | hex(jp.productID) << 0; + + for(unsigned n = 0; n < jp.axes.size(); n++) jp.hid.axis().append({n}); + for(unsigned n = 0; n < jp.hats.size(); n++) jp.hid.hat().append({n}); + for(unsigned n = 0; n < jp.buttons.size(); n++) jp.hid.button().append({n}); + jp.hid.rumble = jp.rumble; + } + + void removeJoypad(udev_device* device, const string& deviceNode) { + for(unsigned n = 0; n < joypads.size(); n++) { + if(joypads[n].deviceNode == deviceNode) { + close(joypads[n].fd); + joypads.remove(n); + return; + } + } + } +}; + +} + +#endif diff --git a/src/burner/qt/ruby/input/joypad/xinput.cpp b/src/burner/qt/ruby/input/joypad/xinput.cpp new file mode 100755 index 000000000..bc058066b --- /dev/null +++ b/src/burner/qt/ruby/input/joypad/xinput.cpp @@ -0,0 +1,162 @@ +#ifndef RUBY_INPUT_JOYPAD_XINPUT +#define RUBY_INPUT_JOYPAD_XINPUT + +//documented functionality +#define oXInputGetState "XInputGetState" +#define oXInputSetState "XInputSetState" +typedef DWORD WINAPI (*pXInputGetState)(DWORD dwUserIndex, XINPUT_STATE* pState); +typedef DWORD WINAPI (*pXInputSetState)(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration); + +//undocumented functionality +#define oXInputGetStateEx (LPCSTR)100 +#define oXInputWaitForGuideButton (LPCSTR)101 +#define oXInputCancelGuideButtonWait (LPCSTR)102 +#define oXInputPowerOffController (LPCSTR)103 +typedef DWORD WINAPI (*pXInputGetStateEx)(DWORD dwUserIndex, XINPUT_STATE* pState); +typedef DWORD WINAPI (*pXInputWaitForGuideButton)(DWORD dwUserIndex, DWORD dwFlag, void* pUnknown); +typedef DWORD WINAPI (*pXInputCancelGuideButtonWait)(DWORD dwUserIndex); +typedef DWORD WINAPI (*pXInputPowerOffController)(DWORD dwUserIndex); + +#define XINPUT_GAMEPAD_GUIDE 0x0400 + +namespace ruby { + +struct InputJoypadXInput { + HMODULE libxinput = nullptr; + pXInputGetStateEx XInputGetStateEx = nullptr; + pXInputSetState XInputSetState = nullptr; + + struct Joypad { + HID::Joypad hid; + unsigned id; + }; + vector joypads; + + void assign(HID::Joypad& hid, unsigned groupID, unsigned inputID, int16_t value) { + auto& group = hid.group[groupID]; + if(group.input[inputID].value == value) return; + if(input.onChange) input.onChange(hid, groupID, inputID, group.input[inputID].value, value); + group.input[inputID].value = value; + } + + void poll(vector& devices) { + for(auto& jp : joypads) { + XINPUT_STATE state; + if(XInputGetStateEx(jp.id, &state) != ERROR_SUCCESS) continue; + + //flip vertical axes so that -32768 = up, +32767 = down + uint16_t axisLY = 32768 + state.Gamepad.sThumbLY; + uint16_t axisRY = 32768 + state.Gamepad.sThumbRY; + assign(jp.hid, HID::Joypad::GroupID::Axis, 0, (int16_t)state.Gamepad.sThumbLX); + assign(jp.hid, HID::Joypad::GroupID::Axis, 1, (int16_t)(~axisLY - 32768)); + assign(jp.hid, HID::Joypad::GroupID::Axis, 2, (int16_t)state.Gamepad.sThumbRX); + assign(jp.hid, HID::Joypad::GroupID::Axis, 3, (int16_t)(~axisRY - 32768)); + + int16_t hatX = 0; + int16_t hatY = 0; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) hatY = -32768; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) hatY = +32767; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) hatX = -32768; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) hatX = +32767; + + assign(jp.hid, HID::Joypad::GroupID::Hat, 0, hatX); + assign(jp.hid, HID::Joypad::GroupID::Hat, 1, hatY); + + //scale trigger ranges for up to down from (0 to 255) to (-32768 to +32767) + uint16_t triggerL = state.Gamepad.bLeftTrigger; + uint16_t triggerR = state.Gamepad.bRightTrigger; + triggerL = triggerL << 8 | triggerL << 0; + triggerR = triggerR << 8 | triggerR << 0; + + assign(jp.hid, HID::Joypad::GroupID::Trigger, 0, (int16_t)(triggerL - 32768)); + assign(jp.hid, HID::Joypad::GroupID::Trigger, 1, (int16_t)(triggerR - 32768)); + + assign(jp.hid, HID::Joypad::GroupID::Button, 0, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_A)); + assign(jp.hid, HID::Joypad::GroupID::Button, 1, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_B)); + assign(jp.hid, HID::Joypad::GroupID::Button, 2, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_X)); + assign(jp.hid, HID::Joypad::GroupID::Button, 3, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y)); + assign(jp.hid, HID::Joypad::GroupID::Button, 4, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK)); + assign(jp.hid, HID::Joypad::GroupID::Button, 5, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_START)); + assign(jp.hid, HID::Joypad::GroupID::Button, 6, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER)); + assign(jp.hid, HID::Joypad::GroupID::Button, 7, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER)); + assign(jp.hid, HID::Joypad::GroupID::Button, 8, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB)); + assign(jp.hid, HID::Joypad::GroupID::Button, 9, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB)); + assign(jp.hid, HID::Joypad::GroupID::Button, 10, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE)); + + devices.append(&jp.hid); + } + } + + bool rumble(uint64_t id, bool enable) { + for(auto& jp : joypads) { + if(jp.hid.id != id) continue; + + XINPUT_VIBRATION vibration; + memset(&vibration, 0, sizeof(XINPUT_VIBRATION)); + vibration.wLeftMotorSpeed = enable ? 65535 : 0; //low-frequency motor (0 = off, 65535 = max) + vibration.wRightMotorSpeed = enable ? 65535 : 0; //high-frequency motor (0 = off, 65535 = max) + XInputSetState(jp.id, &vibration); + return true; + } + + return false; + } + + bool init() { + if(!libxinput) libxinput = LoadLibraryA("xinput1_3.dll"); + if(!libxinput) return false; + + //XInputGetStateEx is an undocumented function; but is required to get the state of the guide button + //if for some reason it is not available, fall back on XInputGetState, which takes the same parameters + XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetStateEx); + XInputSetState = (pXInputSetState)GetProcAddress(libxinput, oXInputSetState); + if(!XInputGetStateEx) XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetState); + if(!XInputGetStateEx || !XInputSetState) return term(), false; + + //XInput supports a maximum of four controllers + //add all four to devices list now. If they are not connected, they will not show up in poll() results + for(unsigned id = 0; id < 4; id++) { + Joypad jp; + jp.id = id; + jp.hid.id = (uint64_t)(1 + id) << 32 | 0x045e << 16 | 0x028e << 0; //Xbox 360 Player# + VendorID + ProductID + + jp.hid.axis().append({"LeftThumbX"}); + jp.hid.axis().append({"LeftThumbY"}); + jp.hid.axis().append({"RightThumbX"}); + jp.hid.axis().append({"RightThumbY"}); + + jp.hid.hat().append({"HatX"}); + jp.hid.hat().append({"HatY"}); + + jp.hid.trigger().append({"LeftTrigger"}); + jp.hid.trigger().append({"RightTrigger"}); + + jp.hid.button().append({"A"}); + jp.hid.button().append({"B"}); + jp.hid.button().append({"X"}); + jp.hid.button().append({"Y"}); + jp.hid.button().append({"Back"}); + jp.hid.button().append({"Start"}); + jp.hid.button().append({"LeftShoulder"}); + jp.hid.button().append({"RightShoulder"}); + jp.hid.button().append({"LeftThumb"}); + jp.hid.button().append({"RightThumb"}); + jp.hid.button().append({"Guide"}); + + joypads.append(jp); + } + + return true; + } + + void term() { + if(!libxinput) return; + + FreeLibrary(libxinput); + libxinput = nullptr; + } +}; + +} + +#endif diff --git a/src/burner/qt/ruby/input/keyboard/rawinput.cpp b/src/burner/qt/ruby/input/keyboard/rawinput.cpp new file mode 100755 index 000000000..ace90547b --- /dev/null +++ b/src/burner/qt/ruby/input/keyboard/rawinput.cpp @@ -0,0 +1,178 @@ +#ifndef RUBY_INPUT_KEYBOARD_RAWINPUT +#define RUBY_INPUT_KEYBOARD_RAWINPUT + +namespace ruby { + +struct InputKeyboardRawInput { + struct Key { + uint16_t code; + uint16_t flag; + string name; + bool value; + }; + vector keys; + + struct Keyboard { + HID::Keyboard hid; + } kb; + + void update(RAWINPUT* input) { + unsigned code = input->data.keyboard.MakeCode; + unsigned flag = input->data.keyboard.Flags; + + for(auto& key : keys) { + if(key.code != code) continue; + key.value = (key.flag == flag); + } + } + + void assign(unsigned inputID, bool value) { + auto& group = kb.hid.group[HID::Keyboard::GroupID::Button]; + if(group.input[inputID].value == value) return; + if(input.onChange) input.onChange(kb.hid, HID::Keyboard::GroupID::Button, inputID, group.input[inputID].value, value); + group.input[inputID].value = value; + } + + void poll(vector& devices) { + for(unsigned n = 0; n < keys.size(); n++) assign(n, keys[n].value); + devices.append(&kb.hid); + } + + bool init() { + rawinput.updateKeyboard = {&InputKeyboardRawInput::update, this}; + + //Pause sends 0x001d,4 + 0x0045,0; NumLock sends only 0x0045,0 + //pressing Pause will falsely trigger NumLock + //further, pause sends its key release even while button is held down + //because of this, we cannot map either reliably + + keys.append({0x0001, 0, "Escape"}); + keys.append({0x003b, 0, "F1"}); + keys.append({0x003c, 0, "F2"}); + keys.append({0x003d, 0, "F3"}); + keys.append({0x003e, 0, "F4"}); + keys.append({0x003f, 0, "F5"}); + keys.append({0x0040, 0, "F6"}); + keys.append({0x0041, 0, "F7"}); + keys.append({0x0042, 0, "F8"}); + keys.append({0x0043, 0, "F9"}); + keys.append({0x0044, 0, "F10"}); + keys.append({0x0057, 0, "F11"}); + keys.append({0x0058, 0, "F12"}); + + keys.append({0x0037, 2, "PrintScreen"}); + keys.append({0x0046, 0, "ScrollLock"}); + //keys.append({0x001d, 4, "Pause"}); + keys.append({0x0029, 0, "Tilde"}); + + keys.append({0x0002, 0, "Num1"}); + keys.append({0x0003, 0, "Num2"}); + keys.append({0x0004, 0, "Num3"}); + keys.append({0x0005, 0, "Num4"}); + keys.append({0x0006, 0, "Num5"}); + keys.append({0x0007, 0, "Num6"}); + keys.append({0x0008, 0, "Num7"}); + keys.append({0x0009, 0, "Num8"}); + keys.append({0x000a, 0, "Num9"}); + keys.append({0x000b, 0, "Num0"}); + + keys.append({0x000c, 0, "Dash"}); + keys.append({0x000d, 0, "Equal"}); + keys.append({0x000e, 0, "Backspace"}); + + keys.append({0x0052, 2, "Insert"}); + keys.append({0x0053, 2, "Delete"}); + keys.append({0x0047, 2, "Home"}); + keys.append({0x004f, 2, "End"}); + keys.append({0x0049, 2, "PageUp"}); + keys.append({0x0051, 2, "PageDown"}); + + keys.append({0x001e, 0, "A"}); + keys.append({0x0030, 0, "B"}); + keys.append({0x002e, 0, "C"}); + keys.append({0x0020, 0, "D"}); + keys.append({0x0012, 0, "E"}); + keys.append({0x0021, 0, "F"}); + keys.append({0x0022, 0, "G"}); + keys.append({0x0023, 0, "H"}); + keys.append({0x0017, 0, "I"}); + keys.append({0x0024, 0, "J"}); + keys.append({0x0025, 0, "K"}); + keys.append({0x0026, 0, "L"}); + keys.append({0x0032, 0, "M"}); + keys.append({0x0031, 0, "N"}); + keys.append({0x0018, 0, "O"}); + keys.append({0x0019, 0, "P"}); + keys.append({0x0010, 0, "Q"}); + keys.append({0x0013, 0, "R"}); + keys.append({0x001f, 0, "S"}); + keys.append({0x0014, 0, "T"}); + keys.append({0x0016, 0, "U"}); + keys.append({0x002f, 0, "V"}); + keys.append({0x0011, 0, "W"}); + keys.append({0x002d, 0, "X"}); + keys.append({0x0015, 0, "Y"}); + keys.append({0x002c, 0, "Z"}); + + keys.append({0x001a, 0, "LeftBracket"}); + keys.append({0x001b, 0, "RightBracket"}); + keys.append({0x002b, 0, "Backslash"}); + keys.append({0x0027, 0, "Semicolon"}); + keys.append({0x0028, 0, "Apostrophe"}); + keys.append({0x0033, 0, "Comma"}); + keys.append({0x0034, 0, "Period"}); + keys.append({0x0035, 0, "Slash"}); + + keys.append({0x004f, 0, "Keypad1"}); + keys.append({0x0050, 0, "Keypad2"}); + keys.append({0x0051, 0, "Keypad3"}); + keys.append({0x004b, 0, "Keypad4"}); + keys.append({0x004c, 0, "Keypad5"}); + keys.append({0x004d, 0, "Keypad6"}); + keys.append({0x0047, 0, "Keypad7"}); + keys.append({0x0048, 0, "Keypad8"}); + keys.append({0x0049, 0, "Keypad9"}); + keys.append({0x0052, 0, "Keypad0"}); + + keys.append({0x0053, 0, "Point"}); + keys.append({0x001c, 2, "Enter"}); + keys.append({0x004e, 0, "Add"}); + keys.append({0x004a, 0, "Subtract"}); + keys.append({0x0037, 0, "Multiply"}); + keys.append({0x0035, 2, "Divide"}); + + //keys.append({0x0045, 0, "NumLock"}); + keys.append({0x003a, 0, "CapsLock"}); + + keys.append({0x0048, 2, "Up"}); + keys.append({0x0050, 2, "Down"}); + keys.append({0x004b, 2, "Left"}); + keys.append({0x004d, 2, "Right"}); + + keys.append({0x000f, 0, "Tab"}); + keys.append({0x001c, 0, "Return"}); + keys.append({0x0039, 0, "Spacebar"}); + + keys.append({0x002a, 0, "LeftShift"}); + keys.append({0x0036, 0, "RightShift"}); + keys.append({0x001d, 0, "LeftControl"}); + keys.append({0x001d, 2, "RightControl"}); + keys.append({0x0038, 0, "LeftAlt"}); + keys.append({0x0038, 2, "RightAlt"}); + keys.append({0x005b, 2, "LeftSuper"}); + keys.append({0x005c, 2, "RightSuper"}); + keys.append({0x005d, 2, "Menu"}); + + kb.hid.id = 1; + for(auto& key : keys) kb.hid.button().append({key.name}); + + return true; + } + + void term() { + } +}; + +} + +#endif diff --git a/src/burner/qt/ruby/input/keyboard/xlib.cpp b/src/burner/qt/ruby/input/keyboard/xlib.cpp new file mode 100755 index 000000000..002e57eaa --- /dev/null +++ b/src/burner/qt/ruby/input/keyboard/xlib.cpp @@ -0,0 +1,174 @@ +#ifndef RUBY_INPUT_KEYBOARD_XLIB +#define RUBY_INPUT_KEYBOARD_XLIB + +namespace ruby { + +struct InputKeyboardXlib { + HID::Keyboard hid; + + Display* display = nullptr; + + struct Key { + string name; + unsigned keysym; + unsigned keycode; + }; + vector keys; + + void assign(unsigned inputID, bool value) { + auto& group = hid.group[HID::Keyboard::GroupID::Button]; + if(group.input[inputID].value == value) return; + if(input.onChange) input.onChange(hid, HID::Keyboard::GroupID::Button, inputID, group.input[inputID].value, value); + group.input[inputID].value = value; + } + + void poll(vector& devices) { + char state[32]; + XQueryKeymap(display, state); + + for(unsigned n = 0; n < keys.size(); n++) { + bool value = state[keys[n].keycode >> 3] & (1 << (keys[n].keycode & 7)); + assign(n, value); + } + + devices.append(&hid); + } + + bool init() { + display = XOpenDisplay(0); + + keys.append({"Escape", XK_Escape}); + + keys.append({"F1", XK_F1}); + keys.append({"F2", XK_F2}); + keys.append({"F3", XK_F3}); + keys.append({"F4", XK_F4}); + keys.append({"F5", XK_F5}); + keys.append({"F6", XK_F6}); + keys.append({"F7", XK_F7}); + keys.append({"F8", XK_F8}); + keys.append({"F9", XK_F9}); + keys.append({"F10", XK_F10}); + keys.append({"F11", XK_F11}); + keys.append({"F12", XK_F12}); + + keys.append({"ScrollLock", XK_Scroll_Lock}); + keys.append({"Pause", XK_Pause}); + + keys.append({"Tilde", XK_asciitilde}); + + keys.append({"Num0", XK_0}); + keys.append({"Num1", XK_1}); + keys.append({"Num2", XK_2}); + keys.append({"Num3", XK_3}); + keys.append({"Num4", XK_4}); + keys.append({"Num5", XK_5}); + keys.append({"Num6", XK_6}); + keys.append({"Num7", XK_7}); + keys.append({"Num8", XK_8}); + keys.append({"Num9", XK_9}); + + keys.append({"Dash", XK_minus}); + keys.append({"Equal", XK_equal}); + keys.append({"Backspace", XK_BackSpace}); + + keys.append({"Insert", XK_Insert}); + keys.append({"Delete", XK_Delete}); + keys.append({"Home", XK_Home}); + keys.append({"End", XK_End}); + keys.append({"PageUp", XK_Prior}); + keys.append({"PageDown", XK_Next}); + + keys.append({"A", XK_A}); + keys.append({"B", XK_B}); + keys.append({"C", XK_C}); + keys.append({"D", XK_D}); + keys.append({"E", XK_E}); + keys.append({"F", XK_F}); + keys.append({"G", XK_G}); + keys.append({"H", XK_H}); + keys.append({"I", XK_I}); + keys.append({"J", XK_J}); + keys.append({"K", XK_K}); + keys.append({"L", XK_L}); + keys.append({"M", XK_M}); + keys.append({"N", XK_N}); + keys.append({"O", XK_O}); + keys.append({"P", XK_P}); + keys.append({"Q", XK_Q}); + keys.append({"R", XK_R}); + keys.append({"S", XK_S}); + keys.append({"T", XK_T}); + keys.append({"U", XK_U}); + keys.append({"V", XK_V}); + keys.append({"W", XK_W}); + keys.append({"X", XK_X}); + keys.append({"Y", XK_Y}); + keys.append({"Z", XK_Z}); + + keys.append({"LeftBracket", XK_bracketleft}); + keys.append({"RightBracket", XK_bracketright}); + keys.append({"Backslash", XK_backslash}); + keys.append({"Semicolon", XK_semicolon}); + keys.append({"Apostrophe", XK_apostrophe}); + keys.append({"Comma", XK_comma}); + keys.append({"Period", XK_period}); + keys.append({"Slash", XK_slash}); + + keys.append({"Keypad0", XK_KP_0}); + keys.append({"Keypad1", XK_KP_1}); + keys.append({"Keypad2", XK_KP_2}); + keys.append({"Keypad3", XK_KP_3}); + keys.append({"Keypad4", XK_KP_4}); + keys.append({"Keypad5", XK_KP_5}); + keys.append({"Keypad6", XK_KP_6}); + keys.append({"Keypad7", XK_KP_7}); + keys.append({"Keypad8", XK_KP_8}); + keys.append({"Keypad9", XK_KP_9}); + + keys.append({"Add", XK_KP_Add}); + keys.append({"Subtract", XK_KP_Subtract}); + keys.append({"Multiply", XK_KP_Multiply}); + keys.append({"Divide", XK_KP_Divide}); + keys.append({"Enter", XK_KP_Enter}); + + keys.append({"Up", XK_Up}); + keys.append({"Down", XK_Down}); + keys.append({"Left", XK_Left}); + keys.append({"Right", XK_Right}); + + keys.append({"Tab", XK_Tab}); + keys.append({"Return", XK_Return}); + keys.append({"Spacebar", XK_space}); + + keys.append({"LeftControl", XK_Control_L}); + keys.append({"RightControl", XK_Control_R}); + keys.append({"LeftAlt", XK_Alt_L}); + keys.append({"RightAlt", XK_Alt_R}); + keys.append({"LeftShift", XK_Shift_L}); + keys.append({"RightShift", XK_Shift_R}); + keys.append({"LeftSuper", XK_Super_L}); + keys.append({"RightSuper", XK_Super_R}); + keys.append({"Menu", XK_Menu}); + + hid.id = 1; + + for(unsigned n = 0; n < keys.size(); n++) { + hid.button().append(keys[n].name); + keys[n].keycode = XKeysymToKeycode(display, keys[n].keysym); + } + + return true; + } + + void term() { + if(display) { + XCloseDisplay(display); + display = nullptr; + } + } +}; + +} + +#endif diff --git a/src/burner/qt/ruby/input/mouse/rawinput.cpp b/src/burner/qt/ruby/input/mouse/rawinput.cpp new file mode 100755 index 000000000..e118753aa --- /dev/null +++ b/src/burner/qt/ruby/input/mouse/rawinput.cpp @@ -0,0 +1,123 @@ +#ifndef RUBY_INPUT_MOUSE_RAWINPUT +#define RUBY_INPUT_MOUSE_RAWINPUT + +namespace ruby { + +struct InputMouseRawInput { + uintptr_t handle = 0; + bool mouseAcquired = false; + + struct Mouse { + HID::Mouse hid; + + signed relativeX = 0; + signed relativeY = 0; + signed relativeZ = 0; + bool buttons[5] = {0}; + } ms; + + bool acquire() { + if(mouseAcquired == false) { + mouseAcquired = true; + ShowCursor(false); + } + return true; + } + + bool unacquire() { + if(mouseAcquired == true) { + mouseAcquired = false; + ReleaseCapture(); + ClipCursor(NULL); + ShowCursor(true); + } + return true; + } + + bool acquired() { + if(mouseAcquired == true) { + SetFocus((HWND)handle); + SetCapture((HWND)handle); + RECT rc; + GetWindowRect((HWND)handle, &rc); + ClipCursor(&rc); + } + return GetCapture() == (HWND)handle; + } + + void update(RAWINPUT* input) { + if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) { + ms.relativeX += input->data.mouse.lLastX; + ms.relativeY += input->data.mouse.lLastY; + } + + if(input->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) { + ms.relativeZ += (int16_t)input->data.mouse.usButtonData; + } + + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) ms.buttons[0] = 1; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP ) ms.buttons[0] = 0; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) ms.buttons[1] = 1; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP ) ms.buttons[1] = 0; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) ms.buttons[2] = 1; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP ) ms.buttons[2] = 0; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) ms.buttons[3] = 1; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP ) ms.buttons[3] = 0; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) ms.buttons[4] = 1; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP ) ms.buttons[4] = 0; + } + + void assign(unsigned groupID, unsigned inputID, int16_t value) { + auto& group = ms.hid.group[groupID]; + if(group.input[inputID].value == value) return; + if(input.onChange) input.onChange(ms.hid, groupID, inputID, group.input[inputID].value, value); + group.input[inputID].value = value; + } + + void poll(vector& devices) { + assign(HID::Mouse::GroupID::Axis, 0, ms.relativeX); + assign(HID::Mouse::GroupID::Axis, 1, ms.relativeY); + assign(HID::Mouse::GroupID::Axis, 2, ms.relativeZ); + + //keys are intentionally reordered below: + //in ruby, button order is {left, middle, right, up, down} + assign(HID::Mouse::GroupID::Button, 0, ms.buttons[0]); + assign(HID::Mouse::GroupID::Button, 2, ms.buttons[1]); + assign(HID::Mouse::GroupID::Button, 1, ms.buttons[2]); + assign(HID::Mouse::GroupID::Button, 4, ms.buttons[3]); + assign(HID::Mouse::GroupID::Button, 3, ms.buttons[4]); + + ms.relativeX = 0; + ms.relativeY = 0; + ms.relativeZ = 0; + + devices.append(&ms.hid); + } + + bool init(uintptr_t handle) { + this->handle = handle; + + ms.hid.id = 2; + + ms.hid.axis().append({"X"}); + ms.hid.axis().append({"Y"}); + ms.hid.axis().append({"Z"}); + + ms.hid.button().append({"Left"}); + ms.hid.button().append({"Middle"}); + ms.hid.button().append({"Right"}); + ms.hid.button().append({"Up"}); + ms.hid.button().append({"Down"}); + + rawinput.updateMouse = {&InputMouseRawInput::update, this}; + return true; + } + + void term() { + unacquire(); + } +}; + +} + +#endif diff --git a/src/burner/qt/ruby/input/mouse/xlib.cpp b/src/burner/qt/ruby/input/mouse/xlib.cpp new file mode 100755 index 000000000..2def3a3f3 --- /dev/null +++ b/src/burner/qt/ruby/input/mouse/xlib.cpp @@ -0,0 +1,154 @@ +#ifndef RUBY_INPUT_MOUSE_XLIB +#define RUBY_INPUT_MOUSE_XLIB + +namespace ruby { + +struct InputMouseXlib { + HID::Mouse hid; + + uintptr_t handle = 0; + + Display* display = nullptr; + Window rootWindow; + Cursor invisibleCursor; + unsigned screenWidth = 0; + unsigned screenHeight = 0; + + struct Mouse { + bool acquired = false; + signed numerator = 0; + signed denominator = 0; + signed threshold = 0; + unsigned relativeX = 0; + unsigned relativeY = 0; + } ms; + + bool acquire() { + if(acquired()) return true; + + if(XGrabPointer(display, handle, True, 0, GrabModeAsync, GrabModeAsync, rootWindow, invisibleCursor, CurrentTime) == GrabSuccess) { + //backup existing cursor acceleration settings + XGetPointerControl(display, &ms.numerator, &ms.denominator, &ms.threshold); + + //disable cursor acceleration + XChangePointerControl(display, True, False, 1, 1, 0); + + //center cursor (so that first relative poll returns 0, 0 if mouse has not moved) + XWarpPointer(display, None, rootWindow, 0, 0, 0, 0, screenWidth / 2, screenHeight / 2); + + return ms.acquired = true; + } else { + return ms.acquired = false; + } + } + + bool unacquire() { + if(acquired()) { + //restore cursor acceleration and release cursor + XChangePointerControl(display, True, True, ms.numerator, ms.denominator, ms.threshold); + XUngrabPointer(display, CurrentTime); + ms.acquired = false; + } + return true; + } + + bool acquired() { + return ms.acquired; + } + + void assign(unsigned groupID, unsigned inputID, int16_t value) { + auto& group = hid.group[groupID]; + if(group.input[inputID].value == value) return; + if(input.onChange) input.onChange(hid, groupID, inputID, group.input[inputID].value, value); + group.input[inputID].value = value; + } + + void poll(vector& devices) { + Window rootReturn; + Window childReturn; + signed rootXReturn = 0; + signed rootYReturn = 0; + signed windowXReturn = 0; + signed windowYReturn = 0; + unsigned maskReturn = 0; + XQueryPointer(display, handle, &rootReturn, &childReturn, &rootXReturn, &rootYReturn, &windowXReturn, &windowYReturn, &maskReturn); + + if(acquired()) { + XWindowAttributes attributes; + XGetWindowAttributes(display, handle, &attributes); + + //absolute -> relative conversion + assign(HID::Mouse::GroupID::Axis, 0, (int16_t)(rootXReturn - screenWidth / 2)); + assign(HID::Mouse::GroupID::Axis, 1, (int16_t)(rootYReturn - screenHeight / 2)); + + if(hid.axis().input[0].value != 0 || hid.axis().input[1].value != 0) { + //if mouse moved, re-center mouse for next poll + XWarpPointer(display, None, rootWindow, 0, 0, 0, 0, screenWidth / 2, screenHeight / 2); + } + } else { + assign(HID::Mouse::GroupID::Axis, 0, (int16_t)(rootXReturn - ms.relativeX)); + assign(HID::Mouse::GroupID::Axis, 1, (int16_t)(rootYReturn - ms.relativeY)); + + ms.relativeX = rootXReturn; + ms.relativeY = rootYReturn; + } + + assign(HID::Mouse::GroupID::Button, 0, (bool)(maskReturn & Button1Mask)); + assign(HID::Mouse::GroupID::Button, 1, (bool)(maskReturn & Button2Mask)); + assign(HID::Mouse::GroupID::Button, 2, (bool)(maskReturn & Button3Mask)); + assign(HID::Mouse::GroupID::Button, 3, (bool)(maskReturn & Button4Mask)); + assign(HID::Mouse::GroupID::Button, 4, (bool)(maskReturn & Button5Mask)); + + devices.append(&hid); + } + + bool init(uintptr_t handle) { + this->handle = handle; + display = XOpenDisplay(0); + rootWindow = DefaultRootWindow(display); + + XWindowAttributes attributes; + XGetWindowAttributes(display, rootWindow, &attributes); + screenWidth = attributes.width; + screenHeight = attributes.height; + + //Xlib: because XShowCursor(display, false) would just be too easy + //create invisible cursor for use when mouse is acquired + Pixmap pixmap; + XColor black, unused; + static char invisibleData[8] = {0}; + Colormap colormap = DefaultColormap(display, DefaultScreen(display)); + XAllocNamedColor(display, colormap, "black", &black, &unused); + pixmap = XCreateBitmapFromData(display, handle, invisibleData, 8, 8); + invisibleCursor = XCreatePixmapCursor(display, pixmap, pixmap, &black, &black, 0, 0); + XFreePixmap(display, pixmap); + XFreeColors(display, colormap, &black.pixel, 1, 0); + + ms.acquired = false; + ms.relativeX = 0; + ms.relativeY = 0; + + hid.id = 2; + + hid.axis().append({"X"}); + hid.axis().append({"Y"}); + + hid.button().append({"Left"}); + hid.button().append({"Middle"}); + hid.button().append({"Right"}); + hid.button().append({"Up"}); + hid.button().append({"Down"}); + + return true; + } + + void term() { + unacquire(); + XFreeCursor(display, invisibleCursor); + XCloseDisplay(display); + } +}; + +} + +#endif diff --git a/src/burner/qt/ruby/input/sdl.cpp b/src/burner/qt/ruby/input/sdl.cpp new file mode 100755 index 000000000..3a6ff3395 --- /dev/null +++ b/src/burner/qt/ruby/input/sdl.cpp @@ -0,0 +1,82 @@ +#include +#include +#include + +#include "keyboard/xlib.cpp" +#include "mouse/xlib.cpp" +#include "joypad/sdl.cpp" + +namespace ruby { + +struct pInputSDL { + InputKeyboardXlib xlibKeyboard; + InputMouseXlib xlibMouse; + InputJoypadSDL sdl; + + struct Settings { + uintptr_t handle = 0; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any &value) { + if(name == Input::Handle) { + settings.handle = any_cast(value); + return true; + } + + return false; + } + + bool acquire() { + return xlibMouse.acquire(); + } + + bool unacquire() { + return xlibMouse.unacquire(); + } + + bool acquired() { + return xlibMouse.acquired(); + } + + vector poll() { + vector devices; + xlibKeyboard.poll(devices); + xlibMouse.poll(devices); + sdl.poll(devices); + return devices; + } + + bool rumble(uint64_t id, bool enable) { + return false; + } + + bool init() { + if(xlibKeyboard.init() == false) return false; + if(xlibMouse.init(settings.handle) == false) return false; + if(sdl.init() == false) return false; + return true; + } + + void term() { + xlibKeyboard.term(); + xlibMouse.term(); + sdl.term(); + } +}; + +DeclareInput(SDL) + +} diff --git a/src/burner/qt/ruby/input/shared/rawinput.cpp b/src/burner/qt/ruby/input/shared/rawinput.cpp new file mode 100755 index 000000000..8e7a9cd8f --- /dev/null +++ b/src/burner/qt/ruby/input/shared/rawinput.cpp @@ -0,0 +1,162 @@ +#ifndef RUBY_INPUT_SHARED_RAWINPUT +#define RUBY_INPUT_SHARED_RAWINPUT + +namespace ruby { + +LRESULT CALLBACK RawInputWindowProc(HWND, UINT, WPARAM, LPARAM); + +struct RawInput { + HANDLE mutex; + HWND hwnd; + bool ready = false; + bool initialized = false; + function updateKeyboard; + function updateMouse; + + struct Device { + HANDLE handle; + string path; + enum class Type : unsigned { Keyboard, Mouse, Joypad } type; + uint16_t vendorID = 0; + uint16_t productID = 0; + bool isXInputDevice = false; + }; + vector devices; + + optional find(uint16_t vendorID, uint16_t productID) { + for(auto& device : devices) { + if(device.vendorID == vendorID && device.productID == productID) return {true, device}; + } + return false; + } + + void scanDevices() { + devices.reset(); + + unsigned deviceCount = 0; + GetRawInputDeviceList(NULL, &deviceCount, sizeof(RAWINPUTDEVICELIST)); + RAWINPUTDEVICELIST* list = new RAWINPUTDEVICELIST[deviceCount]; + GetRawInputDeviceList(list, &deviceCount, sizeof(RAWINPUTDEVICELIST)); + + for(unsigned n = 0; n < deviceCount; n++) { + wchar_t path[4096]; + unsigned size = sizeof(path) - 1; + GetRawInputDeviceInfo(list[n].hDevice, RIDI_DEVICENAME, &path, &size); + + RID_DEVICE_INFO info; + info.cbSize = size = sizeof(RID_DEVICE_INFO); + GetRawInputDeviceInfo(list[n].hDevice, RIDI_DEVICEINFO, &info, &size); + + Device device; + device.path = (const char*)utf8_t(path); + device.handle = list[n].hDevice; + + if(info.dwType == RIM_TYPEKEYBOARD) { + device.type = Device::Type::Keyboard; + device.vendorID = 0; + device.productID = 1; + } + + if(info.dwType == RIM_TYPEMOUSE) { + device.type = Device::Type::Mouse; + device.vendorID = 0; + device.productID = 2; + } + + if(info.dwType == RIM_TYPEHID) { + //verify that this is a joypad device + if(info.hid.usUsagePage != 1 || (info.hid.usUsage != 4 && info.hid.usUsage != 5)) continue; + + device.type = Device::Type::Joypad; + device.vendorID = info.hid.dwVendorId; + device.productID = info.hid.dwProductId; + if(device.path.find("IG_")) device.isXInputDevice = true; //"IG_" is only found inside XInput device paths + } + + devices.append(device); + } + + delete[] list; + } + + LRESULT windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + if(msg != WM_INPUT) return DefWindowProc(hwnd, msg, wparam, lparam); + + unsigned size = 0; + GetRawInputData((HRAWINPUT)lparam, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)); + RAWINPUT* input = new RAWINPUT[size]; + GetRawInputData((HRAWINPUT)lparam, RID_INPUT, input, &size, sizeof(RAWINPUTHEADER)); + WaitForSingleObject(mutex, INFINITE); + + if(input->header.dwType == RIM_TYPEKEYBOARD) { + if(updateKeyboard) updateKeyboard(input); + } + + if(input->header.dwType == RIM_TYPEMOUSE) { + if(updateMouse) updateMouse(input); + } + + ReleaseMutex(mutex); + LRESULT result = DefRawInputProc(&input, size, sizeof(RAWINPUTHEADER)); + delete[] input; + return result; + } + + void main() { + WNDCLASS wc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = RawInputWindowProc; + wc.lpszClassName = L"RawInputClass"; + wc.lpszMenuName = 0; + wc.style = CS_VREDRAW | CS_HREDRAW; + RegisterClass(&wc); + + hwnd = CreateWindow(L"RawInputClass", L"RawInputClass", WS_POPUP, 0, 0, 64, 64, 0, 0, GetModuleHandle(0), 0); + + scanDevices(); + + RAWINPUTDEVICE device[2]; + //capture all keyboard input + device[0].usUsagePage = 1; + device[0].usUsage = 6; + device[0].dwFlags = RIDEV_INPUTSINK; + device[0].hwndTarget = hwnd; + //capture all mouse input + device[1].usUsagePage = 1; + device[1].usUsage = 2; + device[1].dwFlags = RIDEV_INPUTSINK; + device[1].hwndTarget = hwnd; + RegisterRawInputDevices(device, 2, sizeof(RAWINPUTDEVICE)); + + WaitForSingleObject(mutex, INFINITE); + ready = true; + ReleaseMutex(mutex); + + while(true) { + MSG msg; + GetMessage(&msg, hwnd, 0, 0); + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +}; + +static RawInput rawinput; + +DWORD WINAPI RawInputThreadProc(void*) { + rawinput.main(); + return 0; +} + +LRESULT CALLBACK RawInputWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + return rawinput.windowProc(hwnd, msg, wparam, lparam); +} + +} + +#endif diff --git a/src/burner/qt/ruby/input/udev.cpp b/src/burner/qt/ruby/input/udev.cpp new file mode 100755 index 000000000..a53d95d55 --- /dev/null +++ b/src/burner/qt/ruby/input/udev.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "keyboard/xlib.cpp" +#include "mouse/xlib.cpp" +#include "joypad/udev.cpp" + +namespace ruby { + +struct pInputUdev { + InputKeyboardXlib xlibKeyboard; + InputMouseXlib xlibMouse; + InputJoypadUdev udev; + + struct Settings { + uintptr_t handle = 0; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + if(name == Input::JoypadRumbleSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Input::Handle) { + settings.handle = any_cast(value); + return true; + } + return false; + } + + bool acquire() { + return xlibMouse.acquire(); + } + + bool unacquire() { + return xlibMouse.unacquire(); + } + + bool acquired() { + return xlibMouse.acquired(); + } + + vector poll() { + vector devices; + xlibKeyboard.poll(devices); + xlibMouse.poll(devices); + udev.poll(devices); + return devices; + } + + bool rumble(uint64_t id, bool enable) { + return udev.rumble(id, enable); + } + + bool init() { + if(xlibKeyboard.init() == false) return false; + if(xlibMouse.init(settings.handle) == false) return false; + if(udev.init() == false) return false; + return true; + } + + void term() { + xlibKeyboard.term(); + xlibMouse.term(); + udev.term(); + } +}; + +DeclareInput(Udev) + +} diff --git a/src/burner/qt/ruby/input/windows.cpp b/src/burner/qt/ruby/input/windows.cpp new file mode 100755 index 000000000..3833b3b56 --- /dev/null +++ b/src/burner/qt/ruby/input/windows.cpp @@ -0,0 +1,109 @@ +#include +#define DIRECTINPUT_VERSION 0x0800 +#include + +#include "shared/rawinput.cpp" +#include "keyboard/rawinput.cpp" +#include "mouse/rawinput.cpp" +#include "joypad/xinput.cpp" +#include "joypad/directinput.cpp" + +namespace ruby { + +struct pInputWindows { + InputKeyboardRawInput rawinputKeyboard; + InputMouseRawInput rawinputMouse; + InputJoypadXInput xinput; + InputJoypadDirectInput directinput; + + LPDIRECTINPUT8 directinputContext = nullptr; + + struct Settings { + uintptr_t handle = 0; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + if(name == Input::JoypadRumbleSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Input::Handle) { + settings.handle = any_cast(value); + return true; + } + return false; + } + + bool acquire() { + return rawinputMouse.acquire(); + } + + bool unacquire() { + return rawinputMouse.unacquire(); + } + + bool acquired() { + return rawinputMouse.acquired(); + } + + vector poll() { + vector devices; + rawinputKeyboard.poll(devices); + rawinputMouse.poll(devices); + xinput.poll(devices); + directinput.poll(devices); + return devices; + } + + bool rumble(uint64_t id, bool enable) { + if(xinput.rumble(id, enable)) return true; + if(directinput.rumble(id, enable)) return true; + return false; + } + + bool init() { + if(rawinput.initialized == false) { + rawinput.initialized = true; + rawinput.mutex = CreateMutex(NULL, FALSE, NULL); + CreateThread(NULL, 0, RawInputThreadProc, 0, 0, NULL); + + do { + Sleep(1); + WaitForSingleObject(rawinput.mutex, INFINITE); + ReleaseMutex(rawinput.mutex); + } while(rawinput.ready == false); + } + + DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&directinputContext, 0); + if(directinputContext == nullptr) return false; + + if(rawinputKeyboard.init() == false) return false; + if(rawinputMouse.init(settings.handle) == false) return false; + bool xinputAvailable = xinput.init(); + if(directinput.init(settings.handle, directinputContext, xinputAvailable) == false) return false; + return true; + } + + void term() { + rawinputKeyboard.term(); + rawinputMouse.term(); + xinput.term(); + directinput.term(); + + if(directinputContext) { directinputContext->Release(); directinputContext = nullptr; } + } +}; + +DeclareInput(Windows) + +} diff --git a/src/burner/qt/ruby/input/xlib.cpp b/src/burner/qt/ruby/input/xlib.cpp new file mode 100755 index 000000000..8c37d4a00 --- /dev/null +++ b/src/burner/qt/ruby/input/xlib.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include + +#include "keyboard/xlib.cpp" +#include "mouse/xlib.cpp" + +namespace ruby { + +struct pInputXlib { + InputKeyboardXlib xlibKeyboard; + InputMouseXlib xlibMouse; + + struct Settings { + uintptr_t handle = 0; + } settings; + + bool cap(const string& name) { + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any &value) { + if(name == Input::Handle) { + settings.handle = any_cast(value); + return true; + } + + return false; + } + + bool acquire() { + return xlibMouse.acquire(); + } + + bool unacquire() { + return xlibMouse.unacquire(); + } + + bool acquired() { + return xlibMouse.acquired(); + } + + vector poll() { + vector devices; + xlibKeyboard.poll(devices); + xlibMouse.poll(devices); + return devices; + } + + bool rumble(uint64_t id, bool enable) { + return false; + } + + bool init() { + if(xlibKeyboard.init() == false) return false; + if(xlibMouse.init(settings.handle) == false) return false; + return true; + } + + void term() { + xlibKeyboard.term(); + xlibMouse.term(); + } +}; + +DeclareInput(Xlib) + +} diff --git a/src/burner/qt/ruby/ruby.cpp b/src/burner/qt/ruby/ruby.cpp new file mode 100755 index 000000000..edb5581fb --- /dev/null +++ b/src/burner/qt/ruby/ruby.cpp @@ -0,0 +1,496 @@ +#include + +#undef mkdir +#undef usleep +#include + +namespace ruby { + +VideoInterface video; +AudioInterface audio; +InputInterface input; + +/* VideoInterface */ + + +const char* Video::Handle = "Handle"; +const char* Video::Synchronize = "Synchronize"; +const char* Video::Depth = "Depth"; +const char* Video::Filter = "Filter"; +const char* Video::Shader = "Shader"; + +const unsigned Video::FilterNearest = 0; +const unsigned Video::FilterLinear = 1; + +void VideoInterface::driver(const char* driver) { + if(p) term(); + + if(!driver || !*driver) driver = optimalDriver(); + + if(0); + + #ifdef VIDEO_CGL + else if(!strcmp(driver, "OpenGL")) p = new VideoCGL(); + #endif + + #ifdef VIDEO_DIRECT3D + else if(!strcmp(driver, "Direct3D")) p = new VideoD3D(); + #endif + + #ifdef VIDEO_DIRECTDRAW + else if(!strcmp(driver, "DirectDraw")) p = new VideoDD(); + #endif + + #ifdef VIDEO_GDI + else if(!strcmp(driver, "GDI")) p = new VideoGDI(); + #endif + + #ifdef VIDEO_GLX + else if(!strcmp(driver, "OpenGL")) p = new VideoGLX(); + #endif + + #ifdef VIDEO_QTOPENGL + else if(!strcmp(driver, "Qt-OpenGL")) p = new VideoQtOpenGL(); + #endif + + #ifdef VIDEO_QTRASTER + else if(!strcmp(driver, "Qt-Raster")) p = new VideoQtRaster(); + #endif + + #ifdef VIDEO_SDL + else if(!strcmp(driver, "SDL")) p = new VideoSDL(); + #endif + + #ifdef VIDEO_WGL + else if(!strcmp(driver, "OpenGL")) p = new VideoWGL(); + #endif + + #ifdef VIDEO_XSHM + else if(!strcmp(driver, "XShm")) p = new VideoXShm(); + #endif + + #ifdef VIDEO_XV + else if(!strcmp(driver, "X-Video")) p = new VideoXv(); + #endif + + else p = new Video(); +} + +const char* VideoInterface::optimalDriver() { + #if defined(VIDEO_WGL) + return "OpenGL"; + #elif defined(VIDEO_DIRECT3D) + return "Direct3D"; + #elif defined(VIDEO_DIRECTDRAW) + return "DirectDraw"; + #elif defined(VIDEO_GDI) + return "GDI"; + + #elif defined(VIDEO_CGL) + return "OpenGL"; + + #elif defined(VIDEO_GLX) + return "OpenGL"; + #elif defined(VIDEO_XV) + return "X-Video"; + #elif defined(VIDEO_XSHM) + return "XShm"; + #elif defined(VIDEO_SDL) + return "SDL"; + + #else + return "None"; + #endif +} + +const char* VideoInterface::safestDriver() { + #if defined(VIDEO_DIRECT3D) + return "Direct3D"; + #elif defined(VIDEO_WGL) + return "OpenGL"; + #elif defined(VIDEO_DIRECTDRAW) + return "DirectDraw"; + #elif defined(VIDEO_GDI) + return "GDI"; + + #elif defined(VIDEO_CGL) + return "OpenGL"; + + #elif defined(VIDEO_XSHM) + return "XShm"; + #elif defined(VIDEO_SDL) + return "SDL"; + #elif defined(VIDEO_XV) + return "X-Video"; + #elif defined(VIDEO_GLX) + return "OpenGL"; + + #else + return "None"; + #endif +} + +const char* VideoInterface::availableDrivers() { + return + + //Windows + + #if defined(VIDEO_WGL) + "OpenGL;" + #endif + + #if defined(VIDEO_DIRECT3D) + "Direct3D;" + #endif + + #if defined(VIDEO_DIRECTDRAW) + "DirectDraw;" + #endif + + #if defined(VIDEO_GDI) + "GDI;" + #endif + + //OS X + + #if defined(VIDEO_CGL) + "OpenGL;" + #endif + + //Linux + + #if defined(VIDEO_GLX) + "OpenGL;" + #endif + + #if defined(VIDEO_XV) + "X-Video;" + #endif + + #if defined(VIDEO_XSHM) + "XShm;" + #endif + + #if defined(VIDEO_SDL) + "SDL;" + #endif + + "None"; +} + +bool VideoInterface::init() { + if(!p) driver(); + return p->init(); +} + +void VideoInterface::term() { + if(p) { + p->term(); + delete p; + p = nullptr; + } +} + +bool VideoInterface::cap(const string& name) { return p ? p->cap(name) : false; } +any VideoInterface::get(const string& name) { return p ? p->get(name) : false; } +bool VideoInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; } +bool VideoInterface::lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { return p ? p->lock(data, pitch, width, height) : false; } +void VideoInterface::unlock() { if(p) p->unlock(); } +void VideoInterface::clear() { if(p) p->clear(); } +void VideoInterface::refresh() { if(p) p->refresh(); } +VideoInterface::VideoInterface() : p(nullptr) {} +VideoInterface::~VideoInterface() { term(); } + +/* AudioInterface */ + +const char* Audio::Handle = "Handle"; +const char* Audio::Synchronize = "Synchronize"; +const char* Audio::Frequency = "Frequency"; +const char* Audio::Latency = "Latency"; + +void AudioInterface::driver(const char* driver) { + if(p) term(); + + if(!driver || !*driver) driver = optimalDriver(); + + if(0); + + #ifdef AUDIO_ALSA + else if(!strcmp(driver, "ALSA")) p = new AudioALSA(); + #endif + + #ifdef AUDIO_AO + else if(!strcmp(driver, "libao")) p = new AudioAO(); + #endif + + #ifdef AUDIO_DIRECTSOUND + else if(!strcmp(driver, "DirectSound")) p = new AudioDS(); + #endif + + #ifdef AUDIO_OPENAL + else if(!strcmp(driver, "OpenAL")) p = new AudioOpenAL(); + #endif + + #ifdef AUDIO_OSS + else if(!strcmp(driver, "OSS")) p = new AudioOSS(); + #endif + + #ifdef AUDIO_PULSEAUDIO + else if(!strcmp(driver, "PulseAudio")) p = new AudioPulseAudio(); + #endif + + #ifdef AUDIO_PULSEAUDIOSIMPLE + else if(!strcmp(driver, "PulseAudioSimple")) p = new AudioPulseAudioSimple(); + #endif + + #ifdef AUDIO_XAUDIO2 + else if(!strcmp(driver, "XAudio2")) p = new AudioXAudio2(); + #endif + + else p = new Audio(); +} + +const char* AudioInterface::optimalDriver() { + #if defined(AUDIO_XAUDIO2) + return "XAudio2"; + #elif defined(AUDIO_DIRECTSOUND) + return "DirectSound"; + + #elif defined(AUDIO_ALSA) + return "ALSA"; + #elif defined(AUDIO_OPENAL) + return "OpenAL"; + #elif defined(AUDIO_OSS) + return "OSS"; + #elif defined(AUDIO_PULSEAUDIO) + return "PulseAudio"; + #elif defined(AUDIO_PULSEAUDIOSIMPLE) + return "PulseAudioSimple"; + #elif defined(AUDIO_AO) + return "libao"; + + #else + return "None"; + #endif +} + +const char* AudioInterface::safestDriver() { + #if defined(AUDIO_DIRECTSOUND) + return "DirectSound"; + #elif defined(AUDIO_XAUDIO2) + return "XAudio2"; + + #elif defined(AUDIO_ALSA) + return "ALSA"; + #elif defined(AUDIO_OPENAL) + return "OpenAL"; + #elif defined(AUDIO_PULSEAUDIO) + return "PulseAudio"; + #elif defined(AUDIO_PULSEAUDIOSIMPLE) + return "PulseAudioSimple"; + #elif defined(AUDIO_AO) + return "libao"; + #elif defined(AUDIO_OSS) + return "OSS"; + + #else + return "None"; + #endif +} + +const char* AudioInterface::availableDrivers() { + return + + //Windows + + #if defined(AUDIO_XAUDIO2) + "XAudio2;" + #endif + + #if defined(AUDIO_DIRECTSOUND) + "DirectSound;" + #endif + + //Linux + + #if defined(AUDIO_ALSA) + "ALSA;" + #endif + + #if defined(AUDIO_OPENAL) + "OpenAL;" + #endif + + #if defined(AUDIO_OSS) + "OSS;" + #endif + + #if defined(AUDIO_PULSEAUDIO) + "PulseAudio;" + #endif + + #if defined(AUDIO_PULSEAUDIOSIMPLE) + "PulseAudioSimple;" + #endif + + #if defined(AUDIO_AO) + "libao;" + #endif + + "None"; +} + +bool AudioInterface::init() { + if(!p) driver(); + return p->init(); +} + +void AudioInterface::term() { + if(p) { + p->term(); + delete p; + p = nullptr; + } +} + +bool AudioInterface::cap(const string& name) { return p ? p->cap(name) : false; } +any AudioInterface::get(const string& name) { return p ? p->get(name) : false; } +bool AudioInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; } +void AudioInterface::sample(uint16_t left, uint16_t right) { if(p) p->sample(left, right); } +void AudioInterface::clear() { if(p) p->clear(); } +AudioInterface::AudioInterface() : p(nullptr) {} +AudioInterface::~AudioInterface() { term(); } + +/* InputInterface */ + +const char* Input::Handle = "Handle"; +const char* Input::KeyboardSupport = "KeyboardSupport"; +const char* Input::MouseSupport = "MouseSupport"; +const char* Input::JoypadSupport = "JoypadSupport"; +const char* Input::JoypadRumbleSupport = "JoypadRumbleSupport"; + +void InputInterface::driver(const char* driver) { + if(p) term(); + + if(!driver || !*driver) driver = optimalDriver(); + + if(0); + + #ifdef INPUT_WINDOWS + else if(!strcmp(driver, "Windows")) p = new InputWindows(); + #endif + + #ifdef INPUT_CARBON + else if(!strcmp(driver, "Carbon")) p = new InputCarbon(); + #endif + + #ifdef INPUT_UDEV + else if(!strcmp(driver, "udev")) p = new InputUdev(); + #endif + + #ifdef INPUT_SDL + else if(!strcmp(driver, "SDL")) p = new InputSDL(); + #endif + + #ifdef INPUT_XLIB + else if(!strcmp(driver, "Xlib")) p = new InputXlib(); + #endif + + else p = new Input(); +} + +const char* InputInterface::optimalDriver() { + #if defined(INPUT_WINDOWS) + return "Windows"; + + #elif defined(INPUT_CARBON) + return "Carbon"; + + #elif defined(INPUT_UDEV) + return "udev"; + #elif defined(INPUT_SDL) + return "SDL"; + #elif defined(INPUT_XLIB) + return "Xlib"; + + #else + return "None"; + #endif +} + +const char* InputInterface::safestDriver() { + #if defined(INPUT_WINDOWS) + return "Windows"; + + #elif defined(INPUT_CARBON) + return "Carbon"; + + #elif defined(INPUT_UDEV) + return "udev"; + #elif defined(INPUT_SDL) + return "SDL"; + #elif defined(INPUT_XLIB) + return "Xlib"; + + #else + return "none"; + #endif +} + +const char* InputInterface::availableDrivers() { + return + + //Windows + + #if defined(INPUT_WINDOWS) + "Windows;" + #endif + + //OS X + + #if defined(INPUT_CARBON) + "Carbon;" + #endif + + //Linux + + #if defined(INPUT_UDEV) + "udev;" + #endif + + #if defined(INPUT_SDL) + "SDL;" + #endif + + #if defined(INPUT_XLIB) + "Xlib;" + #endif + + "None"; +} + +bool InputInterface::init() { + if(!p) driver(); + return p->init(); +} + +void InputInterface::term() { + if(p) { + p->term(); + delete p; + p = nullptr; + } +} + +bool InputInterface::cap(const string& name) { return p ? p->cap(name) : false; } +any InputInterface::get(const string& name) { return p ? p->get(name) : false; } +bool InputInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; } +bool InputInterface::acquire() { return p ? p->acquire() : false; } +bool InputInterface::unacquire() { return p ? p->unacquire() : false; } +bool InputInterface::acquired() { return p ? p->acquired() : false; } +vector InputInterface::poll() { return p ? p->poll() : vector(); } +bool InputInterface::rumble(uint64_t id, bool enable) { return p ? p->rumble(id, enable) : false; } +InputInterface::InputInterface() : p(nullptr) {} +InputInterface::~InputInterface() { term(); } + +}; diff --git a/src/burner/qt/ruby/ruby.hpp b/src/burner/qt/ruby/ruby.hpp new file mode 100755 index 000000000..ab4215647 --- /dev/null +++ b/src/burner/qt/ruby/ruby.hpp @@ -0,0 +1,98 @@ +/* + ruby + version: 0.11 (2013-12-19) + license: public domain +*/ + +#ifndef RUBY_H +#define RUBY_H + +#include + +namespace ruby { + +#include +#include +#include + +struct VideoInterface { + void driver(const char* driver = ""); + const char* optimalDriver(); + const char* safestDriver(); + const char* availableDrivers(); + bool init(); + void term(); + + bool cap(const nall::string& name); + nall::any get(const nall::string& name); + bool set(const nall::string& name, const nall::any& value); + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height); + void unlock(); + void clear(); + void refresh(); + + VideoInterface(); + ~VideoInterface(); + +private: + Video* p = nullptr; +}; + +struct AudioInterface { + void driver(const char* driver = ""); + const char* optimalDriver(); + const char* safestDriver(); + const char* availableDrivers(); + bool init(); + void term(); + + bool cap(const nall::string& name); + nall::any get(const nall::string& name); + bool set(const nall::string& name, const nall::any& value); + + void sample(uint16_t left, uint16_t right); + void clear(); + + AudioInterface(); + ~AudioInterface(); + +private: + Audio* p = nullptr; +}; + +struct InputInterface { + nall::function onChange; + + void driver(const char* driver = ""); + const char* optimalDriver(); + const char* safestDriver(); + const char* availableDrivers(); + bool init(); + void term(); + + bool cap(const nall::string& name); + nall::any get(const nall::string& name); + bool set(const nall::string& name, const nall::any& value); + + bool acquire(); + bool unacquire(); + bool acquired(); + + nall::vector poll(); + bool rumble(uint64_t id, bool enable); + + InputInterface(); + ~InputInterface(); + +private: + Input* p = nullptr; +}; + +extern VideoInterface video; +extern AudioInterface audio; +extern InputInterface input; + +}; + +#endif diff --git a/src/burner/qt/ruby/video.hpp b/src/burner/qt/ruby/video.hpp new file mode 100755 index 000000000..2fb685257 --- /dev/null +++ b/src/burner/qt/ruby/video.hpp @@ -0,0 +1,25 @@ +struct Video { + static const char* Handle; + static const char* Synchronize; + static const char* Depth; + static const char* Filter; + static const char* Shader; + + static const unsigned FilterNearest; + static const unsigned FilterLinear; + + virtual bool cap(const nall::string& name) { return false; } + virtual nall::any get(const nall::string& name) { return false; } + virtual bool set(const nall::string& name, const nall::any& value) { return false; } + + virtual bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { return false; } + virtual void unlock() {} + + virtual void clear() {} + virtual void refresh() {} + virtual bool init() { return true; } + virtual void term() {} + + Video() {} + virtual ~Video() {} +}; diff --git a/src/burner/qt/ruby/video/cgl.cpp b/src/burner/qt/ruby/video/cgl.cpp new file mode 100755 index 000000000..dca80c07a --- /dev/null +++ b/src/burner/qt/ruby/video/cgl.cpp @@ -0,0 +1,190 @@ +#include "opengl/opengl.hpp" + +namespace ruby { + class pVideoCGL; +} + +@interface RubyVideoCGL : NSOpenGLView { +@public + ruby::pVideoCGL* video; +} +-(id) initWith:(ruby::pVideoCGL*)video pixelFormat:(NSOpenGLPixelFormat*)pixelFormat; +-(void) reshape; +@end + +namespace ruby { + +struct pVideoCGL : OpenGL { + RubyVideoCGL* view; + + struct { + NSView* handle; + + bool synchronize; + unsigned filter; + string shader; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (NSView*)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + + if(view) { + @autoreleasepool { + [[view openGLContext] makeCurrentContext]; + int synchronize = settings.synchronize; + [[view openGLContext] setValues:&synchronize forParameter:NSOpenGLCPSwapInterval]; + } + } + } + return true; + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; + return true; + } + + if(name == Video::Shader) { + settings.shader = any_cast(value); + @autoreleasepool { + [[view openGLContext] makeCurrentContext]; + } + OpenGL::shader(settings.shader); + if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; + return true; + } + + return false; + } + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + OpenGL::size(width, height); + return OpenGL::lock(data, pitch); + } + + void unlock() { + } + + void clear() { + @autoreleasepool { + [view lockFocus]; + OpenGL::clear(); + [[view openGLContext] flushBuffer]; + [view unlockFocus]; + } + } + + void refresh() { + @autoreleasepool { + if([view lockFocusIfCanDraw]) { + auto area = [view frame]; + outputWidth = area.size.width, outputHeight = area.size.height; + OpenGL::refresh(); + [[view openGLContext] flushBuffer]; + [view unlockFocus]; + } + } + } + + bool init() { + term(); + + @autoreleasepool { + NSOpenGLPixelFormatAttribute attributes[] = { + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + NSOpenGLPFAColorSize, 24, + NSOpenGLPFAAlphaSize, 8, + NSOpenGLPFADoubleBuffer, + 0 + }; + + auto size = [settings.handle frame].size; + auto format = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes] autorelease]; + auto context = [[[NSOpenGLContext alloc] initWithFormat:format shareContext:nil] autorelease]; + + view = [[RubyVideoCGL alloc] initWith:this pixelFormat:format]; + [view setOpenGLContext:context]; + [view setFrame:NSMakeRect(0, 0, size.width, size.height)]; + [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [settings.handle addSubview:view]; + [context setView:view]; + + [view lockFocus]; + + OpenGL::init(); + //print((const char*)glGetString(GL_VERSION), "\n"); + + int synchronize = settings.synchronize; + [[view openGLContext] setValues:&synchronize forParameter:NSOpenGLCPSwapInterval]; + + [view unlockFocus]; + } + + clear(); + return true; + } + + void term() { + OpenGL::term(); + + @autoreleasepool { + [view removeFromSuperview]; + [view release]; + view = nil; + } + } + + pVideoCGL() { + view = nil; + + settings.handle = nil; + settings.synchronize = false; + settings.filter = 0; + } + + ~pVideoCGL() { + term(); + } +}; + +DeclareVideo(CGL) + +} + +@implementation RubyVideoCGL : NSOpenGLView + +-(id) initWith:(ruby::pVideoCGL*)videoPointer pixelFormat:(NSOpenGLPixelFormat*)pixelFormat { + if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0) pixelFormat:pixelFormat]) { + video = videoPointer; + } + return self; +} + +-(void) reshape { + video->refresh(); +} + +@end diff --git a/src/burner/qt/ruby/video/direct3d.cpp b/src/burner/qt/ruby/video/direct3d.cpp new file mode 100755 index 000000000..33368da69 --- /dev/null +++ b/src/burner/qt/ruby/video/direct3d.cpp @@ -0,0 +1,463 @@ +#undef interface +#define interface struct +#include +#include +#undef interface + +#define D3DVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX1) + +typedef HRESULT (__stdcall* EffectProc)(LPDIRECT3DDEVICE9, LPCVOID, UINT, D3DXMACRO const*, LPD3DXINCLUDE, DWORD, LPD3DXEFFECTPOOL, LPD3DXEFFECT*, LPD3DXBUFFER*); +typedef HRESULT (__stdcall* TextureProc)(LPDIRECT3DDEVICE9, LPCTSTR, LPDIRECT3DTEXTURE9*); + +namespace ruby { + +class pVideoD3D { +public: + LPDIRECT3D9 lpd3d; + LPDIRECT3DDEVICE9 device; + LPDIRECT3DVERTEXBUFFER9 vertex_buffer; + LPDIRECT3DVERTEXBUFFER9* vertex_ptr; + D3DPRESENT_PARAMETERS presentation; + D3DSURFACE_DESC d3dsd; + D3DLOCKED_RECT d3dlr; + D3DRASTER_STATUS d3drs; + D3DCAPS9 d3dcaps; + LPDIRECT3DTEXTURE9 texture; + LPDIRECT3DSURFACE9 surface; + LPD3DXEFFECT effect; + string shader_source_markup; + + bool lost; + unsigned iwidth, iheight; + + struct d3dvertex { + float x, y, z, rhw; //screen coords + float u, v; //texture coords + }; + + struct { + uint32_t t_usage, v_usage; + uint32_t t_pool, v_pool; + uint32_t lock; + uint32_t filter; + } flags; + + struct { + bool dynamic; //device supports dynamic textures + bool shader; //device supports pixel shaders + } caps; + + struct { + HWND handle; + bool synchronize; + unsigned filter; + + unsigned width; + unsigned height; + } settings; + + struct { + unsigned width; + unsigned height; + } state; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return false; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + if(lpd3d) update_filter(); + return true; + } + + if(name == Video::Shader) { + return false; + set_shader(any_cast(value)); + return true; + } + + return false; + } + + bool recover() { + if(!device) return false; + + if(lost) { + release_resources(); + if(device->Reset(&presentation) != D3D_OK) return false; + } + + lost = false; + + device->SetDialogBoxMode(false); + + device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + + device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + + device->SetRenderState(D3DRS_LIGHTING, false); + device->SetRenderState(D3DRS_ZENABLE, false); + device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + + device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + + device->SetVertexShader(NULL); + device->SetFVF(D3DVERTEX); + + device->CreateVertexBuffer(sizeof(d3dvertex) * 4, flags.v_usage, D3DVERTEX, (D3DPOOL)flags.v_pool, &vertex_buffer, NULL); + iwidth = 0; + iheight = 0; + resize(settings.width = 256, settings.height = 256); + update_filter(); + clear(); + return true; + } + + unsigned rounded_power_of_two(unsigned n) { + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + return n + 1; + } + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + iwidth = rounded_power_of_two(max(width, iwidth )); + iheight = rounded_power_of_two(max(height, iheight)); + + if(d3dcaps.MaxTextureWidth < iwidth || d3dcaps.MaxTextureWidth < iheight) { + //TODO: attempt to handle this more gracefully + return; + } + + if(texture) texture->Release(); + device->CreateTexture(iwidth, iheight, 1, flags.t_usage, D3DFMT_X8R8G8B8, (D3DPOOL)flags.t_pool, &texture, NULL); + } + + void update_filter() { + if(!device) return; + if(lost && !recover()) return; + + flags.filter = (settings.filter == Video::FilterNearest ? D3DTEXF_POINT : D3DTEXF_LINEAR); + device->SetSamplerState(0, D3DSAMP_MINFILTER, flags.filter); + device->SetSamplerState(0, D3DSAMP_MAGFILTER, flags.filter); + } + + // Vertex format: + // + // 0----------1 + // | /| + // | / | + // | / | + // | / | + // | / | + // 2----------3 + // + // (x,y) screen coords, in pixels + // (u,v) texture coords, betweeen 0.0 (top, left) to 1.0 (bottom, right) + void set_vertex( + uint32_t px, uint32_t py, uint32_t pw, uint32_t ph, + uint32_t tw, uint32_t th, + uint32_t x, uint32_t y, uint32_t w, uint32_t h + ) { + d3dvertex vertex[4]; + vertex[0].x = vertex[2].x = (double)(x - 0.5); + vertex[1].x = vertex[3].x = (double)(x + w - 0.5); + vertex[0].y = vertex[1].y = (double)(y - 0.5); + vertex[2].y = vertex[3].y = (double)(y + h - 0.5); + + //Z-buffer and RHW are unused for 2D blit, set to normal values + vertex[0].z = vertex[1].z = vertex[2].z = vertex[3].z = 0.0; + vertex[0].rhw = vertex[1].rhw = vertex[2].rhw = vertex[3].rhw = 1.0; + + double rw = (double)w / (double)pw * (double)tw; + double rh = (double)h / (double)ph * (double)th; + vertex[0].u = vertex[2].u = (double)(px ) / rw; + vertex[1].u = vertex[3].u = (double)(px + w) / rw; + vertex[0].v = vertex[1].v = (double)(py ) / rh; + vertex[2].v = vertex[3].v = (double)(py + h) / rh; + + vertex_buffer->Lock(0, sizeof(d3dvertex) * 4, (void**)&vertex_ptr, 0); + memcpy(vertex_ptr, vertex, sizeof(d3dvertex) * 4); + vertex_buffer->Unlock(); + + device->SetStreamSource(0, vertex_buffer, 0, sizeof(d3dvertex)); + } + + void clear() { + if(lost && !recover()) return; + + texture->GetLevelDesc(0, &d3dsd); + texture->GetSurfaceLevel(0, &surface); + + if(surface) { + device->ColorFill(surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00)); + surface->Release(); + surface = nullptr; + } + + //clear primary display and all backbuffers + for(unsigned i = 0; i < 3; i++) { + device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0); + device->Present(0, 0, 0, 0); + } + } + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + if(lost && !recover()) return false; + + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + texture->GetLevelDesc(0, &d3dsd); + texture->GetSurfaceLevel(0, &surface); + + surface->LockRect(&d3dlr, 0, flags.lock); + pitch = d3dlr.Pitch; + return data = (uint32_t*)d3dlr.pBits; + } + + void unlock() { + surface->UnlockRect(); + surface->Release(); + surface = nullptr; + } + + void refresh() { + if(lost && !recover()) return; + + RECT rd, rs; //dest, source rectangles + GetClientRect(settings.handle, &rd); + SetRect(&rs, 0, 0, settings.width, settings.height); + + //if output size changed, driver must be re-initialized. + //failure to do so causes scaling issues on some video drivers. + if(state.width != rd.right || state.height != rd.bottom) { + init(); + set_shader(shader_source_markup); + return; + } + + if(caps.shader && effect) { + device->BeginScene(); + set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom); + + D3DXVECTOR4 rubyTextureSize; + rubyTextureSize.x = iwidth; + rubyTextureSize.y = iheight; + rubyTextureSize.z = 1.0 / iheight; + rubyTextureSize.w = 1.0 / iwidth; + effect->SetVector("rubyTextureSize", &rubyTextureSize); + + D3DXVECTOR4 rubyInputSize; + rubyInputSize.x = settings.width; + rubyInputSize.y = settings.height; + rubyInputSize.z = 1.0 / settings.height; + rubyInputSize.w = 1.0 / settings.width; + effect->SetVector("rubyInputSize", &rubyInputSize); + + D3DXVECTOR4 rubyOutputSize; + rubyOutputSize.x = rd.right; + rubyOutputSize.y = rd.bottom; + rubyOutputSize.z = 1.0 / rd.bottom; + rubyOutputSize.w = 1.0 / rd.right; + effect->SetVector("rubyOutputSize", &rubyOutputSize); + + UINT passes; + effect->Begin(&passes, 0); + effect->SetTexture("rubyTexture", texture); + device->SetTexture(0, texture); + for(unsigned pass = 0; pass < passes; pass++) { + effect->BeginPass(pass); + device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + effect->EndPass(); + } + effect->End(); + device->EndScene(); + } else { + device->BeginScene(); + set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom); + device->SetTexture(0, texture); + device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + device->EndScene(); + } + + if(settings.synchronize) { + D3DRASTER_STATUS status; + //wait for a previous vblank to finish, if necessary + while(true) { + device->GetRasterStatus(0, &status); + if(status.InVBlank == false) break; + } + //wait for next vblank to begin + while(true) { + device->GetRasterStatus(0, &status); + if(status.InVBlank == true) break; + } + } + + if(device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) lost = true; + } + + void set_shader(const char* source) { + if(!caps.shader) return; + + if(effect) { + effect->Release(); + effect = NULL; + } + + if(!source || !*source) { + shader_source_markup = ""; + return; + } + shader_source_markup = source; + + XML::Document document(shader_source_markup); + bool is_hlsl = document["shader"]["language"].data == "HLSL"; + string shader_source = document["shader"]["source"].data; + if(shader_source == "") return; + + HMODULE d3dx; + for(unsigned i = 0; i < 256; i++) { + char t[256]; + sprintf(t, "d3dx9_%u.dll", i); + d3dx = LoadLibraryW(utf16_t(t)); + if(d3dx) break; + } + if(!d3dx) d3dx = LoadLibraryW(L"d3dx9.dll"); + if(!d3dx) return; + + EffectProc effectProc = (EffectProc)GetProcAddress(d3dx, "D3DXCreateEffect"); + TextureProc textureProc = (TextureProc)GetProcAddress(d3dx, "D3DXCreateTextureFromFileA"); + + LPD3DXBUFFER pBufferErrors = NULL; + effectProc(device, shader_source, lstrlenA(shader_source), NULL, NULL, 0, NULL, &effect, &pBufferErrors); + + D3DXHANDLE hTech; + effect->FindNextValidTechnique(NULL, &hTech); + effect->SetTechnique(hTech); + } + + bool init() { + term(); + + RECT rd; + GetClientRect(settings.handle, &rd); + state.width = rd.right; + state.height = rd.bottom; + + lpd3d = Direct3DCreate9(D3D_SDK_VERSION); + if(!lpd3d) return false; + + memset(&presentation, 0, sizeof(presentation)); + presentation.Flags = D3DPRESENTFLAG_VIDEO; + presentation.SwapEffect = D3DSWAPEFFECT_FLIP; + presentation.hDeviceWindow = settings.handle; + presentation.BackBufferCount = 1; + presentation.MultiSampleType = D3DMULTISAMPLE_NONE; + presentation.MultiSampleQuality = 0; + presentation.EnableAutoDepthStencil = false; + presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN; + presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + presentation.Windowed = true; + presentation.BackBufferFormat = D3DFMT_UNKNOWN; + presentation.BackBufferWidth = 0; + presentation.BackBufferHeight = 0; + + if(lpd3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.handle, + D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) { + return false; + } + + device->GetDeviceCaps(&d3dcaps); + + caps.dynamic = bool(d3dcaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES); + caps.shader = d3dcaps.PixelShaderVersion > D3DPS_VERSION(1, 4); + + if(caps.dynamic == true) { + flags.t_usage = D3DUSAGE_DYNAMIC; + flags.v_usage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC; + flags.t_pool = D3DPOOL_DEFAULT; + flags.v_pool = D3DPOOL_DEFAULT; + flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD; + } else { + flags.t_usage = 0; + flags.v_usage = D3DUSAGE_WRITEONLY; + flags.t_pool = D3DPOOL_MANAGED; + flags.v_pool = D3DPOOL_MANAGED; + flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD; + } + + lost = false; + recover(); + return true; + } + + void release_resources() { + if(effect) { effect->Release(); effect = 0; } + if(vertex_buffer) { vertex_buffer->Release(); vertex_buffer = 0; } + if(surface) { surface->Release(); surface = 0; } + if(texture) { texture->Release(); texture = 0; } + } + + void term() { + release_resources(); + if(device) { device->Release(); device = 0; } + if(lpd3d) { lpd3d->Release(); lpd3d = 0; } + } + + pVideoD3D() { + effect = 0; + vertex_buffer = 0; + surface = 0; + texture = 0; + device = 0; + lpd3d = 0; + lost = true; + + settings.handle = 0; + settings.synchronize = false; + settings.filter = Video::FilterLinear; + } +}; + +DeclareVideo(D3D) + +}; + +#undef D3DVERTEX diff --git a/src/burner/qt/ruby/video/directdraw.cpp b/src/burner/qt/ruby/video/directdraw.cpp new file mode 100755 index 000000000..f7731de99 --- /dev/null +++ b/src/burner/qt/ruby/video/directdraw.cpp @@ -0,0 +1,186 @@ +#include + +namespace ruby { + +class pVideoDD { +public: + LPDIRECTDRAW lpdd; + LPDIRECTDRAW7 lpdd7; + LPDIRECTDRAWSURFACE7 screen, raster; + LPDIRECTDRAWCLIPPER clipper; + DDSURFACEDESC2 ddsd; + DDSCAPS2 ddscaps; + unsigned iwidth, iheight; + + struct { + HWND handle; + bool synchronize; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + iwidth = max(width, iwidth); + iheight = max(height, iheight); + + if(raster) raster->Release(); + + screen->GetSurfaceDesc(&ddsd); + int depth = ddsd.ddpfPixelFormat.dwRGBBitCount; + if(depth == 32) goto try_native_surface; + + memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); + ddsd.dwSize = sizeof(DDSURFACEDESC2); + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //DDSCAPS_SYSTEMMEMORY + ddsd.dwWidth = iwidth; + ddsd.dwHeight = iheight; + + ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); + ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB; + ddsd.ddpfPixelFormat.dwRGBBitCount = 32; + ddsd.ddpfPixelFormat.dwRBitMask = 0xff0000; + ddsd.ddpfPixelFormat.dwGBitMask = 0x00ff00; + ddsd.ddpfPixelFormat.dwBBitMask = 0x0000ff; + + if(lpdd7->CreateSurface(&ddsd, &raster, 0) == DD_OK) return clear(); + + try_native_surface: + memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); + ddsd.dwSize = sizeof(DDSURFACEDESC2); + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //DDSCAPS_SYSTEMMEMORY + ddsd.dwWidth = iwidth; + ddsd.dwHeight = iheight; + + if(lpdd7->CreateSurface(&ddsd, &raster, 0) == DD_OK) return clear(); + } + + void clear() { + DDBLTFX fx; + fx.dwSize = sizeof(DDBLTFX); + fx.dwFillColor = 0x00000000; + screen->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx); + raster->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx); + } + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + if(raster->Lock(0, &ddsd, DDLOCK_WAIT, 0) != DD_OK) { + raster->Restore(); + if(raster->Lock(0, &ddsd, DDLOCK_WAIT, 0) != DD_OK) return false; + } + pitch = ddsd.lPitch; + return data = (uint32_t*)ddsd.lpSurface; + } + + void unlock() { + raster->Unlock(0); + } + + void refresh() { + if(settings.synchronize) { + while(true) { + BOOL in_vblank; + lpdd7->GetVerticalBlankStatus(&in_vblank); + if(in_vblank == true) break; + } + } + + HRESULT hr; + RECT rd, rs; + SetRect(&rs, 0, 0, settings.width, settings.height); + + POINT p = {0, 0}; + ClientToScreen(settings.handle, &p); + GetClientRect(settings.handle, &rd); + OffsetRect(&rd, p.x, p.y); + + if(screen->Blt(&rd, raster, &rs, DDBLT_WAIT, 0) == DDERR_SURFACELOST) { + screen->Restore(); + raster->Restore(); + } + } + + bool init() { + term(); + + DirectDrawCreate(0, &lpdd, 0); + lpdd->QueryInterface(IID_IDirectDraw7, (void**)&lpdd7); + if(lpdd) { lpdd->Release(); lpdd = 0; } + + lpdd7->SetCooperativeLevel(settings.handle, DDSCL_NORMAL); + + memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); + ddsd.dwSize = sizeof(DDSURFACEDESC2); + + ddsd.dwFlags = DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + lpdd7->CreateSurface(&ddsd, &screen, 0); + + lpdd7->CreateClipper(0, &clipper, 0); + clipper->SetHWnd(0, settings.handle); + screen->SetClipper(clipper); + + raster = 0; + iwidth = 0; + iheight = 0; + resize(settings.width = 256, settings.height = 256); + + return true; + } + + void term() { + if(clipper) { clipper->Release(); clipper = 0; } + if(raster) { raster->Release(); raster = 0; } + if(screen) { screen->Release(); screen = 0; } + if(lpdd7) { lpdd7->Release(); lpdd7 = 0; } + if(lpdd) { lpdd->Release(); lpdd = 0; } + } + + pVideoDD() { + lpdd = 0; + lpdd7 = 0; + screen = 0; + raster = 0; + clipper = 0; + + settings.handle = 0; + } +}; + +DeclareVideo(DD) + +}; diff --git a/src/burner/qt/ruby/video/gdi.cpp b/src/burner/qt/ruby/video/gdi.cpp new file mode 100755 index 000000000..ada01b962 --- /dev/null +++ b/src/burner/qt/ruby/video/gdi.cpp @@ -0,0 +1,100 @@ +#include + +namespace ruby { + +class pVideoGDI { +public: + uint32_t* buffer; + HBITMAP bitmap; + HDC bitmapdc; + BITMAPINFO bmi; + + struct { + HWND handle; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + return false; + } + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + settings.width = width; + settings.height = height; + + pitch = 1024 * 4; + return data = buffer; + } + + void unlock() {} + + void clear() {} + + void refresh() { + RECT rc; + GetClientRect(settings.handle, &rc); + + SetDIBits(bitmapdc, bitmap, 0, settings.height, (void*)buffer, &bmi, DIB_RGB_COLORS); + HDC hdc = GetDC(settings.handle); + StretchBlt(hdc, rc.left, rc.top, rc.right, rc.bottom, bitmapdc, 0, 1024 - settings.height, settings.width, settings.height, SRCCOPY); + ReleaseDC(settings.handle, hdc); + } + + bool init() { + HDC hdc = GetDC(settings.handle); + bitmapdc = CreateCompatibleDC(hdc); + assert(bitmapdc); + bitmap = CreateCompatibleBitmap(hdc, 1024, 1024); + assert(bitmap); + SelectObject(bitmapdc, bitmap); + ReleaseDC(settings.handle, hdc); + + memset(&bmi, 0, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = 1024; + bmi.bmiHeader.biHeight = -1024; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; //biBitCount of 15 is invalid, biBitCount of 16 is really RGB555 + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = 1024 * 1024 * sizeof(uint32_t); + + settings.width = 256; + settings.height = 256; + return true; + } + + void term() { + DeleteObject(bitmap); + DeleteDC(bitmapdc); + } + + pVideoGDI() { + buffer = (uint32_t*)malloc(1024 * 1024 * sizeof(uint32_t)); + settings.handle = 0; + } + + ~pVideoGDI() { + if(buffer) free(buffer); + } +}; + +DeclareVideo(GDI) + +}; diff --git a/src/burner/qt/ruby/video/glx.cpp b/src/burner/qt/ruby/video/glx.cpp new file mode 100755 index 000000000..3e6ff3db1 --- /dev/null +++ b/src/burner/qt/ruby/video/glx.cpp @@ -0,0 +1,251 @@ +#include "opengl/opengl.hpp" + +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 + +namespace ruby { + +struct pVideoGLX : OpenGL { + GLXContext (*glXCreateContextAttribs)(Display*, GLXFBConfig, GLXContext, int, const int*) = nullptr; + int (*glXSwapInterval)(int) = nullptr; + + Display* display; + int screen; + Window xwindow; + Colormap colormap; + GLXContext glxcontext; + GLXWindow glxwindow; + + struct { + int version_major, version_minor; + bool double_buffer; + bool is_direct; + } glx; + + struct { + Window handle; + bool synchronize; + unsigned depth; + unsigned filter; + string shader; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Depth) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Depth) return settings.depth; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + if(glXSwapInterval) glXSwapInterval(settings.synchronize); + return true; + } + } + + if(name == Video::Depth) { + unsigned depth = any_cast(value); + if(depth > DefaultDepth(display, screen)) return false; + + switch(depth) { + case 24: inputFormat = GL_RGBA8; break; + case 30: inputFormat = GL_RGB10_A2; break; + default: return false; + } + + settings.depth = depth; + return true; + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; + return true; + } + + if(name == Video::Shader) { + settings.shader = any_cast(value); + OpenGL::shader(settings.shader); + if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; + return true; + } + + return false; + } + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + OpenGL::size(width, height); + return OpenGL::lock(data, pitch); + } + + void unlock() { + } + + void clear() { + OpenGL::clear(); + if(glx.double_buffer) glXSwapBuffers(display, glxwindow); + } + + void refresh() { + //we must ensure that the child window is the same size as the parent window. + //unfortunately, we cannot hook the parent window resize event notification, + //as we did not create the parent window, nor have any knowledge of the toolkit used. + //therefore, inelegant as it may be, we query each window size and resize as needed. + XWindowAttributes parent, child; + XGetWindowAttributes(display, settings.handle, &parent); + XGetWindowAttributes(display, xwindow, &child); + if(child.width != parent.width || child.height != parent.height) { + XResizeWindow(display, xwindow, parent.width, parent.height); + } + + outputWidth = parent.width, outputHeight = parent.height; + OpenGL::refresh(); + if(glx.double_buffer) glXSwapBuffers(display, glxwindow); + } + + bool init() { + term(); + + glXQueryVersion(display, &glx.version_major, &glx.version_minor); + //require GLX 1.2+ API + if(glx.version_major < 1 || (glx.version_major == 1 && glx.version_minor < 2)) return false; + + XWindowAttributes window_attributes; + XGetWindowAttributes(display, settings.handle, &window_attributes); + + //let GLX determine the best Visual to use for GL output; provide a few hints + //note: some video drivers will override double buffering attribute + int attributeList[] = { + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_DOUBLEBUFFER, True, + GLX_RED_SIZE, (signed)(settings.depth / 3), + GLX_GREEN_SIZE, (signed)(settings.depth / 3) + (signed)(settings.depth % 3), + GLX_BLUE_SIZE, (signed)(settings.depth / 3), + None + }; + + int fbCount; + GLXFBConfig* fbConfig = glXChooseFBConfig(display, screen, attributeList, &fbCount); + if(fbCount == 0) return false; + + XVisualInfo* vi = glXGetVisualFromFBConfig(display, fbConfig[0]); + + //Window settings.handle has already been realized, most likely with DefaultVisual. + //GLX requires that the GL output window has the same Visual as the GLX context. + //it is not possible to change the Visual of an already realized (created) window. + //therefore a new child window, using the same GLX Visual, must be created and binded to settings.handle. + colormap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone); + XSetWindowAttributes attributes; + attributes.colormap = colormap; + attributes.border_pixel = 0; + xwindow = XCreateWindow(display, /* parent = */ settings.handle, + /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, + /* border_width = */ 0, vi->depth, InputOutput, vi->visual, + CWColormap | CWBorderPixel, &attributes); + XSetWindowBackground(display, xwindow, /* color = */ 0); + XMapWindow(display, xwindow); + XFlush(display); + + //window must be realized (appear onscreen) before we make the context current + while(XPending(display)) { + XEvent event; + XNextEvent(display, &event); + } + + glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE); + glXMakeCurrent(display, glxwindow = xwindow, glxcontext); + + glXCreateContextAttribs = (GLXContext (*)(Display*, GLXFBConfig, GLXContext, int, const int*))glGetProcAddress("glXCreateContextAttribsARB"); + glXSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalSGI"); + if(!glXSwapInterval) glXSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalMESA"); + + if(glXCreateContextAttribs) { + int attributes[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 2, + None + }; + GLXContext context = glXCreateContextAttribs(display, fbConfig[0], nullptr, true, attributes); + if(context) { + glXMakeCurrent(display, 0, nullptr); + glXDestroyContext(display, glxcontext); + glXMakeCurrent(display, glxwindow, glxcontext = context); + } + } + + if(glXSwapInterval) { + glXSwapInterval(settings.synchronize); + } + + //read attributes of frame buffer for later use, as requested attributes from above are not always granted + int value = 0; + glXGetConfig(display, vi, GLX_DOUBLEBUFFER, &value); + glx.double_buffer = value; + glx.is_direct = glXIsDirect(display, glxcontext); + + OpenGL::init(); + return true; + } + + void term() { + OpenGL::term(); + + if(glxcontext) { + glXDestroyContext(display, glxcontext); + glxcontext = nullptr; + } + + if(xwindow) { + XUnmapWindow(display, xwindow); + xwindow = 0; + } + + if(colormap) { + XFreeColormap(display, colormap); + colormap = 0; + } + } + + pVideoGLX() { + display = XOpenDisplay(0); + screen = DefaultScreen(display); + + settings.handle = 0; + settings.synchronize = false; + settings.depth = 24; + settings.filter = 1; //linear + + xwindow = 0; + colormap = 0; + glxcontext = nullptr; + glxwindow = 0; + } + + ~pVideoGLX() { + term(); + XCloseDisplay(display); + } +}; + +DeclareVideo(GLX) + +}; diff --git a/src/burner/qt/ruby/video/opengl/bind.hpp b/src/burner/qt/ruby/video/opengl/bind.hpp new file mode 100755 index 000000000..196a627f4 --- /dev/null +++ b/src/burner/qt/ruby/video/opengl/bind.hpp @@ -0,0 +1,99 @@ +#if defined(PLATFORM_MACOSX) +static bool OpenGLBind() { + return true; +} +#else +PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr; +PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr; +PFNGLUSEPROGRAMPROC glUseProgram = nullptr; +PFNGLCREATESHADERPROC glCreateShader = nullptr; +PFNGLDELETESHADERPROC glDeleteShader = nullptr; +PFNGLSHADERSOURCEPROC glShaderSource = nullptr; +PFNGLCOMPILESHADERPROC glCompileShader = nullptr; +PFNGLGETSHADERIVPROC glGetShaderiv = nullptr; +PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr; +PFNGLATTACHSHADERPROC glAttachShader = nullptr; +PFNGLDETACHSHADERPROC glDetachShader = nullptr; +PFNGLLINKPROGRAMPROC glLinkProgram = nullptr; +PFNGLVALIDATEPROGRAMPROC glValidateProgram = nullptr; +PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr; +PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr; +PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr; +PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = nullptr; +PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr; +PFNGLGENBUFFERSPROC glGenBuffers = nullptr; +PFNGLDELETEBUFFERSPROC glDeleteBuffers = nullptr; +PFNGLBINDBUFFERPROC glBindBuffer = nullptr; +PFNGLBUFFERDATAPROC glBufferData = nullptr; +PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation = nullptr; +PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr; +PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr; +PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray = nullptr; +PFNGLBINDFRAGDATALOCATIONPROC glBindFragDataLocation = nullptr; +PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr; +PFNGLGETUNIFORMIVPROC glGetUniformiv = nullptr; +PFNGLUNIFORM1IPROC glUniform1i = nullptr; +PFNGLUNIFORM1FPROC glUniform1f = nullptr; +PFNGLUNIFORM2FPROC glUniform2f = nullptr; +PFNGLUNIFORM2FVPROC glUniform2fv = nullptr; +PFNGLUNIFORM4FPROC glUniform4f = nullptr; +PFNGLUNIFORM4FVPROC glUniform4fv = nullptr; +PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv = nullptr; +PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = nullptr; +PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers = nullptr; +PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = nullptr; +PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = nullptr; +PFNGLACTIVETEXTUREPROC glActiveTexture = nullptr; + +static bool OpenGLBind() { + #define bind(prototype, function) \ + function = (prototype)glGetProcAddress(#function); \ + if(function == nullptr) return false + + bind(PFNGLCREATEPROGRAMPROC, glCreateProgram); + bind(PFNGLDELETEPROGRAMPROC, glDeleteProgram); + bind(PFNGLUSEPROGRAMPROC, glUseProgram); + bind(PFNGLCREATESHADERPROC, glCreateShader); + bind(PFNGLDELETESHADERPROC, glDeleteShader); + bind(PFNGLSHADERSOURCEPROC, glShaderSource); + bind(PFNGLCOMPILESHADERPROC, glCompileShader); + bind(PFNGLGETSHADERIVPROC, glGetShaderiv); + bind(PFNGLGETSHADERINFOLOGPROC, glGetShaderInfoLog); + bind(PFNGLATTACHSHADERPROC, glAttachShader); + bind(PFNGLDETACHSHADERPROC, glDetachShader); + bind(PFNGLLINKPROGRAMPROC, glLinkProgram); + bind(PFNGLVALIDATEPROGRAMPROC, glValidateProgram); + bind(PFNGLGETPROGRAMIVPROC, glGetProgramiv); + bind(PFNGLGETPROGRAMINFOLOGPROC, glGetProgramInfoLog); + bind(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays); + bind(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays); + bind(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray); + bind(PFNGLGENBUFFERSPROC, glGenBuffers); + bind(PFNGLDELETEBUFFERSPROC, glDeleteBuffers); + bind(PFNGLBINDBUFFERPROC, glBindBuffer); + bind(PFNGLBUFFERDATAPROC, glBufferData); + bind(PFNGLGETATTRIBLOCATIONPROC, glGetAttribLocation); + bind(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer); + bind(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray); + bind(PFNGLDISABLEVERTEXATTRIBARRAYPROC, glDisableVertexAttribArray); + bind(PFNGLBINDFRAGDATALOCATIONPROC, glBindFragDataLocation); + bind(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation); + bind(PFNGLGETUNIFORMIVPROC, glGetUniformiv); + bind(PFNGLUNIFORM1IPROC, glUniform1i); + bind(PFNGLUNIFORM1FPROC, glUniform1f); + bind(PFNGLUNIFORM2FPROC, glUniform2f); + bind(PFNGLUNIFORM2FVPROC, glUniform2fv); + bind(PFNGLUNIFORM4FPROC, glUniform4f); + bind(PFNGLUNIFORM4FVPROC, glUniform4fv); + bind(PFNGLUNIFORMMATRIX4FVPROC, glUniformMatrix4fv); + bind(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers); + bind(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers); + bind(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer); + bind(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D); + bind(PFNGLACTIVETEXTUREPROC, glActiveTexture); + + #undef bind + + return true; +} +#endif diff --git a/src/burner/qt/ruby/video/opengl/main.hpp b/src/burner/qt/ruby/video/opengl/main.hpp new file mode 100755 index 000000000..1213645d0 --- /dev/null +++ b/src/burner/qt/ruby/video/opengl/main.hpp @@ -0,0 +1,210 @@ +void OpenGL::shader(const char* pathname) { + for(auto& program : programs) program.release(); + programs.reset(); + + settings.reset(); + + format = inputFormat; + filter = GL_LINEAR; + wrap = GL_CLAMP_TO_BORDER; + absoluteWidth = 0, absoluteHeight = 0; + relativeWidth = 0, relativeHeight = 0; + + unsigned historySize = 0; + if(pathname) { + auto document = Markup::Document(file::read({pathname, "manifest.bml"})); + + for(auto& node : document["settings"]) { + settings.insert({node.name, node.text()}); + } + + for(auto& node : document["input"]) { + if(node.name == "history") historySize = node.decimal(); + if(node.name == "format") format = glrFormat(node.text()); + if(node.name == "filter") filter = glrFilter(node.text()); + if(node.name == "wrap") wrap = glrWrap(node.text()); + } + + for(auto& node : document["output"]) { + string text = node.text(); + if(node.name == "width") { + if(text.endsWith("%")) relativeWidth = real(text.rtrim<1>("%")) / 100.0; + else absoluteWidth = decimal(text); + } + if(node.name == "height") { + if(text.endsWith("%")) relativeHeight = real(text.rtrim<1>("%")) / 100.0; + else absoluteHeight = decimal(text); + } + } + + for(auto& node : document.find("program")) { + unsigned n = programs.size(); + programs(n).bind(this, node, pathname); + } + } + + //changing shaders may change input format, which requires the input texture to be recreated + if(texture) { glDeleteTextures(1, &texture); texture = 0; } + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, getFormat(), getType(), buffer); + allocateHistory(historySize); +} + +void OpenGL::allocateHistory(unsigned size) { + for(auto& frame : history) glDeleteTextures(1, &frame.texture); + history.reset(); + while(size--) { + OpenGLTexture frame; + frame.filter = filter; + frame.wrap = wrap; + glGenTextures(1, &frame.texture); + glBindTexture(GL_TEXTURE_2D, frame.texture); + glTexImage2D(GL_TEXTURE_2D, 0, format, frame.width = width, frame.height = height, 0, getFormat(), getType(), buffer); + history.append(frame); + } +} + +bool OpenGL::lock(uint32_t*& data, unsigned& pitch) { + pitch = width * sizeof(uint32_t); + return data = buffer; +} + +void OpenGL::clear() { + for(auto& p : programs) { + glUseProgram(p.program); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, p.framebuffer); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + glUseProgram(0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); +} + +void OpenGL::refresh() { + clear(); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, getFormat(), getType(), buffer); + + struct Source { + GLuint texture; + unsigned width, height; + GLuint filter, wrap; + }; + vector sources; + sources.prepend({texture, width, height, filter, wrap}); + + for(auto& p : programs) { + unsigned targetWidth = p.absoluteWidth ? p.absoluteWidth : outputWidth; + unsigned targetHeight = p.absoluteHeight ? p.absoluteHeight : outputHeight; + if(p.relativeWidth) targetWidth = sources[0].width * p.relativeWidth; + if(p.relativeHeight) targetHeight = sources[0].height * p.relativeHeight; + + p.size(targetWidth, targetHeight); + glUseProgram(p.program); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, p.framebuffer); + + glrUniform1i("phase", p.phase); + glrUniform1i("historyLength", history.size()); + glrUniform1i("sourceLength", sources.size()); + glrUniform1i("pixmapLength", p.pixmaps.size()); + glrUniform4f("targetSize", targetWidth, targetHeight, 1.0 / targetWidth, 1.0 / targetHeight); + glrUniform4f("outputSize", outputWidth, outputHeight, 1.0 / outputWidth, 1.0 / outputHeight); + + unsigned aid = 0; + for(auto& frame : history) { + glrUniform1i({"history[", aid, "]"}, aid); + glrUniform4f({"historySize[", aid, "]"}, frame.width, frame.height, 1.0 / frame.width, 1.0 / frame.height); + glActiveTexture(GL_TEXTURE0 + (aid++)); + glBindTexture(GL_TEXTURE_2D, frame.texture); + glrParameters(frame.filter, frame.wrap); + } + + unsigned bid = 0; + for(auto& source : sources) { + glrUniform1i({"source[", bid, "]"}, aid + bid); + glrUniform4f({"sourceSize[", bid, "]"}, source.width, source.height, 1.0 / source.width, 1.0 / source.height); + glActiveTexture(GL_TEXTURE0 + aid + (bid++)); + glBindTexture(GL_TEXTURE_2D, source.texture); + glrParameters(source.filter, source.wrap); + } + + unsigned cid = 0; + for(auto& pixmap : p.pixmaps) { + glrUniform1i({"pixmap[", cid, "]"}, aid + bid + cid); + glrUniform4f({"pixmapSize[", bid, "]"}, pixmap.width, pixmap.height, 1.0 / pixmap.width, 1.0 / pixmap.height); + glActiveTexture(GL_TEXTURE0 + aid + bid + (cid++)); + glBindTexture(GL_TEXTURE_2D, pixmap.texture); + glrParameters(pixmap.filter, pixmap.wrap); + } + + glActiveTexture(GL_TEXTURE0); + glrParameters(sources[0].filter, sources[0].wrap); + p.render(sources[0].width, sources[0].height, targetWidth, targetHeight); + glBindTexture(GL_TEXTURE_2D, p.texture); + + p.phase = (p.phase + 1) % p.modulo; + sources.prepend({p.texture, p.width, p.height, p.filter, p.wrap}); + } + + unsigned targetWidth = absoluteWidth ? absoluteWidth : outputWidth; + unsigned targetHeight = absoluteHeight ? absoluteHeight : outputHeight; + if(relativeWidth) targetWidth = sources[0].width * relativeWidth; + if(relativeHeight) targetHeight = sources[0].height * relativeHeight; + + glUseProgram(program); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + glrUniform1i("source[0]", 0); + glrUniform4f("targetSize", targetWidth, targetHeight, 1.0 / targetWidth, 1.0 / targetHeight); + glrUniform4f("outputSize", outputWidth, outputHeight, 1.0 / outputWidth, 1.0 / outputHeight); + + glrParameters(sources[0].filter, sources[0].wrap); + render(sources[0].width, sources[0].height, outputWidth, outputHeight); + + if(history.size() > 0) { + OpenGLTexture frame = history.takeLast(); + + glBindTexture(GL_TEXTURE_2D, frame.texture); + if(width == frame.width && height == frame.height) { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, getFormat(), getType(), buffer); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, format, frame.width = width, frame.height = height, 0, getFormat(), getType(), buffer); + } + + history.prepend(frame); + } +} + +bool OpenGL::init() { + if(!OpenGLBind()) return false; + + glDisable(GL_ALPHA_TEST); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_POLYGON_SMOOTH); + glDisable(GL_STENCIL_TEST); + + glEnable(GL_DITHER); + glEnable(GL_TEXTURE_2D); + + program = glCreateProgram(); + vertex = glrCreateShader(program, GL_VERTEX_SHADER, OpenGLOutputVertexShader); +//geometry = glrCreateShader(program, GL_GEOMETRY_SHADER, OpenGLGeometryShader); + fragment = glrCreateShader(program, GL_FRAGMENT_SHADER, OpenGLFragmentShader); + OpenGLSurface::allocate(); + glrLinkProgram(program); + + shader(nullptr); + return true; +} + +void OpenGL::term() { + shader(nullptr); //release shader resources (eg frame[] history) + OpenGLSurface::release(); + if(buffer) { delete[] buffer; buffer = nullptr; } +} diff --git a/src/burner/qt/ruby/video/opengl/opengl.hpp b/src/burner/qt/ruby/video/opengl/opengl.hpp new file mode 100755 index 000000000..4bbc473b2 --- /dev/null +++ b/src/burner/qt/ruby/video/opengl/opengl.hpp @@ -0,0 +1,97 @@ +#if defined(PLATFORM_X) + #include + #include + #define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name)) +#elif defined(PLATFORM_MACOSX) + #include + #include +#elif defined(PLATFORM_WINDOWS) + #include + #include + #define glGetProcAddress(name) wglGetProcAddress(name) +#else + #error "ruby::OpenGL: unsupported platform" +#endif + +namespace ruby { + +#include "bind.hpp" +#include "shaders.hpp" +#include "utility.hpp" + +struct OpenGL; + +struct OpenGLTexture { + GLuint texture = 0; + unsigned width = 0; + unsigned height = 0; + GLuint format = GL_RGBA8; + GLuint filter = GL_LINEAR; + GLuint wrap = GL_CLAMP_TO_BORDER; + + GLuint getFormat() const; + GLuint getType() const; +}; + +struct OpenGLSurface : OpenGLTexture { + GLuint program = 0; + GLuint framebuffer = 0; + GLuint vao = 0; + GLuint vbo[3] = {0, 0, 0}; + GLuint vertex = 0; + GLuint geometry = 0; + GLuint fragment = 0; + uint32_t* buffer = nullptr; + + void allocate(); + void size(unsigned width, unsigned height); + void release(); + void render(unsigned sourceWidth, unsigned sourceHeight, unsigned targetWidth, unsigned targetHeight); +}; + +struct OpenGLProgram : OpenGLSurface { + unsigned phase = 0; //frame counter + unsigned modulo = 0; //frame counter modulus + unsigned absoluteWidth = 0; + unsigned absoluteHeight = 0; + double relativeWidth = 0; + double relativeHeight = 0; + vector pixmaps; + + void bind(OpenGL* instance, const Markup::Node& node, const string& pathname); + void parse(OpenGL* instance, string& source); + void release(); +}; + +struct OpenGL : OpenGLProgram { + vector programs; + vector history; + GLuint inputFormat = GL_RGBA8; + unsigned outputWidth = 0; + unsigned outputHeight = 0; + struct Setting { + string name; + string value; + bool operator< (const Setting& source) { return name < source.name; } + bool operator==(const Setting& source) { return name == source.name; } + Setting() {} + Setting(const string& name) : name(name) {} + Setting(const string& name, const string& value) : name(name), value(value) {} + }; + set settings; + + void shader(const char* pathname); + void allocateHistory(unsigned size); + bool lock(uint32_t*& data, unsigned& pitch); + void clear(); + void refresh(); + bool init(); + void term(); +}; + +#include "texture.hpp" +#include "surface.hpp" +#include "program.hpp" +#include "main.hpp" + +} diff --git a/src/burner/qt/ruby/video/opengl/program.hpp b/src/burner/qt/ruby/video/opengl/program.hpp new file mode 100755 index 000000000..21950846b --- /dev/null +++ b/src/burner/qt/ruby/video/opengl/program.hpp @@ -0,0 +1,108 @@ +void OpenGLProgram::bind(OpenGL* instance, const Markup::Node& node, const string& pathname) { + filter = glrFilter(node["filter"].text()); + wrap = glrWrap(node["wrap"].text()); + modulo = glrModulo(node["modulo"].integer()); + + string w = node["width"].text(), h = node["height"].text(); + if(w.endsWith("%")) relativeWidth = real(w.rtrim<1>("%")) / 100.0; + else absoluteWidth = decimal(w); + if(h.endsWith("%")) relativeHeight = real(h.rtrim<1>("%")) / 100.0; + else absoluteHeight = decimal(h); + + format = glrFormat(node["format"].text()); + + program = glCreateProgram(); + glGenFramebuffers(1, &framebuffer); + + if(file::exists({pathname, node["vertex"].text()})) { + string source = file::read({pathname, node["vertex"].text()}); + parse(instance, source); + vertex = glrCreateShader(program, GL_VERTEX_SHADER, source); + } else { + vertex = glrCreateShader(program, GL_VERTEX_SHADER, OpenGLVertexShader); + } + + if(file::exists({pathname, node["geometry"].text()})) { + string source = file::read({pathname, node["geometry"].text()}); + parse(instance, source); + geometry = glrCreateShader(program, GL_GEOMETRY_SHADER, source); + } else { + //geometry shaders, when attached, must pass all vertex output through to the fragment shaders + //geometry = glrCreateShader(program, GL_GEOMETRY_SHADER, OpenGLGeometryShader); + } + + if(file::exists({pathname, node["fragment"].text()})) { + string source = file::read({pathname, node["fragment"].text()}); + parse(instance, source); + fragment = glrCreateShader(program, GL_FRAGMENT_SHADER, source); + } else { + fragment = glrCreateShader(program, GL_FRAGMENT_SHADER, OpenGLFragmentShader); + } + + for(auto& leaf : node.find("pixmap")) { + nall::image image({pathname, leaf.text()}); + image.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + if(image.empty()) continue; + + GLuint texture; + glGenTextures(1, &texture); + + unsigned n = pixmaps.size(); + pixmaps(n).texture = texture; + pixmaps(n).width = image.width; + pixmaps(n).height = image.height; + pixmaps(n).format = format; + pixmaps(n).filter = filter; + pixmaps(n).wrap = wrap; + if(leaf["format"].exists()) pixmaps(n).format = glrFormat(leaf["format"].text()); + if(leaf["filter"].exists()) pixmaps(n).filter = glrFilter(leaf["filter"].text()); + if(leaf["wrap"].exists()) pixmaps(n).wrap = glrWrap(leaf["wrap"].text()); + + unsigned w = glrSize(image.width), h = glrSize(image.height); + uint32_t* buffer = new uint32_t[w * h](); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, pixmaps(n).format, w, h, 0, pixmaps(n).getFormat(), pixmaps(n).getType(), buffer); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image.width, image.height, getFormat(), getType(), image.data); + delete[] buffer; + } + + OpenGLSurface::allocate(); + glrLinkProgram(program); +} + +//apply manifest settings to shader source #in tags +void OpenGLProgram::parse(OpenGL* instance, string& source) { + lstring lines = source.split("\n"); + for(auto& line : lines) { + string s = line; + if(auto position = s.find("//")) s.resize(position()); //strip comments + s.strip(); //remove extraneous whitespace + if(s.match("#in ?*")) { + s.ltrim<1>("#in ").strip(); + if(auto setting = instance->settings.find({s})) { + line = {"#define ", setting().name, " ", setting().value}; + } else { + line.reset(); //undefined variable (test in source with #ifdef) + } + } + } + source = lines.merge("\n"); +} + +void OpenGLProgram::release() { + OpenGLSurface::release(); + for(auto& pixmap : pixmaps) glDeleteTextures(1, &pixmap.texture); + pixmaps.reset(); + + width = 0; + height = 0; + format = GL_RGBA8; + filter = GL_LINEAR; + wrap = GL_CLAMP_TO_BORDER; + phase = 0; + modulo = 0; + absoluteWidth = 0; + absoluteHeight = 0; + relativeWidth = 0; + relativeHeight = 0; +} diff --git a/src/burner/qt/ruby/video/opengl/shaders.hpp b/src/burner/qt/ruby/video/opengl/shaders.hpp new file mode 100755 index 000000000..91f9efce6 --- /dev/null +++ b/src/burner/qt/ruby/video/opengl/shaders.hpp @@ -0,0 +1,91 @@ +static string OpenGLOutputVertexShader = R"( + #version 150 + + uniform vec4 targetSize; + uniform vec4 outputSize; + + in vec2 texCoord; + + out Vertex { + vec2 texCoord; + } vertexOut; + + void main() { + //center image within output window + if(gl_VertexID == 0 || gl_VertexID == 2) { + gl_Position.x = -(targetSize.x / outputSize.x); + } else { + gl_Position.x = +(targetSize.x / outputSize.x); + } + + //center and flip vertically (buffer[0, 0] = top-left; OpenGL[0, 0] = bottom-left) + if(gl_VertexID == 0 || gl_VertexID == 1) { + gl_Position.y = +(targetSize.y / outputSize.y); + } else { + gl_Position.y = -(targetSize.y / outputSize.y); + } + + //align image to even pixel boundary to prevent aliasing + vec2 align = fract((outputSize.xy + targetSize.xy) / 2.0) * 2.0; + gl_Position.xy -= align / outputSize.xy; + gl_Position.zw = vec2(0.0, 1.0); + + vertexOut.texCoord = texCoord; + } +)"; + +static string OpenGLVertexShader = R"( + #version 150 + + in vec4 position; + in vec2 texCoord; + + out Vertex { + vec2 texCoord; + } vertexOut; + + void main() { + gl_Position = position; + vertexOut.texCoord = texCoord; + } +)"; + +static string OpenGLGeometryShader = R"( + #version 150 + + layout(triangles) in; + layout(triangle_strip, max_vertices = 3) out; + + in Vertex { + vec2 texCoord; + } vertexIn[]; + + out Vertex { + vec2 texCoord; + }; + + void main() { + for(int i = 0; i < gl_in.length(); i++) { + gl_Position = gl_in[i].gl_Position; + texCoord = vertexIn[i].texCoord; + EmitVertex(); + } + EndPrimitive(); + } +)"; + +static string OpenGLFragmentShader = R"( + #version 150 + + uniform sampler2D source[]; + + in Vertex { + vec2 texCoord; + }; + + out vec4 fragColor; + + void main() { + fragColor = texture(source[0], texCoord); + } +)"; diff --git a/src/burner/qt/ruby/video/opengl/surface.hpp b/src/burner/qt/ruby/video/opengl/surface.hpp new file mode 100755 index 000000000..9f368dec1 --- /dev/null +++ b/src/burner/qt/ruby/video/opengl/surface.hpp @@ -0,0 +1,114 @@ +void OpenGLSurface::allocate() { + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glGenBuffers(3, &vbo[0]); +} + +void OpenGLSurface::size(unsigned w, unsigned h) { + if(width == w && height == h) return; + width = w, height = h; + w = glrSize(w), h = glrSize(h); + + if(texture) { glDeleteTextures(1, &texture); texture = 0; } + if(buffer) { delete[] buffer; buffer = nullptr; } + + buffer = new uint32_t[w * h](); + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, getFormat(), getType(), buffer); + + if(framebuffer) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + delete[] buffer; + buffer = nullptr; + } +} + +void OpenGLSurface::release() { + if(vbo[0]) { glDeleteBuffers(3, &vbo[0]); for(auto &o : vbo) o = 0; } + if(vao) { glDeleteVertexArrays(1, &vao); vao = 0; } + if(vertex) { glDetachShader(program, vertex); glDeleteShader(vertex); vertex = 0; } + if(geometry) { glDetachShader(program, geometry); glDeleteShader(geometry); geometry = 0; } + if(fragment) { glDetachShader(program, fragment); glDeleteShader(fragment); fragment = 0; } + if(texture) { glDeleteTextures(1, &texture); texture = 0; } + if(framebuffer) { glDeleteFramebuffers(1, &framebuffer); framebuffer = 0; } + if(program) { glDeleteProgram(program); program = 0; } + width = 0, height = 0; +} + +void OpenGLSurface::render(unsigned sourceWidth, unsigned sourceHeight, unsigned targetWidth, unsigned targetHeight) { + glViewport(0, 0, targetWidth, targetHeight); + + float w = (float)sourceWidth / (float)glrSize(sourceWidth); + float h = (float)sourceHeight / (float)glrSize(sourceHeight); + float u = (float)targetWidth, v = (float)targetHeight; + GLint location; + + GLfloat modelView[] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }; + + GLfloat projection[] = { + 2.0f/u, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f/v, 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, 1.0f, + }; + + GLfloat modelViewProjection[4 * 4]; + Matrix::Multiply(modelViewProjection, modelView, 4, 4, projection, 4, 4); + + GLfloat vertices[] = { + 0, 0, 0, 1, + u, 0, 0, 1, + 0, v, 0, 1, + u, v, 0, 1, + }; + + GLfloat positions[4 * 4]; + for(unsigned n = 0; n < 16; n += 4) { + Matrix::Multiply(&positions[n], &vertices[n], 1, 4, modelViewProjection, 4, 4); + } + + GLfloat texCoords[] = { + 0, 0, + w, 0, + 0, h, + w, h, + }; + + glrUniformMatrix4fv("modelView", modelView); + glrUniformMatrix4fv("projection", projection); + glrUniformMatrix4fv("modelViewProjection", modelViewProjection); + + glBindVertexArray(vao); + + glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); + glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(GLfloat), vertices, GL_STATIC_DRAW); + GLuint locationVertex = glGetAttribLocation(program, "vertex"); + glEnableVertexAttribArray(locationVertex); + glVertexAttribPointer(locationVertex, 4, GL_FLOAT, GL_FALSE, 0, 0); + + glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); + glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(GLfloat), positions, GL_STATIC_DRAW); + GLuint locationPosition = glGetAttribLocation(program, "position"); + glEnableVertexAttribArray(locationPosition); + glVertexAttribPointer(locationPosition, 4, GL_FLOAT, GL_FALSE, 0, 0); + + glBindBuffer(GL_ARRAY_BUFFER, vbo[2]); + glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), texCoords, GL_STATIC_DRAW); + GLuint locationTexCoord = glGetAttribLocation(program, "texCoord"); + glEnableVertexAttribArray(locationTexCoord); + glVertexAttribPointer(locationTexCoord, 2, GL_FLOAT, GL_FALSE, 0, 0); + + glBindFragDataLocation(program, 0, "fragColor"); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(locationVertex); + glDisableVertexAttribArray(locationPosition); + glDisableVertexAttribArray(locationTexCoord); +} diff --git a/src/burner/qt/ruby/video/opengl/texture.hpp b/src/burner/qt/ruby/video/opengl/texture.hpp new file mode 100755 index 000000000..8685bef7e --- /dev/null +++ b/src/burner/qt/ruby/video/opengl/texture.hpp @@ -0,0 +1,12 @@ +GLuint OpenGLTexture::getFormat() const { + if(format == GL_R32I) return GL_RED_INTEGER; + if(format == GL_R32UI) return GL_RED_INTEGER; + return GL_BGRA; +} + +GLuint OpenGLTexture::getType() const { + if(format == GL_R32I) return GL_UNSIGNED_INT; + if(format == GL_R32UI) return GL_UNSIGNED_INT; + if(format == GL_RGB10_A2) return GL_UNSIGNED_INT_2_10_10_10_REV; + return GL_UNSIGNED_INT_8_8_8_8_REV; +} diff --git a/src/burner/qt/ruby/video/opengl/utility.hpp b/src/burner/qt/ruby/video/opengl/utility.hpp new file mode 100755 index 000000000..83c8b0c21 --- /dev/null +++ b/src/burner/qt/ruby/video/opengl/utility.hpp @@ -0,0 +1,106 @@ +static unsigned glrSize(unsigned size) { + return size; +//return bit::round(size); //return nearest power of two +} + +static GLuint glrFormat(const string& format) { + if(format == "r32i" ) return GL_R32I; + if(format == "r32ui" ) return GL_R32UI; + if(format == "rgba8" ) return GL_RGBA8; + if(format == "rgb10a2") return GL_RGB10_A2; + if(format == "rgba12" ) return GL_RGBA12; + if(format == "rgba16" ) return GL_RGBA16; + if(format == "rgba16f") return GL_RGBA16F; + if(format == "rgba32f") return GL_RGBA32F; + return GL_RGBA8; +} + +static GLuint glrFilter(const string& filter) { + if(filter == "nearest") return GL_NEAREST; + if(filter == "linear" ) return GL_LINEAR; + return GL_LINEAR; +} + +static GLuint glrWrap(const string& wrap) { + if(wrap == "border") return GL_CLAMP_TO_BORDER; + if(wrap == "edge" ) return GL_CLAMP_TO_EDGE; + if(wrap == "repeat") return GL_REPEAT; + return GL_CLAMP_TO_BORDER; +} + +static unsigned glrModulo(unsigned modulo) { + if(modulo) return modulo; + return 300; //divisible by 2, 3, 4, 5, 6, 10, 12, 15, 20, 25, 30, 50, 60, 100, 150 +} + +static GLuint glrProgram() { + GLuint program = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&program); + return program; +} + +static void glrUniform1i(const string& name, GLint value) { + GLint location = glGetUniformLocation(glrProgram(), name); + glUniform1i(location, value); +} + +static void glrUniform4f(const string& name, GLfloat value0, GLfloat value1, GLfloat value2, GLfloat value3) { + GLint location = glGetUniformLocation(glrProgram(), name); + glUniform4f(location, value0, value1, value2, value3); +} + +static void glrUniformMatrix4fv(const string& name, GLfloat *values) { + GLint location = glGetUniformLocation(glrProgram(), name); + glUniformMatrix4fv(location, 1, GL_FALSE, values); +} + +static void glrParameters(GLuint filter, GLuint wrap) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap); +} + +static GLuint glrCreateShader(GLuint program, GLuint type, const char* source) { + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &source, 0); + glCompileShader(shader); + GLint result = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &result); + if(result == GL_FALSE) { + GLint length = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); + char text[length + 1]; + glGetShaderInfoLog(shader, length, &length, text); + text[length] = 0; + print("[ruby::OpenGL: shader compiler error]\n", (const char*)text, "\n\n"); + return 0; + } + glAttachShader(program, shader); + return shader; +} + +static void glrLinkProgram(GLuint program) { + glLinkProgram(program); + GLint result = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &result); + if(result == GL_FALSE) { + GLint length = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); + char text[length + 1]; + glGetProgramInfoLog(program, length, &length, text); + text[length] = 0; + print("[ruby::OpenGL: shader linker error]\n", (const char*)text, "\n\n"); + } + glValidateProgram(program); + result = GL_FALSE; + glGetProgramiv(program, GL_VALIDATE_STATUS, &result); + if(result == GL_FALSE) { + GLint length = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); + char text[length + 1]; + glGetProgramInfoLog(program, length, &length, text); + text[length] = 0; + print("[ruby::OpenGL: shader validation error]\n", (const char*)text, "\n\n"); + } +} diff --git a/src/burner/qt/ruby/video/sdl.cpp b/src/burner/qt/ruby/video/sdl.cpp new file mode 100755 index 000000000..0079f87d2 --- /dev/null +++ b/src/burner/qt/ruby/video/sdl.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include + +namespace ruby { + +struct pVideoSDL { + Display* display; + SDL_Surface* screen; + SDL_Surface* buffer; + unsigned iwidth, iheight; + + struct { + uintptr_t handle; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + iwidth = max(width, iwidth); + iheight = max(height, iheight); + + if(buffer) SDL_FreeSurface(buffer); + buffer = SDL_CreateRGBSurface( + SDL_SWSURFACE, iwidth, iheight, 32, + 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 + ); + } + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); + pitch = buffer->pitch; + return data = (uint32_t*)buffer->pixels; + } + + void unlock() { + if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + } + + void clear() { + if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); + for(unsigned y = 0; y < iheight; y++) { + uint32_t* data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2); + for(unsigned x = 0; x < iwidth; x++) *data++ = 0xff000000; + } + if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + refresh(); + } + + void refresh() { + //ruby input is X8R8G8B8, top 8-bits are ignored. + //as SDL forces us to use a 32-bit buffer, we must set alpha to 255 (full opacity) + //to prevent blending against the window beneath when X window visual is 32-bits. + if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); + for(unsigned y = 0; y < settings.height; y++) { + uint32_t *data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2); + for(unsigned x = 0; x < settings.width; x++) *data++ |= 0xff000000; + } + if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + + XWindowAttributes attributes; + XGetWindowAttributes(display, settings.handle, &attributes); + + SDL_Rect src, dest; + + src.x = 0; + src.y = 0; + src.w = settings.width; + src.h = settings.height; + + dest.x = 0; + dest.y = 0; + dest.w = attributes.width; + dest.h = attributes.height; + + SDL_SoftStretch(buffer, &src, screen, &dest); + SDL_UpdateRect(screen, dest.x, dest.y, dest.w, dest.h); + } + + bool init() { + display = XOpenDisplay(0); + + char env[512]; + sprintf(env, "SDL_WINDOWID=%ld", (long int)settings.handle); + putenv(env); + + SDL_InitSubSystem(SDL_INIT_VIDEO); + screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE); + XUndefineCursor(display, settings.handle); + + buffer = 0; + iwidth = 0; + iheight = 0; + resize(settings.width = 256, settings.height = 256); + + return true; + } + + void term() { + XCloseDisplay(display); + SDL_FreeSurface(buffer); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + } + + pVideoSDL() { + settings.handle = 0; + } +}; + +DeclareVideo(SDL) + +}; diff --git a/src/burner/qt/ruby/video/wgl.cpp b/src/burner/qt/ruby/video/wgl.cpp new file mode 100755 index 000000000..a5801f96e --- /dev/null +++ b/src/burner/qt/ruby/video/wgl.cpp @@ -0,0 +1,160 @@ +#include "opengl/opengl.hpp" + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 + +namespace ruby { + +struct pVideoWGL : OpenGL { + HGLRC (APIENTRY* wglCreateContextAttribs)(HDC, HGLRC, const int*) = nullptr; + BOOL (APIENTRY* wglSwapInterval)(int) = nullptr; + + HDC display; + HGLRC wglcontext; + HWND window; + HINSTANCE glwindow; + + struct { + HWND handle; + bool synchronize; + unsigned filter; + string shader; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + if(wglcontext) { + init(); + OpenGL::shader(settings.shader); + if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; + } + } + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; + return true; + } + + if(name == Video::Shader) { + settings.shader = any_cast(value); + OpenGL::shader(settings.shader); + if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST; + return true; + } + + return false; + } + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + OpenGL::size(width, height); + return OpenGL::lock(data, pitch); + } + + void unlock() { + } + + void clear() { + OpenGL::clear(); + SwapBuffers(display); + } + + void refresh() { + RECT rc; + GetClientRect(settings.handle, &rc); + outputWidth = rc.right - rc.left, outputHeight = rc.bottom - rc.top; + OpenGL::refresh(); + SwapBuffers(display); + } + + bool init() { + term(); + + GLuint pixel_format; + PIXELFORMATDESCRIPTOR pfd; + memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + + display = GetDC(settings.handle); + pixel_format = ChoosePixelFormat(display, &pfd); + SetPixelFormat(display, pixel_format, &pfd); + + wglcontext = wglCreateContext(display); + wglMakeCurrent(display, wglcontext); + + wglCreateContextAttribs = (HGLRC (APIENTRY*)(HDC, HGLRC, const int*))glGetProcAddress("wglCreateContextAttribsARB"); + wglSwapInterval = (BOOL (APIENTRY*)(int))glGetProcAddress("wglSwapIntervalEXT"); + + if(wglCreateContextAttribs) { + int attributes[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 2, + 0 + }; + HGLRC context = wglCreateContextAttribs(display, 0, attributes); + if(context) { + wglMakeCurrent(NULL, NULL); + wglDeleteContext(wglcontext); + wglMakeCurrent(display, wglcontext = context); + } + } + + if(wglSwapInterval) { + wglSwapInterval(settings.synchronize); + } + + OpenGL::init(); + return true; + } + + void term() { + OpenGL::term(); + + if(wglcontext) { + wglDeleteContext(wglcontext); + wglcontext = 0; + } + } + + pVideoWGL() { + settings.handle = 0; + settings.synchronize = false; + settings.filter = 0; + + window = 0; + wglcontext = 0; + glwindow = 0; + } + + ~pVideoWGL() { term(); } +}; + +DeclareVideo(WGL) + +}; diff --git a/src/burner/qt/ruby/video/xshm.cpp b/src/burner/qt/ruby/video/xshm.cpp new file mode 100755 index 000000000..bf8a26903 --- /dev/null +++ b/src/burner/qt/ruby/video/xshm.cpp @@ -0,0 +1,228 @@ +#include +#include + +namespace ruby { + +struct pVideoXShm { + struct Device { + Display* display = nullptr; + int screen; + int depth; + Visual* visual = nullptr; + Window window; + + + XShmSegmentInfo shmInfo; + XImage* image = nullptr; + uint32_t* buffer = nullptr; + unsigned width, height; + } device; + + struct Settings { + uintptr_t handle; + unsigned depth = 24; + + uint32_t* buffer = nullptr; + unsigned width, height; + } settings; + + struct Color { + unsigned depth; + unsigned shift; + + unsigned idepth; + unsigned ishift; + } red, green, blue; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Depth) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return settings.handle; + if(name == Video::Depth) return settings.depth; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + if(name == Video::Depth) { + return setDepth(any_cast(value)); + } + return false; + } + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + if(settings.buffer == nullptr || settings.width != width || settings.height != height) { + if(settings.buffer) delete[] settings.buffer; + settings.width = width, settings.height = height; + settings.buffer = new uint32_t[width * height](); + } + + data = settings.buffer; + pitch = settings.width * sizeof(uint32_t); + return true; + } + + void unlock() { + } + + void clear() { + if(settings.buffer == nullptr) return; + memset(settings.buffer, 0, settings.width * settings.height * sizeof(uint32_t)); + refresh(); + } + + void refresh() { + if(settings.buffer == nullptr) return; + size(); + + float xRatio = (float)settings.width / (float)device.width; + float yRatio = (float)settings.height / (float)device.height; + float yStep = 0; + for(unsigned y = 0; y < device.height; y++) { + uint32_t* sp = settings.buffer + (unsigned)yStep * settings.width; + uint32_t* dp = device.buffer + y * device.width; + yStep += yRatio; + float xStep = 0; + for(unsigned x = 0; x < device.width; x++) { + uint32_t color = sp[(unsigned)xStep]; + xStep += xRatio; + unsigned r = (color >> red.ishift ) & ((1 << red.idepth ) - 1); + unsigned g = (color >> green.ishift) & ((1 << green.idepth) - 1); + unsigned b = (color >> blue.ishift ) & ((1 << blue.idepth ) - 1); + *dp++ = image::normalize(r, red.idepth, red.depth ) << red.shift + | image::normalize(g, green.idepth, green.depth) << green.shift + | image::normalize(b, blue.idepth, blue.depth ) << blue.shift; + } + } + + GC gc = XCreateGC(device.display, device.window, 0, 0); + XShmPutImage( + device.display, device.window, gc, device.image, + 0, 0, 0, 0, device.width, device.height, False + ); + XFreeGC(device.display, gc); + XFlush(device.display); + } + + bool init() { + device.display = XOpenDisplay(0); + device.screen = DefaultScreen(device.display); + + XWindowAttributes getAttributes; + XGetWindowAttributes(device.display, (Window)settings.handle, &getAttributes); + device.depth = getAttributes.depth; + device.visual = getAttributes.visual; + unsigned visualID = XVisualIDFromVisual(device.visual); + + XVisualInfo visualTemplate = {0}; + visualTemplate.screen = device.screen; + visualTemplate.depth = device.depth; + int visualsMatched = 0; + XVisualInfo* visualList = XGetVisualInfo(device.display, VisualScreenMask | VisualDepthMask, &visualTemplate, &visualsMatched); + for(unsigned n = 0; n < visualsMatched; n++) { + auto& v = visualList[n]; + if(v.visualid == visualID) { + red.depth = bit::count(v.red_mask), red.shift = bit::first(v.red_mask); + green.depth = bit::count(v.green_mask), green.shift = bit::first(v.green_mask); + blue.depth = bit::count(v.blue_mask), blue.shift = bit::first(v.blue_mask); + break; + } + } + XFree(visualList); + setDepth(settings.depth); + + XSetWindowAttributes setAttributes = {0}; + setAttributes.border_pixel = 0; + device.window = XCreateWindow(device.display, (Window)settings.handle, + 0, 0, 256, 256, 0, + getAttributes.depth, InputOutput, getAttributes.visual, + CWBorderPixel, &setAttributes + ); + XSetWindowBackground(device.display, device.window, 0); + XMapWindow(device.display, device.window); + XFlush(device.display); + + while(XPending(device.display)) { + XEvent event; + XNextEvent(device.display, &event); + } + + if(size() == false) return false; + return true; + } + + void term() { + free(); + + if(device.display) { XCloseDisplay(device.display); device.display = nullptr; } + } + + ~pVideoXShm() { + term(); + } + +//internal: + bool setDepth(unsigned depth) { + if(depth == 24) { + settings.depth = 24; + red.idepth = 8, red.ishift = 16; + green.idepth = 8, green.ishift = 8; + blue.idepth = 8, blue.ishift = 0; + return true; + } + + if(depth == 30) { + settings.depth = 30; + red.idepth = 10, red.ishift = 20; + green.idepth = 10, green.ishift = 10; + blue.idepth = 10, blue.ishift = 0; + return true; + } + + return false; + } + + bool size() { + XWindowAttributes windowAttributes; + XGetWindowAttributes(device.display, settings.handle, &windowAttributes); + + if(device.buffer && device.width == windowAttributes.width && device.height == windowAttributes.height) return true; + device.width = windowAttributes.width, device.height = windowAttributes.height; + XResizeWindow(device.display, device.window, device.width, device.height); + free(); + + //create + device.shmInfo.shmid = shmget(IPC_PRIVATE, device.width * device.height * sizeof(uint32_t), IPC_CREAT | 0777); + if(device.shmInfo.shmid < 0) return false; + + device.shmInfo.shmaddr = (char*)shmat(device.shmInfo.shmid, 0, 0); + device.shmInfo.readOnly = False; + XShmAttach(device.display, &device.shmInfo); + device.buffer = (uint32_t*)device.shmInfo.shmaddr; + device.image = XShmCreateImage(device.display, device.visual, device.depth, + ZPixmap, device.shmInfo.shmaddr, &device.shmInfo, device.width, device.height + ); + + return true; + } + + void free() { + if(device.buffer == nullptr) return; + device.buffer = nullptr; + XShmDetach(device.display, &device.shmInfo); + XDestroyImage(device.image); + shmdt(device.shmInfo.shmaddr); + shmctl(device.shmInfo.shmid, IPC_RMID, 0); + } +}; + +DeclareVideo(XShm) + +} diff --git a/src/burner/qt/ruby/video/xv.cpp b/src/burner/qt/ruby/video/xv.cpp new file mode 100755 index 000000000..2a73ea1b3 --- /dev/null +++ b/src/burner/qt/ruby/video/xv.cpp @@ -0,0 +1,499 @@ +#include +#include +#include +#include +#include + +extern "C" XvImage* XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*); + +namespace ruby { + +struct pVideoXv { + uint32_t* buffer; + uint8_t* ytable; + uint8_t* utable; + uint8_t* vtable; + + enum XvFormat { + XvFormatRGB32, + XvFormatRGB24, + XvFormatRGB16, + XvFormatRGB15, + XvFormatYUY2, + XvFormatUYVY, + XvFormatUnknown + }; + + struct { + Display* display; + GC gc; + Window window; + Colormap colormap; + XShmSegmentInfo shminfo; + + int port; + int depth; + int visualid; + + XvImage* image; + XvFormat format; + uint32_t fourcc; + + unsigned width; + unsigned height; + } device; + + struct { + Window handle; + bool synchronize; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) { + return XInternAtom(XOpenDisplay(0), "XV_SYNC_TO_VBLANK", true) != None; + } + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + Display* display = XOpenDisplay(0); + Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true); + if(atom != None && device.port >= 0) { + settings.synchronize = any_cast(value); + XvSetPortAttribute(display, device.port, atom, settings.synchronize); + return true; + } + return false; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(device.width >= width && device.height >= height) return; + device.width = max(width, device.width); + device.height = max(height, device.height); + + XShmDetach(device.display, &device.shminfo); + shmdt(device.shminfo.shmaddr); + shmctl(device.shminfo.shmid, IPC_RMID, NULL); + XFree(device.image); + delete[] buffer; + + device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo); + + device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777); + device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0); + device.shminfo.readOnly = false; + XShmAttach(device.display, &device.shminfo); + + buffer = new uint32_t[device.width * device.height]; + } + + bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) { + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + pitch = device.width * 4; + return data = buffer; + } + + void unlock() { + } + + void clear() { + memset(buffer, 0, device.width * device.height * sizeof(uint32_t)); + //clear twice in case video is double buffered ... + refresh(); + refresh(); + } + + void refresh() { + unsigned width = settings.width; + unsigned height = settings.height; + + XWindowAttributes target; + XGetWindowAttributes(device.display, device.window, &target); + + //we must ensure that the child window is the same size as the parent window. + //unfortunately, we cannot hook the parent window resize event notification, + //as we did not create the parent window, nor have any knowledge of the toolkit used. + //therefore, query each window size and resize as needed. + XWindowAttributes parent; + XGetWindowAttributes(device.display, settings.handle, &parent); + if(target.width != parent.width || target.height != parent.height) { + XResizeWindow(device.display, device.window, parent.width, parent.height); + } + + //update target width and height attributes + XGetWindowAttributes(device.display, device.window, &target); + + switch(device.format) { + case XvFormatRGB32: render_rgb32(width, height); break; + case XvFormatRGB24: render_rgb24(width, height); break; + case XvFormatRGB16: render_rgb16(width, height); break; + case XvFormatRGB15: render_rgb15(width, height); break; + case XvFormatYUY2: render_yuy2 (width, height); break; + case XvFormatUYVY: render_uyvy (width, height); break; + } + + XvShmPutImage(device.display, device.port, device.window, device.gc, device.image, + 0, 0, width, height, + 0, 0, target.width, target.height, + true); + } + + bool init() { + device.display = XOpenDisplay(0); + + if(!XShmQueryExtension(device.display)) { + fprintf(stderr, "VideoXv: XShm extension not found.\n"); + return false; + } + + //find an appropriate Xv port + device.port = -1; + XvAdaptorInfo* adaptor_info; + unsigned adaptor_count; + XvQueryAdaptors(device.display, DefaultRootWindow(device.display), &adaptor_count, &adaptor_info); + for(unsigned i = 0; i < adaptor_count; i++) { + //find adaptor that supports both input (memory->drawable) and image (drawable->screen) masks + if(adaptor_info[i].num_formats < 1) continue; + if(!(adaptor_info[i].type & XvInputMask)) continue; + if(!(adaptor_info[i].type & XvImageMask)) continue; + + device.port = adaptor_info[i].base_id; + device.depth = adaptor_info[i].formats->depth; + device.visualid = adaptor_info[i].formats->visual_id; + break; + } + XvFreeAdaptorInfo(adaptor_info); + if(device.port < 0) { + fprintf(stderr, "VideoXv: failed to find valid XvPort.\n"); + return false; + } + + //create child window to attach to parent window. + //this is so that even if parent window visual depth doesn't match Xv visual + //(common with composited windows), Xv can still render to child window. + XWindowAttributes window_attributes; + XGetWindowAttributes(device.display, settings.handle, &window_attributes); + + XVisualInfo visualtemplate; + visualtemplate.visualid = device.visualid; + visualtemplate.screen = DefaultScreen(device.display); + visualtemplate.depth = device.depth; + visualtemplate.visual = 0; + int visualmatches = 0; + XVisualInfo *visualinfo = XGetVisualInfo(device.display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches); + if(visualmatches < 1 || !visualinfo->visual) { + if(visualinfo) XFree(visualinfo); + fprintf(stderr, "VideoXv: unable to find Xv-compatible visual.\n"); + return false; + } + + device.colormap = XCreateColormap(device.display, settings.handle, visualinfo->visual, AllocNone); + XSetWindowAttributes attributes; + attributes.colormap = device.colormap; + attributes.border_pixel = 0; + attributes.event_mask = StructureNotifyMask; + device.window = XCreateWindow(device.display, /* parent = */ settings.handle, + /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, + /* border_width = */ 0, device.depth, InputOutput, visualinfo->visual, + CWColormap | CWBorderPixel | CWEventMask, &attributes); + XFree(visualinfo); + XSetWindowBackground(device.display, device.window, /* color = */ 0); + XMapWindow(device.display, device.window); + + device.gc = XCreateGC(device.display, device.window, 0, 0); + + //set colorkey to auto paint, so that Xv video output is always visible + Atom atom = XInternAtom(device.display, "XV_AUTOPAINT_COLORKEY", true); + if(atom != None) XvSetPortAttribute(device.display, device.port, atom, 1); + + //find optimal rendering format + device.format = XvFormatUnknown; + signed format_count; + XvImageFormatValues* format = XvListImageFormats(device.display, device.port, &format_count); + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 32) { + device.format = XvFormatRGB32; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 24) { + device.format = XvFormatRGB24; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0xf800) { + device.format = XvFormatRGB16; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0x7c00) { + device.format = XvFormatRGB15; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { + if(format[i].component_order[0] == 'Y' && format[i].component_order[1] == 'U' + && format[i].component_order[2] == 'Y' && format[i].component_order[3] == 'V' + ) { + device.format = XvFormatYUY2; + device.fourcc = format[i].id; + break; + } + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { + if(format[i].component_order[0] == 'U' && format[i].component_order[1] == 'Y' + && format[i].component_order[2] == 'V' && format[i].component_order[3] == 'Y' + ) { + device.format = XvFormatUYVY; + device.fourcc = format[i].id; + break; + } + } + } + + free(format); + if(device.format == XvFormatUnknown) { + fprintf(stderr, "VideoXv: unable to find a supported image format.\n"); + return false; + } + + device.width = 256; + device.height = 256; + + device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo); + if(!device.image) { + fprintf(stderr, "VideoXv: XShmCreateImage failed.\n"); + return false; + } + + device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777); + device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0); + device.shminfo.readOnly = false; + if(!XShmAttach(device.display, &device.shminfo)) { + fprintf(stderr, "VideoXv: XShmAttach failed.\n"); + return false; + } + + buffer = new uint32_t[device.width * device.height]; + settings.width = 256; + settings.height = 256; + init_yuv_tables(); + clear(); + return true; + } + + void term() { + XShmDetach(device.display, &device.shminfo); + shmdt(device.shminfo.shmaddr); + shmctl(device.shminfo.shmid, IPC_RMID, NULL); + XFree(device.image); + + if(device.window) { + XUnmapWindow(device.display, device.window); + device.window = 0; + } + + if(device.colormap) { + XFreeColormap(device.display, device.colormap); + device.colormap = 0; + } + + if(buffer) { delete[] buffer; buffer = 0; } + if(ytable) { delete[] ytable; ytable = 0; } + if(utable) { delete[] utable; utable = 0; } + if(vtable) { delete[] vtable; vtable = 0; } + } + + void render_rgb32(unsigned width, unsigned height) { + uint32_t* input = (uint32_t*)buffer; + uint32_t* output = (uint32_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + memcpy(output, input, width * 4); + input += device.width; + output += device.width; + } + } + + void render_rgb24(unsigned width, unsigned height) { + uint32_t* input = (uint32_t*)buffer; + uint8_t* output = (uint8_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = p; + *output++ = p >> 8; + *output++ = p >> 16; + } + + input += (device.width - width); + output += (device.width - width) * 3; + } + } + + void render_rgb16(unsigned width, unsigned height) { + uint32_t* input = (uint32_t*)buffer; + uint16_t* output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = ((p >> 8) & 0xf800) | ((p >> 5) & 0x07e0) | ((p >> 3) & 0x001f); //RGB32->RGB16 + } + + input += device.width - width; + output += device.width - width; + } + } + + void render_rgb15(unsigned width, unsigned height) { + uint32_t* input = (uint32_t*)buffer; + uint16_t* output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x001f); //RGB32->RGB15 + } + + input += device.width - width; + output += device.width - width; + } + } + + void render_yuy2(unsigned width, unsigned height) { + uint32_t* input = (uint32_t*)buffer; + uint16_t* output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width >> 1; x++) { + uint32_t p0 = *input++; + uint32_t p1 = *input++; + p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); //RGB32->RGB16 + p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); //RGB32->RGB16 + + uint8_t u = (utable[p0] + utable[p1]) >> 1; + uint8_t v = (vtable[p0] + vtable[p1]) >> 1; + + *output++ = (u << 8) | ytable[p0]; + *output++ = (v << 8) | ytable[p1]; + } + + input += device.width - width; + output += device.width - width; + } + } + + void render_uyvy(unsigned width, unsigned height) { + uint32_t* input = (uint32_t*)buffer; + uint16_t* output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width >> 1; x++) { + uint32_t p0 = *input++; + uint32_t p1 = *input++; + p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); + p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); + + uint8_t u = (utable[p0] + utable[p1]) >> 1; + uint8_t v = (vtable[p0] + vtable[p1]) >> 1; + + *output++ = (ytable[p0] << 8) | u; + *output++ = (ytable[p1] << 8) | v; + } + + input += device.width - width; + output += device.width - width; + } + } + + void init_yuv_tables() { + ytable = new uint8_t[65536]; + utable = new uint8_t[65536]; + vtable = new uint8_t[65536]; + + for(unsigned i = 0; i < 65536; i++) { + //extract RGB565 color data from i + uint8_t r = (i >> 11) & 31, g = (i >> 5) & 63, b = (i) & 31; + r = (r << 3) | (r >> 2); //R5->R8 + g = (g << 2) | (g >> 4); //G6->G8 + b = (b << 3) | (b >> 2); //B5->B8 + + //ITU-R Recommendation BT.601 + //double lr = 0.299, lg = 0.587, lb = 0.114; + int y = int( +(double(r) * 0.257) + (double(g) * 0.504) + (double(b) * 0.098) + 16.0 ); + int u = int( -(double(r) * 0.148) - (double(g) * 0.291) + (double(b) * 0.439) + 128.0 ); + int v = int( +(double(r) * 0.439) - (double(g) * 0.368) - (double(b) * 0.071) + 128.0 ); + + //ITU-R Recommendation BT.709 + //double lr = 0.2126, lg = 0.7152, lb = 0.0722; + //int y = int( double(r) * lr + double(g) * lg + double(b) * lb ); + //int u = int( (double(b) - y) / (2.0 - 2.0 * lb) + 128.0 ); + //int v = int( (double(r) - y) / (2.0 - 2.0 * lr) + 128.0 ); + + ytable[i] = y < 0 ? 0 : y > 255 ? 255 : y; + utable[i] = u < 0 ? 0 : u > 255 ? 255 : u; + vtable[i] = v < 0 ? 0 : v > 255 ? 255 : v; + } + } + + pVideoXv() { + device.window = 0; + device.colormap = 0; + device.port = -1; + + ytable = 0; + utable = 0; + vtable = 0; + + settings.handle = 0; + settings.synchronize = false; + } + + ~pVideoXv() { + term(); + } +}; + +DeclareVideo(Xv) + +}; diff --git a/src/burner/qt/selectdialog.cpp b/src/burner/qt/selectdialog.cpp new file mode 100644 index 000000000..100d1d2b1 --- /dev/null +++ b/src/burner/qt/selectdialog.cpp @@ -0,0 +1,307 @@ +#include +#include +#include +#include "selectdialog.h" +#include "ui_selectdialog.h" +#include "burner.h" +#include "rominfodialog.h" + +SelectDialog::SelectDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SelectDialog) +{ + ui->setupUi(this); + + m_icoNotFound = QIcon(tr(":/resource/tv-not-found.ico")); + m_icoNotFoundNonEssential = QIcon(tr(":/resource/tv-not-found-non-essential.ico")); + m_icoNotWorking = QIcon(tr(":/resource/tv-not-working.ico")); + + m_defaultImage = QPixmap(tr(":/resource/splash.bmp")); + + m_romInfoDlg = new RomInfoDialog(this); + m_romScanner = new RomScanDialog(this); + m_romPathEditor = new RomDirsDialog(this); + + setWindowTitle(tr("Select Game")); + buildDriverTree(); + + connect(ui->tvDrivers, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + this, SLOT(driverChange(QTreeWidgetItem*,QTreeWidgetItem*))); + connect(ui->tvDrivers, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), + this, SLOT(driverSelect(QTreeWidgetItem*,int))); + connect(ui->btnRomInfo, SIGNAL(clicked()), this, SLOT(openRomInfo())); + connect(ui->btnScanRoms, SIGNAL(clicked()), this, SLOT(rescanRoms())); + connect(ui->btnRomDirs, SIGNAL(clicked()), this, SLOT(editRomPaths())); + + connect(ui->btnPlay, SIGNAL(clicked()), this, SLOT(playGame())); + + connect(ui->ckbShowAvaliable, SIGNAL(toggled(bool)), this, SLOT(itemShowAvaliable(bool))); + connect(ui->ckbShowUnavaliable, SIGNAL(toggled(bool)), this, SLOT(itemShowUnavaliable(bool))); + connect(ui->ckbShowClones, SIGNAL(toggled(bool)), this, SLOT(itemShowClones(bool))); + m_selectedDriver = 0; + + m_showAvailable = true; + m_showUnavailable = true; + m_showClones = true; + m_showCount = 0; +} + +SelectDialog::~SelectDialog() +{ + delete ui; +} + +void SelectDialog::driverChange(QTreeWidgetItem *item, QTreeWidgetItem *prev) +{ + TreeDriverItem *driver = static_cast(item); + int tmp = nBurnDrvActive; + int flags = DRV_ASCIIONLY; + + m_selectedDriver = driver->driverNo(); + + // atualiza as informações sobre o driver + nBurnDrvActive = driver->driverNo(); + ui->leGameInfo->setText(BurnDrvGetText(flags | DRV_FULLNAME)); + updateTitleScreen(); + { + QString manufacturer = BurnDrvGetTextA(DRV_MANUFACTURER) ? + BurnDrvGetText(flags | DRV_MANUFACTURER) : tr("Unknown"); + QString date = BurnDrvGetTextA(DRV_DATE); + QString system = BurnDrvGetText(flags | DRV_SYSTEM); + QString prefix = (BurnDrvGetHardwareCode() & HARDWARE_PREFIX_CARTRIDGE) + ? tr("Cartridge") : tr("Hardware"); + QString releaseInfo = QString("%0 (%1, %2 %3)").arg(manufacturer, + date, + system, + prefix); + ui->leReleasedBy->setText(releaseInfo); + } + ui->leRomName->setText(driver->romName()); + nBurnDrvActive = tmp; +} + +void SelectDialog::driverSelect(QTreeWidgetItem *item, int column) +{ + TreeDriverItem *driver = static_cast(item); + m_selectedDriver = driver->driverNo(); + accept(); +} + +void SelectDialog::playGame() +{ + TreeDriverItem *driver = static_cast(ui->tvDrivers->currentItem()); + if (driver != nullptr) { + m_selectedDriver = driver->driverNo(); + accept(); + } +} + + + +void SelectDialog::itemShowAvaliable(bool state) +{ + if (state == m_showAvailable) + return; + m_showAvailable = state; + filterDrivers(); +} + +void SelectDialog::itemShowUnavaliable(bool state) +{ + if (state == m_showUnavailable) + return; + m_showUnavailable = state; + filterDrivers(); +} + +void SelectDialog::itemShowClones(bool state) +{ + if (state == m_showClones) + return; + m_showClones = state; + filterDrivers(); +} + +void SelectDialog::updateTitleScreen() +{ + QString drv = BurnDrvGetTextA(DRV_NAME); + QString path = (QString(tr("support/titles/%0.png").arg(drv))); + if (QFile(path).exists()) { + QPixmap p(path); + ui->imgTitleScreen->setPixmap(p); + } else { + ui->imgTitleScreen->setPixmap(m_defaultImage); + } +} + +void SelectDialog::updateLabelCounter() +{ + QString text(tr("Showing %0 of %1 sets"). + arg(m_showCount).arg(nBurnDrvCount)); + ui->lblCounter->setText(text); +} + +int SelectDialog::selectedDriver() const +{ + return m_selectedDriver; +} + +void SelectDialog::openRomInfo() +{ + m_romInfoDlg->setDriverNo(m_selectedDriver); + m_romInfoDlg->exec(); +} + +void SelectDialog::rescanRoms() +{ + if (m_romScanner->exec() == QDialog::Accepted) { + filterDrivers(); + } +} + +void SelectDialog::editRomPaths() +{ + m_romPathEditor->exec(); +} + +void SelectDialog::buildDriverTree() +{ + int nTmpDrv = nBurnDrvActive; + + // build parent list + for (int i = 0; i < nBurnDrvCount; i++) { + nBurnDrvActive = i; + if (BurnDrvGetFlags() & BDF_BOARDROM) + continue; + + // skip clones + if (BurnDrvGetText(DRV_PARENT) != NULL && (BurnDrvGetFlags() & BDF_CLONE)) + continue; + + TreeDriverItem *ditem = new TreeDriverItem(); + ditem->setIcon(0, m_icoNotFound); + ditem->setText(0, BurnDrvGetText(DRV_ASCIIONLY | DRV_FULLNAME)); + ditem->setRomName(BurnDrvGetTextA(DRV_NAME)); + ditem->setDriverNo(i); + ditem->setIsParent(true); + ui->tvDrivers->addTopLevelItem(ditem); + m_parents[tr(ditem->romName())] = ditem; + } + + // build clones tree + for (int i = 0; i < nBurnDrvCount; i++) { + nBurnDrvActive = i; + + if (BurnDrvGetFlags() & BDF_BOARDROM) + continue; + + // skip parents + if (BurnDrvGetTextA(DRV_PARENT) == NULL || !(BurnDrvGetFlags() & BDF_CLONE)) + continue; + + TreeDriverItem *itemParent = m_parents[tr(BurnDrvGetTextA(DRV_PARENT))]; + if (itemParent) { + TreeDriverItem *ditem = new TreeDriverItem(); + ditem->setIcon(0, m_icoNotFound); + ditem->setText(0, BurnDrvGetText(DRV_ASCIIONLY | DRV_FULLNAME)); + ditem->setRomName(BurnDrvGetTextA(DRV_NAME)); + ditem->setDriverNo(i); + ditem->setIsParent(false); + ditem->setBackgroundColor(0, QColor(230, 230, 230)); + itemParent->addChild(ditem); + } + } + nBurnDrvActive = nTmpDrv; +} + +bool SelectDialog::isFiltered(TreeDriverItem *driver) +{ + bool status = m_romScanner->status(driver->driverNo()) ? true : false; + + if (!m_showUnavailable) + if (!(m_showAvailable && status)) + return false; + return true; +} + +void SelectDialog::filterDrivers() +{ + auto setupIcon = [&](int stat, TreeDriverItem *item) -> void { + switch (stat) { + case 0: item->setIcon(0, m_icoNotFound); break; + case 2: + case 3: item->setIcon(0, m_icoNotFoundNonEssential); break; + case 1: item->setIcon(0, m_icoNotWorking); break; + } + }; + + m_showCount = 0; + foreach (TreeDriverItem *driver, m_parents.values()) { + // skip it + if (driver == nullptr) + continue; + + // show all + driver->setHidden(true); + + // setup icon + int stat = m_romScanner->status(driver->driverNo()); + setupIcon(stat, driver); + + if (!isFiltered(driver)) + continue; + + driver->setHidden(false); + + for (int idx = 0; idx < driver->childCount(); idx++) { + TreeDriverItem *clone = static_cast(driver->child(idx)); + clone->setHidden(true); + + int cstat = m_romScanner->status(driver->driverNo()); + setupIcon(cstat, clone); + + if (m_showClones) { + if (!isFiltered(clone)) + continue; + clone->setHidden(false); + } + } + m_showCount++; + } + updateLabelCounter(); +} + +TreeDriverItem::TreeDriverItem() : QTreeWidgetItem() +{ + +} + +int TreeDriverItem::driverNo() const +{ + return m_driverNo; +} + +void TreeDriverItem::setDriverNo(int driverNo) +{ + m_driverNo = driverNo; +} + +const char *TreeDriverItem::romName() const +{ + return m_romName; +} + +void TreeDriverItem::setRomName(const char *romName) +{ + m_romName = romName; +} + +bool TreeDriverItem::isParent() const +{ + return m_isParent; +} + +void TreeDriverItem::setIsParent(bool isParent) +{ + m_isParent = isParent; +} diff --git a/src/burner/qt/selectdialog.h b/src/burner/qt/selectdialog.h new file mode 100644 index 000000000..8c5d44067 --- /dev/null +++ b/src/burner/qt/selectdialog.h @@ -0,0 +1,84 @@ +#ifndef SELECTDIALOG_H +#define SELECTDIALOG_H + +#include +#include +#include +#include +#include +#include "rominfodialog.h" +#include "romscandialog.h" +#include "romdirsdialog.h" + +namespace Ui { +class SelectDialog; +} + +class TreeDriverItem : public QTreeWidgetItem +{ +public: + TreeDriverItem(); + + int driverNo() const; + void setDriverNo(int driverNo); + + const char *romName() const; + void setRomName(const char *romName); + + bool isParent() const; + void setIsParent(bool isParent); + +private: + int m_driverNo; + const char *m_romName; + bool m_isParent; +}; + +class SelectDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SelectDialog(QWidget *parent = 0); + ~SelectDialog(); + + int selectedDriver() const; +public slots: + void openRomInfo(); + void rescanRoms(); + void editRomPaths(); + void driverChange(QTreeWidgetItem * item, QTreeWidgetItem * prev); + void driverSelect(QTreeWidgetItem * item, int column); + void playGame(); + + void itemShowAvaliable(bool state); + void itemShowUnavaliable(bool state); + void itemShowClones(bool state); +signals: + void driverSelected(int no); +private: + int m_selectedDriver; + void updateTitleScreen(); + void updateLabelCounter(); + void buildDriverTree(); + bool isFiltered(TreeDriverItem *driver); + void filterDrivers(); + + Ui::SelectDialog *ui; + QIcon m_icoNotFound; + QIcon m_icoNotFoundNonEssential; + QIcon m_icoNotWorking; + RomInfoDialog *m_romInfoDlg; + RomScanDialog *m_romScanner; + RomDirsDialog *m_romPathEditor; + QMap m_parents; + + bool m_showAvailable; + bool m_showUnavailable; + bool m_showClones; + int m_showCount; + + QPixmap m_defaultImage; +}; + +#endif // SELECTDIALOG_H diff --git a/src/burner/qt/selectdialog.ui b/src/burner/qt/selectdialog.ui new file mode 100644 index 000000000..a906b7c22 --- /dev/null +++ b/src/burner/qt/selectdialog.ui @@ -0,0 +1,653 @@ + + + SelectDialog + + + + 0 + 0 + 899 + 650 + + + + Dialog + + + + + + + + + + + Select Game + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Game Info + + + + + + + true + + + + + + + ROM Name + + + + + + + true + + + + + + + ROM Info + + + + + + + true + + + + + + + Released by + + + + + + + true + + + + + + + Genre + + + + + + + true + + + + + + + Notes + + + + + + + true + + + + + + + + + + + + 0 + 0 + + + + ... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Rom Info + + + + + + + + + + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + + 320 + 200 + + + + + 320 + 200 + + + + QFrame::StyledPanel + + + + + + :/resource/splash.bmp + + + true + + + + + + + + + Options + + + + + + + + Show avaliable + + + true + + + + + + + Show unavaliable + + + true + + + + + + + Always show clones + + + true + + + + + + + Use zipnames + + + + + + + Latin text only + + + + + + + + + ROMs Dirs... + + + + + + + Scan ROMs + + + + + + + + + + + + + + + Filters + + + + + Filters + + + Unchecked + + + ItemIsSelectable|ItemIsUserCheckable|ItemIsEnabled|ItemIsTristate + + + + BoardType + + + Unchecked + + + ItemIsSelectable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled|ItemIsTristate + + + + Genuine + + + Unchecked + + + + + Bootleg + + + Unchecked + + + + + Demo + + + Unchecked + + + + + Hack + + + Unchecked + + + + + Homebrew + + + Unchecked + + + + + Prototype + + + Unchecked + + + + + + Family + + + Unchecked + + + + + Genre + + + Unchecked + + + + + Hardware + + + Unchecked + + + + Capcom (Other) + + + Unchecked + + + + + Cave + + + Unchecked + + + + + CPS-1 + + + Unchecked + + + + + CPS-2 + + + Unchecked + + + + + CPS-3 + + + Unchecked + + + + + Data East + + + Unchecked + + + + + Galaxian + + + Unchecked + + + + + Irem + + + Unchecked + + + + + Kaneko + + + Unchecked + + + + + Konami + + + Unchecked + + + + + Neo Geo + + + Unchecked + + + + + Pacman + + + Unchecked + + + + + PGM + + + Unchecked + + + + + Psikyo + + + Unchecked + + + + + Sega + + + Unchecked + + + + + Seta + + + Unchecked + + + + + Taito + + + Unchecked + + + + + Technos + + + Unchecked + + + + + Toaplan + + + Unchecked + + + + + Misc (pre 90s) + + + Unchecked + + + + + Misc (post 90s) + + + Unchecked + + + + + Master System + + + Unchecked + + + + + Megadrive + + + Unchecked + + + + + PC-Engine + + + Unchecked + + + + + SNES + + + Unchecked + + + + + + + + + + + + Search + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + Play + + + + + + + + + + + + + + + + + btnCancel + clicked() + SelectDialog + reject() + + + 601 + 558 + + + 545 + 555 + + + + + diff --git a/src/burner/qt/stringset.cpp b/src/burner/qt/stringset.cpp new file mode 100644 index 000000000..74cc77a2e --- /dev/null +++ b/src/burner/qt/stringset.cpp @@ -0,0 +1,52 @@ +// StringSet C++ class +#include "burner.h" + +int __cdecl StringSet::Add(TCHAR* szFormat,...) +{ + TCHAR szAdd[256]; + int nAddLen = 0; + TCHAR* NewMem; + va_list Arg; + + va_start(Arg, szFormat); + vsprintf(szAdd, szFormat, Arg); + + nAddLen = _tcslen(szAdd); // find out the length of the new text + NewMem = (TCHAR*)realloc(szText, (nLen + nAddLen + 1) * sizeof(TCHAR)); + if (NewMem) { + szText = NewMem; + // copy the new text to the end + _tcsncpy(szText + nLen, szAdd, nAddLen); + nLen += nAddLen; + szText[nLen] = 0; // zero-terminate + } + + va_end(Arg); + + return 0; +} + +int StringSet::Reset() +{ + // Reset the text + nLen = 0; + szText= (TCHAR*)realloc(szText, sizeof(TCHAR)); + if (szText == NULL) { + return 1; + } + szText[0] = 0; + + return 0; +} + +StringSet::StringSet() +{ + szText = NULL; + nLen = 0; + Reset(); // reset string to nothing +} + +StringSet::~StringSet() +{ + realloc(szText, 0); // Free BZip text +} diff --git a/src/burner/qt/supportdirsdialog.cpp b/src/burner/qt/supportdirsdialog.cpp new file mode 100644 index 000000000..e306259ec --- /dev/null +++ b/src/burner/qt/supportdirsdialog.cpp @@ -0,0 +1,54 @@ +#include "supportdirsdialog.h" +#include "ui_supportdirsdialog.h" +#include "burner.h" + +TCHAR szAppHiscorePath[MAX_PATH] = _T("support/hiscores/"); +TCHAR szAppSamplesPath[MAX_PATH] = _T("support/samples/"); +TCHAR szAppCheatsPath[MAX_PATH] = _T("support/cheats/"); +TCHAR szAppPreviewsPath[MAX_PATH] = _T("support/previews/"); +TCHAR szAppTitlesPath[MAX_PATH] = _T("support/titles/"); + +SupportDirsDialog::SupportDirsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SupportDirsDialog) +{ + ui->setupUi(this); + setWindowTitle(tr("Edit support paths")); + setFixedHeight(height()); + + m_group = new QButtonGroup(this); + + m_handlers[PATH_PREVIEWS] = util::PathHandler { szAppPreviewsPath, ui->lePreviews, PATH_PREVIEWS }; + m_handlers[PATH_TITLES] = util::PathHandler { szAppTitlesPath, ui->leTitles, PATH_TITLES }; + m_handlers[PATH_HISCORES] = util::PathHandler { szAppHiscorePath, ui->leHiscores, PATH_HISCORES }; + m_handlers[PATH_SAMPLES] = util::PathHandler { szAppSamplesPath, ui->leSamples, PATH_SAMPLES }; + m_handlers[PATH_CHEATS] = util::PathHandler { szAppCheatsPath, ui->leCheats, PATH_CHEATS }; + + m_group->addButton(ui->btnPreviews, PATH_PREVIEWS); + m_group->addButton(ui->btnTitles, PATH_TITLES); + m_group->addButton(ui->btnHiscores, PATH_HISCORES); + m_group->addButton(ui->btnSamples, PATH_SAMPLES); + m_group->addButton(ui->btnCheats, PATH_CHEATS); + connect(m_group, SIGNAL(buttonClicked(int)), this, SLOT(editPath(int))); +} + +SupportDirsDialog::~SupportDirsDialog() +{ + delete ui; +} + +void SupportDirsDialog::editPath(int no) +{ + m_handlers[no].browse(this); +} + +int SupportDirsDialog::exec() +{ + for (int i = 0; i < PATH_MAX_HANDLERS; i++) + m_handlers[i].stringToEditor(); + + if (QDialog::exec() == QDialog::Accepted) { + for (int i = 0; i < PATH_MAX_HANDLERS; i++) + m_handlers[i].editorToString(); + } +} diff --git a/src/burner/qt/supportdirsdialog.h b/src/burner/qt/supportdirsdialog.h new file mode 100644 index 000000000..d0edf4e24 --- /dev/null +++ b/src/burner/qt/supportdirsdialog.h @@ -0,0 +1,40 @@ +#ifndef SUPPORTDIRSDIALOG_H +#define SUPPORTDIRSDIALOG_H + +#include +#include +#include +#include "qutil.h" + +namespace Ui { +class SupportDirsDialog; +} + +class SupportDirsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SupportDirsDialog(QWidget *parent = 0); + ~SupportDirsDialog(); + +public slots: + void editPath(int no); + int exec(); + +private: + Ui::SupportDirsDialog *ui; + QButtonGroup *m_group; + enum { + PATH_PREVIEWS = 0, + PATH_TITLES, + PATH_HISCORES, + PATH_SAMPLES, + PATH_CHEATS, + PATH_MAX_HANDLERS + }; + + util::PathHandler m_handlers[PATH_MAX_HANDLERS]; +}; + +#endif // SUPPORTDIRSDIALOG_H diff --git a/src/burner/qt/supportdirsdialog.ui b/src/burner/qt/supportdirsdialog.ui new file mode 100644 index 000000000..4a439b85c --- /dev/null +++ b/src/burner/qt/supportdirsdialog.ui @@ -0,0 +1,199 @@ + + + SupportDirsDialog + + + + 0 + 0 + 576 + 275 + + + + + 0 + 0 + + + + Dialog + + + true + + + + + + + + Previews + + + + + + + + + + Browse + + + + + + + + + + + Titles + + + + + + + + + + Browse + + + + + + + + + + + Hiscores + + + + + + + + + + Browse + + + + + + + + + + + Cheats + + + + + + + + + + Browse + + + + + + + + + + + Samples + + + + + + + + + + Browse + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + Ok + + + + + + + + + + + btnCancel + clicked() + SupportDirsDialog + reject() + + + 238 + 102 + + + 97 + 102 + + + + + btnOk + clicked() + SupportDirsDialog + accept() + + + 287 + 104 + + + 276 + 116 + + + + + diff --git a/src/burner/qt/tchar.h b/src/burner/qt/tchar.h new file mode 100644 index 000000000..ecc6adea4 --- /dev/null +++ b/src/burner/qt/tchar.h @@ -0,0 +1,65 @@ +#ifndef __PORT_TYPEDEFS_H +#define __PORT_TYPEDEFS_H + +#include +#include + +#include "inp_keys.h" +#define TCHAR char +#define _T(x) x +#define _tfopen fopen +#define _tcstol strtol +#define _tcsstr strstr +#define _istspace(x) isspace(x) +#define _stprintf sprintf +#define _tcslen strlen +#define _tcsicmp(a, b) strcasecmp(a, b) +#define _tcscpy(to, from) strcpy(to, from) +#define _fgetts fgets +#define _strnicmp(s1, s2, n) strncasecmp(s1, s2, n) +#define _sntprintf snprintf +#define _tcscmp strcmp +#define _tcsncmp strncmp +#define _tcsncpy strncpy +#define _stscanf sscanf +#define _ftprintf fprintf + +#ifdef _MSC_VER +#include +#define strncasecmp(s1, s2, n) _strnicmp(s1, s2, n) +#define strcasecmp(x, y) _stricmp(x, y) +#define snprintf _snprintf +#else +#define _stricmp(x, y) strcasecmp(x,y) + +typedef struct { int x, y, width, height; } RECT; +#undef __cdecl +#define __cdecl + +#define bprintf(...) {} +#endif + +#undef __fastcall +#undef _fastcall +#define __fastcall /*what does this correspond to?*/ +#define _fastcall /*same as above - what does this correspond to?*/ +#define ANSIToTCHAR(str, foo, bar) (str) + +/* for Windows / Xbox 360 (below VS2010) - typedefs for missing stdint.h types such as uintptr_t?*/ + +/*FBA defines*/ +#define PUF_TEXT_NO_TRANSLATE (0) +#define PUF_TYPE_ERROR (1) + +extern TCHAR szAppBurnVer[16]; + +typedef int HWND; + +extern int bDrvOkay; +extern int bRunPause; +extern bool bAlwaysProcessKeyboardInput; +extern HWND hScrnWnd; // Handle to the screen window + +extern void InpDIPSWResetDIPs (void); + +#endif diff --git a/src/dep/libs/nall/Makefile b/src/dep/libs/nall/Makefile new file mode 100755 index 000000000..bee3d22a7 --- /dev/null +++ b/src/dep/libs/nall/Makefile @@ -0,0 +1,122 @@ +# 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 -s) + ifeq ($(uname),) + platform := windows + delete = del $(subst /,\,$1) + else ifneq ($(findstring Windows,$(uname)),) + platform := windows + delete = del $(subst /,\,$1) + else ifneq ($(findstring CYGWIN,$(uname)),) + platform := windows + delete = del $(subst /,\,$1) + else ifneq ($(findstring Darwin,$(uname)),) + platform := macosx + delete = rm -f $1 + else ifneq ($(findstring BSD,$(uname)),) + platform := bsd + delete = rm -f $1 + else + platform := linux + delete = rm -f $1 + endif +endif + +# compiler detection +ifeq ($(compiler),) + ifeq ($(platform),windows) + compiler := g++ + flags := + link := + else ifeq ($(platform),macosx) + compiler := clang++ + flags := -w -stdlib=libc++ + link := -lc++ -lobjc + else ifeq ($(platform),bsd) + compiler := clang++ + flags := -w -I/usr/local/include + else + compiler := g++ + flags := + link := + endif + + cflags := -x c -std=c99 + objcflags := -x objective-c -std=c99 + cppflags := -x c++ -std=c++11 + objcppflags := -x objective-c++ -std=c++11 +endif + +# cross-compilation support +ifeq ($(arch),x86) + flags := -m32 $(flags) + link := -m32 $(link) +endif + +ifeq ($(prefix),) + prefix := /usr/local +endif + +# function rwildcard(directory, pattern) +rwildcard = \ + $(strip \ + $(filter $(if $2,$2,%), \ + $(foreach f, \ + $(wildcard $1*), \ + $(eval t = $(call rwildcard,$f/)) \ + $(if $t,$t,$f) \ + ) \ + ) \ + ) + +# function strtr(source, from, to) +strtr = \ + $(eval __temp := $1) \ + $(strip \ + $(foreach c, \ + $(join $(addsuffix :,$2),$3), \ + $(eval __temp := \ + $(subst $(word 1,$(subst :, ,$c)),$(word 2,$(subst :, ,$c)),$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) + +# function strupper(source) +strupper = $(call strtr,$1,$([a-z]),$([A-Z])) + +# function strlower(source) +strlower = $(call strtr,$1,$([A-Z]),$([a-z])) + +# function strlen(source) +strlen = \ + $(eval __temp := $(subst $([space]),_,$1)) \ + $(words \ + $(strip \ + $(foreach c, \ + $([all]), \ + $(eval __temp := \ + $(subst $c,$c ,$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) \ + ) + +# function streq(source) +streq = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),,1) + +# function strne(source) +strne = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),1,) diff --git a/src/dep/libs/nall/algorithm.hpp b/src/dep/libs/nall/algorithm.hpp new file mode 100755 index 000000000..5867c1cd1 --- /dev/null +++ b/src/dep/libs/nall/algorithm.hpp @@ -0,0 +1,19 @@ +#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/src/dep/libs/nall/any.hpp b/src/dep/libs/nall/any.hpp new file mode 100755 index 000000000..27a07ef20 --- /dev/null +++ b/src/dep/libs/nall/any.hpp @@ -0,0 +1,98 @@ +#ifndef NALL_ANY_HPP +#define NALL_ANY_HPP + +#include +#include + +namespace nall { + +struct any { + bool empty() const { return container; } + void reset() { if(container) { delete container; container = nullptr; } } + + 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& operator=(const any& source) { + if(container) { delete container; container = nullptr; } + if(source.container) container = source.container->copy(); + return *this; + } + + any& operator=(any&& source) { + if(container) delete container; + container = source.container; + source.container = nullptr; + return *this; + } + + any() = default; + any(const any& source) { operator=(source); } + any(any&& source) { operator=(std::move(source)); } + template any(const T& value) { operator=(value); } + ~any() { reset(); } + +private: + struct placeholder { + virtual const std::type_info& type() const = 0; + virtual placeholder* copy() const = 0; + virtual ~placeholder() {} + }; + placeholder* container = nullptr; + + template struct holder : placeholder { + T value; + const std::type_info& type() const { return typeid(T); } + placeholder* copy() const { return new holder(value); } + 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/src/dep/libs/nall/atoi.hpp b/src/dep/libs/nall/atoi.hpp new file mode 100755 index 000000000..6c2babae3 --- /dev/null +++ b/src/dep/libs/nall/atoi.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_ATOI_HPP +#define NALL_ATOI_HPP + +#include + +namespace nall { + +constexpr inline uintmax_t binary_(const char* s, uintmax_t sum = 0) { + return ( + *s == '0' || *s == '1' ? binary_(s + 1, (sum << 1) | *s - '0') : + *s == '\'' ? binary_(s + 1, sum) : + 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') : + *s == '\'' ? octal_(s + 1, sum) : + 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') : + *s == '\'' ? decimal_(s + 1, sum) : + 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') : + *s == '\'' ? hex_(s + 1, sum) : + 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 ( + *s == '0' && *(s + 1) == 'O' ? octal_(s + 2) : + *s == '0' && *(s + 1) == 'o' ? octal_(s + 2) : + 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 real(const char* s) { + return atof(s); +} + +} + +#endif diff --git a/src/dep/libs/nall/base64.hpp b/src/dep/libs/nall/base64.hpp new file mode 100755 index 000000000..7d9fb1160 --- /dev/null +++ b/src/dep/libs/nall/base64.hpp @@ -0,0 +1,137 @@ +#ifndef NALL_BASE64_HPP +#define NALL_BASE64_HPP + +#include +#include +#include + +namespace nall { + +struct Base64 { + enum class Format : unsigned { MIME, URI }; + + inline static string encode(const uint8_t* data, unsigned size, Format format = Format::MIME); + inline static string encode(const vector& buffer, Format format = Format::MIME); + inline static string encode(const string& text, Format format = Format::MIME); + + inline static vector decode(const string& text); + +private: + inline static void table(char* data, Format format); + inline static uint8_t value(char data); +}; + +string Base64::encode(const uint8_t* data, unsigned size, Format format) { + vector result; + + char lookup[65]; + table(lookup, format); + + unsigned overflow = (3 - (size % 3)) % 3; //bytes to round to nearest multiple of 3 + uint8_t buffer; + + for(unsigned i = 0; i < size; i++) { + switch(i % 3) { + case 0: + buffer = data[i] >> 2; + result.append(lookup[buffer]); + buffer = (data[i] & 3) << 4; + result.append(lookup[buffer]); + break; + + case 1: + buffer |= data[i] >> 4; + result.last() = lookup[buffer]; + buffer = (data[i] & 15) << 2; + result.append(lookup[buffer]); + break; + + case 2: + buffer |= data[i] >> 6; + result.last() = lookup[buffer]; + buffer = (data[i] & 63); + result.append(lookup[buffer]); + break; + } + } + + if(lookup[64]) { + if(overflow >= 1) result.append(lookup[64]); + if(overflow >= 2) result.append(lookup[64]); + } + + return result; +} + +string Base64::encode(const vector& buffer, Format format) { + return encode(buffer.data(), buffer.size(), format); +} + +string Base64::encode(const string& text, Format format) { + return encode((const uint8_t*)text.data(), text.size(), format); +} + +vector Base64::decode(const string& text) { + vector result; + + uint8_t buffer, output; + for(unsigned i = 0; i < text.size(); i++) { + uint8_t buffer = value(text[i]); + if(buffer == 0) break; + + switch(i & 3) { + case 0: + output = buffer << 2; + break; + + case 1: + result.append(output | buffer >> 4); + output = (buffer & 15) << 4; + break; + + case 2: + result.append(output | buffer >> 2); + output = (buffer & 3) << 6; + break; + + case 3: + result.append(output | buffer); + break; + } + } + + return result; +} + +void Base64::table(char* data, Format format) { + for(unsigned n = 0; n < 26; n++) data[ 0 + n] = 'A' + n; + for(unsigned n = 0; n < 26; n++) data[26 + n] = 'a' + n; + for(unsigned n = 0; n < 10; n++) data[52 + n] = '0' + n; + + switch(format) { + case Format::MIME: + data[62] = '+'; + data[63] = '/'; + data[64] = '='; + break; + + case Format::URI: + data[62] = '-'; + data[63] = '_'; + data[64] = 0; + break; + } +} + +uint8_t Base64::value(char n) { + if(n >= 'A' && n <= 'Z') return n - 'A' + 0; + if(n >= 'a' && n <= 'z') return n - 'a' + 26; + if(n >= '0' && n <= '9') return n - '0' + 52; + if(n == '+' || n == '-') return 62; + if(n == '/' || n == '_') return 63; + return 0; +} + +} + +#endif diff --git a/src/dep/libs/nall/beat/archive.hpp b/src/dep/libs/nall/beat/archive.hpp new file mode 100755 index 000000000..f8c6b6cf0 --- /dev/null +++ b/src/dep/libs/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.find("\\") || name.find("../")) 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/src/dep/libs/nall/beat/base.hpp b/src/dep/libs/nall/beat/base.hpp new file mode 100755 index 000000000..d35b8e7c5 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/beat/delta.hpp b/src/dep/libs/nall/beat/delta.hpp new file mode 100755 index 000000000..04145efb5 --- /dev/null +++ b/src/dep/libs/nall/beat/delta.hpp @@ -0,0 +1,215 @@ +#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 = 0; + Node* next = nullptr; + Node() = default; + ~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]; + Node* targetTree[65536]; + for(unsigned n = 0; n < 65536; n++) sourceTree[n] = nullptr, targetTree[n] = nullptr; + + //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/src/dep/libs/nall/beat/linear.hpp b/src/dep/libs/nall/beat/linear.hpp new file mode 100755 index 000000000..4ee44a578 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/beat/metadata.hpp b/src/dep/libs/nall/beat/metadata.hpp new file mode 100755 index 000000000..2b31fdfd2 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/beat/multi.hpp b/src/dep/libs/nall/beat/multi.hpp new file mode 100755 index 000000000..c99001c8f --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/beat/patch.hpp b/src/dep/libs/nall/beat/patch.hpp new file mode 100755 index 000000000..59c6c5d1b --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/bit.hpp b/src/dep/libs/nall/bit.hpp new file mode 100755 index 000000000..c1cb15ee6 --- /dev/null +++ b/src/dep/libs/nall/bit.hpp @@ -0,0 +1,88 @@ +#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; + } + + //return index of the first bit set (or zero of no bits are set) + //first(0b1000) == 3 + inline unsigned first(uintmax_t x) { + unsigned first = 0; + while(x) { if(x & 1) break; x >>= 1; first++; } + return first; + } + + //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/src/dep/libs/nall/bmp.hpp b/src/dep/libs/nall/bmp.hpp new file mode 100755 index 000000000..9e68617ec --- /dev/null +++ b/src/dep/libs/nall/bmp.hpp @@ -0,0 +1,100 @@ +#ifndef NALL_BMP_HPP +#define NALL_BMP_HPP + +#include + +//BMP reader / writer +//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/src/dep/libs/nall/compositor.hpp b/src/dep/libs/nall/compositor.hpp new file mode 100755 index 000000000..d895b8149 --- /dev/null +++ b/src/dep/libs/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) return false; + + char buffer[512]; + if(!fgets(buffer, sizeof buffer, fp)) 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) 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) return false; + + char buffer[512]; + if(!fgets(buffer, sizeof buffer, fp)) 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) 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 == nullptr) module = LoadLibraryW(L"dwmapi"); + if(module == nullptr) return false; + + auto pDwmIsCompositionEnabled = (HRESULT (WINAPI*)(BOOL*))GetProcAddress(module, "DwmIsCompositionEnabled"); + if(pDwmIsCompositionEnabled == nullptr) 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 == nullptr) module = LoadLibraryW(L"dwmapi"); + if(module == nullptr) return false; + + auto pDwmEnableComposition = (HRESULT (WINAPI*)(UINT))GetProcAddress(module, "DwmEnableComposition"); + if(pDwmEnableComposition == nullptr) 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/src/dep/libs/nall/config.hpp b/src/dep/libs/nall/config.hpp new file mode 100755 index 000000000..4c69bce87 --- /dev/null +++ b/src/dep/libs/nall/config.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_CONFIG_HPP +#define NALL_CONFIG_HPP + +#include +#include +#include + +namespace nall { +namespace Configuration { + +struct Node { + string name; + string desc; + enum class Type : unsigned { Null, Bool, Signed, Unsigned, Double, String } type = Type::Null; + void* data = nullptr; + vector children; + + bool empty() const { + return data == nullptr; + } + + string get() const { + switch(type) { + case Type::Bool: return {*(bool*)data}; + case Type::Signed: return {*(signed*)data}; + case Type::Unsigned: return {*(unsigned*)data}; + case Type::Double: return {*(double*)data}; + case Type::String: return {*(string*)data}; + } + return ""; + } + + void set(const string& value) { + switch(type) { + case Type::Bool: *(bool*)data = (value != "false"); break; + case Type::Signed: *(signed*)data = integer(value); break; + case Type::Unsigned: *(unsigned*)data = decimal(value); break; + case Type::Double: *(double*)data = real(value); break; + case Type::String: *(string*)data = value; break; + } + } + + void assign() { type = Type::Null; data = nullptr; } + void assign(bool& bind) { type = Type::Bool; data = (void*)&bind; } + void assign(signed& bind) { type = Type::Signed; data = (void*)&bind; } + void assign(unsigned& bind) { type = Type::Unsigned; data = (void*)&bind; } + void assign(double& bind) { type = Type::Double; data = (void*)&bind; } + void assign(string& bind) { type = Type::String; data = (void*)&bind; } + void assign(const Node& node) { operator=(node); } + + template void append(T& data, const string& name, const string& desc = "") { + Node node; + node.assign(data); + node.name = name; + node.desc = desc; + children.append(node); + } + + void load(Markup::Node path) { + for(auto& child : children) { + auto leaf = path[child.name]; + if(!leaf.exists()) continue; + if(!child.empty()) child.set(leaf.data.trim<1>(" ", "\r")); + child.load(leaf); + } + } + + void save(file& fp, unsigned depth = 0) { + for(auto& child : children) { + if(child.desc) { + for(unsigned n = 0; n < depth; n++) fp.print(" "); + fp.print("//", child.desc, "\n"); + } + for(unsigned n = 0; n < depth; n++) fp.print(" "); + fp.print(child.name); + if(!child.empty()) fp.print(": ", child.get()); + fp.print("\n"); + child.save(fp, depth + 1); + if(depth == 0) fp.print("\n"); + } + } +}; + +struct Document : Node { + bool load(const string& filename) { + if(!file::exists(filename)) return false; + auto document = Markup::Document(string::read(filename)); + Node::load(document); + return true; + } + + bool save(const string& filename) { + file fp(filename, file::mode::write); + if(!fp.open()) return false; + Node::save(fp); + return true; + } +}; + +} +} + +#endif diff --git a/src/dep/libs/nall/crc16.hpp b/src/dep/libs/nall/crc16.hpp new file mode 100755 index 000000000..be79a5024 --- /dev/null +++ b/src/dep/libs/nall/crc16.hpp @@ -0,0 +1,27 @@ +#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/src/dep/libs/nall/crc32.hpp b/src/dep/libs/nall/crc32.hpp new file mode 100755 index 000000000..757001016 --- /dev/null +++ b/src/dep/libs/nall/crc32.hpp @@ -0,0 +1,68 @@ +#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/src/dep/libs/nall/directory.hpp b/src/dep/libs/nall/directory.hpp new file mode 100755 index 000000000..45a062c70 --- /dev/null +++ b/src/dep/libs/nall/directory.hpp @@ -0,0 +1,230 @@ +#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 = "*") { + lstring folders = directory::ufolders(pathname, pattern); + folders.sort(); + return folders; + } + + static lstring files(const string& pathname, const string& pattern = "*") { + lstring files = directory::ufiles(pathname, pattern); + files.sort(); + return files; + } + + static lstring contents(const string& pathname, const string& pattern = "*") { + lstring folders = directory::ufolders(pathname); //pattern search of contents should only filter files + lstring files = directory::ufiles(pathname, pattern); + folders.sort(); + files.sort(); + for(auto& file : files) folders.append(file); + return folders; + } + + static lstring ifolders(const string& pathname, const string& pattern = "*") { + lstring folders = ufolders(pathname, pattern); + folders.isort(); + return folders; + } + + static lstring ifiles(const string& pathname, const string& pattern = "*") { + lstring files = ufiles(pathname, pattern); + files.isort(); + return files; + } + + static lstring icontents(const string& pathname, const string& pattern = "*") { + lstring folders = directory::ufolders(pathname); //pattern search of contents should only filter files + lstring files = directory::ufiles(pathname, pattern); + folders.isort(); + files.isort(); + for(auto& file : files) folders.append(file); + return folders; + } + +private: + //internal functions; these return unsorted lists + static lstring ufolders(const string& pathname, const string& pattern = "*"); + static lstring ufiles(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::ufolders(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(name.match(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(name.match(pattern)) list.append(name); + } + } + } + FindClose(handle); + } + for(auto& name : list) name.append("/"); //must append after sorting + return list; + } + + inline lstring directory::ufiles(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(name.match(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(name.match(pattern)) list.append(name); + } + } + FindClose(handle); + } + return list; + } +#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::ufolders(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; + bool is_directory = ep->d_type & DT_DIR; + if(ep->d_type & DT_UNKNOWN) { + struct stat sp = {0}; + stat(string{pathname, ep->d_name}, &sp); + is_directory = S_ISDIR(sp.st_mode); + } + if(is_directory) { + if(strmatch(ep->d_name, pattern)) list.append(ep->d_name); + } + } + closedir(dp); + } + for(auto& name : list) name.append("/"); //must append after sorting + return list; + } + + inline lstring directory::ufiles(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(strmatch(ep->d_name, pattern)) list.append(ep->d_name); + } + } + closedir(dp); + } + return list; + } +#endif + +} + +#endif diff --git a/src/dep/libs/nall/dl.hpp b/src/dep/libs/nall/dl.hpp new file mode 100755 index 000000000..e61b45ba4 --- /dev/null +++ b/src/dep/libs/nall/dl.hpp @@ -0,0 +1,119 @@ +#ifndef NALL_DL_HPP +#define NALL_DL_HPP + +//dynamic linking support + +#include +#include +#include +#include + +#if defined(PLATFORM_X) || defined(PLATFORM_MACOSX) + #include +#elif defined(PLATFORM_WINDOWS) + #include + #include +#endif + +namespace nall { + +struct library { + explicit operator bool() const { return open(); } + bool open() const { return handle; } + bool open(const string&, const string& = ""); + bool open_absolute(const string&); + void* sym(const string&); + void close(); + + library() = default; + ~library() { close(); } + + library& operator=(const library&) = delete; + library(const library&) = delete; + +private: + uintptr_t handle = 0; +}; + +#if defined(PLATFORM_X) +inline bool library::open(const string& name, const string& path) { + if(handle) close(); + handle = (uintptr_t)dlopen(string(path, !path.empty() && !path.endsWith("/") ? "/" : "", "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 string& name) { + if(handle) close(); + handle = (uintptr_t)dlopen(name, RTLD_LAZY); + return handle; +} + +inline void* library::sym(const string& name) { + if(!handle) return nullptr; + return dlsym((void*)handle, name); +} + +inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; +} +#elif defined(PLATFORM_MACOSX) +inline bool library::open(const string& name, const string& path) { + if(handle) close(); + handle = (uintptr_t)dlopen(string(path, !path.empty() && !path.endsWith("/") ? "/" : "", "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 string& name) { + if(handle) close(); + handle = (uintptr_t)dlopen(name, RTLD_LAZY); + return handle; +} + +inline void* library::sym(const string& name) { + if(!handle) return nullptr; + 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 string& name, const string& path) { + if(handle) close(); + string filepath(path, !path.empty() && !path.endsWith("/") && !path.endsWith("\\") ? "/" : "", name, ".dll"); + handle = (uintptr_t)LoadLibraryW(utf16_t(filepath)); + return handle; +} + +inline bool library::open_absolute(const string& name) { + if(handle) close(); + handle = (uintptr_t)LoadLibraryW(utf16_t(name)); + return handle; +} + +inline void* library::sym(const string& name) { + if(!handle) return nullptr; + return (void*)GetProcAddress((HMODULE)handle, name); +} + +inline void library::close() { + if(!handle) return; + FreeLibrary((HMODULE)handle); + handle = 0; +} +#else +inline bool library::open(const string&, const string&) { return false; } +inline bool library::open_absolute(const string&) { return false; } +inline void* library::sym(const string&) { return nullptr; } +inline void library::close() {} +#endif + +} + +#endif diff --git a/src/dep/libs/nall/dsp.hpp b/src/dep/libs/nall/dsp.hpp new file mode 100755 index 000000000..3f12a22a2 --- /dev/null +++ b/src/dep/libs/nall/dsp.hpp @@ -0,0 +1,15 @@ +#ifndef NALL_DSP_HPP +#define NALL_DSP_HPP + +#include + +#include +#ifdef __SSE__ + #include +#endif + +#define NALL_DSP_INTERNAL_HPP +#include +#undef NALL_DSP_INTERNAL_HPP + +#endif diff --git a/src/dep/libs/nall/dsp/buffer.hpp b/src/dep/libs/nall/dsp/buffer.hpp new file mode 100755 index 000000000..bdaab79cf --- /dev/null +++ b/src/dep/libs/nall/dsp/buffer.hpp @@ -0,0 +1,52 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct Buffer { + double** sample = nullptr; + uint16_t rdoffset = 0; + uint16_t wroffset = 0; + unsigned channels = 0; + + void setChannels(unsigned channels) { + if(sample) { + for(unsigned c = 0; c < this->channels; c++) { + if(sample[c]) delete[] sample[c]; + } + 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() { + } + + ~Buffer() { + setChannels(0); + } +}; + +#endif diff --git a/src/dep/libs/nall/dsp/core.hpp b/src/dep/libs/nall/dsp/core.hpp new file mode 100755 index 000000000..ee730abca --- /dev/null +++ b/src/dep/libs/nall/dsp/core.hpp @@ -0,0 +1,168 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +#include +#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 = nullptr; + 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/src/dep/libs/nall/dsp/resample/average.hpp b/src/dep/libs/nall/dsp/resample/average.hpp new file mode 100755 index 000000000..db3b29f61 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/dsp/resample/cosine.hpp b/src/dep/libs/nall/dsp/resample/cosine.hpp new file mode 100755 index 000000000..ea65afdbd --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/dsp/resample/cubic.hpp b/src/dep/libs/nall/dsp/resample/cubic.hpp new file mode 100755 index 000000000..6265e2a4a --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/dsp/resample/hermite.hpp b/src/dep/libs/nall/dsp/resample/hermite.hpp new file mode 100755 index 000000000..04c850c1d --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/dsp/resample/linear.hpp b/src/dep/libs/nall/dsp/resample/linear.hpp new file mode 100755 index 000000000..e302b5a31 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/dsp/resample/nearest.hpp b/src/dep/libs/nall/dsp/resample/nearest.hpp new file mode 100755 index 000000000..51b261b13 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/dsp/resample/sinc.hpp b/src/dep/libs/nall/dsp/resample/sinc.hpp new file mode 100755 index 000000000..e23e458c6 --- /dev/null +++ b/src/dep/libs/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] = nullptr; +} + +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/src/dep/libs/nall/dsp/settings.hpp b/src/dep/libs/nall/dsp/settings.hpp new file mode 100755 index 000000000..3a8f24c62 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/emulation/super-famicom-usart.hpp b/src/dep/libs/nall/emulation/super-famicom-usart.hpp new file mode 100755 index 000000000..4de4a1122 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/endian.hpp b/src/dep/libs/nall/endian.hpp new file mode 100755 index 000000000..1f834b5b9 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/file.hpp b/src/dep/libs/nall/file.hpp new file mode 100755 index 000000000..721253d7b --- /dev/null +++ b/src/dep/libs/nall/file.hpp @@ -0,0 +1,362 @@ +#ifndef NALL_FILE_HPP +#define NALL_FILE_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + +inline FILE* fopen_utf8(const string& filename, const string& mode) { + #if !defined(_WIN32) + return fopen(filename, mode); + #else + return _wfopen(utf16_t(filename), utf16_t(mode)); + #endif +} + +struct file : varint { + 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) { + auto result = rename(sourcename, targetname); + if(result == 0) return true; + if(errno == EXDEV) { + //cannot move files between file systems; copy file instead of failing + if(file::copy(sourcename, targetname)) { + file::remove(sourcename); + return true; + } + } + return false; + } + + 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 string& text) { + file fp; + if(fp.open(filename, mode::write) == false) return false; + fp.print(text); + 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 bool create(const string& filename) { + //create an empty file (will replace existing files) + file fp; + if(fp.open(filename, mode::write) == false) return false; + 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 stat data; + if(stat(filename, &data) != 0) return false; + #else + struct __stat64 data; + if(_wstat64(utf16_t(filename), &data) != 0) return false; + #endif + //return true if this is a file, and false if this is a directory + return !(data.st_mode & S_IFDIR); + } + + static uintmax_t size(const string& filename) { + #if !defined(_WIN32) + struct stat data; + stat(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 stat data; + stat(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; + } + + explicit operator bool() const { + return open(); + } + + 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 = nullptr; + } + + file() { + } + + file(const string& filename, mode mode_) { + open(filename, mode_); + } + + ~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] = {0}; + int buffer_offset = -1; //invalidate buffer + bool buffer_dirty = false; + FILE *fp = nullptr; + unsigned file_offset = 0; + unsigned file_size = 0; + mode file_mode = mode::read; + + 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/src/dep/libs/nall/filemap.hpp b/src/dep/libs/nall/filemap.hpp new file mode 100755 index 000000000..0c9ef5fa7 --- /dev/null +++ b/src/dep/libs/nall/filemap.hpp @@ -0,0 +1,215 @@ +#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 { + +struct filemap { + enum class mode : unsigned { read, write, readwrite, writeread }; + + explicit operator bool() const { return open(); } + bool open() const { return p_open(); } + bool open(const string& 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_ctor(); } + filemap(const string& filename, mode mode_) { p_ctor(); p_open(filename, mode_); } + ~filemap() { p_dtor(); } + +private: + uint8_t *p_handle = nullptr; + unsigned p_size = 0; + + #if defined(_WIN32) + //============= + //MapViewOfFile + //============= + + HANDLE p_filehandle, p_maphandle; + + bool p_open() const { + return p_handle; + } + + bool p_open(const string& filename, mode mode_) { + if(file::exists(filename) && file::size(filename) == 0) { + p_handle = nullptr; + 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, nullptr, + creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr); + if(p_filehandle == INVALID_HANDLE_VALUE) return false; + + p_size = GetFileSize(p_filehandle, nullptr); + + p_maphandle = CreateFileMapping(p_filehandle, nullptr, flprotect, 0, p_size, nullptr); + 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 = nullptr; + } + + 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 string& filename, mode mode_) { + if(file::exists(filename) && file::size(filename) == 0) { + p_handle = nullptr; + 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(nullptr, p_size, mmap_flags, MAP_SHARED, p_fd, 0); + if(p_handle == MAP_FAILED) { + p_handle = nullptr; + ::close(p_fd); + p_fd = -1; + return false; + } + + return p_handle; + } + + void p_close() { + if(p_handle) { + munmap(p_handle, p_size); + p_handle = nullptr; + } + + 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/src/dep/libs/nall/function.hpp b/src/dep/libs/nall/function.hpp new file mode 100755 index 000000000..77d2f41a2 --- /dev/null +++ b/src/dep/libs/nall/function.hpp @@ -0,0 +1,64 @@ +#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() {} + }; + + container* callback = nullptr; + + 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: + explicit 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() = default; + function(const function &source) { operator=(source); } + function(void* function) { 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/src/dep/libs/nall/group.hpp b/src/dep/libs/nall/group.hpp new file mode 100755 index 000000000..f4ae68a2a --- /dev/null +++ b/src/dep/libs/nall/group.hpp @@ -0,0 +1,66 @@ +#ifndef NALL_GROUP_HPP +#define NALL_GROUP_HPP + +//group +//vector of unique references + +#include + +namespace nall { + +template struct group : protected vector { + group& operator=(const group& source) { vector::operator=(source); return *this; } + group& operator=(group&& source) { vector::operator=(std::move(source)); return *this; } + template group(Args&&... args) { construct(std::forward(args)...); } + + bool empty() const { return vector::empty(); } + unsigned size() const { return vector::size(); } + void reset() { vector::reset(); } + + T& first() const { return *vector::operator[](0); } + + //return true if at least one item was appended + template bool append(T& value, Args&&... args) { + bool result = append(value); + return append(std::forward(args)...) | result; + } + + bool append(T& value) { + if(vector::find(&value)) return false; + return vector::append(&value), true; + } + + //return true if at least one item was removed + template bool remove(T& value, Args&&... args) { + bool result = remove(value); + return remove(std::forward(args)...) | result; + } + + bool remove(T& value) { + if(auto position = vector::find(&value)) return vector::remove(position()), true; + return false; + } + + struct iterator : protected vector::constIterator { + T& operator*() const { return *vector::constIterator::operator*(); } + bool operator!=(const iterator& source) const { return vector::constIterator::operator!=(source); } + iterator& operator++() { vector::constIterator::operator++(); return *this; } + iterator(const group& source, unsigned position) : vector::constIterator(source, position) {} + }; + + const iterator begin() const { return iterator(*this, 0); } + const iterator end() const { return iterator(*this, size()); } + +private: + void construct() {} + void construct(const group& source) { vector::operator=(source); } + void construct(group&& source) { vector::operator=(std::move(source)); } + template void construct(T& value, Args&&... args) { + append(value); + construct(std::forward(args)...); + } +}; + +} + +#endif diff --git a/src/dep/libs/nall/gzip.hpp b/src/dep/libs/nall/gzip.hpp new file mode 100755 index 000000000..2d43e7ab2 --- /dev/null +++ b/src/dep/libs/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 = nullptr; + unsigned size = 0; + + 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() { +} + +gzip::~gzip() { + if(data) delete[] data; +} + +} + +#endif diff --git a/src/dep/libs/nall/hashset.hpp b/src/dep/libs/nall/hashset.hpp new file mode 100755 index 000000000..3f51199c0 --- /dev/null +++ b/src/dep/libs/nall/hashset.hpp @@ -0,0 +1,137 @@ +#ifndef NALL_HASHSET_HPP +#define NALL_HASHSET_HPP + +//hashset +// +//search: O(1) average; O(n) worst +//insert: O(1) average; O(n) worst +//remove: O(1) average; O(n) worst +// +//requirements: +// unsigned T::hash() const; +// bool T::operator==(const T&) const; + +namespace nall { + +template +struct hashset { +protected: + T** pool = nullptr; + unsigned length = 8; //length of pool + unsigned count = 0; //number of objects inside of the pool + +public: + hashset() {} + hashset(unsigned length) : length(bit::round(length)) {} + hashset(const hashset& source) { operator=(source); } + hashset(hashset&& source) { operator=(std::move(source)); } + ~hashset() { reset(); } + + hashset& operator=(const hashset& source) { + reset(); + if(source.pool) { + for(unsigned n = 0; n < source.count; n++) { + insert(*source.pool[n]); + } + } + return *this; + } + + hashset& operator=(hashset&& source) { + reset(); + pool = source.pool; + length = source.length; + count = source.count; + source.pool = nullptr; + source.length = 8; + source.count = 0; + return *this; + } + + unsigned capacity() const { return length; } + unsigned size() const { return count; } + bool empty() const { return count == 0; } + + void reset() { + if(pool) { + for(unsigned n = 0; n < length; n++) { + if(pool[n]) { + delete pool[n]; + pool[n] = nullptr; + } + } + delete pool; + pool = nullptr; + } + length = 8; + count = 0; + } + + void reserve(unsigned size) { + //ensure all items will fit into pool (with <= 50% load) and amortize growth + size = bit::round(max(size, count << 1)); + T** copy = new T*[size](); + + if(pool) { + for(unsigned n = 0; n < length; n++) { + if(pool[n]) { + unsigned hash = (*pool[n]).hash() & (size - 1); + while(copy[hash]) if(++hash >= size) hash = 0; + copy[hash] = pool[n]; + pool[n] = nullptr; + } + } + } + + delete pool; + pool = copy; + length = size; + } + + optional find(const T& value) { + if(!pool) return false; + + unsigned hash = value.hash() & (length - 1); + while(pool[hash]) { + if(value == *pool[hash]) return {true, *pool[hash]}; + if(++hash >= length) hash = 0; + } + + return false; + } + + optional insert(const T& value) { + if(!pool) pool = new T*[length](); + + //double pool size when load is >= 50% + if(count >= (length >> 1)) reserve(length << 1); + count++; + + unsigned hash = value.hash() & (length - 1); + while(pool[hash]) if(++hash >= length) hash = 0; + pool[hash] = new T(value); + + return {true, *pool[hash]}; + } + + bool remove(const T& value) { + if(!pool) return false; + + unsigned hash = value.hash() & (length - 1); + while(pool[hash]) { + if(value == *pool[hash]) { + delete pool[hash]; + pool[hash] = nullptr; + count--; + return true; + } + if(++hash >= length) hash = 0; + } + + return false; + } +}; + +} + +#endif diff --git a/src/dep/libs/nall/hid.hpp b/src/dep/libs/nall/hid.hpp new file mode 100755 index 000000000..dc7088869 --- /dev/null +++ b/src/dep/libs/nall/hid.hpp @@ -0,0 +1,120 @@ +#ifndef NALL_HID_HPP +#define NALL_HID_HPP + +namespace nall { + +namespace HID { + struct Input { + string name; + int16_t value = 0; + + Input() {} + Input(const string& name) : name(name) {} + }; + + struct Group { + string name; + vector input; + + Group() {} + Group(const string& name) : name(name) {} + + void append(const string& name) { + input.append({name}); + } + + optional find(const string& name) { + for(unsigned id = 0; id < input.size(); id++) { + if(input[id].name == name) return {true, id}; + } + return false; + } + }; + + struct Device { + uint64_t id = 0; + string name; + vector group; + + uint32_t pathID() const { return (uint32_t)(id >> 32); } + uint32_t deviceID() const { return (uint32_t)(id >> 0); } + uint16_t vendorID() const { return (uint16_t)(id >> 16); } + uint16_t productID() const { return (uint16_t)(id >> 0); } + + virtual bool isNull() const { return false; } + virtual bool isKeyboard() const { return false; } + virtual bool isMouse() const { return false; } + virtual bool isJoypad() const { return false; } + + void append(const string& name) { + group.append({name}); + } + + optional find(const string& name) { + for(unsigned id = 0; id < group.size(); id++) { + if(group[id].name == name) return {true, id}; + } + return false; + } + }; + + struct Null : Device { + Null() { + name = "Null"; + } + + bool isNull() const { return true; } + }; + + struct Keyboard : Device { + enum GroupID : unsigned { Button }; + + Group& button() { return group[GroupID::Button]; } + + Keyboard() { + name = "Keyboard"; + append("Button"); + } + + bool isKeyboard() const { return true; } + }; + + struct Mouse : Device { + enum GroupID : unsigned { Axis, Button }; + + Group& axis() { return group[GroupID::Axis]; } + Group& button() { return group[GroupID::Button]; } + + Mouse() { + name = "Mouse"; + append("Axis"); + append("Button"); + } + + bool isMouse() const { return true; } + }; + + struct Joypad : Device { + enum GroupID : unsigned { Axis, Hat, Trigger, Button }; + + Group& axis() { return group[GroupID::Axis]; } + Group& hat() { return group[GroupID::Hat]; } + Group& trigger() { return group[GroupID::Trigger]; } + Group& button() { return group[GroupID::Button]; } + bool rumble = false; + + Joypad() { + name = "Joypad"; + append("Axis"); + append("Hat"); + append("Trigger"); + append("Button"); + } + + bool isJoypad() const { return true; } + }; +} + +} + +#endif diff --git a/src/dep/libs/nall/http.hpp b/src/dep/libs/nall/http.hpp new file mode 100755 index 000000000..55421867e --- /dev/null +++ b/src/dep/libs/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 = nullptr; + 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.ifind("\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.ifind("\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 = nullptr; + 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/src/dep/libs/nall/image.hpp b/src/dep/libs/nall/image.hpp new file mode 100755 index 000000000..5f39fce5f --- /dev/null +++ b/src/dep/libs/nall/image.hpp @@ -0,0 +1,22 @@ +#ifndef NALL_IMAGE_HPP +#define NALL_IMAGE_HPP + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/src/dep/libs/nall/image/base.hpp b/src/dep/libs/nall/image/base.hpp new file mode 100755 index 000000000..5697ec8dd --- /dev/null +++ b/src/dep/libs/nall/image/base.hpp @@ -0,0 +1,124 @@ +#ifndef NALL_IMAGE_BASE_HPP +#define NALL_IMAGE_BASE_HPP + +namespace nall { + +struct image { + uint8_t* data = nullptr; + unsigned width = 0; + unsigned height = 0; + unsigned pitch = 0; + unsigned size = 0; + + bool endian = 0; //0 = lsb, 1 = msb + unsigned depth = 32; + unsigned stride = 4; + + 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); + } + }; + + channel alpha = {255u << 24, 8u, 24u}; + channel red = {255u << 16, 8u, 16u}; + channel green = {255u << 8, 8u, 8u}; + channel blue = {255u << 0, 8u, 0u}; + + enum class blend : unsigned { + add, + sourceAlpha, //color = sourceColor * sourceAlpha + targetColor * (1 - sourceAlpha) + sourceColor, //color = sourceColor + targetAlpha, //color = targetColor * targetAlpha + sourceColor * (1 - targetAlpha) + targetColor, //color = targetColor + }; + + //static.hpp + 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); + + //core.hpp + 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 bool load(const string& filename); + inline void allocate(unsigned width, unsigned height); + + //fill.hpp + inline void fill(uint64_t color = 0); + inline void gradient(uint64_t a, uint64_t b, uint64_t c, uint64_t d); + inline void gradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY, function callback); + inline void crossGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY); + inline void diamondGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY); + inline void horizontalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY); + inline void radialGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY); + inline void sphericalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY); + inline void squareGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY); + inline void verticalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY); + + //scale.hpp + inline void scale(unsigned width, unsigned height, bool linear = true); + + //blend.hpp + inline void impose(blend mode, unsigned targetX, unsigned targetY, image source, unsigned x, unsigned y, unsigned width, unsigned height); + + //utility.hpp + inline bool crop(unsigned x, unsigned y, unsigned width, unsigned height); + inline void alphaBlend(uint64_t alphaColor); + inline void transform(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + +protected: + //core.hpp + inline uint8_t* allocate(unsigned width, unsigned height, unsigned stride); + + //scale.hpp + inline void scaleLinearWidth(unsigned width); + inline void scaleLinearHeight(unsigned height); + inline void scaleLinear(unsigned width, unsigned height); + inline void scaleNearest(unsigned width, unsigned height); + + //load.hpp + inline bool loadBMP(const string& filename); + inline bool loadPNG(const string& filename); + inline bool loadPNG(const uint8_t* data, unsigned size); + + //interpolation.hpp + alwaysinline void isplit(uint64_t* component, uint64_t color); + alwaysinline uint64_t imerge(const uint64_t* component); + alwaysinline uint64_t interpolate1f(uint64_t a, uint64_t b, double x); + alwaysinline uint64_t interpolate1f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y); + alwaysinline uint64_t interpolate1i(int64_t a, int64_t b, uint32_t x); + alwaysinline uint64_t interpolate1i(int64_t a, int64_t b, int64_t c, int64_t d, uint32_t x, uint32_t y); + inline uint64_t interpolate4f(uint64_t a, uint64_t b, double x); + inline uint64_t interpolate4f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y); + inline uint64_t interpolate4i(uint64_t a, uint64_t b, uint32_t x); + inline uint64_t interpolate4i(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint32_t x, uint32_t y); +}; + +} + +#endif diff --git a/src/dep/libs/nall/image/blend.hpp b/src/dep/libs/nall/image/blend.hpp new file mode 100755 index 000000000..6c8997a1e --- /dev/null +++ b/src/dep/libs/nall/image/blend.hpp @@ -0,0 +1,74 @@ +#ifndef NALL_IMAGE_BLEND_HPP +#define NALL_IMAGE_BLEND_HPP + +namespace nall { + +void image::impose(blend mode, unsigned targetX, unsigned targetY, image source, unsigned sourceX, unsigned sourceY, unsigned sourceWidth, unsigned sourceHeight) { + source.transform(endian, depth, alpha.mask, red.mask, green.mask, blue.mask); + + for(unsigned y = 0; y < sourceHeight; y++) { + const uint8_t* sp = source.data + source.pitch * (sourceY + y) + source.stride * sourceX; + uint8_t* dp = data + pitch * (targetY + y) + stride * targetX; + for(unsigned x = 0; x < sourceWidth; x++) { + uint64_t sourceColor = source.read(sp); + uint64_t targetColor = read(dp); + + int64_t sa = (sourceColor & alpha.mask) >> alpha.shift; + int64_t sr = (sourceColor & red.mask ) >> red.shift; + int64_t sg = (sourceColor & green.mask) >> green.shift; + int64_t sb = (sourceColor & blue.mask ) >> blue.shift; + + int64_t da = (targetColor & alpha.mask) >> alpha.shift; + int64_t dr = (targetColor & red.mask ) >> red.shift; + int64_t dg = (targetColor & green.mask) >> green.shift; + int64_t db = (targetColor & blue.mask ) >> blue.shift; + + uint64_t a, r, g, b; + + switch(mode) { + case blend::add: + a = max(sa, da); + r = min(red.mask >> red.shift, ((sr * sa) >> alpha.depth) + ((dr * da) >> alpha.depth)); + g = min(green.mask >> green.shift, ((sg * sa) >> alpha.depth) + ((dg * da) >> alpha.depth)); + b = min(blue.mask >> blue.shift, ((sb * sa) >> alpha.depth) + ((db * da) >> alpha.depth)); + break; + + case blend::sourceAlpha: + a = max(sa, da); + r = dr + (((sr - dr) * sa) >> alpha.depth); + g = dg + (((sg - dg) * sa) >> alpha.depth); + b = db + (((sb - db) * sa) >> alpha.depth); + break; + + case blend::sourceColor: + a = sa; + r = sr; + g = sg; + b = sb; + break; + + case blend::targetAlpha: + a = max(sa, da); + r = sr + (((dr - sr) * da) >> alpha.depth); + g = sg + (((dg - sg) * da) >> alpha.depth); + b = sb + (((db - sb) * da) >> alpha.depth); + break; + + case blend::targetColor: + a = da; + r = dr; + g = dg; + b = db; + break; + } + + write(dp, (a << alpha.shift) | (r << red.shift) | (g << green.shift) | (b << blue.shift)); + sp += source.stride; + dp += stride; + } + } +} + +} + +#endif diff --git a/src/dep/libs/nall/image/core.hpp b/src/dep/libs/nall/image/core.hpp new file mode 100755 index 000000000..5ee040426 --- /dev/null +++ b/src/dep/libs/nall/image/core.hpp @@ -0,0 +1,164 @@ +#ifndef NALL_IMAGE_CORE_HPP +#define NALL_IMAGE_CORE_HPP + +namespace nall { + +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; + size = source.size; + + endian = source.endian; + stride = source.stride; + + alpha = source.alpha; + red = source.red; + green = source.green; + blue = source.blue; + + data = allocate(width, height, stride); + memcpy(data, source.data, source.size); + return *this; +} + +image& image::operator=(image&& source) { + free(); + + width = source.width; + height = source.height; + pitch = source.pitch; + size = source.size; + + 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) { + operator=(source); +} + +image::image(image&& source) { + operator=(std::forward(source)); +} + +image::image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) { + this->endian = endian; + this->depth = depth; + this->stride = (depth / 8) + ((depth & 7) > 0); + + alpha = {alphaMask, bitDepth(alphaMask), bitShift(alphaMask)}; + red = {redMask, bitDepth(redMask), bitShift(redMask )}; + green = {greenMask, bitDepth(greenMask), bitShift(greenMask)}; + blue = {blueMask, bitDepth(blueMask), bitShift(blueMask )}; +} + +image::image(const string& filename) { + load(filename); +} + +image::image(const uint8_t* data, unsigned size) { + loadPNG(data, size); +} + +image::image() { +} + +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; +} + +bool image::load(const string& filename) { + if(loadBMP(filename) == true) return true; + if(loadPNG(filename) == true) return true; + return false; +} + +void image::allocate(unsigned width, unsigned height) { + if(data != nullptr && this->width == width && this->height == height) return; + free(); + data = allocate(width, height, stride); + pitch = width * stride; + size = height * pitch; + this->width = width; + this->height = height; +} + +uint8_t* image::allocate(unsigned width, unsigned height, unsigned stride) { + //allocate 1x1 larger than requested; so that linear interpolation does not require bounds-checking + unsigned size = width * height * stride; + unsigned padding = width * stride + stride; + uint8_t* data = new uint8_t[size + padding]; + memset(data + size, 0x00, padding); + return data; +} + +} + +#endif diff --git a/src/dep/libs/nall/image/fill.hpp b/src/dep/libs/nall/image/fill.hpp new file mode 100755 index 000000000..8009c40a0 --- /dev/null +++ b/src/dep/libs/nall/image/fill.hpp @@ -0,0 +1,88 @@ +#ifndef NALL_IMAGE_FILL_HPP +#define NALL_IMAGE_FILL_HPP + +namespace nall { + +void image::fill(uint64_t color) { + uint8_t* dp = data; + for(unsigned y = 0; y < height; y++) { + uint8_t* dp = data + pitch * y; + for(unsigned x = 0; x < width; x++) { + write(dp, color); + dp += stride; + } + } +} + +void image::gradient(uint64_t a, uint64_t b, uint64_t c, uint64_t d) { + for(unsigned y = 0; y < height; y++) { + uint8_t* dp = data + pitch * y; + double muY = (double)y / (double)height; + for(unsigned x = 0; x < width; x++) { + double muX = (double)x / (double)width; + write(dp, interpolate4f(a, b, c, d, muX, muY)); + dp += stride; + } + } +} + +void image::gradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY, function callback) { + for(signed y = 0; y < height; y++) { + uint8_t* dp = data + pitch * y; + double py = max(-radiusY, min(+radiusY, y - centerY)) * 1.0 / radiusY; + for(signed x = 0; x < width; x++) { + double px = max(-radiusX, min(+radiusX, x - centerX)) * 1.0 / radiusX; + double mu = max(0.0, min(1.0, callback(px, py))); + if(mu != mu) mu = 1.0; //NaN + write(dp, interpolate4f(a, b, mu)); + dp += stride; + } + } +} + +void image::crossGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + x = fabs(x), y = fabs(y); + return min(x, y) * min(x, y); + }); +} + +void image::diamondGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return fabs(x) + fabs(y); + }); +} + +void image::horizontalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return fabs(x); + }); +} + +void image::radialGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return sqrt(x * x + y * y); + }); +} + +void image::sphericalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return x * x + y * y; + }); +} + +void image::squareGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return max(fabs(x), fabs(y)); + }); +} + +void image::verticalGradient(uint64_t a, uint64_t b, signed radiusX, signed radiusY, signed centerX, signed centerY) { + return gradient(a, b, radiusX, radiusY, centerX, centerY, [](double x, double y) -> double { + return fabs(y); + }); +} + +} + +#endif diff --git a/src/dep/libs/nall/image/interpolation.hpp b/src/dep/libs/nall/image/interpolation.hpp new file mode 100755 index 000000000..1abe4d940 --- /dev/null +++ b/src/dep/libs/nall/image/interpolation.hpp @@ -0,0 +1,65 @@ +#ifndef NALL_IMAGE_INTERPOLATION_HPP +#define NALL_IMAGE_INTERPOLATION_HPP + +namespace nall { + +void image::isplit(uint64_t* c, uint64_t color) { + c[0] = (color & alpha.mask) >> alpha.shift; + c[1] = (color & red.mask ) >> red.shift; + c[2] = (color & green.mask) >> green.shift; + c[3] = (color & blue.mask ) >> blue.shift; +} + +uint64_t image::imerge(const uint64_t* c) { + return c[0] << alpha.shift | c[1] << red.shift | c[2] << green.shift | c[3] << blue.shift; +} + +uint64_t image::interpolate1f(uint64_t a, uint64_t b, double x) { + return a * (1.0 - x) + b * x; +} + +uint64_t image::interpolate1f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) { + return a * (1.0 - x) * (1.0 - y) + b * x * (1.0 - y) + c * (1.0 - x) * y + d * x * y; +} + +uint64_t image::interpolate1i(int64_t a, int64_t b, uint32_t x) { + return a + (((b - a) * x) >> 32); //a + (b - a) * x +} + +uint64_t image::interpolate1i(int64_t a, int64_t b, int64_t c, int64_t d, uint32_t x, uint32_t y) { + a = a + (((b - a) * x) >> 32); //a + (b - a) * x + c = c + (((d - c) * x) >> 32); //c + (d - c) * x + return a + (((c - a) * y) >> 32); //a + (c - a) * y +} + +uint64_t image::interpolate4f(uint64_t a, uint64_t b, double x) { + uint64_t o[4], pa[4], pb[4]; + isplit(pa, a), isplit(pb, b); + for(unsigned n = 0; n < 4; n++) o[n] = interpolate1f(pa[n], pb[n], x); + return imerge(o); +} + +uint64_t image::interpolate4f(uint64_t a, uint64_t b, uint64_t c, uint64_t d, double x, double y) { + uint64_t o[4], pa[4], pb[4], pc[4], pd[4]; + isplit(pa, a), isplit(pb, b), isplit(pc, c), isplit(pd, d); + for(unsigned n = 0; n < 4; n++) o[n] = interpolate1f(pa[n], pb[n], pc[n], pd[n], x, y); + return imerge(o); +} + +uint64_t image::interpolate4i(uint64_t a, uint64_t b, uint32_t x) { + uint64_t o[4], pa[4], pb[4]; + isplit(pa, a), isplit(pb, b); + for(unsigned n = 0; n < 4; n++) o[n] = interpolate1i(pa[n], pb[n], x); + return imerge(o); +} + +uint64_t image::interpolate4i(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint32_t x, uint32_t y) { + uint64_t o[4], pa[4], pb[4], pc[4], pd[4]; + isplit(pa, a), isplit(pb, b), isplit(pc, c), isplit(pd, d); + for(unsigned n = 0; n < 4; n++) o[n] = interpolate1i(pa[n], pb[n], pc[n], pd[n], x, y); + return imerge(o); +} + +} + +#endif diff --git a/src/dep/libs/nall/image/load.hpp b/src/dep/libs/nall/image/load.hpp new file mode 100755 index 000000000..6ac17d800 --- /dev/null +++ b/src/dep/libs/nall/image/load.hpp @@ -0,0 +1,98 @@ +#ifndef NALL_IMAGE_LOAD_HPP +#define NALL_IMAGE_LOAD_HPP + +namespace nall { + +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 string& filename) { + if(!file::exists(filename)) return false; + auto buffer = file::read(filename); + return loadPNG(buffer.data(), buffer.size()); +} + +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; +} + +} + +#endif diff --git a/src/dep/libs/nall/image/scale.hpp b/src/dep/libs/nall/image/scale.hpp new file mode 100755 index 000000000..9e6cdda04 --- /dev/null +++ b/src/dep/libs/nall/image/scale.hpp @@ -0,0 +1,190 @@ +#ifndef NALL_IMAGE_SCALE_HPP +#define NALL_IMAGE_SCALE_HPP + +namespace nall { + +void image::scale(unsigned outputWidth, unsigned outputHeight, bool linear) { + if(width == outputWidth && height == outputHeight) return; //no scaling necessary + if(linear == false) return scaleNearest(outputWidth, outputHeight); + + if(width == outputWidth ) return scaleLinearHeight(outputHeight); + if(height == outputHeight) return scaleLinearWidth(outputWidth); + + //find fastest scaling method, based on number of interpolation operations required + //magnification usually benefits from two-pass linear interpolation + //minification usually benefits from one-pass bilinear interpolation + unsigned d1wh = ((width * outputWidth ) + (outputWidth * outputHeight)) * 1; + unsigned d1hw = ((height * outputHeight) + (outputWidth * outputHeight)) * 1; + unsigned d2wh = (outputWidth * outputHeight) * 3; + + if(d1wh <= d1hw && d1wh <= d2wh) return scaleLinearWidth(outputWidth), scaleLinearHeight(outputHeight); + if(d1hw <= d2wh) return scaleLinearHeight(outputHeight), scaleLinearWidth(outputWidth); + return scaleLinear(outputWidth, outputHeight); +} + +void image::scaleLinearWidth(unsigned outputWidth) { + uint8_t* outputData = allocate(outputWidth, height, stride); + unsigned outputPitch = outputWidth * stride; + uint64_t xstride = ((uint64_t)(width - 1) << 32) / max(1u, outputWidth - 1); + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint64_t xfraction = 0; + + const uint8_t* sp = data + pitch * y; + uint8_t* dp = outputData + outputPitch * y; + + uint64_t a = read(sp); + uint64_t b = read(sp + stride); + sp += stride; + + unsigned x = 0; + while(true) { + while(xfraction < 0x100000000 && x++ < outputWidth) { + write(dp, interpolate4i(a, b, xfraction)); + dp += stride; + xfraction += xstride; + } + if(x >= outputWidth) break; + + sp += stride; + a = b; + b = read(sp); + xfraction -= 0x100000000; + } + } + + free(); + data = outputData; + width = outputWidth; + pitch = outputPitch; + size = height * pitch; +} + +void image::scaleLinearHeight(unsigned outputHeight) { + uint8_t* outputData = allocate(width, outputHeight, stride); + uint64_t ystride = ((uint64_t)(height - 1) << 32) / max(1u, outputHeight - 1); + + #pragma omp parallel for + for(unsigned x = 0; x < width; x++) { + uint64_t yfraction = 0; + + const uint8_t* sp = data + stride * x; + uint8_t* dp = outputData + stride * x; + + uint64_t a = read(sp); + uint64_t b = read(sp + pitch); + sp += pitch; + + unsigned y = 0; + while(true) { + while(yfraction < 0x100000000 && y++ < outputHeight) { + write(dp, interpolate4i(a, b, yfraction)); + dp += pitch; + yfraction += ystride; + } + if(y >= outputHeight) break; + + sp += pitch; + a = b; + b = read(sp); + yfraction -= 0x100000000; + } + } + + free(); + data = outputData; + height = outputHeight; + size = height * pitch; +} + +void image::scaleLinear(unsigned outputWidth, unsigned outputHeight) { + uint8_t* outputData = allocate(outputWidth, outputHeight, stride); + unsigned outputPitch = outputWidth * stride; + + uint64_t xstride = ((uint64_t)(width - 1) << 32) / max(1u, outputWidth - 1); + uint64_t ystride = ((uint64_t)(height - 1) << 32) / max(1u, outputHeight - 1); + + #pragma omp parallel for + for(unsigned y = 0; y < outputHeight; y++) { + uint64_t yfraction = ystride * y; + uint64_t xfraction = 0; + + const uint8_t* sp = data + pitch * (yfraction >> 32); + uint8_t* dp = outputData + outputPitch * y; + + uint64_t a = read(sp); + uint64_t b = read(sp + stride); + uint64_t c = read(sp + pitch); + uint64_t d = read(sp + pitch + stride); + sp += stride; + + unsigned x = 0; + while(true) { + while(xfraction < 0x100000000 && x++ < outputWidth) { + write(dp, interpolate4i(a, b, c, d, xfraction, yfraction)); + dp += stride; + xfraction += xstride; + } + if(x >= outputWidth) break; + + sp += stride; + a = b; + c = d; + b = read(sp); + d = read(sp + pitch); + xfraction -= 0x100000000; + } + } + + free(); + data = outputData; + width = outputWidth; + height = outputHeight; + pitch = outputPitch; + size = height * pitch; +} + +void image::scaleNearest(unsigned outputWidth, unsigned outputHeight) { + uint8_t* outputData = allocate(outputWidth, outputHeight, stride); + unsigned outputPitch = outputWidth * stride; + + uint64_t xstride = ((uint64_t)width << 32) / outputWidth; + uint64_t ystride = ((uint64_t)height << 32) / outputHeight; + + #pragma omp parallel for + for(unsigned y = 0; y < outputHeight; y++) { + uint64_t yfraction = ystride * y; + uint64_t xfraction = 0; + + const uint8_t* sp = data + pitch * (yfraction >> 32); + uint8_t* dp = outputData + outputPitch * y; + + uint64_t a = read(sp); + + unsigned x = 0; + while(true) { + while(xfraction < 0x100000000 && x++ < outputWidth) { + write(dp, a); + dp += stride; + xfraction += xstride; + } + if(x >= outputWidth) break; + + sp += stride; + a = read(sp); + xfraction -= 0x100000000; + } + } + + free(); + data = outputData; + width = outputWidth; + height = outputHeight; + pitch = outputPitch; + size = height * pitch; +} + +} + +#endif diff --git a/src/dep/libs/nall/image/static.hpp b/src/dep/libs/nall/image/static.hpp new file mode 100755 index 000000000..161407966 --- /dev/null +++ b/src/dep/libs/nall/image/static.hpp @@ -0,0 +1,31 @@ +#ifndef NALL_IMAGE_STATIC_HPP +#define NALL_IMAGE_STATIC_HPP + +namespace nall { + +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) { + if(sourceDepth == 0 || targetDepth == 0) return 0; + while(sourceDepth < targetDepth) { + color = (color << sourceDepth) | color; + sourceDepth += sourceDepth; + } + if(targetDepth < sourceDepth) color >>= (sourceDepth - targetDepth); + return color; +} + +} + +#endif diff --git a/src/dep/libs/nall/image/utility.hpp b/src/dep/libs/nall/image/utility.hpp new file mode 100755 index 000000000..7b96ee9f9 --- /dev/null +++ b/src/dep/libs/nall/image/utility.hpp @@ -0,0 +1,95 @@ +#ifndef NALL_IMAGE_UTILITY_HPP +#define NALL_IMAGE_UTILITY_HPP + +namespace nall { + +bool image::crop(unsigned outputX, unsigned outputY, unsigned outputWidth, unsigned outputHeight) { + if(outputX + outputWidth > width) return false; + if(outputY + outputHeight > height) return false; + + uint8_t* outputData = allocate(outputWidth, outputHeight, stride); + unsigned outputPitch = outputWidth * stride; + + #pragma omp parallel for + for(unsigned y = 0; y < outputHeight; y++) { + const uint8_t* sp = data + pitch * (outputY + y) + stride * outputX; + uint8_t* dp = outputData + outputPitch * y; + for(unsigned x = 0; x < outputWidth; x++) { + write(dp, read(sp)); + sp += stride; + dp += stride; + } + } + + delete[] data; + data = outputData; + width = outputWidth; + height = outputHeight; + pitch = outputPitch; + size = width * pitch; + return true; +} + +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; + } + } +} + +void image::transform(bool outputEndian, unsigned outputDepth, uint64_t outputAlphaMask, uint64_t outputRedMask, uint64_t outputGreenMask, uint64_t outputBlueMask) { + if(endian == outputEndian && depth == outputDepth && alpha.mask == outputAlphaMask && red.mask == outputRedMask && green.mask == outputGreenMask && blue.mask == outputBlueMask) return; + + image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask); + output.allocate(width, height); + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + const uint8_t* sp = data + pitch * y; + uint8_t* dp = output.data + output.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)); +} + +} + +#endif diff --git a/src/dep/libs/nall/inflate.hpp b/src/dep/libs/nall/inflate.hpp new file mode 100755 index 000000000..d096d1736 --- /dev/null +++ b/src/dep/libs/nall/inflate.hpp @@ -0,0 +1,349 @@ +#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 { + +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 != nullptr) { + 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 != nullptr) { + 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 != nullptr) { + 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/src/dep/libs/nall/interpolation.hpp b/src/dep/libs/nall/interpolation.hpp new file mode 100755 index 000000000..afc7108b9 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/intrinsics.hpp b/src/dep/libs/nall/intrinsics.hpp new file mode 100755 index 000000000..2d6a43ed1 --- /dev/null +++ b/src/dep/libs/nall/intrinsics.hpp @@ -0,0 +1,82 @@ +#ifndef NALL_INTRINSICS_HPP +#define NALL_INTRINSICS_HPP + +namespace nall { + +struct Intrinsics { + enum class Compiler : unsigned { Clang, GCC, VisualCPP, Unknown }; + enum class Platform : unsigned { Windows, MacOSX, X, Unknown }; //X = Linux, BSD, etc + enum class Architecture : unsigned { x86, amd64, Unknown }; + enum class Endian : unsigned { LSB, MSB, Unknown }; + + static inline Compiler compiler(); + static inline Platform platform(); + static inline Architecture architecture(); + static inline Endian endian(); +}; + +/* Compiler detection */ + +#if defined(__clang__) + #define COMPILER_CLANG + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::Clang; } +#elif defined(__GNUC__) + #define COMPILER_GCC + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::GCC; } +#elif defined(_MSC_VER) + #define COMPILER_VISUALCPP + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::VisualCPP; } +#else + #warning "unable to detect compiler" + #define COMPILER_UNKNOWN + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::Unknown; } +#endif + +/* Platform detection */ + +#if defined(_WIN32) + #define PLATFORM_WINDOWS + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::Windows; } +#elif defined(__APPLE__) + #define PLATFORM_MACOSX + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::MacOSX; } +#elif defined(linux) || defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__GNU__) + #define PLATFORM_X + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::X; } +#else + #warning "unable to detect platform" + #define PLATFORM_UNKNOWN + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::Unknown; } +#endif + +/* Architecture Detection */ + +#if defined(__i386__) || defined(_M_IX86) + #define ARCH_X86 + Intrinsics::Architecture Intrinsics::architecture() { return Intrinsics::Architecture::x86; } +#elif defined(__amd64__) || defined(_M_AMD64) + #define ARCH_AMD64 + Intrinsics::Architecture Intrinsics::architecture() { return Intrinsics::Architecture::amd64; } +#else + #warning "unable to detect architecture" + #define ARCH_UNKNOWN + Intrinsics::Architecture Intrinsics::architecture() { return Intrinsics::Architecture::Unknown; } +#endif + +/* Endian detection */ + +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || defined(__LITTLE_ENDIAN__) || defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64) + #define ENDIAN_LSB + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::LSB; } +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) || defined(__BIG_ENDIAN__) || defined(__powerpc__) || defined(_M_PPC) + #define ENDIAN_MSB + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::MSB; } +#else + #warning "unable to detect endian" + #define ENDIAN_UNKNOWN + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::Unknown; } +#endif + +} + +#endif diff --git a/src/dep/libs/nall/invoke.hpp b/src/dep/libs/nall/invoke.hpp new file mode 100755 index 000000000..3e6d29cbe --- /dev/null +++ b/src/dep/libs/nall/invoke.hpp @@ -0,0 +1,58 @@ +#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 +#include + +#if defined(PLATFORM_WINDOWS) + #include +#endif + +namespace nall { + +#if defined(PLATFORM_WINDOWS) + +template inline void invoke(const string& name, Args&&... args) { + lstring argl(std::forward(args)...); + for(auto& arg : argl) if(arg.find(" ")) arg = {"\"", arg, "\""}; + string arguments = argl.merge(" "); + ShellExecuteW(NULL, NULL, utf16_t(name), utf16_t(arguments), NULL, SW_SHOWNORMAL); +} + +#elif defined(PLATFORM_X) + +template inline void invoke(const string& name, Args&&... args) { + pid_t pid = fork(); + if(pid == 0) { + const char* argv[1 + sizeof...(args) + 1]; + const char** 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); + } +} + +#else + +template inline void invoke(const string& name, Args&&... args) { +} + +#endif + +} + +#endif diff --git a/src/dep/libs/nall/ips.hpp b/src/dep/libs/nall/ips.hpp new file mode 100755 index 000000000..1159dee1c --- /dev/null +++ b/src/dep/libs/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 = nullptr; + unsigned size = 0; + const uint8_t* sourceData = nullptr; + unsigned sourceSize = 0; + const uint8_t* modifyData = nullptr; + unsigned modifySize = 0; +}; + +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() { +} + +ips::~ips() { + if(data) delete[] data; + if(sourceData) delete[] sourceData; + if(modifyData) delete[] modifyData; +} + +} + +#endif diff --git a/src/dep/libs/nall/map.hpp b/src/dep/libs/nall/map.hpp new file mode 100755 index 000000000..56e4b5c89 --- /dev/null +++ b/src/dep/libs/nall/map.hpp @@ -0,0 +1,59 @@ +#ifndef NALL_MAP_HPP +#define NALL_MAP_HPP + +#include + +namespace nall { + +template struct map { + struct node_t { + T key; + U value; + bool operator< (const node_t& source) const { return key < source.key; } + bool operator==(const node_t& source) const { return key == source.key; } + node_t() = default; + node_t(const T& key) : key(key) {} + node_t(const T& key, const U& value) : key(key), value(value) {} + }; + + optional find(const T& key) const { + if(auto node = root.find({key})) return {true, node().value}; + return false; + } + + void insert(const T& key, const U& value) { root.insert({key, value}); } + void remove(const T& key) { root.remove({key}); } + unsigned size() const { return root.size(); } + void reset() { root.reset(); } + + typename set::iterator begin() { return root.begin(); } + typename set::iterator end() { return root.end(); } + const typename set::iterator begin() const { return root.begin(); } + const typename set::iterator end() const { return root.end(); } + +protected: + set root; +}; + +template struct bimap { + optional find(const T& key) const { return tmap.find(key); } + optional find(const U& key) const { return umap.find(key); } + void insert(const T& key, const U& value) { tmap.insert(key, value); umap.insert(value, key); } + void remove(const T& key) { if(auto p = tmap.find(key)) { umap.remove(p().value); tmap.remove(key); } } + void remove(const U& key) { if(auto p = umap.find(key)) { tmap.remove(p().value); umap.remove(key); } } + unsigned size() const { return tmap.size(); } + void reset() { tmap.reset(); umap.reset(); } + + typename set::node_t>::iterator begin() { return tmap.begin(); } + typename set::node_t>::iterator end() { return tmap.end(); } + const typename set::node_t>::iterator begin() const { return tmap.begin(); } + const typename set::node_t>::iterator end() const { return tmap.end(); } + +protected: + map tmap; + map umap; +}; + +} + +#endif diff --git a/src/dep/libs/nall/matrix.hpp b/src/dep/libs/nall/matrix.hpp new file mode 100755 index 000000000..991c7e682 --- /dev/null +++ b/src/dep/libs/nall/matrix.hpp @@ -0,0 +1,33 @@ +#ifndef NALL_MATRIX_HPP +#define NALL_MATRIX_HPP + +namespace nall { + +namespace Matrix { + +template inline void Multiply(T* output, const T* xdata, unsigned xrows, unsigned xcols, const T* ydata, unsigned yrows, unsigned ycols) { + if(xcols != yrows) return; + + for(unsigned y = 0; y < xrows; y++) { + for(unsigned x = 0; x < ycols; x++) { + T sum = 0; + for(unsigned z = 0; z < xcols; z++) { + sum += xdata[y * xcols + z] * ydata[z * ycols + x]; + } + *output++ = sum; + } + } +} + +template inline vector Multiply(const T* xdata, unsigned xrows, unsigned xcols, const T* ydata, unsigned yrows, unsigned ycols) { + vector output; + output.resize(xrows * ycols); + Multiply(output.data(), xdata, xrows, xcols, ydata, yrows, ycols); + return output; +} + +} + +} + +#endif diff --git a/src/dep/libs/nall/mosaic.hpp b/src/dep/libs/nall/mosaic.hpp new file mode 100755 index 000000000..16fd0bfd3 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/mosaic/bitstream.hpp b/src/dep/libs/nall/mosaic/bitstream.hpp new file mode 100755 index 000000000..161246bd4 --- /dev/null +++ b/src/dep/libs/nall/mosaic/bitstream.hpp @@ -0,0 +1,55 @@ +#ifdef NALL_MOSAIC_INTERNAL_HPP + +namespace nall { +namespace mosaic { + +struct bitstream { + filemap fp; + uint8_t* data = nullptr; + unsigned size = 0; + bool readonly = false; + bool endian = 1; + + 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; + } + + 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; + } + + 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; + } + + void close() { + fp.close(); + data = nullptr; + } + + bitstream() { + } + + ~bitstream() { + close(); + } +}; + +} +} + +#endif diff --git a/src/dep/libs/nall/mosaic/context.hpp b/src/dep/libs/nall/mosaic/context.hpp new file mode 100755 index 000000000..ee6b5d532 --- /dev/null +++ b/src/dep/libs/nall/mosaic/context.hpp @@ -0,0 +1,227 @@ +#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; + + unsigned objectWidth() const { return blockWidth * tileWidth * mosaicWidth + paddingWidth; } + unsigned objectHeight() const { return blockHeight * tileHeight * mosaicHeight + paddingHeight; } + unsigned objectSize() const { + unsigned size = blockStride * tileWidth * tileHeight * mosaicWidth * mosaicHeight + + blockOffset * tileHeight * mosaicWidth * mosaicHeight + + tileStride * mosaicWidth * mosaicHeight + + tileOffset * mosaicHeight; + return max(1u, size); + } + + unsigned eval(const string& expression) { + if(auto result = Eval::integer(expression)) return result(); + return 0u; + } + + 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.match("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", string{n}); + fn.replace("o", string{offset}); + fn.replace("p", string{buffer.size()}); + buffer.resize(offset + 1); + buffer[offset] = eval(fn); + offset += stride; + } + } else if(item.match("base64*")) { + unsigned offset = 0; + item.ltrim<1>("base64"); + if(item.match("(?*) *")) { + 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.match("file *")) { + item.ltrim<1>("file "); + item.trim(); + //... + } else if(item.empty() == false) { + buffer.append(eval(item)); + } + } + } + + 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(); + } + + bool load(const string& filename) { + string filedata = string::read(filename); + if(filedata.empty()) return false; + parse(filedata); + return true; + } + + 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; + + //set alpha to full opacity + paddingColor |= 255u << 24; + for(auto& color : palette) color |= 255u << 24; + } + + 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 = 0; + palette.reset(); + } + + context() { + reset(); + } +}; + +} +} + +#endif diff --git a/src/dep/libs/nall/mosaic/parser.hpp b/src/dep/libs/nall/mosaic/parser.hpp new file mode 100755 index 000000000..93cfd5b05 --- /dev/null +++ b/src/dep/libs/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 + void load(bitstream& stream, uint64_t offset, context& ctx, unsigned width, unsigned height) { + canvas.allocate(width, height); + canvas.fill(ctx.paddingColor); + parse(1, stream, offset, ctx, width, height); + } + + //import from canvas to bitstream + 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, 255u << 24, 255u << 16, 255u << 8, 255u << 0) { + } + +private: + 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]; + } + + 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; + } + + 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/src/dep/libs/nall/nall.hpp b/src/dep/libs/nall/nall.hpp new file mode 100755 index 000000000..f021e134d --- /dev/null +++ b/src/dep/libs/nall/nall.hpp @@ -0,0 +1,63 @@ +#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 +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include + #include +#endif + +#if defined(PLATFORM_X) + #include +#endif + +#endif diff --git a/src/dep/libs/nall/odbc.hpp b/src/dep/libs/nall/odbc.hpp new file mode 100755 index 000000000..cfcd5c0ea --- /dev/null +++ b/src/dep/libs/nall/odbc.hpp @@ -0,0 +1,153 @@ +#ifndef NALL_ODBC_HPP +#define NALL_ODBC_HPP + +//minimal wrapper for core ODBC v3 API +//requires Windows or unixODBC + +#include +#include + +#include +#include +#include + +namespace nall { + +struct ODBC { + inline ODBC(); + inline ODBC(const string& database, const string& username, const string& password); + inline ~ODBC(); + + inline bool connected(); + inline bool connect(const string& hostname, const string& username, const string& password); + inline void disconnect(); + template inline bool execute(Args&&... args); + inline void release(); + inline unsigned rows(); + inline lstring read(); + +private: + char* buffer = nullptr; + SQLHANDLE sqlEnvironment = nullptr; + SQLHANDLE sqlConnection = nullptr; + SQLHANDLE sqlStatement = nullptr; +}; + +ODBC::ODBC() { + auto result = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &sqlEnvironment); + if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) return; + + SQLSetEnvAttr(sqlEnvironment, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + buffer = new char[65536](); +} + +ODBC::ODBC(const string& database, const string& username, const string& password) : ODBC() { + connect(database, username, password); +} + +ODBC::~ODBC() { + if(sqlEnvironment) { + disconnect(); + SQLFreeHandle(SQL_HANDLE_ENV, sqlEnvironment); + sqlEnvironment = nullptr; + + delete[] buffer; + buffer = nullptr; + } +} + +bool ODBC::connected() { + return sqlConnection; +} + +bool ODBC::connect(const string& hostname, const string& username, const string& password) { + if(!sqlEnvironment) return false; + disconnect(); + + auto result = SQLAllocHandle(SQL_HANDLE_DBC, sqlEnvironment, &sqlConnection); + if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) return false; + + SQLSetConnectAttr(sqlConnection, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0); + result = SQLConnectA(sqlConnection, + (SQLCHAR*)(const char*)hostname, SQL_NTS, + (SQLCHAR*)(const char*)username, SQL_NTS, + (SQLCHAR*)(const char*)password, SQL_NTS + ); + if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + disconnect(); + return false; + } + + return true; +} + +void ODBC::disconnect() { + if(sqlConnection) { + release(); + SQLDisconnect(sqlConnection); + SQLFreeHandle(SQL_HANDLE_DBC, sqlConnection); + sqlConnection = nullptr; + } +} + +template +bool ODBC::execute(Args&&... args) { + string statement({args...}); + + if(!sqlConnection) return false; + release(); + + auto result = SQLAllocHandle(SQL_HANDLE_STMT, sqlConnection, &sqlStatement); + if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) return false; + + result = SQLExecDirectA(sqlStatement, (SQLCHAR*)(const char*)statement, SQL_NTS); + if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) { + release(); + return false; + } + + return true; +} + +void ODBC::release() { + if(sqlStatement) { + SQLFreeHandle(SQL_HANDLE_STMT, sqlStatement); + sqlStatement = nullptr; + } +} + +//valid after update, insert or delete +unsigned ODBC::rows() { + if(!sqlStatement) return 0; + + SQLLEN sqlRows = 0; + auto result = SQLRowCount(sqlStatement, &sqlRows); + if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) return 0; + + return sqlRows; +} + +//valid after select +lstring ODBC::read() { + if(!sqlStatement) return {}; + + auto result = SQLFetch(sqlStatement); + if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) return {}; + + SQLSMALLINT sqlColumns = 0; + result = SQLNumResultCols(sqlStatement, &sqlColumns); + if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) return {}; + + lstring data; + for(unsigned column = 0; column < sqlColumns; column++) { + SQLLEN length = 0; + SQLGetData(sqlStatement, 1 + column, SQL_C_CHAR, buffer, 65535, &length); + data.append(buffer); + } + + return data; +} + +} + +#endif diff --git a/src/dep/libs/nall/platform.hpp b/src/dep/libs/nall/platform.hpp new file mode 100755 index 000000000..245767927 --- /dev/null +++ b/src/dep/libs/nall/platform.hpp @@ -0,0 +1,96 @@ +#ifndef NALL_PLATFORM_HPP +#define NALL_PLATFORM_HPP + +namespace Math { + static const long double e = 2.71828182845904523536; + static const long double Pi = 3.14159265358979323846; +} + +#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 + +#include +#include + +#if defined(_WIN32) + #include + #include + #include + #include + #undef interface + #define dllexport __declspec(dllexport) +#else + #include + #include + #include + #define dllexport +#endif + +//========== +//Visual C++ +//========== + +#if defined(_MSC_VER) + #pragma warning(disable:4996) //disable libc "deprecation" warnings + #define va_copy(dest, src) ((dest) = (src)) +#endif + +#if defined(_WIN32) + __declspec(dllimport) int _fileno(FILE*); + + inline int access(const char* path, int amode) { return _waccess(nall::utf16_t(path), amode); } + inline int fileno(FILE* stream) { return _fileno(stream); } + inline char* getcwd(char* buf, size_t size) { wchar_t wpath[PATH_MAX] = L""; if(!_wgetcwd(wpath, size)) return nullptr; strcpy(buf, nall::utf8_t(wpath)); return buf; } + inline int putenv(char* string) { return _wputenv(nall::utf16_t(string)); } + inline char* realpath(const char* file_name, char* resolved_name) { wchar_t wfile_name[PATH_MAX] = L""; if(!_wfullpath(wfile_name, nall::utf16_t(file_name), PATH_MAX)) return nullptr; strcpy(resolved_name, nall::utf8_t(wfile_name)); return resolved_name; } + inline int rename(const char* oldname, const char* newname) { return _wrename(nall::utf16_t(oldname), nall::utf16_t(newname)); } + inline void usleep(unsigned milliseconds) { Sleep(milliseconds / 1000); } +#endif + +//================ +//inline expansion +//================ + +#if defined(__clang__) || defined(__GNUC__) + #define noinline __attribute__((noinline)) + #define alwaysinline inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define noinline __declspec(noinline) + #define alwaysinline inline __forceinline +#else + #define noinline + #define alwaysinline inline +#endif + +//=========== +//unreachable +//=========== + +#if defined(__clang__) || defined(__GNUC__) + #define unreachable __builtin_unreachable() +#else + #define unreachable throw +#endif + +#endif diff --git a/src/dep/libs/nall/png.hpp b/src/dep/libs/nall/png.hpp new file mode 100755 index 000000000..ccdf64eb7 --- /dev/null +++ b/src/dep/libs/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 { + struct Info { + unsigned width; + unsigned height; + unsigned bitDepth; + //colorType: + //0 = L (luma) + //2 = R,G,B + //3 = P (palette) + //4 = L,A + //6 = R,G,B,A + unsigned colorType; + unsigned compressionMethod; + unsigned filterType; + unsigned interlaceMethod; + + unsigned bytesPerPixel; + unsigned pitch; + + uint8_t palette[256][3]; + } info; + + uint8_t* data = nullptr; + unsigned size = 0; + + inline bool decode(const string& filename); + inline bool decode(const uint8_t* sourceData, unsigned sourceSize); + inline unsigned readbits(const uint8_t*& data); + unsigned bitpos = 0; + + 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 = nullptr; + 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); + free(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 = nullptr; + 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 = nullptr; + 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() { +} + +png::~png() { + if(data) delete[] data; +} + +} + +#endif diff --git a/src/dep/libs/nall/priority-queue.hpp b/src/dep/libs/nall/priority-queue.hpp new file mode 100755 index 000000000..b15769930 --- /dev/null +++ b/src/dep/libs/nall/priority-queue.hpp @@ -0,0 +1,110 @@ +#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 struct priority_queue { + 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/src/dep/libs/nall/property.hpp b/src/dep/libs/nall/property.hpp new file mode 100755 index 000000000..575acba11 --- /dev/null +++ b/src/dep/libs/nall/property.hpp @@ -0,0 +1,44 @@ +#ifndef NALL_PROPERTY_HPP +#define NALL_PROPERTY_HPP + +namespace nall { + +template struct property { + 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 C; + }; + + 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 C; + }; + + 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/src/dep/libs/nall/public-cast.hpp b/src/dep/libs/nall/public-cast.hpp new file mode 100755 index 000000000..c9c4940db --- /dev/null +++ b/src/dep/libs/nall/public-cast.hpp @@ -0,0 +1,34 @@ +#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/src/dep/libs/nall/random.hpp b/src/dep/libs/nall/random.hpp new file mode 100755 index 000000000..bd443f406 --- /dev/null +++ b/src/dep/libs/nall/random.hpp @@ -0,0 +1,43 @@ +#ifndef NALL_RANDOM_HPP +#define NALL_RANDOM_HPP + +#include +#include + +namespace nall { + +struct RandomNumberGenerator { + virtual void seed(uint64_t) = 0; + virtual uint64_t operator()() = 0; + virtual void serialize(serializer&) = 0; +}; + +//Galois LFSR using CRC64 polynomials +struct LinearFeedbackShiftRegisterGenerator : RandomNumberGenerator { + void seed(uint64_t seed) { + lfsr = seed; + for(unsigned n = 0; n < 8; n++) operator()(); + } + + uint64_t operator()() { + return lfsr = (lfsr >> 1) ^ (-(lfsr & 1) & crc64jones); + } + + void serialize(serializer& s) { + s.integer(lfsr); + } + +private: + static const uint64_t crc64ecma = 0x42f0e1eba9ea3693; + static const uint64_t crc64jones = 0xad93d23594c935a9; + uint64_t lfsr = crc64ecma; +}; + +inline uint64_t random() { + static LinearFeedbackShiftRegisterGenerator lfsr; + return lfsr(); +} + +} + +#endif diff --git a/src/dep/libs/nall/serial.hpp b/src/dep/libs/nall/serial.hpp new file mode 100755 index 000000000..ce6bcf8f9 --- /dev/null +++ b/src/dep/libs/nall/serial.hpp @@ -0,0 +1,118 @@ +#ifndef NALL_SERIAL_HPP +#define NALL_SERIAL_HPP + +#include +#include +#include + +#if !defined(PLATFORM_X) && !defined(PLATFORM_MACOSX) + #error "nall/serial: unsupported platform" +#endif + +#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 string& 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/src/dep/libs/nall/serializer.hpp b/src/dep/libs/nall/serializer.hpp new file mode 100755 index 000000000..5b2c8ef62 --- /dev/null +++ b/src/dep/libs/nall/serializer.hpp @@ -0,0 +1,150 @@ +#ifndef NALL_SERIALIZER_HPP +#define NALL_SERIALIZER_HPP + +//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 different implementations + +#include +#include +#include +#include + +namespace nall { + +struct serializer; + +template +struct has_serialize { + template static char test(decltype(std::declval().serialize(std::declval()))*); + template static long test(...); + static const bool value = sizeof(test(0)) == sizeof(char); +}; + +struct serializer { + enum mode_t { Load, Save, Size }; + + mode_t mode() const { + return _mode; + } + + const uint8_t* data() const { + return _data; + } + + unsigned size() const { + return _size; + } + + unsigned capacity() const { + return _capacity; + } + + template serializer& 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(_mode == Save) { + for(unsigned n = 0; n < size; n++) _data[_size++] = p[n]; + } else if(_mode == Load) { + for(unsigned n = 0; n < size; n++) p[n] = _data[_size++]; + } else { + _size += size; + } + return *this; + } + + template serializer& integer(T& value) { + enum { size = std::is_same::value ? 1 : sizeof(T) }; + if(_mode == Save) { + for(unsigned n = 0; n < size; n++) _data[_size++] = (uintmax_t)value >> (n << 3); + } else if(_mode == Load) { + value = 0; + for(unsigned n = 0; n < size; n++) value |= (uintmax_t)_data[_size++] << (n << 3); + } else if(_mode == Size) { + _size += size; + } + return *this; + } + + template serializer& array(T (&array)[N]) { + for(unsigned n = 0; n < N; n++) operator()(array[n]); + return *this; + } + + template serializer& array(T array, unsigned size) { + for(unsigned n = 0; n < size; n++) operator()(array[n]); + return *this; + } + + template serializer& operator()(T& value, typename std::enable_if::value>::type* = 0) { value.serialize(*this); return *this; } + template serializer& operator()(T& value, typename std::enable_if::value>::type* = 0) { return integer(value); } + template serializer& operator()(T& value, typename std::enable_if::value>::type* = 0) { return floatingpoint(value); } + template serializer& operator()(T& value, typename std::enable_if::value>::type* = 0) { return array(value); } + template serializer& operator()(T& value, unsigned size, typename std::enable_if::value>::type* = 0) { return array(value, size); } + + serializer& operator=(const serializer& s) { + if(_data) delete[] _data; + + _mode = s._mode; + _data = new uint8_t[s._capacity]; + _size = s._size; + _capacity = s._capacity; + + memcpy(_data, s._data, s._capacity); + return *this; + } + + serializer& operator=(serializer&& s) { + if(_data) delete[] _data; + + _mode = s._mode; + _data = s._data; + _size = s._size; + _capacity = s._capacity; + + s._data = nullptr; + return *this; + } + + serializer() = default; + serializer(const serializer& s) { operator=(s); } + serializer(serializer&& s) { operator=(std::move(s)); } + + serializer(unsigned capacity) { + _mode = Save; + _data = new uint8_t[capacity](); + _size = 0; + _capacity = capacity; + } + + serializer(const uint8_t* data, unsigned capacity) { + _mode = Load; + _data = new uint8_t[capacity]; + _size = 0; + _capacity = capacity; + memcpy(_data, data, capacity); + } + + ~serializer() { + if(_data) delete[] _data; + } + +private: + mode_t _mode = Size; + uint8_t* _data = nullptr; + unsigned _size = 0; + unsigned _capacity = 0; +}; + +}; + +#endif diff --git a/src/dep/libs/nall/set.hpp b/src/dep/libs/nall/set.hpp new file mode 100755 index 000000000..c8003ec00 --- /dev/null +++ b/src/dep/libs/nall/set.hpp @@ -0,0 +1,267 @@ +#ifndef NALL_SET_HPP +#define NALL_SET_HPP + +//set +//implementation: red-black tree +// +//search: O(log n) average; O(log n) worst +//insert: O(log n) average; O(log n) worst +//remove: O(log n) average; O(log n) worst +// +//requirements: +// bool T::operator==(const T&) const; +// bool T::operator< (const T&) const; + +#include +#include + +namespace nall { + +template struct set { + struct node_t { + T value; + bool red = 1; + node_t* link[2] = {nullptr, nullptr}; + node_t() = default; + node_t(const T& value) : value(value) {} + }; + + node_t* root = nullptr; + unsigned nodes = 0; + + set& operator=(const set& source) { copy(source); return *this; } + set& operator=(set&& source) { move(std::move(source)); return *this; } + set(const set& source) { operator=(source); } + set(set&& source) { operator=(std::move(source)); } + set(std::initializer_list list) { for(auto& value : list) insert(value); } + set() = default; + ~set() { reset(); } + + unsigned size() const { return nodes; } + bool empty() const { return nodes == 0; } + + void reset() { + reset(root); + nodes = 0; + } + + optional find(const T& value) { + if(node_t* node = find(root, value)) return node->value; + return false; + } + + optional find(const T& value) const { + if(node_t* node = find(root, value)) return node->value; + return false; + } + + optional insert(const T& value) { + unsigned count = size(); + node_t* v = insert(root, value); + root->red = 0; + if(size() == count) return false; + return {true, v->value}; + } + + template bool insert(const T& value, Args&&... args) { + bool result = insert(value); + insert(std::forward(args)...) | result; + return result; + } + + bool remove(const T& value) { + unsigned count = size(); + bool done = 0; + remove(root, &value, done); + if(root) root->red = 0; + return size() < count; + } + + template bool remove(const T& value, Args&&... args) { + bool result = remove(value); + return remove(std::forward(args)...) | result; + } + + struct base_iterator { + bool operator!=(const base_iterator& source) const { return position != source.position; } + + base_iterator& operator++() { + if(++position >= source.size()) { position = source.size(); return *this; } + + if(stack.last()->link[1]) { + stack.append(stack.last()->link[1]); + while(stack.last()->link[0]) stack.append(stack.last()->link[0]); + } else { + node_t* child; + do child = stack.take(); + while(child == stack.last()->link[1]); + } + + return *this; + } + + base_iterator(const set& source, unsigned position) : source(source), position(position) { + node_t* node = source.root; + while(node) { + stack.append(node); + node = node->link[0]; + } + } + + protected: + const set& source; + unsigned position; + vector stack; + }; + + struct iterator : base_iterator { + T& operator*() const { return base_iterator::stack.last()->value; } + iterator(const set& source, unsigned position) : base_iterator(source, position) {} + }; + + iterator begin() { return iterator(*this, 0); } + iterator end() { return iterator(*this, size()); } + + struct const_iterator : base_iterator { + const T& operator*() const { return base_iterator::stack.last()->value; } + const_iterator(const set& source, unsigned position) : base_iterator(source, position) {} + }; + + const const_iterator begin() const { return const_iterator(*this, 0); } + const const_iterator end() const { return const_iterator(*this, size()); } + +private: + void reset(node_t*& node) { + if(!node) return; + if(node->link[0]) reset(node->link[0]); + if(node->link[1]) reset(node->link[1]); + delete node; + node = nullptr; + } + + void copy(const set& source) { + reset(); + copy(root, source.root); + nodes = source.nodes; + } + + void copy(node_t*& target, const node_t* source) { + if(!source) return; + target = new node_t(source->value); + target->red = source->red; + copy(target->link[0], source->link[0]); + copy(target->link[1], source->link[1]); + } + + void move(set&& source) { + root = source.root; + nodes = source.nodes; + source.root = nullptr; + source.nodes = 0; + } + + node_t* find(node_t* node, const T& value) const { + if(node == nullptr) return nullptr; + if(node->value == value) return node; + return find(node->link[node->value < value], value); + } + + bool red(node_t* node) const { return node && node->red; } + bool black(node_t* node) const { return !red(node); } + + void rotate(node_t*& a, bool dir) { + node_t*& b = a->link[!dir]; + node_t*& c = b->link[dir]; + a->red = 1, b->red = 0; + std::swap(a, b); + std::swap(b, c); + } + + void rotateTwice(node_t*& node, bool dir) { + rotate(node->link[!dir], !dir); + rotate(node, dir); + } + + node_t* insert(node_t*& node, const T& value) { + if(!node) { nodes++; node = new node_t(value); return node; } + if(node->value == value) { node->value = value; return node; } //prevent duplicate entries + + bool dir = node->value < value; + node_t* v = insert(node->link[dir], value); + if(black(node->link[dir])) return v; + + if(red(node->link[!dir])) { + node->red = 1; + node->link[0]->red = 0; + node->link[1]->red = 0; + } else if(red(node->link[dir]->link[dir])) { + rotate(node, !dir); + } else if(red(node->link[dir]->link[!dir])) { + rotateTwice(node, !dir); + } + + return v; + } + + void balance(node_t*& node, bool dir, bool& done) { + node_t* p = node; + node_t* s = node->link[!dir]; + if(!s) return; + + if(red(s)) { + rotate(node, dir); + s = p->link[!dir]; + } + + if(black(s->link[0]) && black(s->link[1])) { + if(red(p)) done = 1; + p->red = 0, s->red = 1; + } else { + bool save = p->red; + bool head = node == p; + + if(red(s->link[!dir])) rotate(p, dir); + else rotateTwice(p, dir); + + p->red = save; + p->link[0]->red = 0; + p->link[1]->red = 0; + + if(head) node = p; + else node->link[dir] = p; + + done = 1; + } + } + + void remove(node_t*& node, const T* value, bool& done) { + if(!node) { done = 1; return; } + + if(node->value == *value) { + if(!node->link[0] || !node->link[1]) { + node_t* save = node->link[!node->link[0]]; + + if(red(node)) done = 1; + else if(red(save)) save->red = 0, done = 1; + + nodes--; + delete node; + node = save; + return; + } else { + node_t* heir = node->link[0]; + while(heir->link[1]) heir = heir->link[1]; + node->value = heir->value; + value = &heir->value; + } + } + + bool dir = node->value < *value; + remove(node->link[dir], value, done); + if(!done) balance(node, dir, done); + } +}; + +} + +#endif diff --git a/src/dep/libs/nall/sha256.hpp b/src/dep/libs/nall/sha256.hpp new file mode 100755 index 000000000..2be2d7b26 --- /dev/null +++ b/src/dep/libs/nall/sha256.hpp @@ -0,0 +1,147 @@ +#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/src/dep/libs/nall/smtp.hpp b/src/dep/libs/nall/smtp.hpp new file mode 100755 index 000000000..ce1a02ad6 --- /dev/null +++ b/src/dep/libs/nall/smtp.hpp @@ -0,0 +1,318 @@ +#ifndef NALL_SMTP_HPP +#define NALL_SMTP_HPP + +#include +#include +#include + +#if !defined(_WIN32) + #include + #include + #include + #include +#else + #include + #include + #include +#endif + +namespace nall { + +struct SMTP { + enum class Format : unsigned { Plain, HTML }; + + inline void server(string server, uint16_t port = 25); + inline void from(string mail, string name = ""); + inline void to(string mail, string name = ""); + inline void cc(string mail, string name = ""); + inline void bcc(string mail, string name = ""); + inline void attachment(const uint8_t* data, unsigned size, string name); + inline bool attachment(string filename, string name = ""); + inline void subject(string subject); + inline void body(string body, Format format = Format::Plain); + + inline bool send(); + inline string message(); + inline string response(); + + #ifdef _WIN32 + inline int close(int); + inline SMTP(); + #endif + +private: + struct Information { + string server; + uint16_t port; + struct Contact { + string mail; + string name; + }; + Contact from; + vector to; + vector cc; + vector bcc; + struct Attachment { + vector buffer; + string name; + }; + string subject; + string body; + Format format = Format::Plain; + vector attachments; + + string message; + string response; + } info; + + inline bool send(int sock, const string& text); + inline string recv(int sock); + inline string boundary(); + inline string filename(const string& filename); + inline string contact(const Information::Contact& contact); + inline string contacts(const vector& contacts); + inline string split(const string& text); +}; + +void SMTP::server(string server, uint16_t port) { + info.server = server; + info.port = port; +} + +void SMTP::from(string mail, string name) { + info.from = {mail, name}; +} + +void SMTP::to(string mail, string name) { + info.to.append({mail, name}); +} + +void SMTP::cc(string mail, string name) { + info.cc.append({mail, name}); +} + +void SMTP::bcc(string mail, string name) { + info.bcc.append({mail, name}); +} + +void SMTP::attachment(const uint8_t* data, unsigned size, string name) { + vector buffer; + buffer.resize(size); + memcpy(buffer.data(), data, size); + info.attachments.append({std::move(buffer), name}); +} + +bool SMTP::attachment(string filename, string name) { + if(!file::exists(filename)) return false; + if(name == "") name = notdir(filename); + auto buffer = file::read(filename); + info.attachments.append({std::move(buffer), name}); + return true; +} + +void SMTP::subject(string subject) { + info.subject = subject; +} + +void SMTP::body(string body, Format format) { + info.body = body; + info.format = format; +} + +bool SMTP::send() { + info.message.append("From: =?UTF-8?B?", Base64::encode(contact(info.from)), "?=\r\n"); + info.message.append("To: =?UTF-8?B?", Base64::encode(contacts(info.to)), "?=\r\n"); + info.message.append("Cc: =?UTF-8?B?", Base64::encode(contacts(info.cc)), "?=\r\n"); + info.message.append("Subject: =?UTF-8?B?", Base64::encode(info.subject), "?=\r\n"); + + string uniqueID = boundary(); + + info.message.append("MIME-Version: 1.0\r\n"); + info.message.append("Content-Type: multipart/mixed; boundary=", uniqueID, "\r\n"); + info.message.append("\r\n"); + + string format = (info.format == Format::Plain ? "text/plain" : "text/html"); + + info.message.append("--", uniqueID, "\r\n"); + info.message.append("Content-Type: ", format, "; charset=UTF-8\r\n"); + info.message.append("Content-Transfer-Encoding: base64\r\n"); + info.message.append("\r\n"); + info.message.append(split(Base64::encode(info.body)), "\r\n"); + info.message.append("\r\n"); + + for(auto& attachment : info.attachments) { + info.message.append("--", uniqueID, "\r\n"); + info.message.append("Content-Type: application/octet-stream\r\n"); + info.message.append("Content-Transfer-Encoding: base64\r\n"); + info.message.append("Content-Disposition: attachment; size=", attachment.buffer.size(), "; filename*=UTF-8''", filename(attachment.name), "\r\n"); + info.message.append("\r\n"); + info.message.append(split(Base64::encode(attachment.buffer)), "\r\n"); + info.message.append("\r\n"); + } + + info.message.append("--", uniqueID, "--\r\n"); + + addrinfo hints; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + addrinfo* serverinfo; + int status = getaddrinfo(info.server, string(info.port), &hints, &serverinfo); + if(status != 0) return false; + + int sock = socket(serverinfo->ai_family, serverinfo->ai_socktype, serverinfo->ai_protocol); + if(sock == -1) return false; + + int result = connect(sock, serverinfo->ai_addr, serverinfo->ai_addrlen); + if(result == -1) return false; + + string response; + info.response.append(response = recv(sock)); + if(!response.beginswith("220 ")) { close(sock); return false; } + + send(sock, {"HELO ", info.server, "\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + + send(sock, {"MAIL FROM: <", info.from.mail, ">\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + + for(auto& contact : info.to) { + send(sock, {"RCPT TO: <", contact.mail, ">\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + } + + for(auto& contact : info.cc) { + send(sock, {"RCPT TO: <", contact.mail, ">\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + } + + for(auto& contact : info.bcc) { + send(sock, {"RCPT TO: <", contact.mail, ">\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + } + + send(sock, {"DATA\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("354 ")) { close(sock); return false; } + + send(sock, {info.message, "\r\n", ".\r\n"}); + info.response.append(response = recv(sock)); + if(!response.beginswith("250 ")) { close(sock); return false; } + + send(sock, {"QUIT\r\n"}); + info.response.append(response = recv(sock)); +//if(!response.beginswith("221 ")) { close(sock); return false; } + + close(sock); + return true; +} + +string SMTP::message() { + return info.message; +} + +string SMTP::response() { + return info.response; +} + +bool SMTP::send(int sock, const string& text) { + const char* data = text.data(); + unsigned size = text.size(); + while(size) { + int length = ::send(sock, (const char*)data, size, 0); + if(length == -1) return false; + data += length; + size -= length; + } + return true; +} + +string SMTP::recv(int sock) { + vector buffer; + while(true) { + char c; + if(::recv(sock, &c, sizeof(char), 0) < 1) break; + buffer.append(c); + if(c == '\n') break; + } + buffer.append(0); + return buffer; +} + +string SMTP::boundary() { + random_lfsr random; + random.seed(time(0)); + string boundary; + for(unsigned n = 0; n < 16; n++) boundary.append(hex<2>(random())); + return boundary; +} + +string SMTP::filename(const string& filename) { + string result; + for(auto& n : filename) { + if(n <= 32 || n >= 127) result.append("%", hex<2>(n)); + else result.append(n); + } + return result; +} + +string SMTP::contact(const Information::Contact& contact) { + if(!contact.name) return contact.mail; + return {"\"", contact.name, "\" <", contact.mail, ">"}; +} + +string SMTP::contacts(const vector& contacts) { + string result; + for(auto& contact : contacts) { + result.append(this->contact(contact), "; "); + } + result.rtrim<1>("; "); + return result; +} + +string SMTP::split(const string& text) { + string result; + + unsigned offset = 0; + while(offset < text.size()) { + unsigned length = min(76, text.size() - offset); + if(length < 76) { + result.append(text.slice(offset)); + } else { + result.append(text.slice(offset, 76), "\r\n"); + } + offset += length; + } + + return result; +} + +#ifdef _WIN32 +int SMTP::close(int sock) { + return closesocket(sock); +} + +SMTP::SMTP() { + 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/src/dep/libs/nall/sort.hpp b/src/dep/libs/nall/sort.hpp new file mode 100755 index 000000000..5517dd3a5 --- /dev/null +++ b/src/dep/libs/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(copy, list[j])) 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[right], list[left])) { + 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/src/dep/libs/nall/stdint.hpp b/src/dep/libs/nall/stdint.hpp new file mode 100755 index 000000000..b6eed36fd --- /dev/null +++ b/src/dep/libs/nall/stdint.hpp @@ -0,0 +1,44 @@ +#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/src/dep/libs/nall/stream.hpp b/src/dep/libs/nall/stream.hpp new file mode 100755 index 000000000..586ccda72 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/stream/auto.hpp b/src/dep/libs/nall/stream/auto.hpp new file mode 100755 index 000000000..0e4d8705e --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/stream/file.hpp b/src/dep/libs/nall/stream/file.hpp new file mode 100755 index 000000000..be8c2b709 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/stream/gzip.hpp b/src/dep/libs/nall/stream/gzip.hpp new file mode 100755 index 000000000..4af7a24d0 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/stream/http.hpp b/src/dep/libs/nall/stream/http.hpp new file mode 100755 index 000000000..fb38a770f --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/stream/memory.hpp b/src/dep/libs/nall/stream/memory.hpp new file mode 100755 index 000000000..81e68b334 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/stream/mmap.hpp b/src/dep/libs/nall/stream/mmap.hpp new file mode 100755 index 000000000..6ea18a512 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/stream/stream.hpp b/src/dep/libs/nall/stream/stream.hpp new file mode 100755 index 000000000..6e78b18a0 --- /dev/null +++ b/src/dep/libs/nall/stream/stream.hpp @@ -0,0 +1,101 @@ +#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(); + } + + string text() const { + string buffer; + buffer.resize(size() + 1); + buffer[size()] = 0; + seek(0); + read((uint8_t*)buffer.data(), size()); + return buffer; + } + + 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/src/dep/libs/nall/stream/vector.hpp b/src/dep/libs/nall/stream/vector.hpp new file mode 100755 index 000000000..0210e93ad --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/stream/zip.hpp b/src/dep/libs/nall/stream/zip.hpp new file mode 100755 index 000000000..dfb4767bc --- /dev/null +++ b/src/dep/libs/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.match(filter)) { + auto buffer = archive.extract(file); + psize = buffer.size(); + pdata = buffer.move(); + return; + } + } + } + + ~zipstream() { + if(pdata) delete[] pdata; + } +}; + +} + +#endif diff --git a/src/dep/libs/nall/string.hpp b/src/dep/libs/nall/string.hpp new file mode 100755 index 000000000..adf86b31c --- /dev/null +++ b/src/dep/libs/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 +#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 +#undef NALL_STRING_INTERNAL_HPP + +#endif diff --git a/src/dep/libs/nall/string/allocator/copy-on-write.hpp b/src/dep/libs/nall/string/allocator/copy-on-write.hpp new file mode 100755 index 000000000..3890ce4ca --- /dev/null +++ b/src/dep/libs/nall/string/allocator/copy-on-write.hpp @@ -0,0 +1,104 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +/* +copy on write (COW) allocator +sizeof(string) == 24 (amd64) + +utilizes a shared_ptr to reference count strings +allows string copies to execute as fast as string moves +requires extra computations, which will be slower for all string sizes + +pros: +* lower memory usage +* pass-by-value does not require heap allocation; obviates pass-by-const-reference + +cons: +* added overhead to fetch data() +* added heap allocation for reference-count pool +* no potential for in-place resize (always copies) +* larger sizeof(string) + +*/ + +namespace nall { + +char* string::data() { + if(!_data.unique()) _copy(); + return _data.get(); +} + +const char* string::data() const { + if(!_data) return ""; + return _data.get(); +} + +//copy _data (to make unique or to grow in size) +void string::_copy() { + auto copy = new char[_capacity + 1]; + if(_data.get()) memcpy(copy, _data.get(), min(_capacity, _size)); + copy[_size] = 0; + copy[_capacity] = 0; + _data.reset(copy); +} + +//amortize growth to O(log n) +//allocate one extra byte to always store null-terminator for libc usage +void string::reserve(unsigned capacity) { + if(capacity > _capacity) { + _capacity = bit::round(capacity + 1) - 1; + _copy(); + } +} + +void string::resize(unsigned size) { + reserve(size); + data()[_size = size] = 0; +} + +void string::reset() { + _data.reset(); + _capacity = 0; + _size = 0; +} + +string& string::operator=(const string& source) { + if(&source == this) return *this; + reset(); + _data = source._data; + _capacity = source._capacity; + _size = source._size; + return *this; +} + +string& string::operator=(string&& source) { + if(&source == this) return *this; + reset(); + _data = std::move(source._data); + _capacity = source._capacity; + _size = source._size; + source._capacity = 0; + source._size = 0; + return *this; +} + +template string::string(T&& source, Args&&... args) { + construct(); + sprint(*this, std::forward(source), std::forward(args)...); +} + +string::string() { + construct(); +} + +string::~string() { + reset(); +} + +void string::construct() { + _capacity = 0; + _size = 0; +} + +} + +#endif diff --git a/src/dep/libs/nall/string/allocator/small-string-optimization.hpp b/src/dep/libs/nall/string/allocator/small-string-optimization.hpp new file mode 100755 index 000000000..8e3c41178 --- /dev/null +++ b/src/dep/libs/nall/string/allocator/small-string-optimization.hpp @@ -0,0 +1,111 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +/* +small string optimization (SSO) allocator +sizeof(string) == 16 (amd64) + +utilizes a union to store small strings directly into text pointer +bypasses the need to allocate heap memory for small strings +requires extra computations, which can be slower for large strings + +pros: +* potential for in-place resize +* no heap allocation when (capacity < 8) + +cons: +* added overhead to fetch data() +* 32-bit platforms limited to (capacity < 4) +* pass-by-value requires heap allocation + +*/ + +namespace nall { + +char* string::data() { + if(_capacity < SSO) return _text; + return _data; +} + +const char* string::data() const { + if(_capacity < SSO) return _text; + return _data; +} + +void string::reserve(unsigned capacity) { + if(capacity > _capacity) { + if(capacity >= SSO) { + capacity = bit::round(capacity + 1) - 1; + if(_capacity < SSO) { + char temp[SSO]; + memcpy(temp, _text, SSO); + _data = (char*)malloc(capacity + 1); + memcpy(_data, temp, SSO); + } else { + _data = (char*)realloc(_data, capacity + 1); + } + } + _capacity = capacity; + data()[_capacity] = 0; + } +} + +void string::resize(unsigned size) { + reserve(size); + data()[_size = size] = 0; +} + +void string::reset() { + if(_capacity >= SSO) free(_data); + _data = nullptr; + _capacity = SSO - 1; + _size = 0; +} + +string& string::operator=(const string& source) { + if(&source == this) return *this; + reset(); + if(source._capacity >= SSO) { + _data = (char*)malloc(source._capacity + 1); + _capacity = source._capacity; + _size = source._size; + memcpy(_data, source.data(), source.size() + 1); + } else { + memcpy(_text, source._text, SSO); + _capacity = SSO - 1; + _size = strlen(_text); + } + return *this; +} + +string& string::operator=(string&& source) { + if(&source == this) return *this; + reset(); + memcpy(this, &source, sizeof(string)); + source._data = nullptr; + source._capacity = SSO - 1; + source._size = 0; + return *this; +} + +template string::string(T&& source, Args&&... args) { + construct(); + sprint(*this, std::forward(source), std::forward(args)...); +} + +string::string() { + construct(); +} + +string::~string() { + reset(); +} + +void string::construct() { + _data = nullptr; + _capacity = SSO - 1; + _size = 0; +} + +} + +#endif diff --git a/src/dep/libs/nall/string/allocator/vector.hpp b/src/dep/libs/nall/string/allocator/vector.hpp new file mode 100755 index 000000000..7b7aaaaf7 --- /dev/null +++ b/src/dep/libs/nall/string/allocator/vector.hpp @@ -0,0 +1,94 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +/* +vector allocator +sizeof(string) == 16 (amd64) + +utilizes a raw string pointer +always allocates memory onto the heap when string is not empty + +pros: +* potential for in-place resize +* simplicity + +cons: +* always allocates heap memory on (capacity > 0) +* pass-by-value requires heap allocation + +*/ + +namespace nall { + +char* string::data() { + if(_capacity == 0) reserve(1); + return _data; +} + +const char* string::data() const { + if(_capacity == 0) return ""; + return _data; +} + +void string::reserve(unsigned capacity) { + if(capacity > _capacity) { + _capacity = bit::round(capacity + 1) - 1; + _data = (char*)realloc(_data, _capacity + 1); + _data[_capacity] = 0; + } +} + +void string::resize(unsigned size) { + reserve(size); + data()[_size = size] = 0; +} + +void string::reset() { + if(_data) { free(_data); _data = nullptr; } + _capacity = 0; + _size = 0; +} + +string& string::operator=(const string& source) { + if(&source == this) return *this; + reset(); + _data = (char*)malloc(source._size + 1); + _capacity = source._size; + _size = source._size; + memcpy(_data, source.data(), source.size() + 1); + return *this; +} + +string& string::operator=(string&& source) { + if(&source == this) return *this; + reset(); + _data = source._data; + _capacity = source._capacity; + _size = source._size; + source._data = nullptr; + source._capacity = 0; + source._size = 0; + return *this; +} + +template string::string(T&& source, Args&&... args) { + construct(); + sprint(*this, std::forward(source), std::forward(args)...); +} + +string::string() { + construct(); +} + +string::~string() { + reset(); +} + +void string::construct() { + _data = nullptr; + _capacity = 0; + _size = 0; +} + +} + +#endif diff --git a/src/dep/libs/nall/string/base.hpp b/src/dep/libs/nall/string/base.hpp new file mode 100755 index 000000000..284c23124 --- /dev/null +++ b/src/dep/libs/nall/string/base.hpp @@ -0,0 +1,227 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +struct string; +struct stringref; +struct lstring; +typedef const stringref& rstring; + +//#define NALL_STRING_ALLOCATOR_COPY_ON_WRITE +#define NALL_STRING_ALLOCATOR_SMALL_STRING_OPTIMIZATION +//#define NALL_STRING_ALLOCATOR_VECTOR + +struct string { +protected: + #if defined(NALL_STRING_ALLOCATOR_COPY_ON_WRITE) + inline void _copy(); + std::shared_ptr _data; + #endif + + #if defined(NALL_STRING_ALLOCATOR_SMALL_STRING_OPTIMIZATION) + enum : unsigned { SSO = 24 }; + union { + char* _data; + char _text[SSO]; + }; + #endif + + #if defined(NALL_STRING_ALLOCATOR_VECTOR) + char* _data; + #endif + + unsigned _capacity; + unsigned _size; + +public: + //core.hpp + inline char* data(); + inline const char* data() const; + inline unsigned length() const; + inline unsigned size() const; + inline unsigned capacity() const; + inline bool empty() const; + + inline void reset(); + inline void reserve(unsigned); + inline void resize(unsigned); + inline void clear(char); + + inline unsigned hash() const; + + template inline string& assign(Args&&... args); + template inline string& append(Args&&... args); + + //file.hpp + inline static string read(const string& filename); + + //datetime.hpp + inline static string date(); + inline static string time(); + inline static string datetime(); + + //replace.hpp + template inline string& replace(rstring, rstring); + template inline string& ireplace(rstring, rstring); + template inline string& qreplace(rstring, rstring); + template inline string& iqreplace(rstring, rstring); + + //wrapper.hpp + template inline lstring split(rstring) const; + template inline lstring isplit(rstring) const; + template inline lstring qsplit(rstring) const; + template inline lstring iqsplit(rstring) const; + + inline signed compare(rstring) const; + inline signed icompare(rstring) const; + + inline bool equals(rstring) const; + inline bool iequals(rstring) const; + + inline bool match(rstring) const; + inline bool imatch(rstring) const; + + inline bool beginsWith(rstring) const; + inline bool ibeginsWith(rstring) const; + inline bool endsWith(rstring) const; + inline bool iendsWith(rstring) const; + + inline string slice(unsigned offset, unsigned length = ~0u) const; + + inline string& lower(); + inline string& upper(); + inline string& qlower(); + inline string& qupper(); + inline string& transform(rstring before, rstring after); + inline string& reverse(); + + template inline string& ltrim() { return ltrim(" "); } + template inline string& ltrim(rstring key); + + template inline string& rtrim() { return rtrim(" "); } + template inline string& rtrim(rstring key); + + template inline string& trim() { return trim(" "); } + template inline string& trim(rstring key); + template inline string& trim(rstring key, rstring rkey); + + inline string& strip(); + + inline optional find(rstring key) const; + inline optional ifind(rstring key) const; + inline optional qfind(rstring key) const; + inline optional iqfind(rstring key) const; + + //core.hpp + inline explicit operator bool() const; + inline operator const char*() const; + inline char& operator[](signed); + inline const char& operator[](signed) 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 bool operator>=(const char*) const; + + inline string& operator=(const string&); + inline string& operator=(string&&); + + template inline string(T&& source, Args&&... args); + inline string(); + inline string(const string&); + inline string(string&&); + inline ~string(); + + inline char* begin() { return &data()[0]; } + inline char* end() { return &data()[size()]; } + inline const char* begin() const { return &data()[0]; } + inline const char* end() const { return &data()[size()]; } + +//protected: + struct exception_out_of_bounds{}; + template inline string& ureplace(rstring, rstring); + inline string& _append(const char*); + +private: + inline void construct(); + +#if defined(QSTRING_H) +public: + inline operator QString() const; +#endif +}; + +//list.hpp +struct lstring : vector { + inline optional find(rstring) const; + inline string merge(const string&) const; + inline lstring& isort(); + inline lstring& strip(); + inline void append() {} + template inline void append(const string&, Args&&...); + + 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&&); + + //split.hpp + template inline lstring& split(rstring, rstring); + template inline lstring& isplit(rstring, rstring); + template inline lstring& qsplit(rstring, rstring); + template inline lstring& iqsplit(rstring, rstring); + +protected: + template inline lstring& usplit(rstring, rstring); +}; + +//filename.hpp +inline string dir(string name); +inline string notdir(string name); +inline string parentdir(string name); +inline string basename(string name); +inline string extension(string name); +inline string tempname(); + +//format.hpp +template inline string format(const string& value); +template inline string hex(uintmax_t value); +template inline string octal(uintmax_t value); +template inline string binary(uintmax_t value); + +//platform.hpp +inline string activepath(); +inline string realpath(const string& name); +inline string userpath(); +inline string configpath(); +inline string sharedpath(); +inline string temppath(); + +//utility.hpp +inline string substr(rstring source, unsigned offset = 0, unsigned length = ~0u); +inline string sha256(const uint8_t* data, unsigned size); +inline bool tokenize(lstring& list, const char* s, const char* p); + +inline char* integer(char* result, intmax_t value); +inline char* decimal(char* result, uintmax_t value); + +inline unsigned real(char* str, long double value); +inline string real(long double value); + +//variadic.hpp +inline void sprint(string& output); +template inline void sprint(string& output, const T& value, Args&&... args); +template inline void print(Args&&... args); + +} + +#endif diff --git a/src/dep/libs/nall/string/cast.hpp b/src/dep/libs/nall/string/cast.hpp new file mode 100755 index 000000000..ef79a47d2 --- /dev/null +++ b/src/dep/libs/nall/string/cast.hpp @@ -0,0 +1,199 @@ +#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[2]; + operator const char*() const { return data; } + stringify(char value) { data[0] = value, data[1] = 0; } +}; + +// 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) { real(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(double value) { real(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(long double value) { real(data, value); } +}; + +// arrays + +template<> struct stringify> { + char* text; + operator const char*() const { return text; } + stringify(vector value) { + text = new char[value.size() + 1](); + memcpy(text, value.data(), value.size()); + } + ~stringify() { + delete[] text; + } +}; + +template<> struct stringify&> { + char* text; + operator const char*() const { return text; } + stringify(const vector& value) { + text = new char[value.size() + 1](); + memcpy(text, value.data(), value.size()); + } + ~stringify() { + delete[] text; + } +}; + +// 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) {} +}; + +#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/src/dep/libs/nall/string/char.hpp b/src/dep/libs/nall/string/char.hpp new file mode 100755 index 000000000..29ac2c77b --- /dev/null +++ b/src/dep/libs/nall/string/char.hpp @@ -0,0 +1,13 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/src/dep/libs/nall/string/char/base.hpp b/src/dep/libs/nall/string/char/base.hpp new file mode 100755 index 000000000..0a029b2d1 --- /dev/null +++ b/src/dep/libs/nall/string/char/base.hpp @@ -0,0 +1,69 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//collection of functions to extend libc +//none of these functions require nall::string +//and thus, require no changes when nall::string is modified + +namespace nall { + +//compare.hpp +inline char chrlower(char c); +inline char chrupper(char c); +inline int imemcmp(const char* str1, const char* str2, unsigned size); +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); + +//match.hpp +inline bool strmatch(const char* str, const char* pattern); +inline bool istrmatch(const char* str, const char* pattern); +inline bool tokenize(const char* s, const char* p); + +//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 = " "); +template inline char* trim(char* str, const char* lkey, const char* rkey); +inline char* strip(char* s); + +//utf8.hpp +struct UTF8 { + unsigned size; //size of encoded codepoint + uint64_t data; //encoded codepoint + unsigned codepoint; //decoded codepoint +}; +inline UTF8 utf8_read(const char* s); +inline void utf8_write(char* s, const UTF8& utf8); + +//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 char* strduplicate(const char* s); + +} + +#endif diff --git a/src/dep/libs/nall/string/char/compare.hpp b/src/dep/libs/nall/string/char/compare.hpp new file mode 100755 index 000000000..ff9bf54b0 --- /dev/null +++ b/src/dep/libs/nall/string/char/compare.hpp @@ -0,0 +1,81 @@ +#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 imemcmp(const char* str1, const char* str2, unsigned size) { + while(size--) { + if(chrlower(*str1) != chrlower(*str2)) break; + str1++, str2++; + } + return (int)chrlower(*str1) - (int)chrlower(*str2); +} + +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) { + if(!str || !key) return false; + 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) { + if(!str || !key) return false; + 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) { + if(!str || !key) return false; + 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) { + if(!str || !key) return false; + 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/src/dep/libs/nall/string/char/convert.hpp b/src/dep/libs/nall/string/char/convert.hpp new file mode 100755 index 000000000..72e30a12f --- /dev/null +++ b/src/dep/libs/nall/string/char/convert.hpp @@ -0,0 +1,64 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +char* strlower(char* str) { + if(!str) return nullptr; + int i = 0; + while(str[i]) { + str[i] = chrlower(str[i]); + i++; + } + return str; +} + +char* strupper(char* str) { + if(!str) return nullptr; + int i = 0; + while(str[i]) { + str[i] = chrupper(str[i]); + i++; + } + return str; +} + +char* qstrlower(char* s) { + if(!s) return nullptr; + 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 nullptr; + 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/src/dep/libs/nall/string/char/match.hpp b/src/dep/libs/nall/string/char/match.hpp new file mode 100755 index 000000000..2471d1ce7 --- /dev/null +++ b/src/dep/libs/nall/string/char/match.hpp @@ -0,0 +1,61 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +bool strmatch(const char* s, const char* p) { + const char* cp = nullptr; + const char* mp = nullptr; + 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 istrmatch(const char* s, const char* p) { + const char* cp = nullptr; + const char* mp = nullptr; + while(*s && *p != '*') { + if(*p != '?' && chrlower(*s) != chrlower(*p)) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || chrlower(*p) == chrlower(*s)) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +bool 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; +} + +} + +#endif diff --git a/src/dep/libs/nall/string/char/strm.hpp b/src/dep/libs/nall/string/char/strm.hpp new file mode 100755 index 000000000..7e2b6e7c4 --- /dev/null +++ b/src/dep/libs/nall/string/char/strm.hpp @@ -0,0 +1,41 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +//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/src/dep/libs/nall/string/char/strpos.hpp b/src/dep/libs/nall/string/char/strpos.hpp new file mode 100755 index 000000000..19c232172 --- /dev/null +++ b/src/dep/libs/nall/string/char/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; + if(!chrequal(str[n], key[n])) break; + } + str++; + } + + return false; +} + +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/src/dep/libs/nall/string/char/trim.hpp b/src/dep/libs/nall/string/char/trim.hpp new file mode 100755 index 000000000..969b6926d --- /dev/null +++ b/src/dep/libs/nall/string/char/trim.hpp @@ -0,0 +1,62 @@ +#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) { + if(!str || !key || !*key) return str; + unsigned limit = Limit; + while(strbegin(str, key)) { + char* dest = str; + char* 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) { + if(!str || !key || !*key) return str; + unsigned limit = Limit; + while(strend(str, key)) { + str[strlen(str) - strlen(key)] = 0; + if(--limit == 0) break; + } + return str; +} + +template char* trim(char* str, const char* key) { + return ltrim(rtrim(str, key), key); +} + +template char* trim(char* str, const char* lkey, const char* rkey) { + return ltrim(rtrim(str, rkey), lkey); +} + +//remove whitespace characters from both left and right sides of string +char* strip(char* s) { + if(!s) return nullptr; + + 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/src/dep/libs/nall/string/char/utf8.hpp b/src/dep/libs/nall/string/char/utf8.hpp new file mode 100755 index 000000000..03eb5ef0f --- /dev/null +++ b/src/dep/libs/nall/string/char/utf8.hpp @@ -0,0 +1,37 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +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; +} + +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/src/dep/libs/nall/string/char/utility.hpp b/src/dep/libs/nall/string/char/utility.hpp new file mode 100755 index 000000000..bb35c0202 --- /dev/null +++ b/src/dep/libs/nall/string/char/utility.hpp @@ -0,0 +1,50 @@ +#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 != '\"') return false; + + while(*p == '\"') { + char x = *p++; + while(*p && *p++ != x); + } + + return true; +} + +template +bool quotecopy(char*& t, T*& p) { + if(Quoted == false) return false; + if(*p != '\"') return false; + + while(*p == '\"') { + char x = *p++; + *t++ = x; + while(*p && *p != x) *t++ = *p++; + *t++ = *p++; + } + + return true; +} + +//strdup() is not a standard function, so recreate it +char* strduplicate(const char* s) { + if(s == nullptr) return nullptr; + unsigned length = strlen(s); + char* result = (char*)malloc(length + 1); + strcpy(result, s); + return result; +} + +} + +#endif diff --git a/src/dep/libs/nall/string/core.hpp b/src/dep/libs/nall/string/core.hpp new file mode 100755 index 000000000..8a00645e8 --- /dev/null +++ b/src/dep/libs/nall/string/core.hpp @@ -0,0 +1,89 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//only allocators may access _data or modify _size and _capacity +//all other functions must use data(), size(), capacity() + +#if defined(NALL_STRING_ALLOCATOR_COPY_ON_WRITE) + #include +#elif defined(NALL_STRING_ALLOCATOR_SMALL_STRING_OPTIMIZATION) + #include +#elif defined(NALL_STRING_ALLOCATOR_VECTOR) + #include +#endif + +namespace nall { + +unsigned string::length() const { return strlen(data()); } +unsigned string::size() const { return _size; } +unsigned string::capacity() const { return _capacity; } +bool string::empty() const { return _size == 0; } + +void string::clear(char c) { + for(unsigned n = 0; n < size(); n++) data()[n] = c; +} + +unsigned string::hash() const { + const char* p = data(); + unsigned result = 5381; + while(*p) result = (result << 5) + result + *p++; + return result; +} + +template string& string::assign(Args&&... args) { + resize(0); + sprint(*this, std::forward(args)...); + return *this; +} + +template string& string::append(Args&&... args) { + sprint(*this, std::forward(args)...); + return *this; +} + +string& string::_append(const char* s) { + if(s == nullptr) return *this; + unsigned basesize = size(), length = strlen(s); + reserve(basesize + length); + memcpy(data() + basesize, s, length); + resize(basesize + length); + return *this; +} + +string::operator bool() const { + return !empty(); +} + +string::operator const char*() const { + return data(); +} + +char& string::operator[](signed position) { + if(position > size() + 1) throw exception_out_of_bounds{}; + return data()[position]; +} + +const char& string::operator[](signed position) const { + if(position > size() + 1) throw exception_out_of_bounds{}; + return data()[position]; +} + +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(const string& source) { + construct(); + operator=(source); +} + +string::string(string&& source) { + construct(); + operator=(std::move(source)); +} + +} + +#endif diff --git a/src/dep/libs/nall/string/datetime.hpp b/src/dep/libs/nall/string/datetime.hpp new file mode 100755 index 000000000..b55659a10 --- /dev/null +++ b/src/dep/libs/nall/string/datetime.hpp @@ -0,0 +1,31 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string string::date() { + time_t timestamp = ::time(nullptr); + tm* info = localtime(×tamp); + return { + format<4, '0'>(1900 + info->tm_year), "-", + format<2, '0'>(1 + info->tm_mon), "-", + format<2, '0'>(info->tm_mday) + }; +} + +string string::time() { + time_t timestamp = ::time(nullptr); + tm* info = localtime(×tamp); + return { + format<2, '0'>(info->tm_hour), ":", + format<2, '0'>(info->tm_min), ":", + format<2, '0'>(info->tm_sec) + }; +} + +string string::datetime() { + return {string::date(), " ", string::time()}; +} + +} + +#endif diff --git a/src/dep/libs/nall/string/eval/evaluator.hpp b/src/dep/libs/nall/string/eval/evaluator.hpp new file mode 100755 index 000000000..49ac851e7 --- /dev/null +++ b/src/dep/libs/nall/string/eval/evaluator.hpp @@ -0,0 +1,157 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { +namespace Eval { + +inline string evaluateExpression(Node* node) { + #define p(n) evaluateExpression(node->link[n]) + switch(node->type) { + case Node::Type::Null: return "Null"; + case Node::Type::Literal: return {"Literal:", node->literal}; + case Node::Type::Function: return {"Function(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Subscript: return {"Subscript(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Member: return {"Member(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::SuffixIncrement: return {"SuffixIncrement(0:", p(0), ")"}; + case Node::Type::SuffixDecrement: return {"SuffixDecrement(0:", p(0), ")"}; + case Node::Type::Reference: return {"Reference(0:", p(0), ")"}; + case Node::Type::Dereference: return {"Dereference(0:", p(0), ")"}; + case Node::Type::BitwiseNot: return {"Complement(0:", p(0), ")"}; + case Node::Type::PrefixIncrement: return {"PrefixIncrement(0:", p(0), ")"}; + case Node::Type::PrefixDecrement: return {"PrefixDecrement(0:", p(0), ")"}; + case Node::Type::Add: return {"Add(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Multiply: return {"Multiply(0:", p(0), ", 1:", p(1), ")"}; + case Node::Type::Concatenate: return {"Concatenate(0:", p(0), ", ", p(1), ")"}; + case Node::Type::Coalesce: return {"Coalesce(0:", p(0), ", ", p(1), ")"}; + case Node::Type::Condition: return {"Condition(0:", p(0), ", ", p(1), ", ", p(2), ")"}; + case Node::Type::Assign: return {"Assign(0:", p(0), ", ", p(1), ")"}; + case Node::Type::Separator: { + string result = "Separator("; + for(auto& link : node->link) { + result.append(evaluateExpression(link), ", "); + } + return result.rtrim<1>(", ").append(")"); + } + } + #undef p + + throw "invalid operator"; +} + +inline int64_t evaluateInteger(Node* node) { + if(node->type == Node::Type::Literal) { + if(node->literal.beginsWith("0b")) return nall::binary(node->literal); + if(node->literal.beginsWith("0o")) return nall::octal(node->literal); + if(node->literal.beginsWith("0x")) return nall::hex(node->literal); + if(node->literal.beginsWith("%")) return nall::binary(node->literal); + if(node->literal.beginsWith("$")) return nall::hex(node->literal); + return nall::integer(node->literal); + } + + #define p(n) evaluateInteger(node->link[n]) + switch(node->type) { + case Node::Type::SuffixIncrement: return p(0); + case Node::Type::SuffixDecrement: return p(0); + case Node::Type::LogicalNot: return !p(0); + case Node::Type::BitwiseNot: return ~p(0); + case Node::Type::Positive: return +p(0); + case Node::Type::Negative: return -p(0); + case Node::Type::PrefixIncrement: return p(0) + 1; + case Node::Type::PrefixDecrement: return p(0) - 1; + case Node::Type::Multiply: return p(0) * p(1); + case Node::Type::Divide: return p(0) / p(1); + case Node::Type::Modulo: return p(0) % p(1); + case Node::Type::Add: return p(0) + p(1); + case Node::Type::Subtract: return p(0) - p(1); + case Node::Type::ShiftLeft: return p(0) << p(1); + case Node::Type::ShiftRight: return p(0) >> p(1); + case Node::Type::BitwiseAnd: return p(0) & p(1); + case Node::Type::BitwiseOr: return p(0) | p(1); + case Node::Type::BitwiseXor: return p(0) ^ p(1); + case Node::Type::Equal: return p(0) == p(1); + case Node::Type::NotEqual: return p(0) != p(1); + case Node::Type::LessThanEqual: return p(0) <= p(1); + case Node::Type::GreaterThanEqual: return p(0) >= p(1); + case Node::Type::LessThan: return p(0) < p(1); + case Node::Type::GreaterThan: return p(0) > p(1); + case Node::Type::LogicalAnd: return p(0) && p(1); + case Node::Type::LogicalOr: return p(0) || p(1); + case Node::Type::Condition: return p(0) ? p(1) : p(2); + case Node::Type::Assign: return p(1); + case Node::Type::AssignMultiply: return p(0) * p(1); + case Node::Type::AssignDivide: return p(0) / p(1); + case Node::Type::AssignModulo: return p(0) % p(1); + case Node::Type::AssignAdd: return p(0) + p(1); + case Node::Type::AssignSubtract: return p(0) - p(1); + case Node::Type::AssignShiftLeft: return p(0) << p(1); + case Node::Type::AssignShiftRight: return p(0) >> p(1); + case Node::Type::AssignBitwiseAnd: return p(0) & p(1); + case Node::Type::AssignBitwiseOr: return p(0) | p(1); + case Node::Type::AssignBitwiseXor: return p(0) ^ p(1); + } + #undef p + + throw "invalid operator"; +} + +inline optional integer(const string& expression) { + try { + auto tree = new Node; + const char* p = expression; + parse(tree, p, 0); + auto result = evaluateInteger(tree); + delete tree; + return {true, result}; + } catch(const char*) { + return false; + } +} + +inline long double evaluateReal(Node* node) { + if(node->type == Node::Type::Literal) return nall::real(node->literal); + + #define p(n) evaluateReal(node->link[n]) + switch(node->type) { + case Node::Type::LogicalNot: return !p(0); + case Node::Type::Positive: return +p(0); + case Node::Type::Negative: return -p(0); + case Node::Type::Multiply: return p(0) * p(1); + case Node::Type::Divide: return p(0) / p(1); + case Node::Type::Add: return p(0) + p(1); + case Node::Type::Subtract: return p(0) - p(1); + case Node::Type::Equal: return p(0) == p(1); + case Node::Type::NotEqual: return p(0) != p(1); + case Node::Type::LessThanEqual: return p(0) <= p(1); + case Node::Type::GreaterThanEqual: return p(0) >= p(1); + case Node::Type::LessThan: return p(0) < p(1); + case Node::Type::GreaterThan: return p(0) > p(1); + case Node::Type::LogicalAnd: return p(0) && p(1); + case Node::Type::LogicalOr: return p(0) || p(1); + case Node::Type::Condition: return p(0) ? p(1) : p(2); + case Node::Type::Assign: return p(1); + case Node::Type::AssignMultiply: return p(0) * p(1); + case Node::Type::AssignDivide: return p(0) / p(1); + case Node::Type::AssignAdd: return p(0) + p(1); + case Node::Type::AssignSubtract: return p(0) - p(1); + } + #undef p + + throw "invalid operator"; +} + +inline optional real(const string& expression) { + try { + auto tree = new Node; + const char* p = expression; + parse(tree, p, 0); + auto result = evaluateReal(tree); + delete tree; + return {true, result}; + } catch(const char*) { + return false; + } +} + +} +} + +#endif diff --git a/src/dep/libs/nall/string/eval/literal.hpp b/src/dep/libs/nall/string/eval/literal.hpp new file mode 100755 index 000000000..fd0f6549e --- /dev/null +++ b/src/dep/libs/nall/string/eval/literal.hpp @@ -0,0 +1,103 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { +namespace Eval { + +inline bool isLiteral(const char*& s) { + char n = s[0]; + return (n >= 'A' && n <= 'Z') + || (n >= 'a' && n <= 'z') + || (n >= '0' && n <= '9') + || (n == '%' || n == '$' || n == '_' || n == '.') + || (n == '\'' || n == '\"'); +} + +inline string literalNumber(const char*& s) { + const char* p = s; + + //binary + if(p[0] == '%' || (p[0] == '0' && p[1] == 'b')) { + unsigned prefix = 1 + (p[0] == '0'); + p += prefix; + while(p[0] == '\'' || p[0] == '0' || p[0] == '1') p++; + if(p - s <= prefix) throw "invalid binary literal"; + string result = substr(s, 0, p - s); + s = p; + return result; + } + + //octal + if(p[0] == '0' && p[1] == 'o') { + unsigned prefix = 1 + (p[0] == '0'); + p += prefix; + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '7')) p++; + if(p - s <= prefix) throw "invalid octal literal"; + string result = substr(s, 0, p - s); + s = p; + return result; + } + + //hex + if(p[0] == '$' || (p[0] == '0' && p[1] == 'x')) { + unsigned prefix = 1 + (p[0] == '0'); + p += prefix; + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '9') || (p[0] >= 'A' && p[0] <= 'F') || (p[0] >= 'a' && p[0] <= 'f')) p++; + if(p - s <= prefix) throw "invalid hex literal"; + string result = substr(s, 0, p - s); + s = p; + return result; + } + + //decimal + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '9')) p++; + if(p[0] != '.') { + string result = substr(s, 0, p - s); + s = p; + return result; + } + + //floating-point + p++; + while(p[0] == '\'' || (p[0] >= '0' && p[0] <= '9')) p++; + string result = substr(s, 0, p - s); + s = p; + return result; +} + +inline string literalString(const char*& s) { + const char* p = s; + char escape = *p++; + + while(p[0] && p[0] != escape) p++; + if(*p++ != escape) throw "unclosed string literal"; + + string result = substr(s, 0, p - s); + s = p; + return result; +} + +inline string literalVariable(const char*& s) { + const char* p = s; + + while(p[0] == '_' || p[0] == '.' || (p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z') || (p[0] >= '0' && p[0] <= '9')) p++; + + string result = substr(s, 0, p - s); + s = p; + return result; +} + +inline string literal(const char*& s) { + const char* p = s; + + if(p[0] >= '0' && p[0] <= '9') return literalNumber(s); + if(p[0] == '%' || p[0] == '$') return literalNumber(s); + if(p[0] == '\'' || p[0] == '\"') return literalString(s); + if(p[0] == '_' || p[0] == '.' || (p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z')) return literalVariable(s); + + throw "invalid literal"; +} + +} +} + +#endif diff --git a/src/dep/libs/nall/string/eval/node.hpp b/src/dep/libs/nall/string/eval/node.hpp new file mode 100755 index 000000000..b60b3aa72 --- /dev/null +++ b/src/dep/libs/nall/string/eval/node.hpp @@ -0,0 +1,41 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { +namespace Eval { + +struct Node { + enum class Type : unsigned { + Null, + Literal, + Function, Subscript, Member, SuffixIncrement, SuffixDecrement, + Reference, Dereference, LogicalNot, BitwiseNot, Positive, Negative, PrefixIncrement, PrefixDecrement, + Multiply, Divide, Modulo, + Add, Subtract, + RotateLeft, RotateRight, ShiftLeft, ShiftRight, + BitwiseAnd, BitwiseOr, BitwiseXor, + Concatenate, + Equal, NotEqual, LessThanEqual, GreaterThanEqual, LessThan, GreaterThan, + LogicalAnd, LogicalOr, + Coalesce, Condition, + Assign, Create, //all assignment operators have the same precedence + AssignMultiply, AssignDivide, AssignModulo, + AssignAdd, AssignSubtract, + AssignRotateLeft, AssignRotateRight, AssignShiftLeft, AssignShiftRight, + AssignBitwiseAnd, AssignBitwiseOr, AssignBitwiseXor, + AssignConcatenate, + Separator, + }; + + Type type; + string literal; + vector link; + + Node() : type(Type::Null) {} + Node(Type type) : type(type) {} + ~Node() { for(auto& node : link) delete node; } +}; + +} +} + +#endif diff --git a/src/dep/libs/nall/string/eval/parser.hpp b/src/dep/libs/nall/string/eval/parser.hpp new file mode 100755 index 000000000..2aee1474c --- /dev/null +++ b/src/dep/libs/nall/string/eval/parser.hpp @@ -0,0 +1,168 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { +namespace Eval { + +inline bool whitespace(char n) { + return n == ' ' || n == '\t' || n == '\r' || n == '\n'; +} + +inline void parse(Node*& node, const char*& s, unsigned depth) { + auto unaryPrefix = [&](Node::Type type, unsigned seek, unsigned depth) { + auto parent = new Node(type); + parse(parent->link(0) = new Node, s += seek, depth); + node = parent; + }; + + auto unarySuffix = [&](Node::Type type, unsigned seek, unsigned depth) { + auto parent = new Node(type); + parent->link(0) = node; + parse(parent, s += seek, depth); + node = parent; + }; + + auto binary = [&](Node::Type type, unsigned seek, unsigned depth) { + auto parent = new Node(type); + parent->link(0) = node; + parse(parent->link(1) = new Node, s += seek, depth); + node = parent; + }; + + auto ternary = [&](Node::Type type, unsigned seek, unsigned depth) { + auto parent = new Node(type); + parent->link(0) = node; + parse(parent->link(1) = new Node, s += seek, depth); + if(s[0] != ':') throw "mismatched ternary"; + parse(parent->link(2) = new Node, s += seek, depth); + node = parent; + }; + + auto separator = [&](Node::Type type, unsigned seek, unsigned depth) { + if(node->type != Node::Type::Separator) return binary(type, seek, depth); + unsigned n = node->link.size(); + parse(node->link(n) = new Node, s += seek, depth); + }; + + while(whitespace(s[0])) s++; + if(!s[0]) return; + + if(s[0] == '(' && node->link.empty()) { + parse(node, s += 1, 1); + if(*s++ != ')') throw "mismatched group"; + } + + if(isLiteral(s)) { + node->type = Node::Type::Literal; + node->literal = literal(s); + } + + #define p() (node->literal.empty() && node->link.empty()) + while(true) { + while(whitespace(s[0])) s++; + if(!s[0]) return; + + if(depth >= 13) break; + if(s[0] == '(' && !p()) { + binary(Node::Type::Function, 1, 1); + if(*s++ != ')') throw "mismatched function"; + continue; + } + if(s[0] == '[') { + binary(Node::Type::Subscript, 1, 1); + if(*s++ != ']') throw "mismatched subscript"; + continue; + } + if(s[0] == '.') { binary(Node::Type::Member, 1, 13); continue; } + if(s[0] == '+' && s[1] == '+' && !p()) { unarySuffix(Node::Type::SuffixIncrement, 2, 13); continue; } + if(s[0] == '-' && s[1] == '-' && !p()) { unarySuffix(Node::Type::SuffixDecrement, 2, 13); continue; } + + if(s[0] == '&' && p()) { unaryPrefix(Node::Type::Reference, 1, 12); continue; } + if(s[0] == '*' && p()) { unaryPrefix(Node::Type::Dereference, 1, 12); continue; } + if(s[0] == '!' && p()) { unaryPrefix(Node::Type::LogicalNot, 1, 12); continue; } + if(s[0] == '~' && p()) { unaryPrefix(Node::Type::BitwiseNot, 1, 12); continue; } + if(s[0] == '+' && s[1] != '+' && p()) { unaryPrefix(Node::Type::Positive, 1, 12); continue; } + if(s[0] == '-' && s[1] != '-' && p()) { unaryPrefix(Node::Type::Negative, 1, 12); continue; } + if(s[0] == '+' && s[1] == '+' && p()) { unaryPrefix(Node::Type::PrefixIncrement, 2, 12); continue; } + if(s[0] == '-' && s[1] == '-' && p()) { unaryPrefix(Node::Type::PrefixDecrement, 2, 12); continue; } + if(depth >= 12) break; + + if(depth >= 11) break; + if(s[0] == '*' && s[1] != '=') { binary(Node::Type::Multiply, 1, 11); continue; } + if(s[0] == '/' && s[1] != '=') { binary(Node::Type::Divide, 1, 11); continue; } + if(s[0] == '%' && s[1] != '=') { binary(Node::Type::Modulo, 1, 11); continue; } + + if(depth >= 10) break; + if(s[0] == '+' && s[1] != '=') { binary(Node::Type::Add, 1, 10); continue; } + if(s[0] == '-' && s[1] != '=') { binary(Node::Type::Subtract, 1, 10); continue; } + + if(depth >= 9) break; + if(s[0] == '<' && s[1] == '<' && s[2] == '<' && s[3] != '=') { binary(Node::Type::RotateLeft, 3, 9); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] == '>' && s[3] != '=') { binary(Node::Type::RotateRight, 3, 9); continue; } + if(s[0] == '<' && s[1] == '<' && s[2] != '=') { binary(Node::Type::ShiftLeft, 2, 9); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] != '=') { binary(Node::Type::ShiftRight, 2, 9); continue; } + + if(depth >= 8) break; + if(s[0] == '&' && s[1] != '&' && s[1] != '=') { binary(Node::Type::BitwiseAnd, 1, 8); continue; } + if(s[0] == '|' && s[1] != '|' && s[1] != '=') { binary(Node::Type::BitwiseOr, 1, 8); continue; } + if(s[0] == '^' && s[1] != '^' && s[1] != '=') { binary(Node::Type::BitwiseXor, 1, 8); continue; } + + if(depth >= 7) break; + if(s[0] == '~' && s[1] != '=') { binary(Node::Type::Concatenate, 1, 7); continue; } + + if(depth >= 6) break; + if(s[0] == '=' && s[1] == '=') { binary(Node::Type::Equal, 2, 6); continue; } + if(s[0] == '!' && s[1] == '=') { binary(Node::Type::NotEqual, 2, 6); continue; } + if(s[0] == '<' && s[1] == '=') { binary(Node::Type::LessThanEqual, 2, 6); continue; } + if(s[0] == '>' && s[1] == '=') { binary(Node::Type::GreaterThanEqual, 2, 6); continue; } + if(s[0] == '<') { binary(Node::Type::LessThan, 1, 6); continue; } + if(s[0] == '>') { binary(Node::Type::GreaterThan, 1, 6); continue; } + + if(depth >= 5) break; + if(s[0] == '&' && s[1] == '&') { binary(Node::Type::LogicalAnd, 2, 5); continue; } + if(s[0] == '|' && s[1] == '|') { binary(Node::Type::LogicalOr, 2, 5); continue; } + + if(s[0] == '?' && s[1] == '?') { binary(Node::Type::Coalesce, 2, 4); continue; } + if(s[0] == '?' && s[1] != '?') { ternary(Node::Type::Condition, 1, 4); continue; } + if(depth >= 4) break; + + if(s[0] == '=') { binary(Node::Type::Assign, 1, 3); continue; } + if(s[0] == ':' && s[1] == '=') { binary(Node::Type::Create, 2, 3); continue; } + if(s[0] == '*' && s[1] == '=') { binary(Node::Type::AssignMultiply, 2, 3); continue; } + if(s[0] == '/' && s[1] == '=') { binary(Node::Type::AssignDivide, 2, 3); continue; } + if(s[0] == '%' && s[1] == '=') { binary(Node::Type::AssignModulo, 2, 3); continue; } + if(s[0] == '+' && s[1] == '=') { binary(Node::Type::AssignAdd, 2, 3); continue; } + if(s[0] == '-' && s[1] == '=') { binary(Node::Type::AssignSubtract, 2, 3); continue; } + if(s[0] == '<' && s[1] == '<' && s[2] == '<' && s[3] == '=') { binary(Node::Type::AssignRotateLeft, 4, 3); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] == '>' && s[3] == '=') { binary(Node::Type::AssignRotateRight, 4, 3); continue; } + if(s[0] == '<' && s[1] == '<' && s[2] == '=') { binary(Node::Type::AssignShiftLeft, 3, 3); continue; } + if(s[0] == '>' && s[1] == '>' && s[2] == '=') { binary(Node::Type::AssignShiftRight, 3, 3); continue; } + if(s[0] == '&' && s[1] == '=') { binary(Node::Type::AssignBitwiseAnd, 2, 3); continue; } + if(s[0] == '|' && s[1] == '=') { binary(Node::Type::AssignBitwiseOr, 2, 3); continue; } + if(s[0] == '^' && s[1] == '=') { binary(Node::Type::AssignBitwiseXor, 2, 3); continue; } + if(s[0] == '~' && s[1] == '=') { binary(Node::Type::AssignConcatenate, 2, 3); continue; } + if(depth >= 3) break; + + if(depth >= 2) break; + if(s[0] == ',') { separator(Node::Type::Separator, 1, 2); continue; } + + if(depth >= 1 && (s[0] == ')' || s[0] == ']')) break; + + while(whitespace(s[0])) s++; + if(!s[0]) break; + + throw "unrecognized terminal"; + } + #undef p +} + +inline Node* parse(const string& expression) { + auto result = new Node; + const char* p = expression; + parse(result, p, 0); + return result; +} + +} +} + +#endif diff --git a/src/dep/libs/nall/string/file.hpp b/src/dep/libs/nall/string/file.hpp new file mode 100755 index 000000000..1b809c2f5 --- /dev/null +++ b/src/dep/libs/nall/string/file.hpp @@ -0,0 +1,31 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string string::read(const string& filename) { + string result; + + #if !defined(_WIN32) + FILE* fp = fopen(filename, "rb"); + #else + FILE* fp = _wfopen(utf16_t(filename), L"rb"); + #endif + if(!fp) return result; + + fseek(fp, 0, SEEK_END); + unsigned fsize = ftell(fp); + rewind(fp); + char* fdata = new char[fsize + 1]; + unsigned unused = fread(fdata, 1, fsize, fp); + fclose(fp); + fdata[fsize] = 0; + result.resize(fsize); + memcpy(result.data(), fdata, fsize); + delete[] fdata; + + return result; +} + +} + +#endif diff --git a/src/dep/libs/nall/string/filename.hpp b/src/dep/libs/nall/string/filename.hpp new file mode 100755 index 000000000..d748a8ba9 --- /dev/null +++ b/src/dep/libs/nall/string/filename.hpp @@ -0,0 +1,84 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +// "/foo/bar.c" -> "/foo/" +// "/foo/" -> "/foo/" +// "bar.c" -> "./" +string dir(string name) { + for(signed i = name.length(); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + name.resize(i + 1); + break; + } + if(i == 0) name = "./"; + } + return name; +} + +// "/foo/bar.c" -> "bar.c" +// "/foo/" -> "" +// "bar.c" -> "bar.c" +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/" +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.resize(last + 1); + return name; +} + +// "/foo/bar.c" -> "/foo/bar" +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.resize(i); + break; + } + } + return name; +} + +// "/foo/bar.c" -> "c" +// "/foo/bar" -> "" +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; +} + +string tempname() { + string path = temppath(); + srand(time(nullptr)); + while(true) { + uint32_t seed = rand(); + string filename = {path, ".temporary-", hex<8>(seed)}; + if(access(filename, F_OK) != 0) return filename; + } +} + +} + +#endif diff --git a/src/dep/libs/nall/string/format.hpp b/src/dep/libs/nall/string/format.hpp new file mode 100755 index 000000000..98c0c5b79 --- /dev/null +++ b/src/dep/libs/nall/string/format.hpp @@ -0,0 +1,72 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template string format(const string& value) { + if(precision == 0) return value; + + bool padright = precision >= 0; + unsigned padding = abs(precision); + + if(padding <= value.size()) { + if(padright) return substr(value, value.size() - padding); + else return substr(value, 0, padding); + } + + string buffer; + buffer.resize(padding); + buffer.clear(padchar); + + memcpy(buffer.data() + (padright ? padding - value.size() : 0), value, value.size()); + return buffer; +} + +template string hex(uintmax_t value) { + string buffer; + buffer.resize(sizeof(uintmax_t) * 2); + + unsigned size = 0; + do { + unsigned n = value & 15; + buffer[size++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + buffer.resize(size); + buffer.reverse(); + + return format(buffer); +} + +template string octal(uintmax_t value) { + string buffer; + buffer.resize(sizeof(uintmax_t) * 3); + + unsigned size = 0; + do { + buffer[size++] = '0' + (value & 7); + value >>= 3; + } while(value); + buffer.resize(size); + buffer.reverse(); + + return format(buffer); +} + +template string binary(uintmax_t value) { + string buffer; + buffer.resize(sizeof(uintmax_t) * 8); + + unsigned size = 0; + do { + buffer[size++] = '0' + (value & 1); + value >>= 1; + } while(value); + buffer.resize(size); + buffer.reverse(); + + return format(buffer); +} + +} + +#endif diff --git a/src/dep/libs/nall/string/list.hpp b/src/dep/libs/nall/string/list.hpp new file mode 100755 index 000000000..196a7a567 --- /dev/null +++ b/src/dep/libs/nall/string/list.hpp @@ -0,0 +1,86 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +optional lstring::find(rstring key) const { + for(unsigned i = 0; i < size(); i++) { + if(operator[](i) == key) return {true, i}; + } + return false; +} + +string lstring::merge(const string& separator) const { + string output; + for(unsigned i = 0; i < size(); i++) { + output.append(operator[](i)); + if(i < size() - 1) output.append(separator); + } + return output; +} + +lstring& lstring::isort() { + nall::sort(pool, objectsize, [](const string& x, const string& y) { + return istrcmp(x, y) < 0; + }); + return *this; +} + +lstring& lstring::strip() { + for(unsigned n = 0; n < size(); n++) { + operator[](n).strip(); + } + return *this; +} + +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/src/dep/libs/nall/string/markup/bml.hpp b/src/dep/libs/nall/string/markup/bml.hpp new file mode 100755 index 000000000..6743a80e2 --- /dev/null +++ b/src/dep/libs/nall/string/markup/bml.hpp @@ -0,0 +1,157 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//BML v1.0 parser +//revision 0.03 + +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 - '-' < 2u; + } + + //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), "\n"}; + 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), "\n"}; + 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 + if(*(p + 0) == '/' && *(p + 1) == '/') break; //skip comments + + 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); + node.data.rtrim<1>("\n"); + children.append(node); + } + } + + //read a node and all of its child nodes + void parseNode(const lstring& text, unsigned& y) { + const char* p = text[y++]; + level = parseDepth(p); + parseName(p); + parseData(p); + parseAttributes(p); + + while(y < text.size()) { + unsigned depth = readDepth(text[y]); + if(depth <= level) break; + + if(text[y][depth] == ':') { + data.append(substr(text[y++], depth + 1), "\n"); + continue; + } + + Node node; + node.parseNode(text, y); + children.append(node); + } + + data.rtrim<1>("\n"); + } + + //read top-level nodes + void parse(const string& document) { + lstring text = string{document}.replace("\r", "").split("\n"); + + //remove empty lines and comment lines + for(unsigned y = 0; y < text.size();) { + unsigned x = 0; + bool empty = true; + while(x < text[y].size()) { + if(text[y][x] == ' ' || text[y][x] == '\t') { x++; continue; } + empty = (text[y][x + 0] == '/' && text[y][x + 1] == '/'); + break; + } + if(empty) text.remove(y); + else y++; + } + + unsigned y = 0; + while(y < text.size()) { + Node node; + node.parseNode(text, y); + if(node.level > 0) throw "Root nodes cannot be indented"; + children.append(node); + } + } + + friend class Document; +}; + +struct Document : Node { + string error; + + bool load(const string& document) { + name = "", data = ""; + + try { + parse(document); + } catch(const char* error) { + this->error = error; + children.reset(); + return false; + } + return true; + } + + Document(const string& document = "") { + load(document); + } +}; + +} +} + +#endif diff --git a/src/dep/libs/nall/string/markup/document.hpp b/src/dep/libs/nall/string/markup/document.hpp new file mode 100755 index 000000000..d9a8027e9 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/string/markup/node.hpp b/src/dep/libs/nall/string/markup/node.hpp new file mode 100755 index 000000000..ff7ea3d69 --- /dev/null +++ b/src/dep/libs/nall/string/markup/node.hpp @@ -0,0 +1,147 @@ +#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.match("*!=*")) comparator = Comparator::NE; + else if(rule.match("*<=*")) comparator = Comparator::LE; + else if(rule.match("*>=*")) comparator = Comparator::GE; + else if(rule.match ("*=*")) comparator = Comparator::EQ; + else if(rule.match ("*<*")) comparator = Comparator::LT; + else if(rule.match ("*>*")) 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.match(side(1)) == true) continue; break; + case Comparator::NE: if(data.match(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.match("*[*]")) { + 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.match("*(*)")) { + lstring side = name.split<1>("("); + name = side(0); + rule = side(1).rtrim<1>(")"); + } + + unsigned position = 0; + for(auto& node : children) { + if(node.name.match(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.merge("/")); + for(auto& item : list) result.append(item); + } + } + + return result; + } + + Node operator[](const string& query) const { + auto result = find(query); + return result(0); + } + + vector::iterator begin() { return children.begin(); } + vector::iterator end() { return children.end(); } + + const vector::constIterator begin() const { return children.begin(); } + const vector::constIterator end() const { return children.end(); } + + Node() : attribute(false), level(0) {} + +protected: + unsigned level; + vector children; +}; + +} +} + +#endif diff --git a/src/dep/libs/nall/string/markup/xml.hpp b/src/dep/libs/nall/string/markup/xml.hpp new file mode 100755 index 000000000..6debcc373 --- /dev/null +++ b/src/dep/libs/nall/string/markup/xml.hpp @@ -0,0 +1,219 @@ +#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.data(); + 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; + const char* 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/src/dep/libs/nall/string/platform.hpp b/src/dep/libs/nall/string/platform.hpp new file mode 100755 index 000000000..e71bd32fa --- /dev/null +++ b/src/dep/libs/nall/string/platform.hpp @@ -0,0 +1,98 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string activepath() { + char path[PATH_MAX] = ""; + auto unused = getcwd(path, PATH_MAX); + string result = path; + if(result.empty()) result = "."; + result.transform("\\", "/"); + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +string realpath(const string& name) { + string result; + char path[PATH_MAX] = ""; + if(::realpath(name, path)) result = dir(path); + if(result.empty()) result = activepath(); + result.transform("\\", "/"); + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +// /home/username/ +// c:/users/username/ +string userpath() { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_PROFILE | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #else + struct passwd* userinfo = getpwuid(getuid()); + string result = userinfo->pw_dir; + #endif + if(result.empty()) result = "."; + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +// /home/username/.config/ +// c:/users/username/appdata/roaming/ +string configpath() { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(PLATFORM_MACOSX) + string result = {userpath(), "Library/Application Support/"}; + #else + string result = {userpath(), ".config/"}; + #endif + if(result.empty()) result = "."; + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +// /usr/share +// /Library/Application Support/ +// c:/ProgramData/ +string sharedpath() { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_COMMON_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(PLATFORM_MACOSX) + string result = "/Library/Application Support/"; + #else + string result = "/usr/share/"; + #endif + if(result.empty()) result = "."; + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +// /tmp +// c:/users/username/AppData/Local/Temp/ +string temppath() { + #if defined(PLATFORM_WINDOWS) + wchar_t path[PATH_MAX] = L""; + GetTempPathW(PATH_MAX, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(P_tmpdir) + string result = P_tmpdir; + #else + string result = "/tmp/"; + #endif + if(result.endsWith("/") == false) result.append("/"); + return result; +} + +} + +#endif diff --git a/src/dep/libs/nall/string/ref.hpp b/src/dep/libs/nall/string/ref.hpp new file mode 100755 index 000000000..19200891b --- /dev/null +++ b/src/dep/libs/nall/string/ref.hpp @@ -0,0 +1,45 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +struct stringref { + operator const char*() const { + return _data; + } + + const char* data() const { + return _data; + } + + unsigned size() const { + if(!_initialized) { + _initialized = true; + _size = strlen(_data); + } + return _size; + } + + stringref() = delete; + stringref(const stringref& source) = delete; + stringref(stringref&& source) = delete; + + stringref(const char* source) { + _data = source; + _initialized = false; + } + + stringref(const string& source) { + _data = source.data(); + _size = source.size(); + _initialized = true; + } + +protected: + const char* _data; + mutable unsigned _size; + mutable bool _initialized; +}; + +} + +#endif diff --git a/src/dep/libs/nall/string/replace.hpp b/src/dep/libs/nall/string/replace.hpp new file mode 100755 index 000000000..65af7be3d --- /dev/null +++ b/src/dep/libs/nall/string/replace.hpp @@ -0,0 +1,55 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template +string& string::ureplace(rstring key, rstring token) { + if(key.size() == 0) return *this; + enum : unsigned { limit = Limit ? Limit : ~0u }; + + const char* p = data(); + unsigned counter = 0; + + while(*p) { + if(quoteskip(p)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) { counter++; p += n; break; } + if(!chrequal(key[n], p[n])) { p++; break; } + } + } + if(counter == 0) return *this; + if(Limit) counter = min(counter, Limit); + + char* t = data(); + char* base = nullptr; + signed displacement = token.size() - key.size(); + signed displacementSize = displacement * counter; + + if(token.size() > key.size()) { + t = base = strduplicate(data()); + reserve((unsigned)(p - data()) + displacementSize); + } + char* o = data(); + + while(*t && counter) { + if(quotecopy(o, t)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) { counter--; memcpy(o, token, token.size()); t += key.size(); o += token.size(); break; } + if(!chrequal(key[n], t[n])) { *o++ = *t++; break; } + } + } + do *o++ = *t; while(*t++); + if(base) free(base); + + resize(_size + displacementSize); + return *this; +} + +template string& string::replace(rstring key, rstring token) { return ureplace(key, token); } +template string& string::ireplace(rstring key, rstring token) { return ureplace(key, token); } +template string& string::qreplace(rstring key, rstring token) { return ureplace(key, token); } +template string& string::iqreplace(rstring key, rstring token) { return ureplace(key, token); } + +}; + +#endif diff --git a/src/dep/libs/nall/string/split.hpp b/src/dep/libs/nall/string/split.hpp new file mode 100755 index 000000000..90521e1f0 --- /dev/null +++ b/src/dep/libs/nall/string/split.hpp @@ -0,0 +1,37 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template lstring& lstring::usplit(rstring key, rstring base) { + reset(); + if(key.size() == 0) return *this; + + const char* b = base; + 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(b, 0, p - b)); + p += n; + b = p; + break; + } + if(!chrequal(key[n], p[n])) { p++; break; } + } + } + + append(b); + return *this; +} + +template lstring& lstring::split(rstring key, rstring src) { return usplit(key, src); } +template lstring& lstring::isplit(rstring key, rstring src) { return usplit(key, src); } +template lstring& lstring::qsplit(rstring key, rstring src) { return usplit(key, src); } +template lstring& lstring::iqsplit(rstring key, rstring src) { return usplit(key, src); } + +}; + +#endif diff --git a/src/dep/libs/nall/string/utility.hpp b/src/dep/libs/nall/string/utility.hpp new file mode 100755 index 000000000..429636cd4 --- /dev/null +++ b/src/dep/libs/nall/string/utility.hpp @@ -0,0 +1,117 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string substr(rstring source, unsigned offset, unsigned length) { + string result; + if(length == ~0u) length = source.size() - offset; + result.resize(length); + memcpy(result.data(), source.data() + offset, length); + return result; +} + +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; +} + +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; +} + +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); + if(negative) buffer[size++] = '-'; +//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; +} + +//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 real(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 real(long double value) { + string temp; + temp.resize(real(nullptr, value)); + real(temp.data(), value); + return temp; +} + +} + +#endif diff --git a/src/dep/libs/nall/string/variadic.hpp b/src/dep/libs/nall/string/variadic.hpp new file mode 100755 index 000000000..fed6b7f34 --- /dev/null +++ b/src/dep/libs/nall/string/variadic.hpp @@ -0,0 +1,20 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +void sprint(string& output) { +} + +template +void sprint(string& output, const T& value, Args&&... args) { + output._append(make_string(value)); + sprint(output, std::forward(args)...); +} + +template void print(Args&&... args) { + printf("%s", (const char*)string(std::forward(args)...)); +} + +} + +#endif diff --git a/src/dep/libs/nall/string/wrapper.hpp b/src/dep/libs/nall/string/wrapper.hpp new file mode 100755 index 000000000..0422ecdcd --- /dev/null +++ b/src/dep/libs/nall/string/wrapper.hpp @@ -0,0 +1,124 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template lstring string::split(rstring key) const { lstring result; result.split(key, data()); return result; } +template lstring string::isplit(rstring key) const { lstring result; result.isplit(key, data()); return result; } +template lstring string::qsplit(rstring key) const { lstring result; result.qsplit(key, data()); return result; } +template lstring string::iqsplit(rstring key) const { lstring result; result.iqsplit(key, data()); return result; } + +bool string::match(rstring source) const { return nall::strmatch(data(), source); } +bool string::imatch(rstring source) const { return nall::istrmatch(data(), source); } + +signed string::compare(rstring source) const { + return strcmp(data(), source.data()); +} + +signed string::icompare(rstring source) const { + return istrcmp(data(), source.data()); +} + +bool string::equals(rstring source) const { + if(size() != source.size()) return false; + return compare(source) == 0; +} + +bool string::iequals(rstring source) const { + if(size() != source.size()) return false; + return icompare(source) == 0; +} + +bool string::beginsWith(rstring source) const { + if(source.size() > size()) return false; + return memcmp(data(), source.data(), source.size()) == 0; +} + +bool string::ibeginsWith(rstring source) const { + if(source.size() > size()) return false; + return imemcmp(data(), source.data(), source.size()) == 0; +} + +bool string::endsWith(rstring source) const { + if(source.size() > size()) return false; + return memcmp(data() + size() - source.size(), source.data(), source.size()) == 0; +} + +bool string::iendsWith(rstring source) const { + if(source.size() > size()) return false; + return imemcmp(data() + size() - source.size(), source.data(), source.size()) == 0; +} + +string string::slice(unsigned offset, unsigned length) const { + if(offset >= size()) return ""; + if(length == ~0u) length = size() - offset; + return substr(data(), offset, length); +} + +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(rstring before, rstring after) { nall::strtr(data(), before, after); return *this; } + +string& string::reverse() { + unsigned length = size(), pivot = length >> 1; + for(signed x = 0, y = length - 1; x < pivot && y >= 0; x++, y--) std::swap(data()[x], data()[y]); + return *this; +} + +template string& string::ltrim(rstring key) { + if(key.size() == 0) return *this; + unsigned limit = Limit ? Limit : ~0u, offset = 0; + + while(limit && size() - offset >= key.size()) { + if(memcmp(data() + offset, key.data(), key.size())) break; + offset += key.size(); + limit--; + } + + if(offset) memmove(data(), data() + offset, size() - offset); + resize(size() - offset); + return *this; +} + +template string& string::rtrim(rstring key) { + if(key.size() == 0) return *this; + unsigned limit = Limit ? Limit : ~0u, offset = 0; + + while(limit && size() - offset >= key.size()) { + if(memcmp(data() + size() - key.size() - offset, key.data(), key.size())) break; + offset += key.size(); + limit--; + } + + resize(size() - offset); + return *this; +} + +template string& string::trim(rstring key) { + rtrim(key); + ltrim(key); + return *this; +} + +template string& string::trim(rstring lkey, rstring rkey) { + rtrim(rkey); + ltrim(lkey); + return *this; +} + +string& string::strip() { + nall::strip(data()); + resize(length()); + return *this; +} + +optional string::find(rstring key) const { return strpos(data(), key); } +optional string::ifind(rstring key) const { return istrpos(data(), key); } +optional string::qfind(rstring key) const { return qstrpos(data(), key); } +optional string::iqfind(rstring key) const { return iqstrpos(data(), key); } + +} + +#endif diff --git a/src/dep/libs/nall/thread.hpp b/src/dep/libs/nall/thread.hpp new file mode 100755 index 000000000..96536df01 --- /dev/null +++ b/src/dep/libs/nall/thread.hpp @@ -0,0 +1,127 @@ +#ifndef NALL_THREAD_HPP +#define NALL_THREAD_HPP + +#include +#include +#include + +#if defined(PLATFORM_X) || defined(PLATFORM_MACOSX) + #include + +namespace nall { + +inline void* thread_entry_point(void*); + +struct thread { + thread(function entryPoint) : entryPoint(entryPoint), completed(false), dead(false) { + initialize(); + pthread_create(&pthread, nullptr, thread_entry_point, (void*)this); + } + + ~thread() { + join(); + } + + bool active() const { + return completed == false; + } + + void join() { + if(dead) return; + dead = true; + pthread_join(pthread, nullptr); + } + + static bool primary() { + initialize(); + return pthread_equal(primaryThread(), pthread_self()); + } + +private: + pthread_t pthread; + function entryPoint; + volatile bool completed, dead; + friend void* thread_entry_point(void*); + + static void initialize() { + static bool initialized = false; + if(initialized) return; + initialized = true; + primaryThread() = pthread_self(); + } + + static pthread_t& primaryThread() { + static pthread_t thread; + return thread; + } +}; + +void* thread_entry_point(void* parameter) { + thread* context = (thread*)parameter; + context->entryPoint(); + context->completed = true; + pthread_exit(nullptr); +} + +} +#elif defined(PLATFORM_WINDOWS) +namespace nall { + +inline DWORD WINAPI thread_entry_point(LPVOID); + +struct thread { + thread(function entryPoint) : entryPoint(entryPoint), completed(false), dead(false) { + initialize(); + hthread = CreateThread(nullptr, 0, thread_entry_point, (void*)this, 0, nullptr); + } + + ~thread() { + join(); + } + + bool active() const { + return completed == false; + } + + void join() { + if(dead) return; + dead = true; + WaitForSingleObject(hthread, INFINITE); + CloseHandle(hthread); + } + + static bool primary() { + initialize(); + return primaryThread() == GetCurrentThreadId(); + } + +private: + HANDLE hthread; + function entryPoint; + volatile bool completed, dead; + friend DWORD WINAPI thread_entry_point(LPVOID); + + static void initialize() { + static bool initialized = false; + if(initialized) return; + initialized = true; + primaryThread() = GetCurrentThreadId(); + } + + static DWORD& primaryThread() { + static DWORD thread; + return thread; + } +}; + +inline DWORD WINAPI thread_entry_point(LPVOID parameter) { + thread *context = (thread*)parameter; + context->entryPoint(); + context->completed = true; + return 0; +} + +} +#endif + +#endif diff --git a/src/dep/libs/nall/traits.hpp b/src/dep/libs/nall/traits.hpp new file mode 100755 index 000000000..6a140c2b9 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/unzip.hpp b/src/dep/libs/nall/unzip.hpp new file mode 100755 index 000000000..269a15d2f --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/ups.hpp b/src/dep/libs/nall/ups.hpp new file mode 100755 index 000000000..d26a65199 --- /dev/null +++ b/src/dep/libs/nall/ups.hpp @@ -0,0 +1,225 @@ +#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 = nullptr; + uint8_t* source_data = nullptr; + uint8_t* target_data = nullptr; + 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/src/dep/libs/nall/utility.hpp b/src/dep/libs/nall/utility.hpp new file mode 100755 index 000000000..0447b94af --- /dev/null +++ b/src/dep/libs/nall/utility.hpp @@ -0,0 +1,138 @@ +#ifndef NALL_UTILITY_HPP +#define NALL_UTILITY_HPP + +#include +#include +#include + +namespace nall { + +template struct base_from_member { + T value; + base_from_member(T value) : value(value) {} +}; + +template struct ref { + T& operator*() { + if(type == Type::Reference) return *any_cast(value); + return any_cast(value); + } + + operator T&() { return operator*(); } + + ref(T& value) : type(Type::Reference), value(&value) {} + ref(T&& value) : type(Type::Temporary), value(value) {} + +protected: + enum class Type : unsigned { Reference, Temporary } type; + any value; +}; + +template struct optional { + typedef typename std::remove_reference::type T; + static const bool isConst = std::is_const::value; + static const bool isReference = std::is_reference::value; + struct optional_value_not_valid{}; + + bool valid = false; + T* value = nullptr; + + operator bool() const { return valid; } + + void reset() { + valid = false; + if(value) { + if(!isReference) delete value; + value = nullptr; + } + } + + template::type> + T& operator*() { + if(!valid) throw optional_value_not_valid{}; + return *value; + } + + template::type> + T& operator()() { + if(!valid) throw optional_value_not_valid{}; + return *value; + } + + const T& operator*() const { + if(!valid) throw optional_value_not_valid{}; + return *value; + } + + const T& operator()() const { + if(!valid) throw optional_value_not_valid{}; + return *value; + } + + const T& operator()(const T& alternate) const { + if(!valid) return alternate; + return *value; + } + + const bool operator==(const optional& source) const { + if(valid && source.valid) return *value == *source.value; + if(!valid && !source.valid) return true; + return false; + } + + const bool operator!=(const optional& source) const { + return !operator==(source); + } + + optional& operator=(const T& source) { + reset(); + valid = true; + if(isReference) value = (T*)&source; + else value = new T(source); + return *this; + } + + optional& operator=(T&& source) { + reset(); + valid = true; + if(isReference) value = &source; + else value = new T(std::move(source)); + return *this; + } + + optional& operator=(const optional& source) { + reset(); + valid = source.valid; + if(valid) operator=(source); + return *this; + } + + optional& operator=(optional&& source) { + reset(); + valid = source.valid; + value = source.value; + source.valid = false; + source.value = nullptr; + return *this; + } + + optional() = default; + optional(bool valid) : valid(valid) {} + optional(const T& value) { operator=(value); } + optional(T&& value) { operator=(std::move(value)); } + optional(bool valid, const T& value) : valid(valid) { if(valid) operator=(value); } + optional(bool valid, T&& value) : valid(valid) { if(valid) operator=(std::move(value)); } + optional(const optional& source) { operator=(source); } + optional(optional&& source) { operator=(std::move(source)); } + ~optional() { reset(); } +}; + +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/src/dep/libs/nall/varint.hpp b/src/dep/libs/nall/varint.hpp new file mode 100755 index 000000000..944e193e7 --- /dev/null +++ b/src/dep/libs/nall/varint.hpp @@ -0,0 +1,220 @@ +#ifndef NALL_VARINT_HPP +#define NALL_VARINT_HPP + +#include +#include +#include +#include + +namespace nall { + +struct varint { + virtual uint8_t read() = 0; + virtual void write(uint8_t) = 0; + + uintmax_t readvu() { + uintmax_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; + } + + intmax_t readvs() { + uintmax_t data = readvu(); + bool sign = data & 1; + data >>= 1; + if(sign) data = -data; + return data; + } + + void writevu(uintmax_t data) { + while(true) { + uint8_t x = data & 0x7f; + data >>= 7; + if(data == 0) return write(0x80 | x); + write(x); + data--; + } + } + + void writevs(intmax_t data) { + bool sign = data < 0; + if(sign) data = -data; + data = (data << 1) | sign; + writevu(data); + } +}; + +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)) {} + + void serialize(serializer& s) { s(data); } +}; + +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)) {} + + void serialize(serializer& s) { s(data); } +}; + +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) {} + + void serialize(serializer& s) { s(data); s(mask); } +}; + +} + +//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/src/dep/libs/nall/vector.hpp b/src/dep/libs/nall/vector.hpp new file mode 100755 index 000000000..7ccb53dbe --- /dev/null +++ b/src/dep/libs/nall/vector.hpp @@ -0,0 +1,275 @@ +#ifndef NALL_VECTOR_HPP +#define NALL_VECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + +template struct vector { + struct exception_out_of_bounds{}; + +protected: + T* pool = nullptr; + unsigned poolbase = 0; + unsigned poolsize = 0; + unsigned objectsize = 0; + +public: + explicit operator bool() const { return objectsize; } + T* data() { return pool + poolbase; } + const T* data() const { return pool + poolbase; } + + bool empty() const { return objectsize == 0; } + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + T* move() { + T* result = pool + poolbase; + pool = nullptr; + poolbase = 0; + poolsize = 0; + objectsize = 0; + return result; + } + + void reset() { + if(pool) { + for(unsigned n = 0; n < objectsize; n++) pool[poolbase + n].~T(); + free(pool); + } + pool = nullptr; + poolbase = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned size) { + if(size <= poolsize) return; + size = bit::round(size); //amortize growth + + T* copy = (T*)calloc(size, sizeof(T)); + for(unsigned n = 0; n < objectsize; n++) new(copy + n) T(std::move(pool[poolbase + n])); + free(pool); + pool = copy; + poolbase = 0; + poolsize = size; + } + + void resize(unsigned size) { + T* copy = (T*)calloc(size, sizeof(T)); + for(unsigned n = 0; n < size && n < objectsize; n++) new(copy + n) T(std::move(pool[poolbase + n])); + reset(); + pool = copy; + poolbase = 0; + poolsize = size; + objectsize = size; + } + + template void prepend(const T& data, Args&&... args) { + prepend(std::forward(args)...); + prepend(data); + } + + T& prepend(const T& data) { + reserve(objectsize + 1); + if(poolbase == 0) { + unsigned available = poolsize - objectsize; + poolbase = max(1u, available >> 1); + for(signed n = objectsize - 1; n >= 0; n--) { + pool[poolbase + n] = std::move(pool[n]); + } + } + new(pool + --poolbase) T(data); + objectsize++; + return first(); + } + + template void append(const T& data, Args&&... args) { + append(data); + append(std::forward(args)...); + } + + T& append(const T& data) { + reserve(poolbase + objectsize + 1); + new(pool + poolbase + objectsize++) T(data); + return last(); + } + + bool appendOnce(const T& data) { + if(find(data)) return false; + return append(data), true; + } + + void insert(unsigned position, const T& data) { + if(position == 0) return prepend(data); + append(data); + if(position == ~0u) return; + for(signed n = objectsize - 1; n > position; n--) { + pool[poolbase + n] = std::move(pool[poolbase + n - 1]); + } + pool[poolbase + position] = data; + } + + void remove(unsigned position = ~0u, unsigned length = 1) { + if(position == ~0u) position = objectsize - 1; + if(position + length > objectsize) throw exception_out_of_bounds{}; + + if(position == 0) { + for(unsigned n = 0; n < length; n++) pool[poolbase + n].~T(); + poolbase += length; + } else { + for(unsigned n = position; n < objectsize; n++) { + if(n + length < objectsize) { + pool[poolbase + n] = std::move(pool[poolbase + n + length]); + } else { + pool[poolbase + n].~T(); + } + } + } + objectsize -= length; + } + + void removeFirst() { return remove(0); } + void removeLast() { return remove(~0u); } + + T take(unsigned position = ~0u) { + if(position == ~0u) position = objectsize - 1; + T object = pool[poolbase + position]; + remove(position); + return object; + } + + T takeFirst() { return take(0); } + T takeLast() { return take(~0u); } + + void reverse() { + unsigned pivot = size() / 2; + for(unsigned l = 0, r = size() - 1; l < pivot; l++, r--) { + std::swap(pool[poolbase + l], pool[poolbase + r]); + } + } + + void sort() { + nall::sort(pool + poolbase, objectsize); + } + + template void sort(const Comparator &lessthan) { + nall::sort(pool + poolbase, objectsize, lessthan); + } + + optional find(const T& data) { + for(unsigned n = 0; n < objectsize; n++) if(pool[poolbase + n] == data) return {true, n}; + return false; + } + + T& first() { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[poolbase]; + } + + const T& first() const { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[poolbase]; + } + + T& last() { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[poolbase + objectsize - 1]; + } + + const T& last() const { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[poolbase + objectsize - 1]; + } + + //access + inline T& operator[](unsigned position) { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[poolbase + position]; + } + + inline const T& operator[](unsigned position) const { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[poolbase + position]; + } + + inline T& operator()(unsigned position) { + if(position >= poolsize) reserve(position + 1); + while(position >= objectsize) append(T()); + return pool[poolbase + position]; + } + + inline const T& operator()(unsigned position, const T& data) const { + if(position >= objectsize) return data; + return pool[poolbase + position]; + } + + //iteration + struct iterator { + T& operator*() { return source.operator[](position); } + bool operator!=(const iterator& source) const { return position != source.position; } + iterator& operator++() { position++; return *this; } + iterator(vector& source, unsigned position) : source(source), position(position) {} + + private: + vector& source; + unsigned position; + }; + + iterator begin() { return iterator(*this, 0); } + iterator end() { return iterator(*this, size()); } + + struct constIterator { + const T& operator*() const { return source.operator[](position); } + bool operator!=(const constIterator& source) const { return position != source.position; } + constIterator& operator++() { position++; return *this; } + constIterator(const vector& source, unsigned position) : source(source), position(position) {} + + private: + const vector& source; + unsigned position; + }; + + const constIterator begin() const { return constIterator(*this, 0); } + const constIterator end() const { return constIterator(*this, size()); } + + //copy + inline vector& operator=(const vector& source) { + reset(); + reserve(source.size()); + for(auto& data : source) append(data); + return *this; + } + + //move + inline vector& operator=(vector&& source) { + reset(); + pool = source.pool; + poolbase = source.poolbase; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = nullptr; + source.poolbase = 0; + source.poolsize = 0; + source.objectsize = 0; + return *this; + } + + //construction and destruction + vector() = default; + vector(std::initializer_list list) { for(auto& data : list) append(data); } + vector(const vector& source) { operator=(source); } + vector(vector&& source) { operator=(std::move(source)); } + ~vector() { reset(); } +}; + +} + +#endif diff --git a/src/dep/libs/nall/windows/detour.hpp b/src/dep/libs/nall/windows/detour.hpp new file mode 100755 index 000000000..34a5d3f8b --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/windows/guid.hpp b/src/dep/libs/nall/windows/guid.hpp new file mode 100755 index 000000000..adee891d0 --- /dev/null +++ b/src/dep/libs/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(nullptr)); + 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/src/dep/libs/nall/windows/launcher.hpp b/src/dep/libs/nall/windows/launcher.hpp new file mode 100755 index 000000000..7ff8e9392 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/windows/registry.hpp b/src/dep/libs/nall/windows/registry.hpp new file mode 100755 index 000000000..f702afe4f --- /dev/null +++ b/src/dep/libs/nall/windows/registry.hpp @@ -0,0 +1,121 @@ +#ifndef NALL_WINDOWS_REGISTRY_HPP +#define NALL_WINDOWS_REGISTRY_HPP + +#include +#include + +#include +#undef interface +#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.merge("\\"); + 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), nullptr, nullptr, (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.merge("\\"); + 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), nullptr, nullptr, (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, nullptr, 0, NWR_FLAGS | KEY_ALL_ACCESS, nullptr, &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.merge("\\"); + 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.merge("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + DWORD folders, nodes; + RegQueryInfoKey(handle, nullptr, nullptr, nullptr, &folders, nullptr, nullptr, &nodes, nullptr, nullptr, nullptr, nullptr); + 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, nullptr, nullptr, nullptr, nullptr); + 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, nullptr, nullptr, nullptr, nullptr); + 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 nullptr; + } +}; + +} + +#endif diff --git a/src/dep/libs/nall/windows/utf8.hpp b/src/dep/libs/nall/windows/utf8.hpp new file mode 100755 index 000000000..dc3ea3bb8 --- /dev/null +++ b/src/dep/libs/nall/windows/utf8.hpp @@ -0,0 +1,89 @@ +#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 + +#if !defined(PATH_MAX) + #define PATH_MAX 260 +#endif + +namespace nall { + //UTF-8 to UTF-16 + struct utf16_t { + 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, nullptr, 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 + struct utf8_t { + 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, nullptr, 0, nullptr, nullptr); + buffer = new char[length + 1](); + WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, nullptr, nullptr); + } + + ~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[PATH_MAX]; + strcpy(argv[i], nall::utf8_t(wargv[i])); + } + } +} + +#endif //if defined(_WIN32) + +#endif diff --git a/src/dep/libs/nall/xorg/guard.hpp b/src/dep/libs/nall/xorg/guard.hpp new file mode 100755 index 000000000..9b5288662 --- /dev/null +++ b/src/dep/libs/nall/xorg/guard.hpp @@ -0,0 +1,31 @@ +#ifndef NALL_XORG_GUARD_HPP +#define NALL_XORG_GUARD_HPP + +#define None +#undef XlibNone +#define XlibNone 0L +#define Bool XlibBool +#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 Bool +#undef Button1 +#undef Button2 +#undef Button3 +#undef Button4 +#undef Button5 +#undef Display +#undef Screen +#undef Window + +#endif diff --git a/src/dep/libs/nall/xorg/xorg.hpp b/src/dep/libs/nall/xorg/xorg.hpp new file mode 100755 index 000000000..bcf48b463 --- /dev/null +++ b/src/dep/libs/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/src/dep/libs/nall/zip.hpp b/src/dep/libs/nall/zip.hpp new file mode 100755 index 000000000..2c27a3288 --- /dev/null +++ b/src/dep/libs/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(nullptr); + 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/src/intf/audio/aud_interface.cpp b/src/intf/audio/aud_interface.cpp index b46de2005..a434c390b 100644 --- a/src/intf/audio/aud_interface.cpp +++ b/src/intf/audio/aud_interface.cpp @@ -23,6 +23,8 @@ static UINT32 nAudActive = 0; extern struct AudOut AudOutSDL; #elif defined (_XBOX) extern struct AudOut AudOutXAudio2; +#elif defined (BUILD_QT) + extern struct AudOut AudOutQtSound; #endif static struct AudOut *pAudOut[]= @@ -34,6 +36,8 @@ static struct AudOut *pAudOut[]= &AudOutSDL, #elif defined (_XBOX) &AudOutXAudio2, +#elif defined (BUILD_QT) + &AudOutQtSound, #endif }; diff --git a/src/intf/input/inp_interface.cpp b/src/intf/input/inp_interface.cpp index 16250d22e..2c90f2669 100644 --- a/src/intf/input/inp_interface.cpp +++ b/src/intf/input/inp_interface.cpp @@ -13,6 +13,8 @@ static bool bCinpOkay; extern struct InputInOut InputInOutSDL; #elif defined (_XBOX) extern struct InputInOut InputInOutXInput2; +#elif defined (BUILD_QT) + extern struct InputInOut InputInOutQt; #endif static struct InputInOut *pInputInOut[]= @@ -23,6 +25,8 @@ static struct InputInOut *pInputInOut[]= &InputInOutSDL, #elif defined (_XBOX) &InputInOutXInput2, +#elif defined (BUILD_QT) + &InputInOutQt, #endif }; diff --git a/src/intf/video/vid_interface.cpp b/src/intf/video/vid_interface.cpp index 360cf1dea..417742cba 100644 --- a/src/intf/video/vid_interface.cpp +++ b/src/intf/video/vid_interface.cpp @@ -17,6 +17,8 @@ extern struct VidOut VidOutSDLFX; #elif defined (_XBOX) extern struct VidOut VidOutD3D; +#elif defined (BUILD_QT) + extern struct VidOut VidRuby; #endif static struct VidOut *pVidOut[] = { @@ -31,6 +33,8 @@ static struct VidOut *pVidOut[] = { &VidOutSDLFX, #elif defined (_XBOX) &VidOutD3D, +#elif defined (BUILD_QT) + &VidRuby, #endif };