changeset 1692:5dacaef602a7 segacd

Merge from default
author Michael Pavone <pavone@retrodev.com>
date Sat, 05 Jan 2019 00:58:08 -0800
parents 95b3a1a8b26c (current diff) b1ad6339de4f (diff)
children 3414a4423de1
files Makefile blastem.c genesis.c genesis.h romdb.c romdb.h system.c system.h
diffstat 122 files changed, 50279 insertions(+), 3466 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Tue Dec 19 00:49:13 2017 -0800
+++ b/.hgtags	Sat Jan 05 00:58:08 2019 -0800
@@ -8,3 +8,5 @@
 1ffa7891b4ec87a2b51afe012c7b5c001f37f780 v0.4.1
 990a2639193394059355cd790c19eaf47eb376e9 v0.5.0
 3d48cb0c28be9045866e00795b698086018b825f v0.5.1
+ef50c9affe6a7c86398f2c36eb5439a559808108 v0.6.0
+357b4951d9b2d1999e4c2765ee53e946aaab864d v0.6.1
--- a/68kinst.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/68kinst.c	Sat Jan 05 00:58:08 2019 -0800
@@ -1537,6 +1537,7 @@
 					immed = 8;
 				}
 				decoded->src.params.immed = immed;
+				decoded->variant = VAR_QUICK;
 			}
 			decoded->dst.addr_mode = MODE_REG;
 			decoded->dst.params.regs.pri = *istream & 0x7;
@@ -2608,9 +2609,12 @@
 #endif
 	default:
 		size = decoded->extra.size;
+		uint8_t is_quick = decoded->variant == VAR_QUICK && decoded->op != M68K_ASL && decoded->op != M68K_ASR 
+			&& decoded->op != M68K_LSL && decoded->op != M68K_LSR && decoded->op != M68K_ROXR && decoded->op != M68K_ROXL
+			&& decoded->op != M68K_ROR && decoded->op != M68K_ROL;
 		ret = sprintf(dst, "%s%s%s",
 				mnemonics[decoded->op],
-				decoded->variant == VAR_QUICK ? "q" : (decoded->variant == VAR_IMMEDIATE ? "i" : ""),
+				is_quick ? "q" : (decoded->variant == VAR_IMMEDIATE ? "i" : ""),
 				size == OPSIZE_BYTE ? ".b" : (size == OPSIZE_WORD ? ".w" : (size == OPSIZE_LONG ? ".l" : "")));
 	}
 	if (decoded->op == M68K_MOVEM) {
--- a/CHANGELOG	Tue Dec 19 00:49:13 2017 -0800
+++ b/CHANGELOG	Sat Jan 05 00:58:08 2019 -0800
@@ -1,3 +1,47 @@
+0.6.1
+-----
+*Bugfixes*
+
+ - Fixed build script so controller images are actually included so UI doesn't crash
+ - Disabled most bindings when UI active (fixes crashes/wonkiness when pressing certain keys)
+ - Fixed Windows implementation of get_config_dir() so config file ends up in %localappdata%\blastem like it should
+ - Fixed the location of sticky_path on all platforms
+ - Added virtual root directory used by ROM UI to new Nuklear UI
+
+0.6.0
+-----
+*New Features*
+
+ - New Nuklear UI with almost complete access to configuration options
+ - Added support for creating SDL2 mappings from inside the emulator
+ - Loading ROMs from ZIP archives is now supported
+ - Loading gzip compressed ROMs is now supported
+ - Internal screenshots can now be in PNG format
+ - New VDP plane debug view
+ - VDP debug views are now in separate windows
+ - Experimental support for sync to video (not enabled by default)
+ - Preliminary support for MegaWifi cart emulation
+ 
+*Bugfixes*
+
+ - Fixed a number of 68K decoder bugs (mostly illegal instruction decoding)
+ - Fixed implementation of the UNLK instruction when register is a7
+ - Fixed a number of memory leaks
+ 
+ *Accuracy/Completeness Improvements*
+ 
+ - Added support for J-Cart emulation
+ - Implemented Z80 interrupt mode 2
+ - Fixed the timing of a number of 68K instructions
+ - Fixed the timing of the Z80 JP cc, nn instruction
+ - Fixed the order bytes of a word are written to VRAM from the FIFO (fixes minor corruption in Road Rash 3 bike menu)
+ 
+*Other Changes*
+ - Added support for Open GL ES in addition to the existing desktop GL support
+ - Some small optimizations
+ - Added ROM DB entry for Squirrel King to support it's copy protection
+ - Added support for float32 audio output (fixes an issue with defautl SDL2 driver in Windows when using more recent SDL2 versions)
+ 
 0.5.1
 -----
 *New Features*
--- a/Makefile	Tue Dec 19 00:49:13 2017 -0800
+++ b/Makefile	Sat Jan 05 00:58:08 2019 -0800
@@ -16,6 +16,8 @@
 
 MEM:=mem_win.o
 TERMINAL:=terminal_win.o
+FONT:=nuklear_ui/font_win.o
+NET:=net_win.o
 EXE:=.exe
 CC:=i686-w64-mingw32-gcc-win32
 CFLAGS:=-std=gnu99 -Wreturn-type -Werror=return-type -Werror=implicit-function-declaration -I"$(SDL2_PREFIX)/include/SDL2" -I"$(GLEW_PREFIX)/include" -DGLEW_STATIC
@@ -26,31 +28,50 @@
 
 MEM:=mem.o
 TERMINAL:=terminal.o
+NET:=net.o
 EXE:=
 
+HAS_PROC:=$(shell if [ -d /proc ]; then /bin/echo -e -DHAS_PROC; fi)
+CFLAGS:=-std=gnu99 -Wreturn-type -Werror=return-type -Werror=implicit-function-declaration -Wno-unused-value $(HAS_PROC) -DHAVE_UNISTD_H
+
 ifeq ($(OS),Darwin)
 LIBS=sdl2 glew
+FONT:=nuklear_ui/font_mac.o
+else
+ifdef USE_GLES
+LIBS=sdl2 glesv2
+CFLAGS+= -DUSE_GLES
 else
 LIBS=sdl2 glew gl
+endif #USE_GLES
+FONT:=nuklear_ui/font.o
 endif #Darwin
 
-HAS_PROC:=$(shell if [ -d /proc ]; then /bin/echo -e -DHAS_PROC; fi)
-CFLAGS:=-std=gnu99 -Wreturn-type -Werror=return-type -Werror=implicit-function-declaration -Wno-unused-value $(HAS_PROC)
 ifeq ($(OS),Darwin)
 #This should really be based on whether or not the C compiler is clang rather than based on the OS
 CFLAGS+= -Wno-logical-op-parentheses
 endif
 ifdef PORTABLE
+ifdef USE_GLES
+ifndef GLES_LIB
+GLES_LIB:=$(shell pkg-config --libs glesv2)
+endif
+LDFLAGS:=-lm $(GLES_LIB)
+else
 CFLAGS+= -DGLEW_STATIC -Iglew/include
 LDFLAGS:=-lm glew/lib/libGLEW.a
+endif
 
 ifeq ($(OS),Darwin)
 CFLAGS+= -IFrameworks/SDL2.framework/Headers
-LDFLAGS+= -FFrameworks -framework SDL2 -framework OpenGL
+LDFLAGS+= -FFrameworks -framework SDL2 -framework OpenGL -framework AppKit
 FIXUP:=install_name_tool -change @rpath/SDL2.framework/Versions/A/SDL2 @executable_path/Frameworks/SDL2.framework/Versions/A/SDL2
 else
 CFLAGS+= -Isdl/include
-LDFLAGS+= -Wl,-rpath='$$ORIGIN/lib' -Llib -lSDL2 $(shell pkg-config --libs gl)
+LDFLAGS+= -Wl,-rpath='$$ORIGIN/lib' -Llib -lSDL2
+ifndef USE_GLES
+LDFLAGS+= $(shell pkg-config --libs gl)
+endif
 endif #Darwin
 
 else
@@ -58,24 +79,26 @@
 LDFLAGS:=-lm $(shell pkg-config --libs $(LIBS))
 
 ifeq ($(OS),Darwin)
-LDFLAGS+= -framework OpenGL
+LDFLAGS+= -framework OpenGL -framework AppKit
 endif
 
 endif #PORTABLE
 endif #Windows
 
+ifndef OPT
 ifdef DEBUG
-CFLAGS:=-ggdb $(CFLAGS)
-LDFLAGS:=-ggdb $(LDFLAGS)
+OPT:=-g3 -O0
 else
 ifdef NOLTO
-CFLAGS:=-O2 $(CFLAGS)
-LDFLAGS:=-O2 $(LDFLAGS)
+OPT:=-O2
 else
-CFLAGS:=-O2 -flto $(CFLAGS)
-LDFLAGS:=-O2 -flto $(LDFLAGS)
+OPT:=-O2 -flto
 endif #NOLTO
 endif #DEBUG
+endif #OPT
+
+CFLAGS:=$(OPT) $(CFLAGS)
+LDFLAGS:=$(OPT) $(LDFLAGS)
 
 ifdef Z80_LOG_ADDRESS
 CFLAGS+= -DZ80_LOG_ADDRESS
@@ -86,6 +109,7 @@
 endif
 ifdef NOGL
 CFLAGS+= -DDISABLE_OPENGL
+NONUKLEAR:=1
 endif
 
 ifdef M68030
@@ -125,9 +149,27 @@
 
 Z80OBJS=z80inst.o z80_to_x86.o
 AUDIOOBJS=ym2612.o psg.o wave.o
-CONFIGOBJS=config.o tern.o util.o
+CONFIGOBJS=config.o tern.o util.o paths.o 
+NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o controller_info.o
+RENDEROBJS=render_sdl.o ppm.o
+LIBZOBJS=zlib/adler32.o zlib/compress.o zlib/crc32.o zlib/deflate.o zlib/gzclose.o zlib/gzlib.o zlib/gzread.o\
+	zlib/gzwrite.o zlib/infback.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o zlib/uncompr.o zlib/zutil.o
+	
+ifdef NOZLIB
+CFLAGS+= -DDISABLE_ZLIB
+else
+RENDEROBJS+= $(LIBZOBJS) png.o
+endif
 
-MAINOBJS=blastem.o system.o genesis.o segacd.o debug.o gdb_remote.o vdp.o render_sdl.o ppm.o io.o romdb.o hash.o menu.o xband.o realtec.o i2c.o nor.o sega_mapper.o multi_game.o serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS)
+MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o $(RENDEROBJS) io.o romdb.o hash.o menu.o xband.o \
+	realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
+	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o segacd.o
+	
+ifdef NONUKLEAR
+CFLAGS+= -DDISABLE_NUKLEAR
+else
+MAINOBJS+= $(NUKLEAROBJS)
+endif
 
 ifeq ($(CPU),x86_64)
 CFLAGS+=-DX86_64 -m64
@@ -162,11 +204,11 @@
 	$(CC) -o $@ $^ $(LDFLAGS)
 	$(FIXUP) ./$@
 	
-blastjag$(EXE) : jaguar.o jag_video.o render_sdl.o serialize.o $(M68KOBJS) $(TRANSOBJS) $(CONFIGOBJS)
+blastjag$(EXE) : jaguar.o jag_video.o $(RENDEROBJS) serialize.o $(M68KOBJS) $(TRANSOBJS) $(CONFIGOBJS)
 	$(CC) -o $@ $^ $(LDFLAGS)
 
 dis$(EXE) : dis.o 68kinst.o tern.o vos_program_module.o
-	$(CC) -o $@ $^
+	$(CC) -o $@ $^ $(OPT)
 	
 jagdis : jagdis.o jagcpu.o tern.o
 	$(CC) -o $@ $^
@@ -178,27 +220,27 @@
 	ar rcs libemu68k.a $(M68KOBJS) $(TRANSOBJS)
 
 trans : trans.o serialize.o $(M68KOBJS) $(TRANSOBJS) util.o
-	$(CC) -o trans trans.o $(M68KOBJS) $(TRANSOBJS) util.o
+	$(CC) -o trans trans.o $(M68KOBJS) $(TRANSOBJS) util.o $(OPT)
 
 transz80 : transz80.o $(Z80OBJS) $(TRANSOBJS)
 	$(CC) -o transz80 transz80.o $(Z80OBJS) $(TRANSOBJS)
 
 ztestrun : ztestrun.o serialize.o $(Z80OBJS) $(TRANSOBJS)
-	$(CC) -o ztestrun ztestrun.o $(Z80OBJS) $(TRANSOBJS)
+	$(CC) -o ztestrun ztestrun.o $(Z80OBJS) $(TRANSOBJS) $(OPT)
 
 ztestgen : ztestgen.o z80inst.o
 	$(CC) -ggdb -o ztestgen ztestgen.o z80inst.o
 
-stateview$(EXE) : stateview.o vdp.o render_sdl.o ppm.o serialize.o $(CONFIGOBJS) gst.o
+stateview$(EXE) : stateview.o vdp.o $(RENDEROBJS) serialize.o $(CONFIGOBJS) gst.o
 	$(CC) -o $@ $^ $(LDFLAGS)
 	$(FIXUP) ./$@
 
-vgmplay$(EXE) : vgmplay.o render_sdl.o ppm.o serialize.o $(CONFIGOBJS) $(AUDIOOBJS)
+vgmplay$(EXE) : vgmplay.o $(RENDEROBJS) serialize.o $(CONFIGOBJS) $(AUDIOOBJS)
 	$(CC) -o $@ $^ $(LDFLAGS)
 	$(FIXUP) ./$@
 
 blastcpm : blastcpm.o util.o serialize.o $(Z80OBJS) $(TRANSOBJS)
-	$(CC) -o $@ $^
+	$(CC) -o $@ $^ $(OPT)
 
 test : test.o vdp.o
 	$(CC) -o test test.o vdp.o
@@ -229,6 +271,10 @@
 
 %.o : %.c
 	$(CC) $(CFLAGS) -c -o $@ $<
+  
+%.o : %.m
+	$(CC) $(CFLAGS) -c -o $@ $<
+
 %.png : %.xcf
 	xcf2png $< > $@
 
@@ -252,4 +298,4 @@
 menu.bin : font_interlace_variable.tiles arrow.tiles cursor.tiles button.tiles font.tiles
 
 clean :
-	rm -rf $(ALL) trans ztestrun ztestgen *.o
+	rm -rf $(ALL) trans ztestrun ztestgen *.o nuklear_ui/*.o zlib/*.o
--- a/README	Tue Dec 19 00:49:13 2017 -0800
+++ b/README	Sat Jan 05 00:58:08 2019 -0800
@@ -1,14 +1,10 @@
-BlastEm 0.5.1
+BlastEm 0.6.0
 -------------
 
 Installation
 ------------
 
-Extract this archive to a directory of your choosing. If you wish to change the
-configuration settings, copy default.cfg to $HOME/.config/blastem/blastem.cfg 
-and modify the copy. If you are on Windows, the config file should be placed in
-%localappdata%\blastem. You may also whish to add the blastem directory to your
-PATH environment variable.
+Extract this archive to a directory of your choosing.
 
 NOTE: Prior to version 0.4.1, BlastEm was still using Unixy locations for config
 and save files. If you're upgrading from a previous version on Windows, you will
@@ -19,11 +15,15 @@
 Usage
 -----
 
-This version of BlastEm has an experimental GUI that is implemented as a Genesis
-ROM running inside the emulator. This UI can be operated with either a mouse or
-the first emulated gamepad. By default, both the keyboard and the first game
-controller are mapped to said gamepad. For more information on bindings see the
-Bindings section.
+This version of BlastEm has a GUI that allows access to most configuration options.
+Simply start BlastEm without passing a ROM filename on the command line to access
+the main menu. You can also access the menu by hitting the button mapped to the ui.exit
+action (default Esc).
+
+If Open GL is disabled or unavaible, or you explicitly request it, the old ROM-based UI
+will be used instead. This UI does not support configuration so you will need to modify
+the configuration file manually if you use it. See the rest of this README for instructions
+on modifying the configuration file.
 
 Some operations are currently only supported through the command line. To get a
 list of supported command line options on Linux or OSX type:
@@ -149,9 +149,12 @@
 gamepad binding.
 
 ui.release_mouse             Releases the mouse if it is currently captured
-ui.vdp_debug_mode            Cycles through the various VDP debug views
-ui.vdp_debug_pal             Cycles through the selected palette when viewing
-                             the VDP memory viewer
+ui.plane_debug               Toggles the VDP plane debug view
+ui.vram_debug                Toggles the VDP VRAM debug view
+ui.cram_debug                Toggles the VDP CRAM debug view
+ui.compositing_debug         Toggles the VDP compositing debug view
+ui.vdp_debug_mode            Cycles the mode/palette of the VDP debug view
+                             that currently has focus
 ui.enter_debugger            Enters the debugger for the main CPU of the
 							 currently emulated system
 ui.screenshot                Takes an internal screenshot
@@ -213,11 +216,9 @@
 refresh rate. Valid values for this setting are "off", "on" and "tear". The
 latter will attempt to use the "late tear" option if it's available and normal
 vsync otherwise. Currently it's recommended to leave this at the default of
-"off" as BlastEm synchronizes to audio and does not yet have the necessary code
-to fully handle conflicts between the audio rate and monitor refresh rate.
-Additionally, the "turbo" feature does not function properly with vsync
-enabled. These issues will be addressed in a future release. If you wish to use
-vsync, please see the VSync section at the bottom of the README.
+"off" as it may not work well with the default "audio" sync method and the
+"video" sync method will automatically enable "vsync". See "Sync Source and
+VSync" for more details.
 
 "fullscreen" controls whether BlastEm starts in fullscreen or windowed mode.
 This can be overridden on the command line with the -f flag. If fullscreen
@@ -366,7 +367,19 @@
 
 "default_region" determines the console region that will be used when region
 detection fails and when there are multiple valid regions. The default of 'U'
-specifies a 60Hz "foreign" console. 
+specifies a 60Hz "foreign" console.
+
+"sync_source" controls whether BlastEm uses audio or video output to control
+execution speed. "video" can provide a smoother experience when your display
+has a similar refresh rate to the emulated system, but has some limitations
+in the current version. The default value is "audio".
+
+"megawifi" enables or disables support for MegaWiFi cart emulation. MegaWiFi
+is a cartridge that contains WiFi hardware for network functionality. Enabling
+this means that ROMs potentially have access to your network (and the internet)
+which obviously has security implications. For this reason, it is disabled by
+default. If you wish to try out MegaWiFi emulation, set this to "on". Note that
+the support for MegaWiFi hardware is preliminary in this release.
 
 Debugger
 --------
@@ -445,32 +458,26 @@
     vgmplay   - Very basic VGM player
     stateview - GST save state viewer
     
-VSync
+Sync Source and VSync
 -----
 
-This section includes information about using VSync with BlastEm. As mentioned
-above, the code is currently designed to only sync to audio and has some issues
-with VSync as a result. That said, if your computer is fast enough and you
-don't care about turbo mode, it can generally made to work.
+This section includes information about using VSync with BlastEm. Currently,
+the best way to use VSync is to set the sync source to "video". This will force
+VSync on and use video output for controlling the speed of emulation. In this
+mode, audio will have it's rate automatically adjusted to keep pace with video.
+The code for this is still a bit immature, so you may experience dropouts or
+pitch changes in this mode.
 
-The native refresh rate of an NTSC Genesis is approximately 59.92 Hz which is
-probably not the native refresh rate of your monitor. Fortunately, it is
-most likely lower than your refresh rate. As long as this is true, VSync will
-generally work as long as your computer is fast enough to cope with the time
-lost waiting for VSync and the audio buffer is large enough to not run out of
-samples during that delay. Latency will suffer a bit and you'll get a doubled
-frame every once and a while, but generally things will be fine.
+If you experience problems, please switch back to the "audio" sync source,
+which is the default. You can also enable vsync when using the "audio" sync
+source by changing the "vsync" setting. This will generally work okay as long
+as the emulated refresh rate is below your monitor refresh rate (even if only
+slightly), but you will occassionally get a doubled frame (or frequently if
+the refresh rates are very different).
 
-If you enable VSync and you're getting audio dropouts, first try doubling the
-audio buffer setting. If you still experience dropouts, it's possible your
-computer is not fast enough or that your monitor's actual refresh rate is in
-fact lower than that of the emualted console. Not much can be done about the
-former (apart from disabling VSync), but the latter can be dealt with by
-lowering the default speed slightly in the "clocks" section.
+Turbo mode will currently not work when vsync is on, regardless of which sync
+source is used. Slow mode will work with "audio" sync, but not "video" sync.
 
-A future release will support VSync in a less hacky fashion.
-
-Special Thanks
 --------------
 
 My work has been made much easier by the contributions of those in the Genesis
@@ -500,7 +507,15 @@
                      impressive demos on the Megadrive. Additionally, I am very
                      grateful for the documentation provided by Kabuto and the
                      assistance of Kabuto, Sik and Jorge in getting Overdrive 2
-                     to run properly in BlastEm.					 
+                     to run properly in BlastEm.
+					 
+flamewing          - flamewing created a very handy exhaustive test ROM for 68K
+                     BCD instructions and documented the proper behavior for
+                     certain BCD edge cases
+
+r57shell           - r57shell created a test ROM for 68K instruction sizes that
+                     was invaluable in fixing the remaining bugs in my 68K instruction
+                     decoder
 
 I'd also like to thank the following people who have performed compatibility
 testing or submitted helpful bug reports
--- a/analyze_olp.py	Tue Dec 19 00:49:13 2017 -0800
+++ b/analyze_olp.py	Sat Jan 05 00:58:08 2019 -0800
@@ -148,6 +148,30 @@
 						print 'refresh @ {0}'.format(num)
 						state = 'begin'
 			last = sample
+			
+def analyze_z80_mreq(chanmap, datafile):
+	m1 = chanmap['!M1']
+	mreq = chanmap['!MREQ']
+	addressMask = 0x3FF
+	last = None
+	lastWasM1 = False
+	for line in datafile.readlines():
+		line = line.strip()
+		if line and not line.startswith(';'):
+			sample,_,num = line.partition('@')
+			sample = int(sample, 16)
+			if not (last is None):
+				if detect_rise(last, sample, mreq):
+					address = last & addressMask
+					if detect_low(last, m1):
+						print 'M1 read {0:02X} @ {1}'.format(address, num)
+						lastWasM1 = True
+					elif lastWasM1:
+						print 'Refresh {0:02X} @ {1}'.format(address, num)
+						lastWasM1 = False
+					else:
+						print 'Access {0:02X} @ {1}'.format(address, num)
+			last = sample
 
 def main(args):
 	if len(args) < 2:
@@ -163,10 +187,11 @@
 		chanmap[channels[i]] = i
 	datafile = olpfile.open('data.ols')
 	#analyze_delays(chanmap, datafile)
-	analyze_vram(chanmap, datafile)
+	#analyze_vram(chanmap, datafile)
+	#analyze_refresh(chanmap, datafile)
+	analyze_z80_mreq(chanmap, datafile)
 	datafile.close()
-	#datafile = olpfile.open('data.ols')
-	#analyze_refresh(chanmap, datafile)
+	
 
 if __name__ == '__main__':
 	main(argv)
--- a/backend.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/backend.c	Sat Jan 05 00:58:08 2019 -0800
@@ -75,7 +75,7 @@
 	for (uint32_t chunk = 0; chunk < opts->memmap_chunks; chunk++)
 	{
 		if (address >= memmap[chunk].start && address < memmap[chunk].end) {
-			if (!(memmap[chunk].flags & MMAP_READ)) {
+			if (!(memmap[chunk].flags & (MMAP_READ|MMAP_READ_CODE))) {
 				return NULL;
 			}
 			uint8_t * base = memmap[chunk].flags & MMAP_PTR_IDX
--- a/backend.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/backend.h	Sat Jan 05 00:58:08 2019 -0800
@@ -40,42 +40,7 @@
 	uint32_t             address;
 } deferred_addr;
 
-typedef enum {
-	READ_16,
-	READ_8,
-	WRITE_16,
-	WRITE_8
-} ftype;
-
-#define MMAP_READ      0x01
-#define MMAP_WRITE     0x02
-#define MMAP_CODE      0x04
-#define MMAP_PTR_IDX   0x08
-#define MMAP_ONLY_ODD  0x10
-#define MMAP_ONLY_EVEN 0x20
-#define MMAP_FUNC_NULL 0x40
-#define MMAP_BYTESWAP  0x80
-#define MMAP_AUX_BUFF  0x100
-
-typedef uint16_t (*read_16_fun)(uint32_t address, void * context);
-typedef uint8_t (*read_8_fun)(uint32_t address, void * context);
-typedef void * (*write_16_fun)(uint32_t address, void * context, uint16_t value);
-typedef void * (*write_8_fun)(uint32_t address, void * context, uint8_t value);
-
-typedef struct {
-	uint32_t     start;
-	uint32_t     end;
-	uint32_t     mask;
-	uint32_t     aux_mask;
-	uint16_t     ptr_index;
-	uint16_t     flags;
-	void *       buffer;
-	read_16_fun  read_16;
-	write_16_fun write_16;
-	read_8_fun   read_8;
-	write_8_fun  write_8;
-} memmap_chunk;
-
+#include "memmap.h"
 #include "system.h"
 
 typedef struct {
--- a/backend_x86.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/backend_x86.c	Sat Jan 05 00:58:08 2019 -0800
@@ -244,7 +244,7 @@
 						if (is_write && (memmap[chunk].flags & MMAP_CODE)) {
 							pop_r(code, opts->scratch2);
 						} else {
-							add_ir(code, sizeof(void*), RSP, SZ_D);
+							add_ir(code, sizeof(void*), RSP, SZ_PTR);
 							code->stack_off -= sizeof(void *);
 						}
 					} else {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,1081 @@
+#include <string.h>
+#include "render.h"
+#include "system.h"
+#include "io.h"
+#include "blastem.h"
+#include "saves.h"
+#include "util.h"
+#include "genesis.h"
+#include "sms.h"
+#include "menu.h"
+#include "bindings.h"
+#include "controller_info.h"
+#ifndef DISABLE_NUKLEAR
+#include "nuklear_ui/blastem_nuklear.h"
+#endif
+
+enum {
+	BIND_NONE,
+	BIND_UI,
+	BIND_GAMEPAD,
+	BIND_MOUSE
+};
+
+typedef enum {
+	UI_DEBUG_MODE_INC,
+	UI_ENTER_DEBUGGER,
+	UI_SAVE_STATE,
+	UI_SET_SPEED,
+	UI_NEXT_SPEED,
+	UI_PREV_SPEED,
+	UI_RELEASE_MOUSE,
+	UI_TOGGLE_KEYBOARD_CAPTURE,
+	UI_TOGGLE_FULLSCREEN,
+	UI_SOFT_RESET,
+	UI_RELOAD,
+	UI_SMS_PAUSE,
+	UI_SCREENSHOT,
+	UI_EXIT,
+	UI_PLANE_DEBUG,
+	UI_VRAM_DEBUG,
+	UI_CRAM_DEBUG,
+	UI_COMPOSITE_DEBUG
+} ui_action;
+
+typedef struct {
+	uint8_t bind_type;
+	uint8_t subtype_a;
+	uint8_t subtype_b;
+} keybinding;
+
+typedef struct {
+	keybinding bindings[4];
+	uint8_t    state;
+} joydpad;
+
+typedef struct {
+	keybinding positive;
+	keybinding negative;
+	int16_t    value;
+} joyaxis;
+
+typedef struct {
+	keybinding *buttons;
+	joydpad    *dpads;
+	joyaxis    *axes;
+	uint32_t   num_buttons; //number of entries in the buttons array, not necessarily the number of buttons on the device
+	uint32_t   num_dpads;   //number of entries in the dpads array, not necessarily the number of dpads on the device
+	uint32_t   num_axes;    //number of entries in the axes array, not necessarily the number of dpads on the device
+} joystick;
+
+typedef struct {
+	keybinding buttons[MAX_MOUSE_BUTTONS];
+	keybinding motion;
+} mousebinding;
+
+#define DEFAULT_JOYBUTTON_ALLOC 12
+static keybinding *bindings[0x10000];
+static joystick joysticks[MAX_JOYSTICKS];
+static mousebinding mice[MAX_MICE];
+const uint8_t dpadbits[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
+
+static void do_bind(keybinding *binding, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
+{
+	binding->bind_type = bind_type;
+	binding->subtype_a = subtype_a;
+	binding->subtype_b = subtype_b;
+}
+
+void bind_key(int keycode, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
+{
+	int bucket = keycode >> 15 & 0xFFFF;
+	if (!bindings[bucket]) {
+		bindings[bucket] = malloc(sizeof(keybinding) * 0x8000);
+		memset(bindings[bucket], 0, sizeof(keybinding) * 0x8000);
+	}
+	int idx = keycode & 0x7FFF;
+	do_bind(bindings[bucket] + idx, bind_type, subtype_a, subtype_b);
+}
+
+void bind_button(int joystick, int button, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
+{
+	if (joystick >= MAX_JOYSTICKS) {
+		return;
+	}
+	if (!joysticks[joystick].buttons) {
+		joysticks[joystick].num_buttons = button < DEFAULT_JOYBUTTON_ALLOC ? DEFAULT_JOYBUTTON_ALLOC : button + 1;
+		joysticks[joystick].buttons = calloc(joysticks[joystick].num_buttons, sizeof(keybinding));
+	} else if (joysticks[joystick].num_buttons <= button) {
+		uint32_t old_capacity = joysticks[joystick].num_buttons;
+		joysticks[joystick].num_buttons *= 2;
+		joysticks[joystick].buttons = realloc(joysticks[joystick].buttons, sizeof(keybinding) * joysticks[joystick].num_buttons);
+		memset(joysticks[joystick].buttons + old_capacity, 0, joysticks[joystick].num_buttons - old_capacity);
+	}
+	do_bind(joysticks[joystick].buttons + button, bind_type, subtype_a, subtype_b);
+}
+
+void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
+{
+	if (joystick >= MAX_JOYSTICKS) {
+		return;
+	}
+	if (!joysticks[joystick].dpads) {
+		//multiple D-pads/hats are not common, so don't allocate any extra space
+		joysticks[joystick].dpads = calloc(dpad+1, sizeof(joydpad));
+		joysticks[joystick].num_dpads = dpad+1;
+	} else if (joysticks[joystick].num_dpads <= dpad) {
+		uint32_t old_capacity = joysticks[joystick].num_dpads;
+		joysticks[joystick].num_dpads *= 2;
+		joysticks[joystick].dpads = realloc(joysticks[joystick].dpads, sizeof(joydpad) * joysticks[joystick].num_dpads);
+		memset(joysticks[joystick].dpads + old_capacity, 0, (joysticks[joystick].num_dpads - old_capacity) * sizeof(joydpad));
+	}
+	for (int i = 0; i < 4; i ++) {
+		if (dpadbits[i] & direction) {
+			do_bind(joysticks[joystick].dpads[dpad].bindings + i, bind_type, subtype_a, subtype_b);
+			break;
+		}
+	}
+}
+
+void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b)
+{
+	if (joystick >= MAX_JOYSTICKS) {
+		return;
+	}
+	if (!joysticks[joystick].axes) {
+		//typical gamepad has 4 axes
+		joysticks[joystick].num_axes = axis+1 > 4 ? axis+1 : 4;
+		joysticks[joystick].axes = calloc(joysticks[joystick].num_axes, sizeof(joyaxis));
+	} else if (joysticks[joystick].num_axes <= axis) {
+		uint32_t old_capacity = joysticks[joystick].num_axes;
+		joysticks[joystick].num_axes *= 2;
+		joysticks[joystick].axes = realloc(joysticks[joystick].axes, sizeof(joyaxis) * joysticks[joystick].num_axes);
+		memset(joysticks[joystick].axes + old_capacity, 0, (joysticks[joystick].num_axes - old_capacity) * sizeof(joyaxis));
+	}
+	if (positive) {
+		do_bind(&joysticks[joystick].axes[axis].positive, bind_type, subtype_a, subtype_b);
+	} else {
+		do_bind(&joysticks[joystick].axes[axis].negative, bind_type, subtype_a, subtype_b);
+	}
+}
+
+void reset_joystick_bindings(int joystick)
+{
+	if (joystick >= MAX_JOYSTICKS) {
+		return;
+	}
+	if (joysticks[joystick].buttons) {
+		for (int i = 0; i < joysticks[joystick].num_buttons; i++)
+		{
+			joysticks[joystick].buttons[i].bind_type = BIND_NONE;
+		}
+	}
+	if (joysticks[joystick].dpads) {
+		for (int i = 0; i < joysticks[joystick].num_dpads; i++)
+		{
+			for (int dir = 0; dir < 4; dir++)
+			{
+				joysticks[joystick].dpads[i].bindings[dir].bind_type = BIND_NONE;
+			}
+		}
+	}
+	if (joysticks[joystick].axes) {
+		for (int i = 0; i < joysticks[joystick].num_axes; i++)
+		{
+			joysticks[joystick].axes[i].positive.bind_type = BIND_NONE;
+			joysticks[joystick].axes[i].negative.bind_type = BIND_NONE;
+		}
+	}
+}
+
+static uint8_t content_binds_enabled = 1;
+void set_content_binding_state(uint8_t enabled)
+{
+	content_binds_enabled = enabled;
+}
+
+void handle_binding_down(keybinding * binding)
+{
+	if (!current_system) {
+		return;
+	}
+	if (binding->bind_type == BIND_GAMEPAD && current_system && current_system->gamepad_down)
+	{
+		current_system->gamepad_down(current_system, binding->subtype_a, binding->subtype_b);
+	}
+	else if (binding->bind_type == BIND_MOUSE && current_system && current_system->mouse_down)
+	{
+		current_system->mouse_down(current_system, binding->subtype_a, binding->subtype_b);
+	}
+}
+
+static uint8_t keyboard_captured;
+void handle_keydown(int keycode, uint8_t scancode)
+{
+	int bucket = keycode >> 15 & 0xFFFF;
+	int idx = keycode & 0x7FFF;
+	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
+	if (binding && (!keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
+		handle_binding_down(binding);
+	} else if (keyboard_captured && current_system && current_system->keyboard_down) {
+		current_system->keyboard_down(current_system, scancode);
+	}
+}
+
+void handle_joydown(int joystick, int button)
+{
+	if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons) {
+		return;
+	}
+	keybinding * binding = joysticks[joystick].buttons + button;
+	handle_binding_down(binding);
+}
+
+static uint8_t mouse_mode = MOUSE_NONE;
+static uint8_t mouse_captured;
+void handle_mousedown(int mouse, int button)
+{
+	if (mouse_mode == MOUSE_CAPTURE && !mouse_captured) {
+		mouse_captured = 1;
+		render_relative_mouse(1);
+		return;
+	}
+	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
+		return;
+	}
+	keybinding * binding = mice[mouse].buttons + button - 1;
+	handle_binding_down(binding);
+}
+
+static int current_speed = 0;
+static int num_speeds = 1;
+static uint32_t * speeds = NULL;
+
+static uint8_t mouse_captured;
+
+#ifdef _WIN32
+#define localtime_r(a,b) localtime(a)
+#endif
+
+void handle_binding_up(keybinding * binding)
+{
+	switch(binding->bind_type)
+	{
+	case BIND_GAMEPAD:
+		if (content_binds_enabled && current_system->gamepad_up) {
+			current_system->gamepad_up(current_system, binding->subtype_a, binding->subtype_b);
+		}
+		break;
+	case BIND_MOUSE:
+		if (content_binds_enabled && current_system->mouse_up) {
+			current_system->mouse_up(current_system, binding->subtype_a, binding->subtype_b);
+		}
+		break;
+	case BIND_UI:
+		switch (binding->subtype_a)
+		{
+		case UI_DEBUG_MODE_INC:
+			if (content_binds_enabled) {
+				current_system->inc_debug_mode(current_system);
+			}
+			break;
+		case UI_ENTER_DEBUGGER:
+			if (content_binds_enabled) {
+				current_system->enter_debugger = 1;
+			}
+			break;
+		case UI_SAVE_STATE:
+			if (content_binds_enabled) {
+				current_system->save_state = QUICK_SAVE_SLOT+1;
+			}
+			break;
+		case UI_NEXT_SPEED:
+			if (content_binds_enabled) {
+				current_speed++;
+				if (current_speed >= num_speeds) {
+					current_speed = 0;
+				}
+				printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
+				current_system->set_speed_percent(current_system, speeds[current_speed]);
+			}
+			break;
+		case UI_PREV_SPEED:
+			if (content_binds_enabled) {
+				current_speed--;
+				if (current_speed < 0) {
+					current_speed = num_speeds - 1;
+				}
+				printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
+				current_system->set_speed_percent(current_system, speeds[current_speed]);
+			}
+			break;
+		case UI_SET_SPEED:
+			if (content_binds_enabled) {
+				if (binding->subtype_b < num_speeds) {
+					current_speed = binding->subtype_b;
+					printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
+					current_system->set_speed_percent(current_system, speeds[current_speed]);
+				} else {
+					printf("Setting speed to %d\n", speeds[current_speed]);
+					current_system->set_speed_percent(current_system, speeds[current_speed]);
+				}
+			}
+			break;
+		case UI_RELEASE_MOUSE:
+			if (mouse_captured) {
+				mouse_captured = 0;
+				render_relative_mouse(0);
+			}
+			break;
+		case UI_TOGGLE_KEYBOARD_CAPTURE:
+			if (content_binds_enabled && current_system->has_keyboard) {
+				keyboard_captured = !keyboard_captured;
+			}
+			break;
+		case UI_TOGGLE_FULLSCREEN:
+			render_toggle_fullscreen();
+			break;
+		case UI_SOFT_RESET:
+			if (content_binds_enabled) {
+				current_system->soft_reset(current_system);
+			}
+			break;
+		case UI_RELOAD:
+			if (content_binds_enabled) {
+				reload_media();
+			}
+			break;
+		case UI_SMS_PAUSE:
+			if (content_binds_enabled && current_system->gamepad_down) {
+				current_system->gamepad_down(current_system, GAMEPAD_MAIN_UNIT, MAIN_UNIT_PAUSE);
+			}
+			break;
+		case UI_SCREENSHOT: {
+			if (content_binds_enabled) {
+				char *screenshot_base = tern_find_path(config, "ui\0screenshot_path\0", TVAL_PTR).ptrval;
+				if (!screenshot_base) {
+					screenshot_base = "$HOME";
+				}
+				tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
+				vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
+				screenshot_base = replace_vars(screenshot_base, vars, 1);
+				tern_free(vars);
+				time_t now = time(NULL);
+				struct tm local_store;
+				char fname_part[256];
+				char *template = tern_find_path(config, "ui\0screenshot_template\0", TVAL_PTR).ptrval;
+				if (!template) {
+					template = "blastem_%c.ppm";
+				}
+				strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store));
+				char const *parts[] = {screenshot_base, PATH_SEP, fname_part};
+				char *path = alloc_concat_m(3, parts);
+				free(screenshot_base);
+				render_save_screenshot(path);
+			}
+			break;
+		}
+		case UI_EXIT:
+#ifndef DISABLE_NUKLEAR
+			if (is_nuklear_active()) {
+				show_pause_menu();
+			} else {
+#endif
+			current_system->request_exit(current_system);
+			if (current_system->type == SYSTEM_GENESIS) {
+				genesis_context *gen = (genesis_context *)current_system;
+				if (gen->extra) {
+					//TODO: More robust mechanism for detecting menu
+					menu_context *menu = gen->extra;
+					menu->external_game_load = 1;
+				}
+			}
+#ifndef DISABLE_NUKLEAR
+			}
+#endif
+			break;
+		case UI_PLANE_DEBUG: 
+		case UI_VRAM_DEBUG: 
+		case UI_CRAM_DEBUG:
+		case UI_COMPOSITE_DEBUG:
+			if (content_binds_enabled) {
+				vdp_context *vdp = NULL;
+				if (current_system->type == SYSTEM_GENESIS) {
+					genesis_context *gen = (genesis_context *)current_system;
+					vdp = gen->vdp;
+				} else if (current_system->type == SYSTEM_SMS) {
+					sms_context *sms = (sms_context *)current_system;
+					vdp = sms->vdp;
+				}
+				if (vdp) {
+					uint8_t debug_type;
+					switch(binding->subtype_a)
+					{
+					case UI_PLANE_DEBUG: debug_type = VDP_DEBUG_PLANE; break;
+					case UI_VRAM_DEBUG: debug_type = VDP_DEBUG_VRAM; break;
+					case UI_CRAM_DEBUG: debug_type = VDP_DEBUG_CRAM; break;
+					case UI_COMPOSITE_DEBUG: debug_type = VDP_DEBUG_COMPOSITE; break;
+					default: return;
+					}
+					vdp_toggle_debug_view(vdp, debug_type);
+				}
+				break;
+			}
+		}
+		break;
+	}
+}
+
+void handle_keyup(int keycode, uint8_t scancode)
+{
+	int bucket = keycode >> 15 & 0xFFFF;
+	int idx = keycode & 0x7FFF;
+	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
+	if (binding && (!keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
+		handle_binding_up(binding);
+	} else if (keyboard_captured && current_system && current_system->keyboard_up) {
+		current_system->keyboard_up(current_system, scancode);
+	}
+}
+
+void handle_joyup(int joystick, int button)
+{
+	if (joystick >= MAX_JOYSTICKS  || button >= joysticks[joystick].num_buttons) {
+		return;
+	}
+	keybinding * binding = joysticks[joystick].buttons + button;
+	handle_binding_up(binding);
+}
+
+void handle_joy_dpad(int joystick, int dpadnum, uint8_t value)
+{
+	if (joystick >= MAX_JOYSTICKS  || dpadnum >= joysticks[joystick].num_dpads) {
+		return;
+	}
+	joydpad * dpad = joysticks[joystick].dpads + dpadnum;
+	uint8_t newdown = (value ^ dpad->state) & value;
+	uint8_t newup = ((~value) ^ (~dpad->state)) & (~value);
+	dpad->state = value;
+	for (int i = 0; i < 4; i++) {
+		if (newdown & dpadbits[i]) {
+			handle_binding_down(dpad->bindings + i);
+		} else if(newup & dpadbits[i]) {
+			handle_binding_up(dpad->bindings + i);
+		}
+	}
+}
+
+#define JOY_AXIS_THRESHOLD 2000
+
+void handle_joy_axis(int joystick, int axis, int16_t value)
+{
+	if (joystick >= MAX_JOYSTICKS  || axis >= joysticks[joystick].num_axes) {
+		return;
+	}
+	joyaxis *jaxis = joysticks[joystick].axes + axis;
+	int old_active = abs(jaxis->value) > JOY_AXIS_THRESHOLD;
+	int new_active = abs(value) > JOY_AXIS_THRESHOLD;
+	int old_pos = jaxis->value > 0;
+	int new_pos = value > 0;
+	jaxis->value = value;
+	if (old_active && (!new_active || old_pos != new_pos)) {
+		//previously activated direction is no longer active
+		handle_binding_up(old_pos ? &jaxis->positive : &jaxis->negative);
+	}
+	if (new_active && (!old_active || old_pos != new_pos)) {
+		//previously unactivated direction is now active
+		handle_binding_down(new_pos ? &jaxis->positive : &jaxis->negative);
+	}
+}
+
+void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay)
+{
+	if (mouse >= MAX_MICE || !current_system) {
+		return;
+	}
+	if (mice[mouse].motion.bind_type == BIND_MOUSE && mice[mouse].motion.subtype_b == PSEUDO_BUTTON_MOTION) {
+		uint8_t target_mouse = mice[mouse].motion.subtype_a;
+		switch(mouse_mode)
+		{
+		case MOUSE_NONE:
+			break;
+		case MOUSE_ABSOLUTE: {
+			if (current_system->mouse_motion_absolute) {
+				float scale_x = (render_emulated_width() * 2.0f) / ((float)render_width());
+				float scale_y = (render_emulated_height() * 2.0f) / ((float)render_height());
+				int32_t adj_x = x * scale_x + 2 * render_overscan_left() - 2 * BORDER_LEFT;
+				int32_t adj_y = y * scale_y + 2 * render_overscan_top() - 4;
+				
+				current_system->mouse_motion_absolute(current_system, target_mouse, adj_x, adj_y);
+			}
+			break;
+		}
+		case MOUSE_RELATIVE: {
+			if (current_system->mouse_motion_relative) {
+				current_system->mouse_motion_relative(current_system, target_mouse, deltax, deltay);
+			}
+			break;
+		}
+		case MOUSE_CAPTURE: {
+			if (mouse_captured && current_system->mouse_motion_relative) {
+				current_system->mouse_motion_relative(current_system, target_mouse, deltax, deltay);
+			}
+			break;
+		}
+		}
+	} else {
+		handle_binding_up(&mice[mouse].motion);
+	}
+}
+
+void handle_mouseup(int mouse, int button)
+{
+	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
+		return;
+	}
+	keybinding * binding = mice[mouse].buttons + button - 1;
+	handle_binding_up(binding);
+}
+
+void bindings_release_capture(void)
+{
+	if (mouse_mode == MOUSE_RELATIVE || (mouse_mode == MOUSE_CAPTURE && mouse_captured)) {
+		render_relative_mouse(0);
+	}
+	keyboard_captured = 0;
+}
+
+void bindings_reacquire_capture(void)
+{
+	if (mouse_mode == MOUSE_RELATIVE || (mouse_mode == MOUSE_CAPTURE && mouse_captured)) {
+		render_relative_mouse(1);
+	}
+}
+
+int parse_binding_target(int device_num, char * target, tern_node * padbuttons, tern_node *mousebuttons, uint8_t * subtype_a, uint8_t * subtype_b)
+{
+	const int gpadslen = strlen("gamepads.");
+	const int mouselen = strlen("mouse.");
+	if (!strncmp(target, "gamepads.", gpadslen)) {
+		int padnum = target[gpadslen] == 'n' ? device_num + 1 : target[gpadslen] - '0';
+		if (padnum >= 1 && padnum <= 8) {
+			int button = tern_find_int(padbuttons, target + gpadslen + 1, 0);
+			if (button) {
+				*subtype_a = padnum;
+				*subtype_b = button;
+				return BIND_GAMEPAD;
+			} else {
+				if (target[gpadslen+1]) {
+					warning("Gamepad mapping string '%s' refers to an invalid button '%s'\n", target, target + gpadslen + 1);
+				} else {
+					warning("Gamepad mapping string '%s' has no button component\n", target);
+				}
+			}
+		} else {
+			warning("Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]);
+		}
+	} else if(!strncmp(target, "mouse.", mouselen)) {
+		int mousenum = target[mouselen] == 'n' ? device_num + 1 : target[mouselen] - '0';
+		if (mousenum >= 1 && mousenum <= 8) {
+			int button = tern_find_int(mousebuttons, target + mouselen + 1, 0);
+			if (button) {
+				*subtype_a = mousenum;
+				*subtype_b = button;
+				return BIND_MOUSE;
+			} else {
+				if (target[mouselen+1]) {
+					warning("Mouse mapping string '%s' refers to an invalid button '%s'\n", target, target + mouselen + 1);
+				} else {
+					warning("Mouse mapping string '%s' has no button component\n", target);
+				}
+			}
+		} else {
+			warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]);
+		}
+	} else if(!strncmp(target, "ui.", strlen("ui."))) {
+		if (!strcmp(target + 3, "vdp_debug_mode")) {
+			*subtype_a = UI_DEBUG_MODE_INC;
+		} else if(!strcmp(target + 3, "vdp_debug_pal")) {
+			//legacy binding, ignore
+			return 0;
+		} else if(!strcmp(target + 3, "enter_debugger")) {
+			*subtype_a = UI_ENTER_DEBUGGER;
+		} else if(!strcmp(target + 3, "save_state")) {
+			*subtype_a = UI_SAVE_STATE;
+		} else if(!strncmp(target + 3, "set_speed.", strlen("set_speed."))) {
+			*subtype_a = UI_SET_SPEED;
+			*subtype_b = atoi(target + 3 + strlen("set_speed."));
+		} else if(!strcmp(target + 3, "next_speed")) {
+			*subtype_a = UI_NEXT_SPEED;
+		} else if(!strcmp(target + 3, "prev_speed")) {
+			*subtype_a = UI_PREV_SPEED;
+		} else if(!strcmp(target + 3, "release_mouse")) {
+			*subtype_a = UI_RELEASE_MOUSE;
+		} else if(!strcmp(target + 3, "toggle_keyboard_captured")) {
+			*subtype_a = UI_TOGGLE_KEYBOARD_CAPTURE;
+		} else if (!strcmp(target + 3, "toggle_fullscreen")) {
+			*subtype_a = UI_TOGGLE_FULLSCREEN;
+		} else if (!strcmp(target + 3, "soft_reset")) {
+			*subtype_a = UI_SOFT_RESET;
+		} else if (!strcmp(target + 3, "reload")) {
+			*subtype_a = UI_RELOAD;
+		} else if (!strcmp(target + 3, "sms_pause")) {
+			*subtype_a = UI_SMS_PAUSE;
+		} else if (!strcmp(target + 3, "screenshot")) {
+			*subtype_a = UI_SCREENSHOT;
+		} else if(!strcmp(target + 3, "exit")) {
+			*subtype_a = UI_EXIT;
+		} else if (!strcmp(target + 3, "plane_debug")) {
+			*subtype_a = UI_PLANE_DEBUG;
+		} else if (!strcmp(target + 3, "vram_debug")) {
+			*subtype_a = UI_VRAM_DEBUG;
+		} else if (!strcmp(target + 3, "cram_debug")) {
+			*subtype_a = UI_CRAM_DEBUG;
+		} else if (!strcmp(target + 3, "compositing_debug")) {
+			*subtype_a = UI_COMPOSITE_DEBUG;
+		} else {
+			warning("Unreconized UI binding type %s\n", target);
+			return 0;
+		}
+		return BIND_UI;
+	} else {
+		warning("Unrecognized binding type %s\n", target);
+	}
+	return 0;
+}
+
+void process_keys(tern_node * cur, tern_node * special, tern_node * padbuttons, tern_node *mousebuttons, char * prefix)
+{
+	char * curstr = NULL;
+	int len;
+	if (!cur) {
+		return;
+	}
+	char onec[2];
+	if (prefix) {
+		len = strlen(prefix);
+		curstr = malloc(len + 2);
+		memcpy(curstr, prefix, len);
+	} else {
+		curstr = onec;
+		len = 0;
+	}
+	curstr[len] = cur->el;
+	curstr[len+1] = 0;
+	if (cur->el) {
+		process_keys(cur->straight.next, special, padbuttons, mousebuttons, curstr);
+	} else {
+		int keycode = tern_find_int(special, curstr, 0);
+		if (!keycode) {
+			keycode = curstr[0];
+			if (curstr[1] != 0) {
+				warning("%s is not recognized as a key identifier, truncating to %c\n", curstr, curstr[0]);
+			}
+		}
+		char * target = cur->straight.value.ptrval;
+		uint8_t subtype_a = 0, subtype_b = 0;
+		int bindtype = parse_binding_target(0, target, padbuttons, mousebuttons, &subtype_a, &subtype_b);
+		bind_key(keycode, bindtype, subtype_a, subtype_b);
+	}
+	process_keys(cur->left, special, padbuttons, mousebuttons, prefix);
+	process_keys(cur->right, special, padbuttons, mousebuttons, prefix);
+	if (curstr && len) {
+		free(curstr);
+	}
+}
+
+void process_speeds(tern_node * cur, char * prefix)
+{
+	char * curstr = NULL;
+	int len;
+	if (!cur) {
+		return;
+	}
+	char onec[2];
+	if (prefix) {
+		len = strlen(prefix);
+		curstr = malloc(len + 2);
+		memcpy(curstr, prefix, len);
+	} else {
+		curstr = onec;
+		len = 0;
+	}
+	curstr[len] = cur->el;
+	curstr[len+1] = 0;
+	if (cur->el) {
+		process_speeds(cur->straight.next, curstr);
+	} else {
+		char *end;
+		long speed_index = strtol(curstr, &end, 10);
+		if (speed_index < 0 || end == curstr || *end) {
+			warning("%s is not a valid speed index", curstr);
+		} else {
+			if (speed_index >= num_speeds) {
+				speeds = realloc(speeds, sizeof(uint32_t) * (speed_index+1));
+				for(; num_speeds < speed_index + 1; num_speeds++) {
+					speeds[num_speeds] = 0;
+				}
+			}
+			speeds[speed_index] = atoi(cur->straight.value.ptrval);
+			if (speeds[speed_index] < 1) {
+				warning("%s is not a valid speed percentage, setting speed %d to 100", cur->straight.value.ptrval, speed_index);
+				speeds[speed_index] = 100;
+			}
+		}
+	}
+	process_speeds(cur->left, prefix);
+	process_speeds(cur->right, prefix);
+	if (curstr && len) {
+		free(curstr);
+	}
+}
+
+typedef struct {
+	tern_node *padbuttons;
+	tern_node *mousebuttons;
+	int       mouseidx;
+} pmb_state;
+
+void process_mouse_button(char *buttonstr, tern_val value, uint8_t valtype, void *data)
+{
+	pmb_state *state = data;
+	int buttonnum = atoi(buttonstr);
+	if (buttonnum < 1 || buttonnum > MAX_MOUSE_BUTTONS) {
+		warning("Mouse button %s is out of the supported range of 1-8\n", buttonstr);
+		return;
+	}
+	if (valtype != TVAL_PTR) {
+		warning("Mouse button %s is not a scalar value!\n", buttonstr);
+		return;
+	}
+	buttonnum--;
+	uint8_t subtype_a = 0, subtype_b = 0;
+	int bindtype = parse_binding_target(state->mouseidx, value.ptrval, state->padbuttons, state->mousebuttons, &subtype_a, &subtype_b);
+	mice[state->mouseidx].buttons[buttonnum].bind_type = bindtype;
+	mice[state->mouseidx].buttons[buttonnum].subtype_a = subtype_a;
+	mice[state->mouseidx].buttons[buttonnum].subtype_b = subtype_b;
+}
+
+void process_mouse(char *mousenum, tern_val value, uint8_t valtype, void *data)
+{
+	tern_node **buttonmaps = data;
+	if (valtype != TVAL_NODE) {
+		warning("Binding for mouse %s is a scalar!\n", mousenum);
+		return;
+	}
+	tern_node *mousedef = value.ptrval;
+	tern_node *padbuttons = buttonmaps[0];
+	tern_node *mousebuttons = buttonmaps[1];
+
+	int mouseidx = atoi(mousenum);
+	if (mouseidx < 0 || mouseidx >= MAX_MICE) {
+		warning("Mouse numbers must be between 0 and %d, but %d is not\n", MAX_MICE, mouseidx);
+		return;
+	}
+	char *motion = tern_find_ptr(mousedef, "motion");
+	if (motion) {
+		uint8_t subtype_a = 0, subtype_b = 0;
+		int bindtype = parse_binding_target(mouseidx, motion, padbuttons, mousebuttons, &subtype_a, &subtype_b);
+		mice[mouseidx].motion.bind_type = bindtype;
+		mice[mouseidx].motion.subtype_a = subtype_a;
+		mice[mouseidx].motion.subtype_b = subtype_b;
+	}
+	tern_node *buttons = tern_find_path(mousedef, "buttons\0\0", TVAL_NODE).ptrval;
+	if (buttons) {
+		pmb_state state = {padbuttons, mousebuttons, mouseidx};
+		tern_foreach(buttons, process_mouse_button, &state);
+	}
+}
+
+typedef struct {
+	int       padnum;
+	tern_node *padbuttons;
+	tern_node *mousebuttons;
+} pad_button_state;
+
+
+static long map_warning_pad = -1;
+void process_pad_button(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	pad_button_state *state = data;
+	int hostpadnum = state->padnum;
+	if (valtype != TVAL_PTR) {
+		warning("Pad button %s has a non-scalar value\n", key);
+		return;
+	}
+	uint8_t subtype_a = 0, subtype_b = 0;
+	int bindtype = parse_binding_target(hostpadnum, val.ptrval, state->padbuttons, state->mousebuttons, &subtype_a, &subtype_b);
+	char *end;
+	long hostbutton = strtol(key, &end, 10);
+	if (*end) {
+		//key is not a valid base 10 integer
+		hostbutton = render_translate_input_name(hostpadnum, key, 0);
+		if (hostbutton < 0) {
+			if (hostbutton == RENDER_INVALID_NAME) {
+				warning("%s is not a valid gamepad input name\n", key);
+			} else if (hostbutton == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) {
+				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
+				map_warning_pad = hostpadnum;
+			}
+			return;
+		}
+		if (hostbutton & RENDER_DPAD_BIT) {
+			bind_dpad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b);
+			return;
+		} else if (hostbutton & RENDER_AXIS_BIT) {
+			bind_axis(hostpadnum, render_axis_part(hostbutton), 1, bindtype, subtype_a, subtype_b);
+			return;
+		}
+	}
+	bind_button(hostpadnum, hostbutton, bindtype, subtype_a, subtype_b);
+}
+
+void process_pad_axis(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	key = strdup(key);
+	pad_button_state *state = data;
+	int hostpadnum = state->padnum;
+	if (valtype != TVAL_PTR) {
+		warning("Mapping for axis %s has a non-scalar value", key);
+		return;
+	}
+	uint8_t subtype_a = 0, subtype_b = 0;
+	int bindtype = parse_binding_target(hostpadnum, val.ptrval, state->padbuttons, state->mousebuttons, &subtype_a, &subtype_b);
+	char *modifier = strchr(key, '.');
+	int positive = 1;
+	if (modifier) {
+		*modifier = 0;
+		modifier++;
+		if (!strcmp("negative", modifier)) {
+			positive = 0;
+		} else if(strcmp("positive", modifier)) {
+			warning("Invalid axis modifier %s for axis %s on pad %d\n", modifier, key, hostpadnum);
+		}
+	}
+	char *end;
+	long axis = strtol(key, &end, 10);
+	if (*end) {
+		//key is not a valid base 10 integer
+		axis = render_translate_input_name(hostpadnum, key, 1);
+		if (axis < 0) {
+			if (axis == RENDER_INVALID_NAME) {
+				warning("%s is not a valid gamepad input name\n", key);
+			} else if (axis == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) {
+				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
+				map_warning_pad = hostpadnum;
+			}
+			goto done;
+		}
+		if (axis & RENDER_DPAD_BIT) {
+			bind_dpad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), bindtype, subtype_a, subtype_b);
+			goto done;
+		} else if (axis & RENDER_AXIS_BIT) {
+			axis = render_axis_part(axis);
+		} else {
+			bind_button(hostpadnum, axis, bindtype, subtype_a, subtype_b);
+			goto done;
+		}
+	}
+	bind_axis(hostpadnum, axis, positive, bindtype, subtype_a, subtype_b);
+done:
+	free(key);
+	return;
+}
+
+static tern_node *get_pad_buttons()
+{
+	static tern_node *padbuttons;
+	if (!padbuttons) {
+		padbuttons = tern_insert_int(NULL, ".up", DPAD_UP);
+		padbuttons = tern_insert_int(padbuttons, ".down", DPAD_DOWN);
+		padbuttons = tern_insert_int(padbuttons, ".left", DPAD_LEFT);
+		padbuttons = tern_insert_int(padbuttons, ".right", DPAD_RIGHT);
+		padbuttons = tern_insert_int(padbuttons, ".a", BUTTON_A);
+		padbuttons = tern_insert_int(padbuttons, ".b", BUTTON_B);
+		padbuttons = tern_insert_int(padbuttons, ".c", BUTTON_C);
+		padbuttons = tern_insert_int(padbuttons, ".x", BUTTON_X);
+		padbuttons = tern_insert_int(padbuttons, ".y", BUTTON_Y);
+		padbuttons = tern_insert_int(padbuttons, ".z", BUTTON_Z);
+		padbuttons = tern_insert_int(padbuttons, ".start", BUTTON_START);
+		padbuttons = tern_insert_int(padbuttons, ".mode", BUTTON_MODE);
+	}
+	return padbuttons;
+}
+
+static tern_node *get_mouse_buttons()
+{
+	static tern_node *mousebuttons;
+	if (!mousebuttons) {
+		mousebuttons = tern_insert_int(NULL, ".left", MOUSE_LEFT);
+		mousebuttons = tern_insert_int(mousebuttons, ".middle", MOUSE_MIDDLE);
+		mousebuttons = tern_insert_int(mousebuttons, ".right", MOUSE_RIGHT);
+		mousebuttons = tern_insert_int(mousebuttons, ".start", MOUSE_START);
+		mousebuttons = tern_insert_int(mousebuttons, ".motion", PSEUDO_BUTTON_MOTION);
+	}
+	return mousebuttons;
+}
+
+tern_node *get_binding_node_for_pad(int padnum)
+{
+	if (padnum > MAX_JOYSTICKS) {
+		return NULL;
+	}
+	tern_node * pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
+	if (!pads) {
+		return NULL;
+	}
+	char numstr[11];
+	sprintf(numstr, "%d", padnum);
+	tern_node * pad = tern_find_node(pads, numstr);
+	if (!pad) {
+		char *type_id = render_joystick_type_id(padnum);
+		pad = tern_find_node(pads, type_id);
+		free(type_id);
+	}
+	if (!pad) {
+		controller_info info = get_controller_info(padnum);
+		char *key = make_controller_type_key(&info);
+		pad = tern_find_node(pads, key);
+		free(key);
+	}
+	if (!pad) {
+		pad = tern_find_node(pads, "default");
+	}
+	return pad;
+}
+
+void handle_joy_added(int joystick)
+{
+	tern_node *pad = get_binding_node_for_pad(joystick);
+	if (!pad) {
+		return;
+	}
+	tern_node * dpad_node = tern_find_node(pad, "dpads");
+	if (dpad_node) {
+		for (int dpad = 0; dpad < 10; dpad++)
+		{
+			char numstr[2] = {dpad + '0', 0};
+			tern_node * pad_dpad = tern_find_node(dpad_node, numstr);
+			char * dirs[] = {"up", "down", "left", "right"};
+			//TODO: Support controllers that have d-pads implemented as analog axes or buttons
+			int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
+			for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) {
+				char * target = tern_find_ptr(pad_dpad, dirs[dir]);
+				if (target) {
+					uint8_t subtype_a = 0, subtype_b = 0;
+					int bindtype = parse_binding_target(joystick, target, get_pad_buttons(), get_mouse_buttons(), &subtype_a, &subtype_b);
+					bind_dpad(joystick, dpad, dirnums[dir], bindtype, subtype_a, subtype_b);
+				}
+			}
+		}
+	}
+	tern_node *button_node = tern_find_node(pad, "buttons");
+	if (button_node) {
+		pad_button_state state = {
+			.padnum = joystick,
+			.padbuttons = get_pad_buttons(),
+			.mousebuttons = get_mouse_buttons()
+		};
+		tern_foreach(button_node, process_pad_button, &state);
+	}
+	tern_node *axes_node = tern_find_node(pad, "axes");
+	if (axes_node) {
+		pad_button_state state = {
+			.padnum = joystick,
+			.padbuttons = get_pad_buttons(),
+			.mousebuttons = get_mouse_buttons()
+		};
+		tern_foreach(axes_node, process_pad_axis, &state);
+	}
+}
+
+//only handles keyboards and mice as gamepads are handled on hotplug events
+void set_bindings(void)
+{
+	tern_node * special = tern_insert_int(NULL, "up", RENDERKEY_UP);
+	special = tern_insert_int(special, "down", RENDERKEY_DOWN);
+	special = tern_insert_int(special, "left", RENDERKEY_LEFT);
+	special = tern_insert_int(special, "right", RENDERKEY_RIGHT);
+	special = tern_insert_int(special, "enter", '\r');
+	special = tern_insert_int(special, "space", ' ');
+	special = tern_insert_int(special, "tab", '\t');
+	special = tern_insert_int(special, "backspace", '\b');
+	special = tern_insert_int(special, "esc", RENDERKEY_ESC);
+	special = tern_insert_int(special, "delete", RENDERKEY_DEL);
+	special = tern_insert_int(special, "lshift", RENDERKEY_LSHIFT);
+	special = tern_insert_int(special, "rshift", RENDERKEY_RSHIFT);
+	special = tern_insert_int(special, "lctrl", RENDERKEY_LCTRL);
+	special = tern_insert_int(special, "rctrl", RENDERKEY_RCTRL);
+	special = tern_insert_int(special, "lalt", RENDERKEY_LALT);
+	special = tern_insert_int(special, "ralt", RENDERKEY_RALT);
+	special = tern_insert_int(special, "home", RENDERKEY_HOME);
+	special = tern_insert_int(special, "end", RENDERKEY_END);
+	special = tern_insert_int(special, "pageup", RENDERKEY_PAGEUP);
+	special = tern_insert_int(special, "pagedown", RENDERKEY_PAGEDOWN);
+	special = tern_insert_int(special, "f1", RENDERKEY_F1);
+	special = tern_insert_int(special, "f2", RENDERKEY_F2);
+	special = tern_insert_int(special, "f3", RENDERKEY_F3);
+	special = tern_insert_int(special, "f4", RENDERKEY_F4);
+	special = tern_insert_int(special, "f5", RENDERKEY_F5);
+	special = tern_insert_int(special, "f6", RENDERKEY_F6);
+	special = tern_insert_int(special, "f7", RENDERKEY_F7);
+	special = tern_insert_int(special, "f8", RENDERKEY_F8);
+	special = tern_insert_int(special, "f9", RENDERKEY_F9);
+	special = tern_insert_int(special, "f10", RENDERKEY_F10);
+	special = tern_insert_int(special, "f11", RENDERKEY_F11);
+	special = tern_insert_int(special, "f12", RENDERKEY_F12);
+	special = tern_insert_int(special, "select", RENDERKEY_SELECT);
+	special = tern_insert_int(special, "play", RENDERKEY_PLAY);
+	special = tern_insert_int(special, "search", RENDERKEY_SEARCH);
+	special = tern_insert_int(special, "back", RENDERKEY_BACK);
+	special = tern_insert_int(special, "np0", RENDERKEY_NP0);
+	special = tern_insert_int(special, "np1", RENDERKEY_NP1);
+	special = tern_insert_int(special, "np2", RENDERKEY_NP2);
+	special = tern_insert_int(special, "np3", RENDERKEY_NP3);
+	special = tern_insert_int(special, "np4", RENDERKEY_NP4);
+	special = tern_insert_int(special, "np5", RENDERKEY_NP5);
+	special = tern_insert_int(special, "np6", RENDERKEY_NP6);
+	special = tern_insert_int(special, "np7", RENDERKEY_NP7);
+	special = tern_insert_int(special, "np8", RENDERKEY_NP8);
+	special = tern_insert_int(special, "np9", RENDERKEY_NP9);
+	special = tern_insert_int(special, "np/", RENDERKEY_NP_DIV);
+	special = tern_insert_int(special, "np*", RENDERKEY_NP_MUL);
+	special = tern_insert_int(special, "np-", RENDERKEY_NP_MIN);
+	special = tern_insert_int(special, "np+", RENDERKEY_NP_PLUS);
+	special = tern_insert_int(special, "npenter", RENDERKEY_NP_ENTER);
+	special = tern_insert_int(special, "np.", RENDERKEY_NP_STOP);
+
+	tern_node *padbuttons = get_pad_buttons();
+
+	tern_node *mousebuttons = get_mouse_buttons();
+	
+	tern_node * keys = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
+	process_keys(keys, special, padbuttons, mousebuttons, NULL);
+	tern_free(special);
+	
+	memset(mice, 0, sizeof(mice));
+	tern_node * mice = tern_find_path(config, "bindings\0mice\0", TVAL_NODE).ptrval;
+	if (mice) {
+		tern_node *buttonmaps[2] = {padbuttons, mousebuttons};
+		tern_foreach(mice, process_mouse, buttonmaps);
+	}
+	tern_node * speed_nodes = tern_find_path(config, "clocks\0speeds\0", TVAL_NODE).ptrval;
+	speeds = malloc(sizeof(uint32_t));
+	speeds[0] = 100;
+	process_speeds(speed_nodes, NULL);
+	for (int i = 0; i < num_speeds; i++)
+	{
+		if (!speeds[i]) {
+			warning("Speed index %d was not set to a valid percentage!", i);
+			speeds[i] = 100;
+		}
+	}
+}
+
+void bindings_set_mouse_mode(uint8_t mode)
+{
+	mouse_mode = mode;
+	if (mode == MOUSE_RELATIVE) {
+		render_relative_mouse(1);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bindings.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,30 @@
+#ifndef BINDINGS_H_
+#define BINDINGS_H_
+#include <stdint.h>
+
+typedef enum {
+	MOUSE_NONE,     //mouse is ignored
+	MOUSE_ABSOLUTE, //really only useful for menu ROM
+	MOUSE_RELATIVE, //for full screen
+	MOUSE_CAPTURE   //for windowed mode
+} mouse_modes;
+
+void set_bindings(void);
+void bindings_set_mouse_mode(uint8_t mode);
+tern_node *get_binding_node_for_pad(int padnum);
+void handle_keydown(int keycode, uint8_t scancode);
+void handle_keyup(int keycode, uint8_t scancode);
+void handle_joydown(int joystick, int button);
+void handle_joyup(int joystick, int button);
+void handle_joy_dpad(int joystick, int dpad, uint8_t state);
+void handle_joy_axis(int joystick, int axis, int16_t value);
+void handle_joy_added(int joystick);
+void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay);
+void handle_mousedown(int mouse, int button);
+void handle_mouseup(int mouse, int button);
+
+void bindings_release_capture(void);
+void bindings_reacquire_capture(void);
+void set_content_binding_state(uint8_t enabled);
+
+#endif //BINDINGS_H_
--- a/blastem.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/blastem.c	Sat Jan 05 00:58:08 2019 -0800
@@ -23,9 +23,14 @@
 #include "terminal.h"
 #include "arena.h"
 #include "config.h"
+#include "bindings.h"
 #include "menu.h"
+#include "zip.h"
+#ifndef DISABLE_NUKLEAR
+#include "nuklear_ui/blastem_nuklear.h"
+#endif
 
-#define BLASTEM_VERSION "0.5.2-pre"
+#define BLASTEM_VERSION "0.6.1"
 
 #ifdef __ANDROID__
 #define FULLSCREEN_DEFAULT 1
@@ -47,37 +52,107 @@
 #define SMD_MAGIC3 0xBB
 #define SMD_BLOCK_SIZE 0x4000
 
-int load_smd_rom(long filesize, FILE * f, void **buffer)
+#ifdef DISABLE_ZLIB
+#define ROMFILE FILE*
+#define romopen fopen
+#define romread fread
+#define romseek fseek
+#define romgetc fgetc
+#define romclose fclose
+#else
+#include "zlib/zlib.h"
+#define ROMFILE gzFile
+#define romopen gzopen
+#define romread gzfread
+#define romseek gzseek
+#define romgetc gzgetc
+#define romclose gzclose
+#endif
+
+int load_smd_rom(ROMFILE f, void **buffer)
 {
 	uint8_t block[SMD_BLOCK_SIZE];
-	filesize -= SMD_HEADER_SIZE;
-	fseek(f, SMD_HEADER_SIZE, SEEK_SET);
+	romseek(f, SMD_HEADER_SIZE, SEEK_SET);
+
+	size_t filesize = 512 * 1024;
+	size_t readsize = 0;
+	uint16_t *dst = malloc(filesize);
+	
 
-	uint16_t *dst = *buffer = malloc(nearest_pow2(filesize));
-	int rom_size = filesize;
-	while (filesize > 0) {
-		fread(block, 1, SMD_BLOCK_SIZE, f);
-		for (uint8_t *low = block, *high = (block+SMD_BLOCK_SIZE/2), *end = block+SMD_BLOCK_SIZE; high < end; high++, low++) {
-			*(dst++) = *low << 8 | *high;
+	size_t read;
+	do {
+		if ((readsize + SMD_BLOCK_SIZE > filesize)) {
+			filesize *= 2;
+			dst = realloc(dst, filesize);
 		}
-		filesize -= SMD_BLOCK_SIZE;
-	}
-	return rom_size;
+		read = romread(block, 1, SMD_BLOCK_SIZE, f);
+		if (read > 0) {
+			for (uint8_t *low = block, *high = (block+read/2), *end = block+read; high < end; high++, low++) {
+				*(dst++) = *low << 8 | *high;
+			}
+			readsize += read;
+		}
+	} while(read > 0);
+	romclose(f);
+	
+	*buffer = dst;
+	
+	return readsize;
 }
 
-uint32_t load_media(char * filename, system_media *dst, system_type *stype)
+uint32_t load_media_zip(const char *filename, system_media *dst)
+{
+	static const char *valid_exts[] = {"bin", "md", "gen", "sms", "rom"};
+	const uint32_t num_exts = sizeof(valid_exts)/sizeof(*valid_exts);
+	zip_file *z = zip_open(filename);
+	if (!z) {
+		return 0;
+	}
+	
+	for (uint32_t i = 0; i < z->num_entries; i++)
+	{
+		char *ext = path_extension(z->entries[i].name);
+		if (!ext) {
+			continue;
+		}
+		for (uint32_t j = 0; j < num_exts; j++)
+		{
+			if (!strcasecmp(ext, valid_exts[j])) {
+				size_t out_size = nearest_pow2(z->entries[i].size);
+				dst->buffer = zip_read(z, i, &out_size);
+				if (dst->buffer) {
+					dst->extension = ext;
+					dst->dir = path_dirname(filename);
+					dst->name = basename_no_extension(filename);
+					dst->size = out_size;
+					zip_close(z);
+					return out_size;
+				}
+			}
+		}
+		free(ext);
+	}
+	zip_close(z);
+	return 0;
+}
+
+uint32_t load_media(const char * filename, system_media *dst, system_type *stype)
 {
 	uint8_t header[10];
-	FILE * f = fopen(filename, "rb");
+	char *ext = path_extension(filename);
+	if (ext && !strcasecmp(ext, "zip")) {
+		free(ext);
+		return load_media_zip(filename, dst);
+	}
+	free(ext);
+	ROMFILE f = romopen(filename, "rb");
 	if (!f) {
 		return 0;
 	}
-	if (sizeof(header) != fread(header, 1, sizeof(header), f)) {
+	if (sizeof(header) != romread(header, 1, sizeof(header), f)) {
 		fatal_error("Error reading from %s\n", filename);
 	}
-	fseek(f, 0, SEEK_END);
-	long filesize = ftell(f);
-	fseek(f, 0, SEEK_SET);
+	
 	uint32_t ret = 0;
 	if (header[1] == SMD_MAGIC1 && header[8] == SMD_MAGIC2 && header[9] == SMD_MAGIC3) {
 		int i;
@@ -93,21 +168,43 @@
 			if (stype) {
 				*stype = SYSTEM_GENESIS;
 			}
-			ret = load_smd_rom(filesize, f, &dst->buffer);
+			ret = load_smd_rom(f, &dst->buffer);
 		}
 	}
+	
 	if (!ret) {
-		dst->buffer = malloc(nearest_pow2(filesize));
-		if (filesize != fread(dst->buffer, 1, filesize, f)) {
-			fatal_error("Error reading from %s\n", filename);
-		}
-		ret = filesize;
+		size_t filesize = 512 * 1024;
+		size_t readsize = sizeof(header);
+		
+		char *buf = malloc(filesize);
+		memcpy(buf, header, readsize);
+	
+		size_t read;
+		do {
+			read = romread(buf + readsize, 1, filesize - readsize, f);
+			if (read > 0) {
+				readsize += read;
+				if (readsize == filesize) {
+					int one_more = romgetc(f);
+					if (one_more >= 0) {
+						filesize *= 2;
+						buf = realloc(buf, filesize);
+						buf[readsize++] = one_more;
+					} else {
+						read = 0;
+					}
+				}
+			}
+		} while (read > 0);
+		dst->buffer = buf;
+		ret = (uint32_t)readsize;
 	}
 	dst->dir = path_dirname(filename);
 	dst->name = basename_no_extension(filename);
 	dst->extension = path_extension(filename);
 	dst->size = ret;
-	fclose(f);
+	
+	romclose(f);
 	return ret;
 }
 
@@ -161,9 +258,10 @@
 	return save_dir;
 }
 
-void setup_saves(system_media *media, rom_info *info, system_header *context)
+void setup_saves(system_media *media, system_header *context)
 {
 	static uint8_t persist_save_registered;
+	rom_info *info = &context->info;
 	char *save_dir = get_save_dir(info->is_save_lock_on ? media->chain : media);
 	char const *parts[] = {save_dir, PATH_SEP, info->save_type == SAVE_I2C ? "save.eeprom" : info->save_type == SAVE_NOR ? "save.nor" : "save.sram"};
 	free(save_filename);
@@ -172,7 +270,7 @@
 		//initial save dir was calculated based on lock-on cartridge because that's where the save device is
 		//save directory used for save states should still be located in the normal place
 		free(save_dir);
-		save_dir = get_save_dir(media);
+		parts[0] = save_dir = get_save_dir(media);
 	}
 	if (use_native_states || context->type != SYSTEM_GENESIS) {
 		parts[2] = "quicksave.state";
@@ -191,29 +289,45 @@
 	}
 }
 
+void apply_updated_config(void)
+{
+	render_config_updated();
+	if (current_system && current_system->config_updated) {
+		current_system->config_updated(current_system);
+	}
+}
+
 static void on_drag_drop(const char *filename)
 {
-	if (current_system->next_rom) {
-		free(current_system->next_rom);
-	}
-	current_system->next_rom = strdup(filename);
-	current_system->request_exit(current_system);
-	if (menu_system && menu_system->type == SYSTEM_GENESIS) {
-		genesis_context *gen = (genesis_context *)menu_system;
-		if (gen->extra) {
-			menu_context *menu = gen->extra;
-			menu->external_game_load = 1;
-		} else {
-			puts("No extra");
+	if (current_system) {
+		if (current_system->next_rom) {
+			free(current_system->next_rom);
+		}
+		current_system->next_rom = strdup(filename);
+		current_system->request_exit(current_system);
+		if (menu_system && menu_system->type == SYSTEM_GENESIS) {
+			genesis_context *gen = (genesis_context *)menu_system;
+			if (gen->extra) {
+				menu_context *menu = gen->extra;
+				menu->external_game_load = 1;
+			}
 		}
 	} else {
-		puts("no menu");
+		init_system_with_media(filename, SYSTEM_UNKNOWN);
 	}
+#ifndef DISABLE_NUKLEAR
+	if (is_nuklear_active()) {
+		show_play_view();
+	}
+#endif
 }
 
 static system_media cart, lock_on;
 void reload_media(void)
 {
+	if (!current_system) {
+		return;
+	}
 	if (current_system->next_rom) {
 		free(current_system->next_rom);
 	}
@@ -239,6 +353,52 @@
 	load_media(lock_on_path, &lock_on, NULL);
 }
 
+static uint32_t opts = 0;
+static uint8_t force_region = 0;
+void init_system_with_media(const char *path, system_type force_stype)
+{
+	if (game_system) {
+		game_system->persist_save(game_system);
+		//swap to game context arena and mark all allocated pages in it free
+		if (current_system == menu_system) {
+			current_system->arena = set_current_arena(game_system->arena);
+		}
+		mark_all_free();
+		game_system->free_context(game_system);
+	} else if(current_system) {
+		//start a new arena and save old one in suspended system context
+		current_system->arena = start_new_arena();
+	}
+	free(cart.dir);
+	free(cart.name);
+	free(cart.extension);
+	system_type stype = SYSTEM_UNKNOWN;
+	if (!(cart.size = load_media(path, &cart, &stype))) {
+		fatal_error("Failed to open %s for reading\n", path);
+	}
+	
+	if (force_stype != SYSTEM_UNKNOWN) {
+		stype = force_stype;
+	}
+	if (stype == SYSTEM_UNKNOWN) {
+		stype = detect_system_type(&cart);
+	}
+	if (stype == SYSTEM_UNKNOWN) {
+		fatal_error("Failed to detect system type for %s\n", path);
+	}
+	//allocate new system context
+	game_system = alloc_config_system(stype, &cart, opts, force_region);
+	if (!game_system) {
+		fatal_error("Failed to configure emulated machine for %s\n", path);
+	}
+	if (menu_system) {
+		menu_system->next_context = game_system;
+	}
+	game_system->next_context = menu_system;
+	setup_saves(&cart, game_system);
+	update_title(game_system->info.name);
+}
+
 int main(int argc, char ** argv)
 {
 	set_exe_str(argv[0]);
@@ -246,10 +406,8 @@
 	int width = -1;
 	int height = -1;
 	int debug = 0;
-	uint32_t opts = 0;
 	int loaded = 0;
 	system_type stype = SYSTEM_UNKNOWN, force_stype = SYSTEM_UNKNOWN;
-	uint8_t force_region = 0;
 	char * romfname = NULL;
 	char * statefile = NULL;
 	debugger_type dtype = DEBUGGER_NATIVE;
@@ -380,35 +538,6 @@
 			height = atoi(argv[i]);
 		}
 	}
-	uint8_t menu = !loaded;
-	if (!loaded) {
-		//load menu
-		romfname = tern_find_path(config, "ui\0rom\0", TVAL_PTR).ptrval;
-		if (!romfname) {
-			romfname = "menu.bin";
-		}
-		if (is_absolute_path(romfname)) {
-			if (!load_media(romfname, &cart, &stype)) {
-				fatal_error("Failed to open UI ROM %s for reading", romfname);
-			}
-		} else {
-			cart.buffer = (uint16_t *)read_bundled_file(romfname, &cart.size);
-			if (!cart.buffer) {
-				fatal_error("Failed to open UI ROM %s for reading", romfname);
-			}
-			uint32_t rom_size = nearest_pow2(cart.size);
-			if (rom_size > cart.size) {
-				cart.buffer = realloc(cart.buffer, rom_size);
-				cart.size = rom_size;
-			}
-			cart.dir = path_dirname(romfname);
-			cart.name = basename_no_extension(romfname);
-			cart.extension = path_extension(romfname);
-		}
-		//force system detection, value on command line is only for games not the menu
-		stype = detect_system_type(&cart);
-		loaded = 1;
-	}
 	
 	int def_width = 0, def_height = 0;
 	char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval;
@@ -436,17 +565,40 @@
 		render_init(width, height, "BlastEm", fullscreen);
 		render_set_drag_drop_handler(on_drag_drop);
 	}
-
-	if (stype == SYSTEM_UNKNOWN) {
+	set_bindings();
+	
+	uint8_t menu = !loaded;
+	uint8_t use_nuklear = 0;
+#ifndef DISABLE_NUKLEAR
+	use_nuklear = !headless && is_nuklear_available();
+#endif
+	if (!loaded && !use_nuklear) {
+		//load menu
+		romfname = tern_find_path(config, "ui\0rom\0", TVAL_PTR).ptrval;
+		if (!romfname) {
+			romfname = "menu.bin";
+		}
+		if (is_absolute_path(romfname)) {
+			if (!(cart.size = load_media(romfname, &cart, &stype))) {
+				fatal_error("Failed to open UI ROM %s for reading", romfname);
+			}
+		} else {
+			cart.buffer = (uint16_t *)read_bundled_file(romfname, &cart.size);
+			if (!cart.buffer) {
+				fatal_error("Failed to open UI ROM %s for reading", romfname);
+			}
+			uint32_t rom_size = nearest_pow2(cart.size);
+			if (rom_size > cart.size) {
+				cart.buffer = realloc(cart.buffer, rom_size);
+				cart.size = rom_size;
+			}
+			cart.dir = path_dirname(romfname);
+			cart.name = basename_no_extension(romfname);
+			cart.extension = path_extension(romfname);
+		}
+		//force system detection, value on command line is only for games not the menu
 		stype = detect_system_type(&cart);
-	}
-	if (stype == SYSTEM_UNKNOWN) {
-		fatal_error("Failed to detect system type for %s\n", romfname);
-	}
-	rom_info info;
-	current_system = alloc_config_system(stype, &cart, menu ? 0 : opts, force_region, &info);
-	if (!current_system) {
-		fatal_error("Failed to configure emulated machine for %s\n", romfname);
+		loaded = 1;
 	}
 	char *state_format = tern_find_path(config, "ui\0state_format\0", TVAL_PTR).ptrval;
 	if (state_format && !strcmp(state_format, "gst")) {
@@ -454,14 +606,36 @@
 	} else if (state_format && strcmp(state_format, "native")) {
 		warning("%s is not a valid value for the ui.state_format setting. Valid values are gst and native\n", state_format);
 	}
-	setup_saves(&cart, &info, current_system);
-	update_title(info.name);
-	if (menu) {
-		menu_system = current_system;
-	} else {
-		game_system = current_system;
+
+	if (loaded) {
+		if (stype == SYSTEM_UNKNOWN) {
+			stype = detect_system_type(&cart);
+		}
+		if (stype == SYSTEM_UNKNOWN) {
+			fatal_error("Failed to detect system type for %s\n", romfname);
+		}
+		current_system = alloc_config_system(stype, &cart, menu ? 0 : opts, force_region);
+		if (!current_system) {
+			fatal_error("Failed to configure emulated machine for %s\n", romfname);
+		}
+	
+		setup_saves(&cart, current_system);
+		update_title(current_system->info.name);
+		if (menu) {
+			menu_system = current_system;
+		} else {
+			game_system = current_system;
+		}
 	}
-
+	
+#ifndef DISABLE_NUKLEAR
+	if (use_nuklear) {
+		blastem_nuklear_init(!menu);
+		current_system = game_system;
+		menu = 0;
+	}
+#endif
+	
 	current_system->debugger_type = dtype;
 	current_system->enter_debugger = start_in_debugger && menu == debug_target;
 	current_system->start_context(current_system,  menu ? NULL : statefile);
@@ -473,42 +647,7 @@
 		if (current_system->next_rom) {
 			char *next_rom = current_system->next_rom;
 			current_system->next_rom = NULL;
-			if (game_system) {
-				game_system->persist_save(game_system);
-				//swap to game context arena and mark all allocated pages in it free
-				if (menu) {
-					current_system->arena = set_current_arena(game_system->arena);
-				}
-				mark_all_free();
-				game_system->free_context(game_system);
-			} else {
-				//start a new arena and save old one in suspended genesis context
-				current_system->arena = start_new_arena();
-			}
-			free(cart.dir);
-			free(cart.name);
-			free(cart.extension);
-			if (!load_media(next_rom, &cart, &stype)) {
-				fatal_error("Failed to open %s for reading\n", next_rom);
-			}
-			stype = force_stype;
-			if (stype == SYSTEM_UNKNOWN) {
-				stype = detect_system_type(&cart);
-			}
-			if (stype == SYSTEM_UNKNOWN) {
-				fatal_error("Failed to detect system type for %s\n", next_rom);
-			}
-			//allocate new system context
-			game_system = alloc_config_system(stype, &cart, opts,force_region, &info);
-			if (!game_system) {
-				fatal_error("Failed to configure emulated machine for %s\n", next_rom);
-			}
-			if (menu_system) {
-				menu_system->next_context = game_system;
-			}
-			game_system->next_context = menu_system;
-			setup_saves(&cart, &info, game_system);
-			update_title(info.name);
+			init_system_with_media(next_rom, force_stype);
 			free(next_rom);
 			menu = 0;
 			current_system = game_system;
@@ -520,11 +659,19 @@
 			current_system = game_system;
 			menu = 0;
 			current_system->resume_context(current_system);
-		} else if (!menu && menu_system) {
-			current_system->arena = set_current_arena(menu_system->arena);
-			current_system = menu_system;
-			menu = 1;
-			current_system->resume_context(current_system);
+		} else if (!menu && (menu_system || use_nuklear)) {
+			if (use_nuklear) {
+#ifndef DISABLE_NUKLEAR
+				ui_idle_loop();
+#endif
+			} else {
+				current_system->arena = set_current_arena(menu_system->arena);
+				current_system = menu_system;
+				menu = 1;
+			}
+			if (!current_system->next_rom) {
+				current_system->resume_context(current_system);
+			}
 		} else {
 			break;
 		}
--- a/blastem.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/blastem.h	Sat Jan 05 00:58:08 2019 -0800
@@ -15,8 +15,9 @@
 extern char *save_state_path;
 extern char *save_filename;
 extern uint8_t use_native_states;
-#define QUICK_SAVE_SLOT 10
 void reload_media(void);
 void lockon_media(char *lock_on_path);
+void init_system_with_media(const char *path, system_type force_stype);
+void apply_updated_config(void);
 
 #endif //BLASTEM_H_
--- a/build_release	Tue Dec 19 00:49:13 2017 -0800
+++ b/build_release	Sat Jan 05 00:58:08 2019 -0800
@@ -54,7 +54,7 @@
 echo $dir
 rm -rf "$dir"
 mkdir "$dir"
-cp -r $binaries shaders default.cfg rom.db gamecontrollerdb.txt "$dir"
+cp -r $binaries shaders images default.cfg rom.db gamecontrollerdb.txt "$dir"
 for file in README COPYING CHANGELOG; do
 	cp "$file" "$dir"/"$file$txt"
 done
--- a/config.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/config.c	Sat Jan 05 00:58:08 2019 -0800
@@ -5,6 +5,7 @@
 */
 #include "tern.h"
 #include "util.h"
+#include "paths.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -36,7 +37,7 @@
 }
 #endif
 
-tern_node * parse_config_int(char **state, int started, int *line)
+static tern_node * parse_config_int(char **state, int started, int *line)
 {
 	char *config_data, *curline;
 	tern_node * head = NULL;
@@ -88,6 +89,80 @@
 	return parse_config_int(&config_data, 0, &line);
 }
 
+typedef struct {
+	char     *buf;
+	uint32_t capacity;
+	uint32_t size;
+	uint32_t indent;
+} serialize_state;
+
+static void ensure_buf_capacity(uint32_t ensure, serialize_state *state)
+{
+	if (ensure + state->size > state->capacity) {
+		state->capacity = state->capacity * 2;
+		state->buf = realloc(state->buf, state->capacity);
+	}
+}
+
+static void indent(serialize_state *state)
+{
+	memset(state->buf + state->size, '\t', state->indent);
+	state->size += state->indent;
+}
+
+static void serialize_config_int(tern_node *config, serialize_state *state);
+
+static void serialize_iter(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	serialize_state *state = data;
+	uint32_t keylen = strlen(key);
+	uint32_t vallen = 0;
+	if (valtype == TVAL_PTR) {
+		vallen = strlen(val.ptrval);
+	}
+	ensure_buf_capacity(state->indent + keylen + 2 + vallen, state);
+	state->buf[state->size++] = '\n';
+	indent(state);
+	memcpy(state->buf + state->size, key, keylen);
+	state->size += keylen;
+	state->buf[state->size++] = ' ';
+	if (valtype == TVAL_PTR) {
+		memcpy(state->buf + state->size, val.ptrval, vallen);
+		state->size += vallen;
+	} else {
+		serialize_config_int(val.ptrval, state);
+	}
+}
+
+static void serialize_config_int(tern_node *config, serialize_state *state)
+{
+	ensure_buf_capacity(1, state);
+	state->buf[state->size++] = '{';
+	state->indent++;
+	
+	tern_foreach(config, serialize_iter, state);
+
+	--state->indent;
+	ensure_buf_capacity(2 + state->indent, state);
+	state->buf[state->size++] = '\n';
+	indent(state);
+	state->buf[state->size++] = '}';
+}
+
+char *serialize_config(tern_node *config, uint32_t *size_out)
+{
+	serialize_state state = {
+		.size = 0,
+		.capacity = 1024,
+		.indent = 0
+	};
+	state.buf = malloc(state.capacity);
+	tern_foreach(config, serialize_iter, &state);
+	//serialize_config_int(config, &state);
+	*size_out = state.size;
+	return state.buf;
+}
+
 tern_node *parse_config_file(char *config_path)
 {
 	tern_node * ret = NULL;
@@ -114,6 +189,20 @@
 	return ret;
 }
 
+uint8_t serialize_config_file(tern_node *config, char *path)
+{
+	FILE *f = fopen(path, "w");
+	if (!f) {
+		return 0;
+	}
+	uint32_t buf_size;
+	char *buffer = serialize_config(config, &buf_size);
+	uint8_t ret = buf_size == fwrite(buffer, 1, buf_size, f);
+	free(buffer);
+	fclose(f);
+	return ret;
+}
+
 tern_node *parse_bundled_config(char *config_name)
 {
 	uint32_t confsize;
@@ -127,13 +216,35 @@
 	return ret;
 }
 
-tern_node *load_config()
+tern_node *load_overrideable_config(char *name, char *bundled_name)
 {
 	char const *confdir = get_config_dir();
 	char *confpath = NULL;
 	tern_node *ret;
 	if (confdir) {
-		confpath = alloc_concat(confdir, "/blastem.cfg");
+		confpath = path_append(confdir, name);
+		ret = parse_config_file(confpath);
+		if (ret) {
+			free(confpath);
+			return ret;
+		}
+	}
+
+	ret = parse_bundled_config(bundled_name);
+	if (ret) {
+		free(confpath);
+		return ret;
+	}
+	return NULL;
+}
+
+tern_node *load_config()
+{
+	char const *confdir = get_config_dir();
+	char *confpath = NULL;
+	tern_node *ret = load_overrideable_config("blastem.cfg", "default.cfg");
+	if (confdir) {
+		confpath = path_append(confdir, "blastem.cfg");
 		ret = parse_config_file(confpath);
 		if (ret) {
 			free(confpath);
@@ -147,11 +258,56 @@
 		return ret;
 	}
 
-	if (confpath) {
-		fatal_error("Failed to find a config file at %s or in the blastem executable directory\n", confpath);
+	if (get_config_dir()) {
+		fatal_error("Failed to find a config file at %s or in the blastem executable directory\n", get_config_dir());
 	} else {
 		fatal_error("Failed to find a config file in the BlastEm executable directory and the config directory path could not be determined\n");
 	}
 	//this will never get reached, but the compiler doesn't know that. Let's make it happy
 	return NULL;
 }
+
+void persist_config_at(tern_node *config, char *fname)
+{
+	char const *confdir = get_config_dir();
+	if (!confdir) {
+		fatal_error("Failed to locate config file directory\n");
+	}
+	ensure_dir_exists(confdir);
+	char *confpath = path_append(confdir, fname);
+	if (!serialize_config_file(config, confpath)) {
+		fatal_error("Failed to write config to %s\n", confpath);
+	}
+	free(confpath);
+}
+
+void persist_config(tern_node *config)
+{
+	persist_config_at(config, "blastem.cfg");
+}
+
+char **get_extension_list(tern_node *config, uint32_t *num_exts_out)
+{
+	char *ext_filter = strdup(tern_find_path_default(config, "ui\0extensions\0", (tern_val){.ptrval = "bin gen md smd sms gg"}, TVAL_PTR).ptrval);
+	uint32_t num_exts = 0, ext_storage = 5;
+	char **ext_list = malloc(sizeof(char *) * ext_storage);
+	char *cur_filter = ext_filter;
+	while (*cur_filter)
+	{
+		if (num_exts == ext_storage) {
+			ext_storage *= 2;
+			ext_list = realloc(ext_list, sizeof(char *) * ext_storage);
+		}
+		ext_list[num_exts++] = cur_filter;
+		cur_filter = split_keyval(cur_filter);
+	}
+	*num_exts_out = num_exts;
+	return ext_list;
+}
+
+#define DEFAULT_LOWPASS_CUTOFF 3390
+uint32_t get_lowpass_cutoff(tern_node *config)
+{
+	char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval;
+	return lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : DEFAULT_LOWPASS_CUTOFF;
+}
--- a/config.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/config.h	Sat Jan 05 00:58:08 2019 -0800
@@ -9,7 +9,14 @@
 
 tern_node *parse_config_file(char *config_path);
 tern_node *parse_bundled_config(char *config_name);
+tern_node *load_overrideable_config(char *name, char *bundled_name);
 tern_node *load_config();
+char *serialize_config(tern_node *config, uint32_t *size_out);
+uint8_t serialize_config_file(tern_node *config, char *path);
+void persist_config_at(tern_node *config, char *fname);
+void persist_config(tern_node *config);
+char **get_extension_list(tern_node *config, uint32_t *num_exts_out);
+uint32_t get_lowpass_cutoff(tern_node *config);
 
 #endif //CONFIG_H_
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controller_info.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,334 @@
+#include <string.h>
+#include "render_sdl.h"
+#include "controller_info.h"
+#include "config.h"
+#include "util.h"
+
+typedef struct {
+	char const      *name;
+	controller_info info;
+} heuristic;
+
+static heuristic heuristics[] = {
+	//TODO: Add more heuristic rules
+	{"DualShock 4", {.type = TYPE_PSX, .subtype = SUBTYPE_PS4}},
+	{"PS4", {.type = TYPE_PSX, .subtype = SUBTYPE_PS4}},
+	{"PS3", {.type = TYPE_PSX, .subtype = SUBTYPE_PS3}},
+	{"X360", {.type = TYPE_XBOX, .subtype = SUBTYPE_X360}},
+	{"Xbox 360", {.type = TYPE_XBOX, .subtype = SUBTYPE_X360}},
+	{"X-box 360", {.type = TYPE_XBOX, .subtype = SUBTYPE_X360}},
+	{"Xbox One", {.type = TYPE_XBOX, .subtype = SUBTYPE_XBONE}},
+	{"X-box One", {.type = TYPE_XBOX, .subtype = SUBTYPE_XBONE}},
+	{"WiiU", {.type = TYPE_NINTENDO, .subtype = SUBTYPE_WIIU}},
+	{"Wii U", {.type = TYPE_NINTENDO, .subtype = SUBTYPE_WIIU}},
+	{"Nintendo Switch", {.type = TYPE_NINTENDO, .subtype = SUBTYPE_SWITCH}},
+	{"Saturn", {.type = TYPE_SEGA, .subtype = SUBTYPE_SATURN}}
+};
+const uint32_t num_heuristics = sizeof(heuristics)/sizeof(*heuristics);
+
+static tern_node *info_config;
+static uint8_t loaded;
+static const char *subtype_names[] = {
+	"unknown",
+	"xbox",
+	"xbox 360",
+	"xbone",
+	"ps2",
+	"ps3",
+	"ps4",
+	"wiiu",
+	"switch",
+	"genesis",
+	"saturn"
+};
+static const char *subtype_human_names[] = {
+	"unknown",
+	"Xbos",
+	"Xbox 360",
+	"Xbox One",
+	"PS2",
+	"PS3",
+	"PS4",
+	"Wii-U",
+	"Switch",
+	"Genesis",
+	"Saturn"
+};
+static const char *variant_names[] = {
+	"normal",
+	"6b bumpers",
+	"6b right"
+};
+
+static void load_ctype_config(void)
+{
+	if (!loaded) {
+		info_config = load_overrideable_config("controller_types.cfg", "controller_types.cfg");
+		loaded = 1;
+	}
+}
+
+controller_info get_controller_info(int joystick)
+{
+	load_ctype_config();
+	char guid_string[33];
+	SDL_Joystick *stick = render_get_joystick(joystick);
+	SDL_GameController *control = render_get_controller(joystick);
+	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(stick), guid_string, sizeof(guid_string));
+	tern_node *info = tern_find_node(info_config, guid_string);
+	if (info) {
+		controller_info res;
+		char *subtype = tern_find_ptr(info, "subtype");
+		res.subtype = SUBTYPE_UNKNOWN;
+		if (subtype) {
+			for (int i = 0; i < SUBTYPE_NUM; i++)
+			{
+				if (!strcmp(subtype_names[i], subtype)) {
+					res.subtype = i;
+					break;
+				}
+			}
+		}
+		switch (res.subtype)
+		{
+		case SUBTYPE_XBOX:
+		case SUBTYPE_X360:
+		case SUBTYPE_XBONE:
+			res.type = TYPE_XBOX;
+			break;
+		case SUBTYPE_PS2:
+		case SUBTYPE_PS3:
+		case SUBTYPE_PS4:
+			res.type = TYPE_PSX;
+			break;
+		case SUBTYPE_WIIU:
+		case SUBTYPE_SWITCH:
+			res.type = TYPE_NINTENDO;
+			break;
+		case SUBTYPE_GENESIS:
+		case SUBTYPE_SATURN:
+			res.type = TYPE_SEGA;
+			break;
+		default:
+			res.type = TYPE_UNKNOWN;
+			break;
+		}
+		char *variant = tern_find_ptr(info, "variant");
+		res.variant = VARIANT_NORMAL;
+		if (variant) {
+			for (int i = 0; i < VARIANT_NUM; i++)
+			{
+				if (!strcmp(variant_names[i], variant)) {
+					res.variant = i;
+					break;
+				}
+			}
+		}
+		res.name = control ? SDL_GameControllerName(control) : SDL_JoystickName(stick);
+		if (control) {
+			SDL_GameControllerClose(control);
+		}
+		return res;
+	}
+	if (!control) {
+		return (controller_info) {
+			.type = TYPE_UNKNOWN,
+			.subtype = SUBTYPE_UNKNOWN,
+			.variant = VARIANT_NORMAL,
+			.name = SDL_JoystickName(stick)
+		};
+	}
+	const char *name = SDL_GameControllerName(control);
+	SDL_GameControllerClose(control);
+	for (uint32_t i = 0; i < num_heuristics; i++)
+	{
+		if (strstr(name, heuristics[i].name)) {
+			controller_info res = heuristics[i].info;
+			res.name = name;
+			return res;
+		}
+	}
+	//default to a 360
+	return (controller_info){
+		.type = TYPE_GENERIC_MAPPING,
+		.subtype = SUBTYPE_UNKNOWN,
+		.variant = VARIANT_NORMAL,
+		.name = name
+	};
+}
+
+static void mappings_iter(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	if (valtype != TVAL_NODE) {
+		return;
+	}
+	char *mapping = tern_find_ptr(val.ptrval, "mapping");
+	if (mapping) {
+		const char *parts[] = {key, ",", mapping};
+		char * full = alloc_concat_m(3, parts);
+		SDL_GameControllerAddMapping(full);
+		free(full);
+	}
+}
+
+void controller_add_mappings(void)
+{
+	load_ctype_config();
+	if (info_config) {
+		tern_foreach(info_config, mappings_iter, NULL);
+	}
+}
+
+void save_controller_info(int joystick, controller_info *info)
+{
+	char guid_string[33];
+	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string));
+	tern_node *existing = tern_find_node(info_config, guid_string);
+	existing = tern_insert_ptr(existing, "subtype", (void *)subtype_names[info->subtype]);
+	existing = tern_insert_ptr(existing, "variant",  (void *)variant_names[info->variant]);
+	info_config = tern_insert_node(info_config, guid_string, existing);
+	persist_config_at(info_config, "controller_types.cfg");
+	
+}
+
+void save_controller_mapping(int joystick, char *mapping_string)
+{
+	char guid_string[33];
+	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(render_get_joystick(joystick)), guid_string, sizeof(guid_string));
+	tern_node *existing = tern_find_node(info_config, guid_string);
+	existing = tern_insert_ptr(existing, "mapping", mapping_string);
+	info_config = tern_insert_node(info_config, guid_string, existing);
+	persist_config_at(info_config, "controller_types.cfg");
+}
+
+char const *labels_xbox[] = {
+	"A", "B", "X", "Y", "Back", NULL, "Start", "Click", "Click", "White", "Black", "LT", "RT"
+};
+char const *labels_360[] = {
+	"A", "B", "X", "Y", "Back", "Xbox", "Start", "Click", "Click", "LB", "RB", "LT", "RT"
+};
+static char const *labels_xbone[] = {
+	"A", "B", "X", "Y", "View", "Xbox", "Menu", "Click", "Click", "LB", "RB", "LT", "RT"
+};
+static char const *labels_ps3[] = {
+	"cross", "circle", "square", "triangle", "Select", "PS", "Start", "L3", "R3", "L1", "R1", "L2", "R2"
+};
+static char const *labels_ps4[] = {
+	"cross", "circle", "square", "triangle", "Share", "PS", "Options", "L3", "R3", "L1", "R1", "L2", "R2"
+};
+static char const *labels_nintendo[] = {
+	"B", "A", "Y", "X", "-", "Home", "+", "Click", "Click", "L", "R", "ZL", "ZR"
+};
+static char const *labels_genesis[] = {
+	"A", "B", "X", "Y", NULL, NULL, "Start", NULL, NULL, "Z", "C", NULL, "Mode"
+};
+static char const *labels_saturn[] = {
+	"A", "B", "X", "Y", NULL, NULL, "Start", NULL, NULL, "Z", "C", "LT", "RT"
+};
+
+static const char** label_source(controller_info *info)
+{
+	if (info->type == TYPE_UNKNOWN || info->type == TYPE_GENERIC_MAPPING || info->subtype ==SUBTYPE_X360) {
+		return labels_360;
+	} else if (info->type == TYPE_NINTENDO) {
+		return labels_nintendo;
+	} else if (info->type == TYPE_PSX) {
+		if (info->subtype == SUBTYPE_PS4) {
+			return labels_ps4;
+		} else {
+			return labels_ps3;
+		}
+	} else if (info->type == TYPE_XBOX) {
+		if (info->subtype == SUBTYPE_XBONE) {
+			return labels_xbone;
+		} else {
+			return labels_xbox;
+		}
+	} else {
+		if (info->subtype == SUBTYPE_GENESIS) {
+			return labels_genesis;
+		} else {
+			return labels_saturn;
+		}
+	}
+}
+
+const char *get_button_label(controller_info *info, int button)
+{
+	if (button >= SDL_CONTROLLER_BUTTON_DPAD_UP) {
+		static char const * dirs[] = {"Up", "Down", "Left", "Right"};
+		return dirs[button - SDL_CONTROLLER_BUTTON_DPAD_UP];
+	}
+	return label_source(info)[button];
+}
+
+static char const *axis_labels[] = {
+	"Left X", "Left Y", "Right X", "Right Y"
+};
+const char *get_axis_label(controller_info *info, int axis)
+{
+	if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT) {
+		return axis_labels[axis];
+	} else {
+		return label_source(info)[axis - SDL_CONTROLLER_AXIS_TRIGGERLEFT + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER + 1];
+	}
+}
+
+char *make_controller_type_key(controller_info *info)
+{
+	const char *subtype;
+	if (info->subtype == SUBTYPE_UNKNOWN) {
+		switch(info->type)
+		{
+		case TYPE_XBOX:
+			subtype = subtype_names[SUBTYPE_X360];
+			break;
+		case TYPE_PSX:
+			subtype = subtype_names[SUBTYPE_PS4];
+			break;
+		case TYPE_NINTENDO:
+			subtype = subtype_names[SUBTYPE_SWITCH];
+			break;
+		default:
+			subtype = "unknown";
+		}
+	} else {
+		subtype = subtype_names[info->subtype];
+	}
+	const char *variant = variant_names[info->variant];
+	const char *parts[] = {subtype, "_", variant};
+	char *ret = alloc_concat_m(3, parts);
+	for (char *cur = ret; *cur; cur++)
+	{
+		if (*cur == ' ')
+		{
+			*cur = '_';
+		}
+	}
+	return ret;
+}
+
+char *make_human_readable_type_name(controller_info *info)
+{
+	const char *base = subtype_human_names[info->subtype];
+	char *prefix;
+	if (info->variant == VARIANT_NORMAL) {
+		prefix = "Normal ";
+	} else {
+		static const char *parts[] = {"6 button (", NULL, "/", NULL, ") "};
+		if (info->variant == VARIANT_6B_BUMPERS) {
+			parts[1] = get_button_label(info, SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
+			parts[3] = get_button_label(info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
+		} else {
+			parts[1] = get_button_label(info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
+			parts[3] = get_axis_label(info, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
+		}
+		prefix = alloc_concat_m(5, parts);
+	}
+	char *ret = alloc_concat(prefix, base);
+	if (info->variant != VARIANT_NORMAL) {
+		free(prefix);
+	}
+	return ret;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controller_info.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,52 @@
+#ifndef CONTROLLER_INFO_H_
+#define CONTROLLER_INFO_H_
+#include <stdint.h>
+
+enum {
+	TYPE_UNKNOWN,
+	TYPE_GENERIC_MAPPING,
+	TYPE_XBOX,
+	TYPE_PSX,
+	TYPE_NINTENDO,
+	TYPE_SEGA
+};
+
+enum {
+	SUBTYPE_UNKNOWN,
+	SUBTYPE_XBOX,
+	SUBTYPE_X360,
+	SUBTYPE_XBONE,
+	SUBTYPE_PS2,
+	SUBTYPE_PS3,
+	SUBTYPE_PS4,
+	SUBTYPE_WIIU,
+	SUBTYPE_SWITCH,
+	SUBTYPE_GENESIS,
+	SUBTYPE_SATURN,
+	SUBTYPE_NUM
+};
+
+enum {
+	VARIANT_NORMAL,
+	VARIANT_6B_BUMPERS, //C and Z positions are RB and LB respectively
+	VARIANT_6B_RIGHT, //C and Z positions are RT and RB respectively
+	VARIANT_NUM
+};
+
+typedef struct {
+	char const *name;
+	uint8_t    type;
+	uint8_t    subtype;
+	uint8_t    variant;
+} controller_info;
+
+controller_info get_controller_info(int index);
+const char *get_button_label(controller_info *info, int button);
+const char *get_axis_label(controller_info *info, int axis);
+void save_controller_info(int joystick, controller_info *info);
+void save_controller_mapping(int joystick, char *mapping_string);
+void controller_add_mappings(void);
+char *make_controller_type_key(controller_info *info);
+char *make_human_readable_type_name(controller_info *info);
+
+#endif //CONTROLLER_INFO_H_
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cpu_dsl.py	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,1194 @@
+#!/usr/bin/env python3
+
+
+class Block:
+	def addOp(self, op):
+		pass
+	
+	def processLine(self, parts):
+		if parts[0] == 'switch':
+			o = Switch(self, parts[1])
+			self.addOp(o)
+			return o
+		elif parts[0] == 'if':
+			o = If(self, parts[1])
+			self.addOp(o)
+			return o
+		elif parts[0] == 'end':
+			raise Exception('end is only allowed inside a switch or if block')
+		else:
+			self.addOp(NormalOp(parts))
+		return self
+		
+	def resolveLocal(self, name):
+		return None
+			
+class ChildBlock(Block):
+	def processLine(self, parts):
+		if parts[0] == 'end':
+			return self.parent
+		return super().processLine(parts)
+
+#Represents an instruction of the emulated CPU
+class Instruction(Block):
+	def __init__(self, value, fields, name):
+		self.value = value
+		self.fields = fields
+		self.name = name
+		self.implementation = []
+		self.locals = {}
+		self.regValues = {}
+		self.varyingBits = 0
+		self.invalidFieldValues = {}
+		self.newLocals = []
+		for field in fields:
+			self.varyingBits += fields[field][1]
+	
+	def addOp(self, op):
+		if op.op == 'local':
+			name = op.params[0]
+			size = op.params[1]
+			self.locals[name] = size
+		elif op.op == 'invalid':
+			name = op.params[0]
+			value = int(op.params[1])
+			self.invalidFieldValues.setdefault(name, set()).add(value)
+		else:
+			self.implementation.append(op)
+			
+	def resolveLocal(self, name):
+		if name in self.locals:
+			return name
+		return None
+	
+	def addLocal(self, name, size):
+		self.locals[name] = size
+		self.newLocals.append(name)
+		
+	def localSize(self, name):
+		return self.locals.get(name)
+			
+	def __lt__(self, other):
+		if isinstance(other, Instruction):
+			if self.varyingBits != other.varyingBits:
+				return self.varyingBits < other.varyingBits
+			return self.value < other.value
+		else:
+			return NotImplemented
+			
+	def allValues(self):
+		values = []
+		for i in range(0, 1 << self.varyingBits):
+			iword = self.value
+			doIt = True
+			for field in self.fields:
+				shift,bits = self.fields[field]
+				val = i & ((1 << bits) - 1)
+				if field in self.invalidFieldValues and val in self.invalidFieldValues[field]:
+					doIt = False
+					break
+				i >>= bits
+				iword |= val << shift
+			if doIt:
+				values.append(iword)
+		return values
+		
+	def getFieldVals(self, value):
+		fieldVals = {}
+		fieldBits = {}
+		for field in self.fields:
+			shift,bits = self.fields[field]
+			val = (value >> shift) & ((1 << bits) - 1)
+			fieldVals[field] = val
+			fieldBits[field] = bits
+		return (fieldVals, fieldBits)
+	
+	def generateName(self, value):
+		fieldVals,fieldBits = self.getFieldVals(value)
+		names = list(fieldVals.keys())
+		names.sort()
+		funName = self.name
+		for name in names:
+			funName += '_{0}_{1:0>{2}}'.format(name, bin(fieldVals[name])[2:], fieldBits[name])
+		return funName
+		
+	def generateBody(self, value, prog, otype):
+		output = []
+		prog.meta = {}
+		prog.pushScope(self)
+		self.regValues = {}
+		for var in self.locals:
+			output.append('\n\tuint{sz}_t {name};'.format(sz=self.locals[var], name=var))
+		self.newLocals = []
+		fieldVals,_ = self.getFieldVals(value)
+		for op in self.implementation:
+			op.generate(prog, self, fieldVals, output, otype)
+		begin = '\nvoid ' + self.generateName(value) + '(' + prog.context_type + ' *context)\n{'
+		if prog.needFlagCoalesce:
+			begin += prog.flags.coalesceFlags(prog, otype)
+		if prog.needFlagDisperse:
+			output.append(prog.flags.disperseFlags(prog, otype))
+		for var in self.newLocals:
+			begin += '\n\tuint{sz}_t {name};'.format(sz=self.locals[var], name=var)
+		prog.popScope()
+		return begin + ''.join(output) + '\n}'
+		
+	def __str__(self):
+		pieces = [self.name + ' ' + hex(self.value) + ' ' + str(self.fields)]
+		for name in self.locals:
+			pieces.append('\n\tlocal {0} {1}'.format(name, self.locals[name]))
+		for op in self.implementation:
+			pieces.append(str(op))
+		return ''.join(pieces)
+	
+#Represents the definition of a helper function
+class SubRoutine(Block):
+	def __init__(self, name):
+		self.name = name
+		self.implementation = []
+		self.args = []
+		self.arg_map = {}
+		self.locals = {}
+		self.regValues = {}
+	
+	def addOp(self, op):
+		if op.op == 'arg':
+			name = op.params[0]
+			size = op.params[1]
+			self.arg_map[name] = len(self.args)
+			self.args.append((name, size))
+		elif op.op == 'local':
+			name = op.params[0]
+			size = op.params[1]
+			self.locals[name] = size
+		else:
+			self.implementation.append(op)
+			
+	def resolveLocal(self, name):
+		if name in self.locals:
+			return self.name + '_' + name
+		return None
+	
+	def addLocal(self, name, size):
+		self.locals[name] = size
+	
+	def localSize(self, name):
+		return self.locals.get(name)
+			
+	def inline(self, prog, params, output, otype, parent):
+		if len(params) != len(self.args):
+			raise Exception('{0} expects {1} arguments, but was called with {2}'.format(self.name, len(self.args), len(params)))
+		argValues = {}
+		if parent:
+			self.regValues = parent.regValues
+		prog.pushScope(self)
+		i = 0
+		for name,size in self.args:
+			argValues[name] = params[i]
+			i += 1
+		for name in self.locals:
+			size = self.locals[name]
+			output.append('\n\tuint{size}_t {sub}_{local};'.format(size=size, sub=self.name, local=name))
+		for op in self.implementation:
+			op.generate(prog, self, argValues, output, otype)
+		prog.popScope()
+		
+	def __str__(self):
+		pieces = [self.name]
+		for name,size in self.args:
+			pieces.append('\n\targ {0} {1}'.format(name, size))
+		for name in self.locals:
+			pieces.append('\n\tlocal {0} {1}'.format(name, self.locals[name]))
+		for op in self.implementation:
+			pieces.append(str(op))
+		return ''.join(pieces)
+	
+class Op:
+	def __init__(self, evalFun = None):
+		self.evalFun = evalFun
+		self.impls = {}
+		self.outOp = ()
+	def cBinaryOperator(self, op):
+		def _impl(prog, params):
+			if op == '-':
+				a = params[1]
+				b = params[0]
+			else:
+				a = params[0]
+				b = params[1]
+			return '\n\t{dst} = {a} {op} {b};'.format(
+				dst = params[2], a = a, b = b, op = op
+			)
+		self.impls['c'] = _impl
+		self.outOp = (2,)
+		return self
+	def cUnaryOperator(self, op):
+		def _impl(prog, params):
+			return '\n\t{dst} = {op}{a};'.format(
+				dst = params[1], a = params[0], op = op
+			)
+		self.impls['c'] = _impl
+		self.outOp = (1,)
+		return self
+	def addImplementation(self, lang, outOp, impl):
+		self.impls[lang] = impl
+		if not outOp is None:
+			if type(outOp) is tuple:
+				self.outOp = outOp
+			else:
+				self.outOp = (outOp,)
+		return self
+	def evaluate(self, params):
+		return self.evalFun(*params)
+	def canEval(self):
+		return not self.evalFun is None
+	def numArgs(self):
+		return self.evalFun.__code__.co_argcount
+	def generate(self, otype, prog, params, rawParams):
+		if self.impls[otype].__code__.co_argcount == 2:
+			return self.impls[otype](prog, params)
+		else:
+			return self.impls[otype](prog, params, rawParams)
+		
+		
+def _xchgCImpl(prog, params, rawParams):
+	size = prog.paramSize(rawParams[0])
+	decl,name = prog.getTemp(size)
+	return decl + '\n\t{tmp} = {a};\n\t{a} = {b};\n\t{b} = {tmp};'.format(a = params[0], b = params[1], tmp = name)
+
+def _dispatchCImpl(prog, params):
+	if len(params) == 1:
+		table = 'main'
+	else:
+		table = params[1]
+	return '\n\timpl_{tbl}[{op}](context);'.format(tbl = table, op = params[0])
+
+def _updateFlagsCImpl(prog, params, rawParams):
+	i = 0
+	last = ''
+	autoUpdate = set()
+	explicit = {}
+	for c in params[0]:
+		if c.isdigit():
+			if last.isalpha():
+				num = int(c)
+				if num > 1:
+					raise Exception(c + ' is not a valid digit for update_flags')
+				explicit[last] = num
+				last = c
+			else:
+				raise Exception('Digit must follow flag letter in update_flags')
+		else:
+			if last.isalpha():
+				autoUpdate.add(last)
+			last = c
+	if last.isalpha():
+		autoUpdate.add(last)
+	output = []
+	#TODO: handle autoUpdate flags
+	for flag in autoUpdate:
+		calc = prog.flags.flagCalc[flag]
+		calc,_,resultBit = calc.partition('-')
+		lastDst = prog.resolveParam(prog.lastDst, None, {})
+		storage = prog.flags.getStorage(flag)
+		if calc == 'bit' or calc == 'sign':
+			if calc == 'sign':
+				resultBit = prog.paramSize(prog.lastDst) - 1
+			else:
+				resultBit = int(resultBit)
+			if type(storage) is tuple:
+				reg,storageBit = storage
+				reg = prog.resolveParam(reg, None, {})
+				if storageBit == resultBit:
+					#TODO: optimize this case
+					output.append('\n\t{reg} = ({reg} & ~{mask}U) | ({res} & {mask}U);'.format(
+						reg = reg, mask = 1 << resultBit, res = lastDst
+					))
+				else:
+					if resultBit > storageBit:
+						op = '>>'
+						shift = resultBit - storageBit
+					else:
+						op = '<<'
+						shift = storageBit - resultBit
+					output.append('\n\t{reg} = ({reg} & ~{mask}U) | ({res} {op} {shift}U & {mask}U);'.format(
+						reg = reg, mask = 1 << storageBit, res = lastDst, op = op, shift = shift
+					))
+			else:
+				reg = prog.resolveParam(storage, None, {})
+				output.append('\n\t{reg} = {res} & {mask}U;'.format(reg=reg, res=lastDst, mask = 1 << resultBit))
+		elif calc == 'zero':
+			if type(storage) is tuple:
+				reg,storageBit = storage
+				reg = prog.resolveParam(reg, None, {})
+				output.append('\n\t{reg} = {res} ? ({reg} & {mask}U) : ({reg} | {bit}U);'.format(
+					reg = reg, mask = ~(1 << storageBit), res = lastDst, bit = 1 << storageBit
+				))
+			elif prog.paramSize(prog.lastDst) > prog.paramSize(storage):
+				reg = prog.resolveParam(storage, None, {})
+				output.append('\n\t{reg} = {res} != 0;'.format(
+					reg = reg, res = lastDst
+				))
+			else:
+				reg = prog.resolveParam(storage, None, {})
+				output.append('\n\t{reg} = {res};'.format(reg = reg, res = lastDst))
+		elif calc == 'half-carry':
+			pass
+		elif calc == 'carry':
+			pass
+		elif calc == 'overflow':
+			pass
+		elif calc == 'parity':
+			pass
+	#TODO: combine explicit flags targeting the same storage location
+	for flag in explicit:
+		location = prog.flags.getStorage(flag)
+		if type(location) is tuple:
+			reg,bit = location
+			reg = prog.resolveReg(reg, None, {})
+			value = str(1 << bit)
+			if explicit[flag]:
+				operator = '|='
+			else:
+				operator = '&='
+				value = '~' + value
+			output.append('\n\t{reg} {op} {val};'.format(reg=reg, op=operator, val=value))
+		else:
+			reg = prog.resolveReg(location, None, {})
+			output.append('\n\t{reg} = {val};'.format(reg=reg, val=explicit[flag]))
+	return ''.join(output)
+	
+def _cmpCImpl(prog, params):
+	size = prog.paramSize(params[1])
+	tmpvar = 'cmp_tmp{sz}__'.format(sz=size)
+	typename = ''
+	scope = prog.getRootScope()
+	if not scope.resolveLocal(tmpvar):
+		scope.addLocal(tmpvar, size)
+	prog.lastDst = tmpvar
+	return '\n\t{var} = {b} - {a};'.format(var = tmpvar, a = params[0], b = params[1])
+
+def _asrCImpl(prog, params, rawParams):
+	shiftSize = prog.paramSize(rawParams[0])
+	mask = 1 << (shiftSize - 1)
+	return '\n\t{dst} = ({a} >> {b}) | ({a} & {mask});'.format(a = params[0], b = params[1], dst = params[2], mask = mask)
+		
+_opMap = {
+	'mov': Op(lambda val: val).cUnaryOperator(''),
+	'not': Op(lambda val: ~val).cUnaryOperator('~'),
+	'lnot': Op(lambda val: 0 if val else 1).cUnaryOperator('!'),
+	'neg': Op(lambda val: -val).cUnaryOperator('-'),
+	'add': Op(lambda a, b: a + b).cBinaryOperator('+'),
+	'sub': Op(lambda a, b: b - a).cBinaryOperator('-'),
+	'lsl': Op(lambda a, b: a << b).cBinaryOperator('<<'),
+	'lsr': Op(lambda a, b: a >> b).cBinaryOperator('>>'),
+	'asr': Op(lambda a, b: a >> b).addImplementation('c', 2, _asrCImpl),
+	'and': Op(lambda a, b: a & b).cBinaryOperator('&'),
+	'or':  Op(lambda a, b: a | b).cBinaryOperator('|'),
+	'xor': Op(lambda a, b: a ^ b).cBinaryOperator('^'),
+	'abs': Op(lambda val: abs(val)).addImplementation(
+		'c', 1, lambda prog, params: '\n\t{dst} = abs({src});'.format(dst=params[1], src=params[0])
+	),
+	'cmp': Op().addImplementation('c', None, _cmpCImpl),
+	'ocall': Op().addImplementation('c', None, lambda prog, params: '\n\t{pre}{fun}({args});'.format(
+		pre = prog.prefix, fun = params[0], args = ', '.join(['context'] + [str(p) for p in params[1:]])
+	)),
+	'cycles': Op().addImplementation('c', None,
+		lambda prog, params: '\n\tcontext->cycles += context->opts->gen.clock_divider * {0};'.format(
+			params[0]
+		)
+	),
+	'addsize': Op(
+		lambda a, b: b + (2 * a if a else 1)
+	).addImplementation('c', 2, lambda prog, params: '\n\t{dst} = {val} + {sz} ? {sz} * 2 : 1;'.format(
+		dst = params[2], sz = params[0], val = params[1]
+	)),
+	'decsize': Op(
+		lambda a, b: b - (2 * a if a else 1)
+	).addImplementation('c', 2, lambda prog, params: '\n\t{dst} = {val} - {sz} ? {sz} * 2 : 1;'.format(
+		dst = params[2], sz = params[0], val = params[1]
+	)),
+	'xchg': Op().addImplementation('c', (0,1), _xchgCImpl),
+	'dispatch': Op().addImplementation('c', None, _dispatchCImpl),
+	'update_flags': Op().addImplementation('c', None, _updateFlagsCImpl)
+}
+
+#represents a simple DSL instruction
+class NormalOp:
+	def __init__(self, parts):
+		self.op = parts[0]
+		self.params = parts[1:]
+		
+	def generate(self, prog, parent, fieldVals, output, otype):
+		procParams = []
+		allParamsConst = True
+		opDef = _opMap.get(self.op)
+		for param in self.params:
+			allowConst = (self.op in prog.subroutines or len(procParams) != len(self.params) - 1) and param in parent.regValues
+			isDst = (not opDef is None) and len(procParams) in opDef.outOp
+			param = prog.resolveParam(param, parent, fieldVals, allowConst, isDst)
+			
+			if (not type(param) is int) and len(procParams) != len(self.params) - 1:
+				allParamsConst = False
+			procParams.append(param)
+			
+		if self.op == 'meta':
+			param,_,index = self.params[1].partition('.')
+			if index:
+				index = (parent.resolveLocal(index) or index)
+				if index in fieldVals:
+					index = str(fieldVals[index])
+				param = param + '.' + index
+			else:
+				param = parent.resolveLocal(param) or param
+				if param in fieldVals:
+					param = fieldVals[index]
+			prog.meta[self.params[0]] = param
+		elif self.op == 'dis':
+			#TODO: Disassembler
+			pass
+		elif not opDef is None:
+			if opDef.canEval() and allParamsConst:
+				#do constant folding
+				if opDef.numArgs() >= len(procParams):
+					raise Exception('Insufficient args for ' + self.op + ' (' + ', '.join(self.params) + ')')
+				dst = self.params[opDef.numArgs()]
+				result = opDef.evaluate(procParams[:opDef.numArgs()])
+				while dst in prog.meta:
+					dst = prog.meta[dst]
+				maybeLocal = parent.resolveLocal(dst)
+				if maybeLocal:
+					dst = maybeLocal
+				parent.regValues[dst] = result
+				if prog.isReg(dst):
+					output.append(_opMap['mov'].generate(otype, prog, procParams, self.params))
+			else:
+				output.append(opDef.generate(otype, prog, procParams, self.params))
+		elif self.op in prog.subroutines:
+			prog.subroutines[self.op].inline(prog, procParams, output, otype, parent)
+		else:
+			output.append('\n\t' + self.op + '(' + ', '.join([str(p) for p in procParams]) + ');')
+		prog.lastOp = self
+	
+	def __str__(self):
+		return '\n\t' + self.op + ' ' + ' '.join(self.params)
+		
+#represents a DSL switch construct
+class Switch(ChildBlock):
+	def __init__(self, parent, param):
+		self.op = 'switch'
+		self.parent = parent
+		self.param = param
+		self.cases = {}
+		self.regValues = None
+		self.current_locals = {}
+		self.case_locals = {}
+		self.current_case = None
+		self.default = None
+		self.default_locals = None
+	
+	def addOp(self, op):
+		if op.op == 'case':
+			val = int(op.params[0], 16) if op.params[0].startswith('0x') else int(op.params[0])
+			self.cases[val] = self.current_case = []
+			self.case_locals[val] = self.current_locals = {}
+		elif op.op == 'default':
+			self.default = self.current_case = []
+			self.default_locals = self.current_locals = {}
+		elif self.current_case == None:
+			raise ion('Orphan instruction in switch')
+		elif op.op == 'local':
+			name = op.params[0]
+			size = op.params[1]
+			self.current_locals[name] = size
+		else:
+			self.current_case.append(op)
+			
+	def resolveLocal(self, name):
+		if name in self.current_locals:
+			return name
+		return self.parent.resolveLocal(name)
+	
+	def addLocal(self, name, size):
+		self.current_locals[name] = size
+		
+	def localSize(self, name):
+		if name in self.current_locals:
+			return self.current_locals[name]
+		return self.parent.localSize(name)
+			
+	def generate(self, prog, parent, fieldVals, output, otype):
+		prog.pushScope(self)
+		param = prog.resolveParam(self.param, parent, fieldVals)
+		if type(param) is int:
+			self.regValues = self.parent.regValues
+			if param in self.cases:
+				self.current_locals = self.case_locals[param]
+				output.append('\n\t{')
+				for local in self.case_locals[param]:
+					output.append('\n\tuint{0}_t {1};'.format(self.case_locals[param][local], local))
+				for op in self.cases[param]:
+					op.generate(prog, self, fieldVals, output, otype)
+				output.append('\n\t}')
+			elif self.default:
+				self.current_locals = self.default_locals
+				output.append('\n\t{')
+				for local in self.default_locals:
+					output.append('\n\tuint{0}_t {1};'.format(self.default[local], local))
+				for op in self.default:
+					op.generate(prog, self, fieldVals, output, otype)
+				output.append('\n\t}')
+		else:
+			output.append('\n\tswitch(' + param + ')')
+			output.append('\n\t{')
+			for case in self.cases:
+				self.current_locals = self.case_locals[case]
+				self.regValues = dict(self.parent.regValues)
+				output.append('\n\tcase {0}U: '.format(case) + '{')
+				for local in self.case_locals[case]:
+					output.append('\n\tuint{0}_t {1};'.format(self.case_locals[case][local], local))
+				for op in self.cases[case]:
+					op.generate(prog, self, fieldVals, output, otype)
+				output.append('\n\tbreak;')
+				output.append('\n\t}')
+			if self.default:
+				self.current_locals = self.default_locals
+				self.regValues = dict(self.parent.regValues)
+				output.append('\n\tdefault: {')
+				for local in self.default_locals:
+					output.append('\n\tuint{0}_t {1};'.format(self.default_locals[local], local))
+				for op in self.default:
+					op.generate(prog, self, fieldVals, output, otype)
+			output.append('\n\t}')
+		prog.popScope()
+	
+	def __str__(self):
+		keys = self.cases.keys()
+		keys.sort()
+		lines = ['\n\tswitch']
+		for case in keys:
+			lines.append('\n\tcase {0}'.format(case))
+			lines.append(''.join([str(op) for op in self.cases[case]]))
+		lines.append('\n\tend')
+		return ''.join(lines)
+
+		
+def _geuCImpl(prog, parent, fieldVals, output):
+	if prog.lastOp.op == 'cmp':
+		output.pop()
+		params = [prog.resolveParam(p, parent, fieldVals) for p in prog.lastOp.params]
+		return '\n\tif ({a} >= {b}) '.format(a=params[1], b = params[0]) + '{'
+	else:
+		raise ion(">=U not implemented in the general case yet")
+	
+_ifCmpImpl = {
+	'c': {
+		'>=U': _geuCImpl
+	}
+}
+#represents a DSL conditional construct
+class If(ChildBlock):
+	def __init__(self, parent, cond):
+		self.op = 'if'
+		self.parent = parent
+		self.cond = cond
+		self.body = []
+		self.elseBody = []
+		self.curBody = self.body
+		self.locals = {}
+		self.elseLocals = {}
+		self.curLocals = self.locals
+		self.regValues = None
+		
+	def addOp(self, op):
+		if op.op in ('case', 'arg'):
+			raise Exception(self.op + ' is not allows inside an if block')
+		if op.op == 'local':
+			name = op.params[0]
+			size = op.params[1]
+			self.locals[name] = size
+		elif op.op == 'else':
+			self.curLocals = self.elseLocals
+			self.curBody = self.elseBody
+		else:
+			self.curBody.append(op)
+			
+	def localSize(self, name):
+		return self.curLocals.get(name)
+		
+	def resolveLocal(self, name):
+		if name in self.locals:
+			return name
+		return self.parent.resolveLocal(name)
+		
+	def _genTrueBody(self, prog, fieldVals, output, otype):
+		self.curLocals = self.locals
+		for local in self.locals:
+			output.append('\n\tuint{sz}_t {nm};'.format(sz=self.locals[local], nm=local))
+		for op in self.body:
+			op.generate(prog, self, fieldVals, output, otype)
+			
+	def _genFalseBody(self, prog, fieldVals, output, otype):
+		self.curLocals = self.elseLocals
+		for local in self.elseLocals:
+			output.append('\n\tuint{sz}_t {nm};'.format(sz=self.elseLocals[local], nm=local))
+		for op in self.elseBody:
+			op.generate(prog, self, fieldVals, output, otype)
+	
+	def _genConstParam(self, param, prog, fieldVals, output, otype):
+		if param:
+			self._genTrueBody(prog, fieldVals, output, otype)
+		else:
+			self._genFalseBody(prog, fieldVals, output, otype)
+			
+	def generate(self, prog, parent, fieldVals, output, otype):
+		self.regValues = parent.regValues
+		try:
+			self._genConstParam(prog.checkBool(self.cond), prog, fieldVals, output, otype)
+		except Exception:
+			if self.cond in _ifCmpImpl[otype]:
+				output.append(_ifCmpImpl[otype][self.cond](prog, parent, fieldVals, output))
+				self._genTrueBody(prog, fieldVals, output, otype)
+				if self.elseBody:
+					output.append('\n\t} else {')
+					self._genFalseBody(prog, fieldVals, output, otype)
+				output.append('\n\t}')
+			else:
+				cond = prog.resolveParam(self.cond, parent, fieldVals)
+				if type(cond) is int:
+					self._genConstParam(cond, prog, fieldVals, output, otype)
+				else:
+					output.append('\n\tif ({cond}) '.format(cond=cond) + '{')
+					self._genTrueBody(prog, fieldVals, output, otype)
+					if self.elseBody:
+						output.append('\n\t} else {')
+						self._genFalseBody(prog, fieldVals, output, otype)
+					output.append('\n\t}')
+						
+	
+	def __str__(self):
+		lines = ['\n\tif']
+		for op in self.body:
+			lines.append(str(op))
+		lines.append('\n\tend')
+		return ''.join(lines)
+
+class Registers:
+	def __init__(self):
+		self.regs = {}
+		self.pointers = {}
+		self.regArrays = {}
+		self.regToArray = {}
+	
+	def addReg(self, name, size):
+		self.regs[name] = size
+		
+	def addPointer(self, name, size):
+		self.pointers[name] = size
+	
+	def addRegArray(self, name, size, regs):
+		self.regArrays[name] = (size, regs)
+		idx = 0
+		if not type(regs) is int:
+			for reg in regs:
+				self.regs[reg] = size
+				self.regToArray[reg] = (name, idx)
+				idx += 1
+	
+	def isReg(self, name):
+		return name in self.regs
+	
+	def isRegArray(self, name):
+		return name in self.regArrays
+		
+	def isRegArrayMember(self, name):
+		return name in self.regToArray
+		
+	def arrayMemberParent(self, name):
+		return self.regToArray[name][0]
+	
+	def arrayMemberIndex(self, name):
+		return self.regToArray[name][1]
+	
+	def arrayMemberName(self, array, index):
+		if type(index) is int and not type(self.regArrays[array][1]) is int:
+			return self.regArrays[array][1][index]
+		else:
+			return None
+			
+	def isNamedArray(self, array):
+		return array in self.regArrays and type(self.regArrays[array][1]) is int
+	
+	def processLine(self, parts):
+		if len(parts) == 3:
+			self.addRegArray(parts[0], int(parts[1]), int(parts[2]))
+		elif len(parts) > 2:
+			self.addRegArray(parts[0], int(parts[1]), parts[2:])
+		else:
+			if parts[1].startswith('ptr'):
+				self.addPointer(parts[0], int(parts[1][3:]))
+			else:
+				self.addReg(parts[0], int(parts[1]))
+		return self
+
+	def writeHeader(self, otype, hFile):
+		fieldList = []
+		for pointer in self.pointers:
+			hFile.write('\n\tuint{sz}_t *{nm};'.format(nm=pointer, sz=self.pointers[pointer]))
+		for reg in self.regs:
+			if not self.isRegArrayMember(reg):
+				fieldList.append((self.regs[reg], 1, reg))
+		for arr in self.regArrays:
+			size,regs = self.regArrays[arr]
+			if not type(regs) is int:
+				regs = len(regs)
+			fieldList.append((size, regs, arr))
+		fieldList.sort()
+		fieldList.reverse()
+		for size, count, name in fieldList:
+			if count > 1:
+				hFile.write('\n\tuint{sz}_t {nm}[{ct}];'.format(sz=size, nm=name, ct=count))
+			else:
+				hFile.write('\n\tuint{sz}_t {nm};'.format(sz=size, nm=name))
+	
+class Flags:
+	def __init__(self):
+		self.flagBits = {}
+		self.flagCalc = {}
+		self.flagStorage = {}
+		self.flagReg = None
+		self.maxBit = -1
+	
+	def processLine(self, parts):
+		if parts[0] == 'register':
+			self.flagReg = parts[1]
+		else:
+			flag,bit,calc,storage = parts
+			bit,_,top = bit.partition('-')
+			bit = int(bit)
+			if top:
+				top = int(bit)
+				if top > self.maxBit:
+					self.maxBit = top
+				self.flagBits[flag] = (bit,top)
+			else:
+				if bit > self.maxBit:
+					self.maxBit = bit
+				self.flagBits[flag] = bit
+			self.flagCalc[flag] = calc
+			self.flagStorage[flag] = storage
+		return self
+	
+	def getStorage(self, flag):
+		if not flag in self.flagStorage:
+			raise Exception('Undefined flag ' + flag)
+		loc,_,bit = self.flagStorage[flag].partition('.')
+		if bit:
+			return (loc, int(bit))
+		else:
+			return loc 
+	
+	def disperseFlags(self, prog, otype):
+		bitToFlag = [None] * (self.maxBit+1)
+		src = prog.resolveReg(self.flagReg, None, {})
+		output = []
+		for flag in self.flagBits:
+			bit = self.flagBits[flag]
+			if type(bit) is tuple:
+				bot,top = bit
+				mask = ((1 << (top + 1 - bot)) - 1) << bot
+				output.append('\n\t{dst} = {src} & mask;'.format(
+					dst=prog.resolveReg(self.flagStorage[flag], None, {}), src=src, mask=mask
+				))
+			else:
+				bitToFlag[self.flagBits[flag]] = flag		
+		multi = {}
+		for bit in range(len(bitToFlag)-1,-1,-1):
+			flag = bitToFlag[bit]
+			if not flag is None:
+				field,_,dstbit = self.flagStorage[flag].partition('.')
+				dst = prog.resolveReg(field, None, {})
+				if dstbit:
+					dstbit = int(dstbit)
+					multi.setdefault(dst, []).append((dstbit, bit))
+				else:
+					output.append('\n\t{dst} = {src} & {mask};'.format(dst=dst, src=src, mask=(1 << bit)))
+		for dst in multi:
+			didClear = False
+			direct = []
+			for dstbit, bit in multi[dst]:
+				if dstbit == bit:
+					direct.append(bit)
+				else:
+					if not didClear:
+						output.append('\n\t{dst} = 0;'.format(dst=dst))
+						didClear = True
+					if dstbit > bit:
+						shift = '<<'
+						diff = dstbit - bit
+					else:
+						shift = '>>'
+						diff = bit - dstbit
+					output.append('\n\t{dst} |= {src} {shift} {diff} & {mask};'.format(
+						src=src, dst=dst, shift=shift, diff=diff, mask=(1 << dstbit)
+					))
+			if direct:
+				if len(direct) == len(multi[dst]):
+					output.append('\n\t{dst} = {src};'.format(dst=dst, src=src))
+				else:
+					mask = 0
+					for bit in direct:
+						mask = mask | (1 << bit)
+					output.append('\n\t{dst} = {src} & {mask};'.format(dst=dst, src=src, mask=mask))
+		return ''.join(output)
+	
+	def coalesceFlags(self, prog, otype):
+		dst = prog.resolveReg(self.flagReg, None, {})
+		output = ['\n\t{dst} = 0;'.format(dst=dst)]
+		bitToFlag = [None] * (self.maxBit+1)
+		for flag in self.flagBits:
+			bit = self.flagBits[flag]
+			if type(bit) is tuple:
+				bot,_ = bit
+				src = prog.resolveReg(self.flagStorage[flag], None, {})
+				if bot:
+					output.append('\n\t{dst} |= {src} << {shift};'.format(
+						dst=dst, src = src, shift = bot
+					))
+				else:
+					output.append('\n\t{dst} |= {src};'.format(
+						dst=dst, src = src
+					))
+			else:
+				bitToFlag[bit] = flag
+		multi = {}
+		for bit in range(len(bitToFlag)-1,-1,-1):
+			flag = bitToFlag[bit]
+			if not flag is None:
+				field,_,srcbit = self.flagStorage[flag].partition('.')
+				src = prog.resolveReg(field, None, {})
+				if srcbit:
+					srcbit = int(srcbit)
+					multi.setdefault(src, []).append((srcbit,bit))
+				else:
+					output.append('\n\tif ({src}) {{\n\t\t{dst} |= 1 << {bit};\n\t}}'.format(
+						dst=dst, src=src, bit=bit
+					))
+		for src in multi:
+			direct = 0
+			for srcbit, dstbit in multi[src]:
+				if srcbit == dstbit:
+					direct = direct | (1 << srcbit)
+				else:
+					output.append('\n\tif ({src} & (1 << {srcbit})) {{\n\t\t{dst} |= 1 << {dstbit};\n\t}}'.format(
+						src=src, dst=dst, srcbit=srcbit, dstbit=dstbit
+					))
+			if direct:
+				output.append('\n\t{dst} |= {src} & {mask}'.format(
+					dst=dst, src=src, mask=direct
+				))
+		return ''.join(output)
+		
+		
+class Program:
+	def __init__(self, regs, instructions, subs, info, flags):
+		self.regs = regs
+		self.instructions = instructions
+		self.subroutines = subs
+		self.meta = {}
+		self.booleans = {}
+		self.prefix = info.get('prefix', [''])[0]
+		self.opsize = int(info.get('opcode_size', ['8'])[0])
+		self.extra_tables = info.get('extra_tables', [])
+		self.context_type = self.prefix + 'context'
+		self.body = info.get('body', [None])[0]
+		self.includes = info.get('include', [])
+		self.flags = flags
+		self.lastDst = None
+		self.scopes = []
+		self.currentScope = None
+		self.lastOp = None
+		
+	def __str__(self):
+		pieces = []
+		for reg in self.regs:
+			pieces.append(str(self.regs[reg]))
+		for name in self.subroutines:
+			pieces.append('\n'+str(self.subroutines[name]))
+		for instruction in self.instructions:
+			pieces.append('\n'+str(instruction))
+		return ''.join(pieces)
+		
+	def writeHeader(self, otype, header):
+		hFile = open(header, 'w')
+		macro = header.upper().replace('.', '_')
+		hFile.write('#ifndef {0}_'.format(macro))
+		hFile.write('\n#define {0}_'.format(macro))
+		hFile.write('\n#include "backend.h"')
+		hFile.write('\n\ntypedef struct {')
+		hFile.write('\n\tcpu_options gen;')
+		hFile.write('\n}} {0}options;'.format(self.prefix))
+		hFile.write('\n\ntypedef struct {')
+		hFile.write('\n\t{0}options *opts;'.format(self.prefix))
+		hFile.write('\n\tuint32_t cycles;')
+		self.regs.writeHeader(otype, hFile)
+		hFile.write('\n}} {0}context;'.format(self.prefix))
+		hFile.write('\n')
+		hFile.write('\n#endif //{0}_'.format(macro))
+		hFile.write('\n')
+		hFile.close()
+	def build(self, otype):
+		body = []
+		pieces = []
+		for include in self.includes:
+			body.append('#include "{0}"\n'.format(include))
+		for table in self.instructions:
+			opmap = [None] * (1 << self.opsize)
+			bodymap = {}
+			instructions = self.instructions[table]
+			instructions.sort()
+			for inst in instructions:
+				for val in inst.allValues():
+					if opmap[val] is None:
+						self.meta = {}
+						self.temp = {}
+						self.needFlagCoalesce = False
+						self.needFlagDisperse = False
+						self.lastOp = None
+						opmap[val] = inst.generateName(val)
+						bodymap[val] = inst.generateBody(val, self, otype)
+			
+			pieces.append('\ntypedef void (*impl_fun)({pre}context *context);'.format(pre=self.prefix))
+			pieces.append('\nstatic impl_fun impl_{name}[{sz}] = {{'.format(name = table, sz=len(opmap)))
+			for inst in range(0, len(opmap)):
+				op = opmap[inst]
+				if op is None:
+					pieces.append('\n\tunimplemented,')
+				else:
+					pieces.append('\n\t' + op + ',')
+					body.append(bodymap[inst])
+			pieces.append('\n};')
+		if self.body in self.subroutines:
+			pieces.append('\nvoid {pre}execute({type} *context, uint32_t target_cycle)'.format(pre = self.prefix, type = self.context_type))
+			pieces.append('\n{')
+			pieces.append('\n\twhile (context->cycles < target_cycle)')
+			pieces.append('\n\t{')
+			self.meta = {}
+			self.temp = {}
+			self.subroutines[self.body].inline(self, [], pieces, otype, None)
+			pieces.append('\n\t}')
+			pieces.append('\n}')
+		body.append('\nstatic void unimplemented({pre}context *context)'.format(pre = self.prefix))
+		body.append('\n{')
+		body.append('\n\tfatal_error("Unimplemented instruction");')
+		body.append('\n}\n')
+		return ''.join(body) +  ''.join(pieces)
+		
+	def checkBool(self, name):
+		if not name in self.booleans:
+			raise Exception(name + ' is not a defined boolean flag')
+		return self.booleans[name]
+	
+	def getTemp(self, size):
+		if size in self.temp:
+			return ('', self.temp[size])
+		self.temp[size] = 'tmp{sz}'.format(sz=size);
+		return ('\n\tuint{sz}_t tmp{sz};'.format(sz=size), self.temp[size])
+		
+	def resolveParam(self, param, parent, fieldVals, allowConstant=True, isdst=False):
+		keepGoing = True
+		while keepGoing:
+			keepGoing = False
+			try:
+				if type(param) is int:
+					pass
+				elif param.startswith('0x'):
+					param = int(param, 16)
+				else:
+					param = int(param)
+			except ValueError:
+				
+				if parent:
+					if param in parent.regValues and allowConstant:
+						return parent.regValues[param]
+					maybeLocal = parent.resolveLocal(param)
+					if maybeLocal:
+						return maybeLocal
+				if param in fieldVals:
+					param = fieldVals[param]
+				elif param in self.meta:
+					param = self.meta[param]
+					keepGoing = True
+				elif self.isReg(param):
+					param = self.resolveReg(param, parent, fieldVals, isdst)
+		return param
+	
+	def isReg(self, name):
+		if not type(name) is str:
+			return False
+		begin,sep,_ = name.partition('.')
+		if sep:
+			if begin in self.meta:
+				begin = self.meta[begin]
+			return self.regs.isRegArray(begin)
+		else:
+			return self.regs.isReg(name)
+	
+	def resolveReg(self, name, parent, fieldVals, isDst=False):
+		begin,sep,end = name.partition('.')
+		if sep:
+			if begin in self.meta:
+				begin = self.meta[begin]
+			if not self.regs.isRegArrayMember(end):
+				end = self.resolveParam(end, parent, fieldVals)
+			if not type(end) is int and self.regs.isRegArrayMember(end):
+				arrayName = self.regs.arrayMemberParent(end)
+				end = self.regs.arrayMemberIndex(end)
+				if arrayName != begin:
+					end = 'context->{0}[{1}]'.format(arrayName, end)
+			if self.regs.isNamedArray(begin):
+				regName = self.regs.arrayMemberName(begin, end)
+			else:
+				regName = '{0}.{1}'.format(begin, end)
+			ret = 'context->{0}[{1}]'.format(begin, end)
+		else:
+			regName = name
+			if self.regs.isRegArrayMember(name):
+				arr,idx = self.regs.regToArray[name]
+				ret = 'context->{0}[{1}]'.format(arr, idx)
+			else:
+				ret = 'context->' + name
+		if regName == self.flags.flagReg:
+			if isDst:
+				self.needFlagDisperse = True
+			else:
+				self.needFlagCoalesce = True
+		if isDst:
+			self.lastDst = regName
+		return ret
+		
+	
+	
+	def paramSize(self, name):
+		size = self.currentScope.localSize(name)
+		if size:
+			return size
+		begin,sep,_ = name.partition('.')
+		if sep and self.regs.isRegArray(begin):
+			return self.regs.regArrays[begin][0]
+		if self.regs.isReg(name):
+			return self.regs.regs[name]
+		return 32
+	
+	def pushScope(self, scope):
+		self.scopes.append(scope)
+		self.currentScope = scope
+		
+	def popScope(self):
+		ret = self.scopes.pop()
+		self.currentScope = self.scopes[-1] if self.scopes else None
+		return ret
+		
+	def getRootScope(self):
+		return self.scopes[0]
+
+def parse(f):
+	instructions = {}
+	subroutines = {}
+	registers = None
+	flags = None
+	errors = []
+	info = {}
+	line_num = 0
+	cur_object = None
+	for line in f:
+		line_num += 1
+		line,_,comment = line.partition('#')
+		if not line.strip():
+			continue
+		if line[0].isspace():
+			if not cur_object is None:
+				parts = [el.strip() for el in line.split(' ')]
+				if type(cur_object) is dict:
+					cur_object[parts[0]] = parts[1:]
+				else:
+					cur_object = cur_object.processLine(parts)
+				
+#				if type(cur_object) is Registers:
+#					if len(parts) > 2:
+#						cur_object.addRegArray(parts[0], int(parts[1]), parts[2:])
+#					else:
+#						cur_object.addReg(parts[0], int(parts[1]))
+#				elif type(cur_object) is dict:
+#					cur_object[parts[0]] = parts[1:]
+#				elif parts[0] == 'switch':
+#					o = Switch(cur_object, parts[1])
+#					cur_object.addOp(o)
+#					cur_object = o
+#				elif parts[0] == 'if':
+#					o = If(cur_object, parts[1])
+#					cur_object.addOp(o)
+#					cur_object = o
+#				elif parts[0] == 'end':
+#					cur_object = cur_object.parent
+#				else:
+#					cur_object.addOp(NormalOp(parts))
+			else:
+				errors.append("Orphan instruction on line {0}".format(line_num))
+		else:
+			parts = line.split(' ')
+			if len(parts) > 1:
+				if len(parts) > 2:
+					table,bitpattern,name = parts
+				else:
+					bitpattern,name = parts
+					table = 'main'
+				value = 0
+				fields = {}
+				curbit = len(bitpattern) - 1
+				for char in bitpattern:
+					value <<= 1
+					if char in ('0', '1'):
+						value |= int(char)
+					else:
+						if char in fields:
+							fields[char] = (curbit, fields[char][1] + 1)
+						else:
+							fields[char] = (curbit, 1)
+					curbit -= 1
+				cur_object = Instruction(value, fields, name.strip())
+				instructions.setdefault(table, []).append(cur_object)
+			elif line.strip() == 'regs':
+				if registers is None:
+					registers = Registers()
+				cur_object = registers
+			elif line.strip() == 'info':
+				cur_object = info
+			elif line.strip() == 'flags':
+				if flags is None:
+					flags = Flags()
+				cur_object = flags
+			else:
+				cur_object = SubRoutine(line.strip())
+				subroutines[cur_object.name] = cur_object
+	if errors:
+		print(errors)
+	else:
+		p = Program(registers, instructions, subroutines, info, flags)
+		p.booleans['dynarec'] = False
+		p.booleans['interp'] = True
+		
+		if 'header' in info:
+			print('#include "{0}"'.format(info['header'][0]))
+			p.writeHeader('c', info['header'][0])
+		print('#include "util.h"')
+		print('#include <stdlib.h>')
+		print(p.build('c'))
+
+def main(argv):
+	f =  open(argv[1])
+	parse(f)
+
+if __name__ == '__main__':
+	from sys import argv
+	main(argv)
\ No newline at end of file
--- a/debug.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/debug.c	Sat Jan 05 00:58:08 2019 -0800
@@ -787,31 +787,81 @@
 			insert_breakpoint(context, after, debugger);
 			return 0;
 		case 's':
-			if (inst.op == M68K_RTS) {
-				after = m68k_read_long(context->aregs[7], context);
-			} else if (inst.op == M68K_RTE || inst.op == M68K_RTR) {
-				after = m68k_read_long(context->aregs[7] + 2, context);
-			} else if(m68k_is_branch(&inst)) {
-				if (inst.op == M68K_BCC && inst.extra.cond != COND_TRUE) {
-					branch_f = after;
-					branch_t = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
-					insert_breakpoint(context, branch_t, debugger);
-				} else if(inst.op == M68K_DBCC) {
-					if (inst.extra.cond == COND_FALSE) {
-						if (context->dregs[inst.dst.params.regs.pri] & 0xFFFF) {
-							after = m68k_branch_target(&inst, context->dregs, context->aregs);
+			if (input_buf[1] == 'e') {
+				param = find_param(input_buf);
+				if (!param) {
+					fputs("Missing destination parameter for set\n", stderr);
+				}
+				char *val = find_param(param);
+				if (!val) {
+					fputs("Missing value parameter for set\n", stderr);
+				}
+				long int_val;
+				int reg_num;
+				switch (val[0])
+				{
+				case 'd':
+				case 'a':
+					reg_num = val[1] - '0';
+					if (reg_num < 0 || reg_num > 8) {
+						fprintf(stderr, "Invalid register %s\n", val);
+						return 1;
+					}
+					int_val = (val[0] == 'd' ? context->dregs : context->aregs)[reg_num];
+					break;
+				case '$':
+					int_val = strtol(val+1, NULL, 16);
+					break;
+				case '0':
+					if (val[1] == 'x') {
+						int_val = strtol(val+2, NULL, 16);
+						break;
+					}
+				default:
+					int_val = strtol(val, NULL, 10);
+				}
+				switch(param[0])
+				{
+				case 'd':
+				case 'a':
+					reg_num = param[1] - '0';
+					if (reg_num < 0 || reg_num > 8) {
+						fprintf(stderr, "Invalid register %s\n", param);
+						return 1;
+					}
+					(param[0] == 'd' ? context->dregs : context->aregs)[reg_num] = int_val;
+					break;
+				default:
+					fprintf(stderr, "Invalid destinatino %s\n", param);
+				}
+				break;
+			} else {
+				if (inst.op == M68K_RTS) {
+					after = m68k_read_long(context->aregs[7], context);
+				} else if (inst.op == M68K_RTE || inst.op == M68K_RTR) {
+					after = m68k_read_long(context->aregs[7] + 2, context);
+				} else if(m68k_is_branch(&inst)) {
+					if (inst.op == M68K_BCC && inst.extra.cond != COND_TRUE) {
+						branch_f = after;
+						branch_t = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
+						insert_breakpoint(context, branch_t, debugger);
+					} else if(inst.op == M68K_DBCC) {
+						if (inst.extra.cond == COND_FALSE) {
+							if (context->dregs[inst.dst.params.regs.pri] & 0xFFFF) {
+								after = m68k_branch_target(&inst, context->dregs, context->aregs);
+							}
+						} else {
+							branch_t = after;
+							branch_f = m68k_branch_target(&inst, context->dregs, context->aregs);
+							insert_breakpoint(context, branch_f, debugger);
 						}
 					} else {
-						branch_t = after;
-						branch_f = m68k_branch_target(&inst, context->dregs, context->aregs);
-						insert_breakpoint(context, branch_f, debugger);
+						after = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
 					}
-				} else {
-					after = m68k_branch_target(&inst, context->dregs, context->aregs) & 0xFFFFFF;
 				}
+				insert_breakpoint(context, after, debugger);
+				return 0;
 			}
-			insert_breakpoint(context, after, debugger);
-			return 0;
 		case 'v': {
 			genesis_context * gen = context->system;
 			//VDP debug commands
@@ -900,6 +950,8 @@
 	init_terminal();
 
 	sync_components(context, 0);
+	genesis_context *gen = context->system;
+	vdp_force_update_framebuffer(gen->vdp);
 	//probably not necessary, but let's play it safe
 	address &= 0xFFFFFF;
 	if (address == branch_t) {
--- a/default.cfg	Tue Dec 19 00:49:13 2017 -0800
+++ b/default.cfg	Sat Jan 05 00:58:08 2019 -0800
@@ -16,9 +16,12 @@
 
 		r ui.release_mouse
 		[ ui.vdp_debug_mode
-		] ui.vdp_debug_pal
 		u ui.enter_debugger
 		p ui.screenshot
+		b ui.plane_debug
+		v ui.vram_debug
+		c ui.cram_debug
+		n ui.compositing_debug
 		esc ui.exit
 		` ui.save_state
 		0 ui.set_speed.0
@@ -38,65 +41,208 @@
 		rctrl ui.toggle_keyboard_captured
 	}
 	pads {
-		0 {
+		default {
 			dpads {
 				0 {
-					up gamepads.1.up
-					down gamepads.1.down
-					left gamepads.1.left
-					right gamepads.1.right
+					up gamepads.n.up
+					down gamepads.n.down
+					left gamepads.n.left
+					right gamepads.n.right
 				}
 			}
 			buttons {
-				a gamepads.1.a
-				b gamepads.1.b
-				rightshoulder gamepads.1.c
-				x gamepads.1.x
-				y gamepads.1.y
-				leftshoulder gamepads.1.z
-				back gamepads.1.mode
-				start gamepads.1.start
+				a gamepads.n.a
+				b gamepads.n.b
+				rightshoulder gamepads.n.c
+				x gamepads.n.x
+				y gamepads.n.y
+				leftshoulder gamepads.n.z
+				back gamepads.n.mode
+				start gamepads.n.start
 				guide ui.exit
 				leftstick ui.save_state
 			}
 			axes {
-				lefty.positive gamepads.1.down
-				lefty.negative gamepads.1.up
-				leftx.positive gamepads.1.right
-				leftx.negative gamepads.1.left
+				lefty.positive gamepads.n.down
+				lefty.negative gamepads.n.up
+				leftx.positive gamepads.n.right
+				leftx.negative gamepads.n.left
 				lefttrigger ui.prev_speed
 				righttrigger ui.next_speed
 			}
 		}
-		1 {
+		ps4_6b_right {
+			axes {
+				lefttrigger ui.next_speed
+				leftx.negative gamepads.n.up
+				leftx.positive gamepads.n.down
+				lefty.negative gamepads.n.left
+				lefty.positive gamepads.n.right
+				righttrigger gamepads.n.c
+			}
+			buttons {
+				a gamepads.n.a
+				b gamepads.n.b
+				back ui.sms_pause
+				guide ui.exit
+				leftshoulder gamepads.n.mode
+				leftstick ui.save_state
+				rightshoulder gamepads.n.z
+				rightstick ui.prev_speed
+				start gamepads.n.start
+				x gamepads.n.x
+				y gamepads.n.y
+			}
 			dpads {
 				0 {
-					up gamepads.2.up
-					down gamepads.2.down
-					left gamepads.2.left
-					right gamepads.2.right
+					down gamepads.n.down
+					left gamepads.n.left
+					right gamepads.n.right
+					up gamepads.n.up
 				}
 			}
+		}
+		ps3_6b_right {
+			axes {
+				lefttrigger ui.next_speed
+				leftx.negative gamepads.n.up
+				leftx.positive gamepads.n.down
+				lefty.negative gamepads.n.left
+				lefty.positive gamepads.n.right
+				righttrigger gamepads.n.c
+			}
+			buttons {
+				a gamepads.n.a
+				b gamepads.n.b
+				back ui.sms_pause
+				guide ui.exit
+				leftshoulder gamepads.n.mode
+				leftstick ui.save_state
+				rightshoulder gamepads.n.z
+				rightstick ui.prev_speed
+				start gamepads.n.start
+				x gamepads.n.x
+				y gamepads.n.y
+			}
+			dpads {
+				0 {
+					down gamepads.n.down
+					left gamepads.n.left
+					right gamepads.n.right
+					up gamepads.n.up
+				}
+			}
+		}
+		xbox_360_6b_right {
+			axes {
+				lefttrigger ui.next_speed
+				leftx.negative gamepads.n.up
+				leftx.positive gamepads.n.down
+				lefty.negative gamepads.n.left
+				lefty.positive gamepads.n.right
+				righttrigger gamepads.n.c
+			}
 			buttons {
-				#this is exactly the same mapping as above, but with PS4 style names
-				cross gamepads.2.a
-				circle gamepads.2.b
-				r1 gamepads.2.c
-				square gamepads.2.x
-				triangle gamepads.2.y
-				l1 gamepads.2.z
-				share gamepads.2.mode
-				options gamepads.2.start
+				a gamepads.n.a
+				b gamepads.n.b
+				back ui.sms_pause
+				guide ui.exit
+				leftshoulder gamepads.n.mode
+				leftstick ui.save_state
+				rightshoulder gamepads.n.z
+				rightstick ui.prev_speed
+				start gamepads.n.start
+				x gamepads.n.x
+				y gamepads.n.y
+			}
+			dpads {
+				0 {
+					down gamepads.n.down
+					left gamepads.n.left
+					right gamepads.n.right
+					up gamepads.n.up
+				}
+			}
+		}
+		xbone_6b_right {
+			axes {
+				lefttrigger ui.next_speed
+				leftx.negative gamepads.n.up
+				leftx.positive gamepads.n.down
+				lefty.negative gamepads.n.left
+				lefty.positive gamepads.n.right
+				righttrigger gamepads.n.c
+			}
+			buttons {
+				a gamepads.n.a
+				b gamepads.n.b
+				back ui.sms_pause
 				guide ui.exit
-				l3 ui.save_state
+				leftshoulder gamepads.n.mode
+				leftstick ui.save_state
+				rightshoulder gamepads.n.z
+				rightstick ui.prev_speed
+				start gamepads.n.start
+				x gamepads.n.x
+				y gamepads.n.y
 			}
+			dpads {
+				0 {
+					down gamepads.n.down
+					left gamepads.n.left
+					right gamepads.n.right
+					up gamepads.n.up
+				}
+			}
+		}
+		genesis_6b_bumpers {
 			axes {
-				lefty.positive gamepads.2.down
-				lefty.negative gamepads.2.up
-				leftx.positive gamepads.2.right
-				leftx.negative gamepads.2.left
-				l2 ui.prev_speed
-				r2 ui.next_speed
+				lefttrigger ui.exit
+				righttrigger gamepads.n.mode
+			}
+			buttons {
+				a gamepads.n.a
+				b gamepads.n.b
+				back ui.sms_pause
+				guide ui.exit
+				leftshoulder gamepads.n.z
+				rightshoulder gamepads.n.c
+				start gamepads.n.start
+				x gamepads.n.x
+				y gamepads.n.y
+			}
+			dpads {
+				0 {
+					down gamepads.n.down
+					left gamepads.n.left
+					right gamepads.n.right
+					up gamepads.n.up
+				}
+			}
+		}
+		saturn_6b_bumpers {
+			axes {
+				lefttrigger ui.exit
+				righttrigger gamepads.n.mode
+			}
+			buttons {
+				a gamepads.n.a
+				b gamepads.n.b
+				back ui.sms_pause
+				guide ui.exit
+				leftshoulder gamepads.n.z
+				rightshoulder gamepads.n.c
+				start gamepads.n.start
+				x gamepads.n.x
+				y gamepads.n.y
+			}
+			dpads {
+				0 {
+					down gamepads.n.down
+					left gamepads.n.left
+					right gamepads.n.right
+					up gamepads.n.up
+				}
 			}
 		}
 	}
@@ -217,19 +363,28 @@
 	#path for storing internal screenshots, accepts the same variables as initial_path
 	screenshot_path $HOME
 	#see strftime for the format specifiers valid in screenshot_template
-	screenshot_template blastem_%Y%m%d_%H%M%S.ppm
+	screenshot_template blastem_%Y%m%d_%H%M%S.png
 	#path template for saving SRAM, EEPROM and savestates
 	#accepts special variables $HOME, $EXEDIR, $USERDATA, $ROMNAME
 	save_path $USERDATA/blastem/$ROMNAME
 	#space delimited list of file extensions to filter against in menu
-	extensions bin gen md smd sms gg
+	extensions bin gen md smd sms gg zip gz
 	#specifies the preferred save-state format, set to gst for Genecyst compatible states
 	state_format native
 }
 
 system {
+	#controls how the emulated system is synced to the host
+	#video provides the smoothest experience when the host and emulated system have similar refresh rates
+	#audio provides lower audio latency, especially when there is a refresh rate mismatch
+	sync_source audio
+	#set this to random to debug initialization bugs
 	ram_init zero
 	default_region U
+	#controls whether MegaWiFi support is enabled or not
+	#MegaWiFi allows ROMs to make connections to the internet
+	#so it should only be enabled for ROMs you trust
+	megawifi off
 }
 
 
--- a/dis.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/dis.c	Sat Jan 05 00:58:08 2019 -0800
@@ -206,6 +206,7 @@
 						}
 					}
 				}
+				fclose(address_log);
 			}
 		} else {
 			char *end;
--- a/fib.s68	Tue Dec 19 00:49:13 2017 -0800
+++ b/fib.s68	Sat Jan 05 00:58:08 2019 -0800
@@ -1,8 +1,8 @@
     dc.l $0, start
 start:
-	moveq #36, d0
+	moveq #42, d0
 	bsr fib
-	illegal
+	reset
 fib:
 	cmp.l #2, d0
 	blt base
--- a/gamecontrollerdb.txt	Tue Dec 19 00:49:13 2017 -0800
+++ b/gamecontrollerdb.txt	Sat Jan 05 00:58:08 2019 -0800
@@ -1,176 +1,585 @@
-# Windows - DINPUT
-8f0e1200000000000000504944564944,Acme,platform:Windows,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,
-341a3608000000000000504944564944,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
-ffff0000000000000000504944564944,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
-6d0416c2000000000000504944564944,Generic DirectInput Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
-0d0f6e00000000000000504944564944,HORIPAD 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,
-6d0419c2000000000000504944564944,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
-88880803000000000000504944564944,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,
-4c056802000000000000504944564944,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows,
-25090500000000000000504944564944,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,
-4c05c405000000000000504944564944,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,
-4c05cc09000000000000504944564944,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,
-4c05a00b000000000000504944564944,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,
-6d0418c2000000000000504944564944,Logitech RumblePad 2 USB,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-36280100000000000000504944564944,OUYA Controller,platform:Windows,a:b0,b:b3,y:b2,x:b1,start:b14,guide:b15,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b8,dpleft:b10,dpdown:b9,dpright:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b12,righttrigger:b13,
-4f0400b3000000000000504944564944,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Windows,
-00f00300000000000000504944564944,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows,
-00f0f100000000000000504944564944,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows,
-28040140000000000000504944564944,GamePad Pro USB,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7,
-ff113133000000000000504944564944,SVEN X-PAD,platform:Windows,a:b2,b:b3,y:b1,x:b0,start:b5,back:b4,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b8,righttrigger:b9,
-8f0e0300000000000000504944564944,Piranha xtreme,platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,
-8f0e0d31000000000000504944564944,Multilaser JS071 USB,platform:Windows,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,
-10080300000000000000504944564944,PS2 USB,platform:Windows,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a4,righty:a2,lefttrigger:b4,righttrigger:b5,
-79000600000000000000504944564944,G-Shark GS-GP702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Windows,
-4b12014d000000000000504944564944,NYKO AIRFLO,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b10,start:b9,leftstick:a0,rightstick:a2,leftshoulder:a3,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:h0.6,lefty:h0.12,rightx:h0.9,righty:h0.4,lefttrigger:b6,righttrigger:b7,platform:Windows,
-d6206dca000000000000504944564944,PowerA Pro Ex,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,
-a3060cff000000000000504944564944,Saitek P2500,a:b2,b:b3,y:b1,x:b0,start:b4,guide:b10,back:b5,leftstick:b8,rightstick:b9,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Windows,
-4f0415b3000000000000504944564944,Thrustmaster Dual Analog 3.2,platform:Windows,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-6f0e1e01000000000000504944564944,Rock Candy Gamepad for PS3,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,
-83056020000000000000504944564944,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,y:b2,x:b3,start:b7,back:b6,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Windows,
-10080100000000000000504944564944,PS1 USB,platform:Windows,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,
-49190204000000000000504944564944,Ipega PG-9023,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b8,righttrigger:b9,platform:Windows,
-4f0423b3000000000000504944564944,Dual Trigger 3-in-1,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Windows,
-0d0f4900000000000000504944564944,Hatsune Miku Sho Controller,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,
-79004318000000000000504944564944,Mayflash GameCube Controller Adapter,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b0,start:b9,guide:b0,leftshoulder:b4,rightshoulder:b7,leftstick:b0,rightstick:b0,leftx:a0,lefty:a1,rightx:a5,righty:a2,lefttrigger:a3,righttrigger:a4,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,
-79000018000000000000504944564944,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,
-2509e803000000000000504944564944,Mayflash Wii Classic Controller,a:b1,b:b0,x:b3,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:b11,dpdown:b13,dpleft:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,
-300f1001000000000000504944564944,Saitek P480 Rumble Pad,a:b2,b:b3,x:b0,y:b1,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b5,righttrigger:b7,platform:Windows,
-10280900000000000000504944564944,8Bitdo SFC30 GamePad,a:b1,b:b0,y:b3,x:b4,start:b11,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,platform:Windows,
-63252305000000000000504944564944,USB Vibration Joystick (BM),platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-20380900000000000000504944564944,8Bitdo NES30 PRO Wireless,platform:Windows,a:b0,b:b1,x:b3,y:b4,leftshoulder:b6,rightshoulder:b7,lefttrigger:b8,righttrigger:b9,back:b10,start:b11,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
-02200090000000000000504944564944,8Bitdo NES30 PRO USB,platform:Windows,a:b0,b:b1,x:b3,y:b4,leftshoulder:b6,rightshoulder:b7,lefttrigger:b8,righttrigger:b9,back:b10,start:b11,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
-ff113133000000000000504944564944,Gembird JPD-DualForce,platform:Windows,a:b2,b:b3,x:b0,y:b1,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,leftstick:b10,rightstick:b11,
-341a0108000000000000504944564944,EXEQ RF USB Gamepad 8206,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,leftstick:b8,rightstick:b7,back:b8,start:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Windows,
-c0111352000000000000504944564944,Battalife Joystick,platform:Windows,x:b4,a:b6,b:b7,y:b5,back:b2,start:b3,leftshoulder:b0,rightshoulder:b1,leftx:a0,lefty:a1,
-100801e5000000000000504944564944,NEXT Classic USB Game Controller,a:b0,b:b1,back:b8,start:b9,rightx:a2,righty:a3,leftx:a0,lefty:a1,platform:Windows,
-79000600000000000000504944564944,NGS Phantom,a:b2,b:b3,y:b1,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Windows,
+# Game Controller DB for SDL in 2.0.9 format
+# Source: https://github.com/gabomdq/SDL_GameControllerDB
 
-# OS X
-0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
-6d0400000000000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
-6d0400000000000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
-6d040000000000001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
-6d0400000000000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
-4c050000000000006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,
-4c05000000000000c405000000000000,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Mac OS X,
-4c05000000000000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Mac OS X,
-5e040000000000008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
-891600000000000000fd000000000000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b8,guide:b10,back:b9,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b11,dpleft:b13,dpdown:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Mac OS X,
-4f0400000000000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Mac OS X,
-8f0e0000000000000300000000000000,Piranha xtreme,platform:Mac OS X,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,
-0d0f0000000000004d00000000000000,HORI Gem Pad 3,platform:Mac OS X,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,
-79000000000000000600000000000000,G-Shark GP-702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Mac OS X,
-4f0400000000000015b3000000000000,Thrustmaster Dual Analog 3.2,platform:Mac OS X,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-AD1B00000000000001F9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
-050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,y:b9,x:b10,start:b6,guide:b8,back:b7,dpup:b2,dpleft:b0,dpdown:b3,dpright:b1,leftx:a0,lefty:a1,lefttrigger:b12,righttrigger:,leftshoulder:b11,platform:Mac OS X,
-83050000000000006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Mac OS X,
-bd1200000000000015d0000000000000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Mac OS X,
-79000000000000001100000000000000,Retrolink Classic Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a3,lefty:a4,platform:Mac OS X,
-5e04000000000000dd02000000000000,Xbox One Wired Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b9,guide:b10,start:b8,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b6,rightstick:b7,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-5e04000000000000ea02000000000000,Xbox Wireless Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b9,guide:b10,start:b8,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b6,rightstick:b7,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-5e04000000000000e002000000000000,Xbox Wireless Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b10,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,x:b18,y:b17,back:b7,guide:b8,start:b6,leftstick:b23,rightstick:b24,leftshoulder:b19,rightshoulder:b20,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b21,righttrigger:b22,platform:Mac OS X,
-79000000000000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,x:b0,y:b12,back:b32,start:b36,leftstick:b40,rightstick:b44,leftshoulder:b16,rightshoulder:b20,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a4,rightx:a8,righty:a12,lefttrigger:b24,righttrigger:b28,platform:Mac OS X,
-2509000000000000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,x:b3,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:b11,dpdown:b13,dpleft:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Mac OS X,
-351200000000000021ab000000000000,SFC30 Joystick,a:b1,b:b0,x:b4,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X,
-b4040000000000000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,x:b3,y:b4,back:b5,guide:b2,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X,
-81170000000000007e05000000000000,Sega Saturn,x:b0,a:b2,b:b4,y:b6,start:b13,dpleft:b15,dpdown:b16,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,lefttrigger:b10,rightshoulder:b9,righttrigger:a4,righttrigger:b11,leftx:a0,lefty:a2,platform:Mac OS X,
-10280000000000000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,x:b4,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X,
-d814000000000000cecf000000000000,MC Cthulhu,platform:Mac OS X,leftx:,lefty:,rightx:,righty:,lefttrigger:b6,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,righttrigger:b7,
-0d0f0000000000006600000000000000,HORIPAD FPS PLUS 4,platform:Mac OS X,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:a4,
+# Windows
+03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows,
+03000000c82d00002038000000000000,8bitdo,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
+03000000022000000090000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
+03000000203800000900000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
+03000000c82d00000060000000000000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
+03000000c82d00000061000000000000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
+03000000102800000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,
+03000000a00500003232000000000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,
+030000008f0e00001200000000000000,Acme GA-02,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
+03000000fa190000f0ff000000000000,Acteck AGJ-3200,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006f0e00000263000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006f0e00001101000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006f0e00001401000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006f0e00001402000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006f0e00001901000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006f0e00001a01000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000d62000001d57000000000000,Airflo PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000d6200000e557000000000000,Batarang,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows,
+030000006f0e00003201000000000000,Battlefield 4 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000bc2000006012000000000000,Betop 2126F,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000bc2000000055000000000000,Betop BFM Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
+03000000bc2000006312000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000bc2000006412000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000c01100000555000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000c01100000655000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000790000000700000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
+03000000808300000300000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
+030000006b1400000055000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+030000006b1400000103000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,
+0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
+03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows,
+03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
+030000005e040000a102000000000000,Controller (Xbox 360 Wireless Receiver for Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
+03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows,
+03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
+03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000bd12000002e0000000000000,Dual USB Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows,
+030000006f0e00003001000000000000,EA SPORTS PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000b80500000410000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,
+03000000b80500000610000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,
+030000008f0e00000f31000000000000,EXEQ,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,
+03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+03000000852100000201000000000000,FF-GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00008500000000000000,Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00008400000000000000,Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00008700000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,
+030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
+78696e70757403000000000000000000,Fightstick TES,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows,
+03000000790000000600000000000000,G-Shark GS-GP702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
+03000000790000002201000000000000,Game Controller for PC,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+0300000066f700000100000000000000,Game VIB Joystick,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows,
+03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows,
+030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000280400000140000000000000,GamePad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
+03000000ac0500003d03000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
+03000000ac0500004d04000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
+03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+030000005c1a00003330000000000000,Genius MaxFire Grandias 12V,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
+03000000300f00000b01000000000000,GGE909 Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
+03000000f0250000c283000000000000,Gioteck,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000f025000021c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000f0250000c383000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000f0250000c483000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
+03000000632500002605000000000000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
+030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00005400000000000000,Hori Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00000900000000000000,Hori Pad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00004d00000000000000,Hori Pad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f0000c100000000000000,Horipad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows,
+030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Windows,
+03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Windows,
+03000000b50700001403000000000000,Impact Black,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
+030000006f0e00002401000000000000,INJUSTICE FightStick PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
+03000000ac0500002c02000000000000,IPEGA,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
+03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
+030000006e0500000520000000000000,JC-P301U,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,
+030000006e0500000320000000000000,JC-U3613M (DInput),a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,
+030000006e0500000720000000000000,JC-W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,
+030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows,
+030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,
+03000000790000000200000000000000,King PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
+030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006d040000d2ca000000000000,Logitech Cordless Precision,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,
+030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700006652000000000000,Mad Catz C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700008433000000000000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700008483000000000000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700006252000000000000,Mad Catz Micro C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows,
+03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows,
+03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
+03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b0,leftshoulder:b4,leftstick:b0,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
+030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,platform:Windows,
+0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,
+03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000380700006382000000000000,MLG GamePad PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000efbe0000edfe000000000000,Monect Virtual Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
+03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
+030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows,
+03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
+03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,
+030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows,
+030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,rightshoulder:b5,rightstick:a2,righttrigger:b7,start:b9,x:b2,y:b3,platform:Windows,
+03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,platform:Windows,
+03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
+03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,
+03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,
+030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
+03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows,
+03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
+03000000100800000100000000000000,PS1 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
+030000008f0e00007530000000000000,PS1 Controller,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000100800000300000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
+03000000250900008888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
+03000000666600006706000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Windows,
+030000006b1400000303000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+030000009d0d00001330000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+03000000250900000500000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,
+030000004c0500006802000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b8,x:b3,y:b0,platform:Windows,
+03000000632500007505000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,
+030000008f0e00001431000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000003807000056a8000000000000,PS3 RF pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
+030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows,
+03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,
+03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows,
+03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,
+03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows,
+03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
+03000000321500000204000000000000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000321500000104000000000000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,
+0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,
+0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,
+030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
+03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
+03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
+03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,x:b0,y:b1,platform:Windows,
+03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
+03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
+03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
+03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows,
+03000000a30600002106000000000000,Saitek PS1000,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
+03000000a306000020f6000000000000,Saitek PS2700,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
+03000000300f00001101000000000000,Saitek Rumble Pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
+0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
+030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
+03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows,
+03000000341a00000908000000000000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+030000008f0e00000800000000000000,SpeedLink Strike FX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000c01100000591000000000000,Speedlink Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
+03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,platform:Windows,
+03000000790000001c18000000000000,STK-7024X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
+03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,platform:Windows,
+03000000d620000011a7000000000000,Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000004f04000007d0000000000000,T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
+030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
+030000004f04000023b3000000000000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
+030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows,
+030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
+03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
+03000000d62000006000000000000000,Tournament PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
+03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000300f00000701000000000000,USB 4-Axis 12-Button Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
+03000000341a00002308000000000000,USB gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+030000005509000000b4000000000000,USB gamepad,a:b10,b:b11,back:b5,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,guide:b14,leftshoulder:b8,leftstick:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b7,righttrigger:a5,rightx:a2,righty:a3,start:b4,x:b12,y:b13,platform:Windows,
+030000006b1400000203000000000000,USB gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+03000000790000000a00000000000000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
+03000000f0250000c183000000000000,USB gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000ff1100004133000000000000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
+03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
+03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
+03000000341a00000608000000000000,Xeox,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
+03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
+03000000786901006e70000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
+xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
+03000000790000004f18000000000000,ZD-T Android,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
+
+# Mac OS X
+03000000022000000090000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
+03000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
+03000000c82d00000190000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
+03000000102800000900000000000000,8Bitdo SFC30 GamePad Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
+03000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,
+03000000a00500003232000009010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,
+030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X,
+03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000790000000600000000000000,G-Shark GP-702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Mac OS X,
+03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
+030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,platform:Mac OS X,
+03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,
+03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,
+030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Mac OS X,
+030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Mac OS X,
+030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+030000006d04000018c2000000010000,Logitech RumblePad 2 USB,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000380700008433000000010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000380700008483000000010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X,
+0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X,
+03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X,
+03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X,
+030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
+030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,
+03000000d62000006dca000000010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,
+030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,
+030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+03000000321500000204000000010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000321500000104000000010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
+03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
+030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+03000000c6240000fefa000000000000,Rock Candy Gamepad for PS3,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X,
+03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X,
+030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
+030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
+030000005e0400008e02000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Mac OS X,
+03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,
+03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,
+03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,
+03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,
+030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X,
+030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,
+03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
+03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
+03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,
+050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X,
+050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X,
+030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,
+030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,
+030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
+030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
+03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X,
+03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
 
 # Linux
-0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
-03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
-030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
+05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
+05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
+03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
+05000000c82d00000061000000010000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
+05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
+05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
+05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,
+05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,
+030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
+05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,
+03000000120c00000500000010010000,AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux,
+03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux,
+03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
+03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,
+03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
+03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,
+03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,
+030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,
+03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
+0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
+030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:a0,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:a3,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
+030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux,
+030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
+0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
+06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux,
+03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
+030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,platform:Linux,
+030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
+030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
+030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux,
+03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
+050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,
+03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,platform:Linux,
+0500000049190000020400001b010000,Ipega PG-9069 - Bluetooth Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b161,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
+030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,
+03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,
+03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
+030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux,
+030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux,
+030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
 030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-030000006d04000016c2000011010000,Logitech F310 Gamepad (DInput),x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Linux,
 030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
 030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000006d04000015c2000010010000,Logitech Logitech Extreme 3D,a:b0,b:b4,back:b6,guide:b8,leftshoulder:b9,leftstick:h0.8,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:h0.2,start:b7,x:b2,y:b5,platform:Linux,
+030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,
+05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,
+03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
+03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+0300000079000000d218000011010000,MAGIC-NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000120c00000500000000010000,Manta Dualshock 2,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
+03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,
+03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,
+030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,
+030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000e302000003020000,Microsoft X-Box One Elite pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000dd02000003020000,Microsoft X-Box One pad (Firmware 2015),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
+030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
+05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,
+03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
+030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux,
+050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
+050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,
+05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
+030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,
+03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
+03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
+05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,
+05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,
+03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
+030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000d62000006dca000011010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,
+03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
+030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
 030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
-030000004c050000c405000011010000,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,
-050000004c050000c405000000010000,Sony DualShock 4 BT,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
-030000004c050000cc09000011010000,Sony DualShock 4 V2,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,
-050000004c050000cc09000000010000,Sony DualShock 4 V2 BT,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,
-030000004c050000a00b000011010000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,
-030000006f0e00003001000001010000,EA Sports PS3 Controller,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,
-03000000de280000ff11000001000000,Valve Streaming Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
+030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
+050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
+050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
+050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
+05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
+060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
+030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
+030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
+030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
+050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
+050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
+050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
+03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux,
+030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,
+030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000008916000000fd000024010000,Razer Onza Tournament Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
+050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
+0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,
+0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,
+030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
+03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,
+03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux,
+03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,
+03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux,
+03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
+03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,platform:Linux,
+03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
+03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
+03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
+03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
+03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
+030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
+03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
+03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
+03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
+05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
+05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
+03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
+0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,
+030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,
+030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,
+030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux,
+030000004f04000026b3000002040000,Thrustmaster Gamepad GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000c6240000025b000002020000,Thrustmaster GPX Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+030000004f04000012b3000010010000,Thrustmaster vibrating gamepad,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,
+03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,
+03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,
+03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
+03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
+03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,
+03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,
+05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
+030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,y:b0,x:b3,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux,
-03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Linux,
-030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,y:b3,x:b1,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux,
-030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5,
-030000008f0e00000300000010010000,GreenAsia Inc.    USB Joystick     ,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,
-030000008f0e00001200000010010000,GreenAsia Inc.      USB  Joystick  ,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,
-030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,
-030000006d04000016c2000010010000,Logitech Logitech Dual Action,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-03000000260900008888000000010000,GameCube {WiseGroup USB box},a:b0,b:b2,y:b3,x:b1,start:b7,leftshoulder:,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,rightstick:,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Linux,
-030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,y:b4,x:b3,start:b8,guide:b5,back:b2,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b9,righttrigger:b10,platform:Linux,
-030000006d04000018c2000010010000,Logitech Logitech RumblePad 2 USB,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-05000000d6200000ad0d000001000000,Moga Pro,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,
-030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,
-030000004f04000008d0000000010000,Thrustmaster Run N Drive  Wireless,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,
-0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux,
-0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux,
-030000006f0e00001f01000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-03000000280400000140000000010000,Gravis GamePad Pro USB ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1,
-030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-030000005e0400008502000000010000,Microsoft X-Box pad (Japan),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,
-03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,platform:Linux,a:b2,b:b1,y:b0,x:b3,start:b8,back:b9,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,
-030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,
-030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux,
-03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,
-060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux,
-050000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux,
-03000000790000000600000010010000,DragonRise Inc.   Generic   USB  Joystick  ,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a3,rightx:a1,righty:a4,
-03000000666600000488000000010000,Super Joy Box 5 Pro,platform:Linux,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,dpup:b12,dpleft:b15,dpdown:b14,dpright:b13,
-05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,
-05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,
-030000008916000001fd000024010000,Razer Onza Classic Edition,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:b11,dpdown:b14,dpright:b12,dpup:b13,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-030000005e040000d102000001010000,Microsoft X-Box One pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-030000005e040000dd02000003020000,Microsoft X-Box One pad v2,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux,
-03000000790000001100000010010000,RetroLink Saturn Classic Controller,platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b5,guide:b2,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,
-050000007e0500003003000001000000,Nintendo Wii U Pro Controller,platform:Linux,a:b0,b:b1,x:b3,y:b2,back:b8,start:b9,guide:b10,leftshoulder:b4,rightshoulder:b5,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:b13,dpleft:b15,dpdown:b14,dpright:b16,
-030000005e0400008e02000004010000,Microsoft X-Box 360 pad,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,
-030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,
-030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7
-03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b7,back:b6,guide:b8,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,lefttrigger:a5,righttrigger:a4,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux,
-030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-030000006f0e00001304000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:a0,rightstick:a3,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-03000000830500006020000010010000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux,
-03000000bd12000015d0000010010000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux,
-03000000790000001100000010010000,Retrolink Classic Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux,
-03000000c9110000f055000011010000,HJC Game GAMEPAD,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b11,rightshoulder:b5,rightx:a2,start:b9,righty:a3,dpleft:h0.8,lefttrigger:b6,x:b2,dpup:h0.1,back:b8,leftstick:b10,leftshoulder:b4,y:b3,a:b0,dpright:h0.2,righttrigger:b7,b:b1,platform:Linux,
-03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux,
-03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,y:b3,x:b0,start:b9,guide:,back:,leftstick:,rightstick:,leftshoulder:,dpleft:b15,dpdown:b14,dpright:b13,leftx:a0,lefty:a1,rightx:a5,righty:a2,lefttrigger:a3,righttrigger:a4,rightshoulder:b7,dpup:b12,platform:Linux,
-030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,platform:Linux,x:b0,a:b2,b:b3,y:b1,back:b10,guide:b12,start:b11,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,platform:Linux,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,guide:b8,leftstick:b9,rightstick:b10,lefttrigger:a2,righttrigger:a5,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux,
-030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,platform:Linux,a:b0,b:b2,x:b1,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,
-05000000102800000900000000010000,8Bitdo SFC30 GamePad,platform:Linux,x:b4,a:b1,b:b0,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,
-03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,righttrigger:b7,leftx:a0,lefty:a1,
-030000000d0f00000d00000000010000,hori,platform:Linux,a:b0,b:b6,y:b2,x:b1,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,start:b9,guide:b10,back:b8,leftshoulder:b3,rightshoulder:b7,leftx:b4,lefty:b5,
-03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,
-03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,platform:Linux,a:b0,b:b1,y:b2,x:b3,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7,
-03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),platform:Linux,a:b3,b:b4,y:b1,x:b0,start:b7,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,
-05000000010000000100000003000000,Nintendo Wiimote,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b9,guide:b10,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,
-030000005e0400008e02000062230000,Microsoft X-Box 360 pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,y:b1,x:b0,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b6,righttrigger:b7,platform:Linux,
-030000006f0e00000103000000020000,Logic3 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-05000000380700006652000025010000,Mad Catz C.T.R.L.R ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,
-030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,
-03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,lefttrigger:a2,righttrigger:a5,
-05000000a00500003232000001000000,8Bitdo Zero GamePad,platform:Linux,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,
-030000001008000001e5000010010000,NEXT Classic USB Game Controller,a:b0,b:b1,back:b8,start:b9,rightx:a2,righty:a3,leftx:a0,lefty:a1,platform:Linux,
-03000000100800000300000010010000,USB Gamepad,platform:Linux,a:b2,b:b1,x:b3,y:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,
-05000000ac0500003232000001000000,VR-BOX,platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,
-03000000780000000600000010010000,Microntek USB Joystick,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftx:a0,lefty:a1,
+030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,
+030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000ea02000001030000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
+03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
+05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,
+03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,
+xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
+
+# Android
+05000000bc20000000550000ffff3f00,GameSir G3w,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
+05000000d6020000e5890000dfff3f00,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android,
+64633436313965656664373634323364,Microsoft X-Box 360 pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,
+050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b17,y:b2,platform:Android,
+37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
+4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
+61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
+050000005509000003720000cf7f3f00,NVIDIA Controller v01.01,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
+050000005509000010720000ffff3f00,NVIDIA Controller v01.03,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
+050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
+050000004c050000c4050000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,
+050000004c050000cc090000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,
+35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,
+050000003215000000090000bf7f3f00,Razer Serval,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,
+05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,
+05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,
+5477696e20555342204a6f7973746963,Twin USB Joystick,a:b22,b:b21,back:b28,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android,
+050000005e040000e00200000ffe3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b17,y:b2,platform:Android,
+050000005e040000fd020000ffff3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
+050000005e04000091020000ff073f00,Xbox Wireless Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,
+34356136633366613530316338376136,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android,
+
+# iOS
+05000000ac0500000100000000006d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS,
+05000000ac0500000200000000006d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS,
+4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS,
+4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS,
+05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS,
+05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,
+05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,
--- a/gen_x86.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/gen_x86.c	Sat Jan 05 00:58:08 2019 -0800
@@ -170,6 +170,12 @@
 	"b", "w", "d", "q"
 };
 
+#ifdef X86_64
+#define CHECK_DISP(disp) (disp <= 0x7FFFFFFF && disp >= -2147483648)
+#else
+#define CHECK_DISP(disp) 1
+#endif
+
 void jmp_nocheck(code_info *code, code_ptr dest)
 {
 	code_ptr out = code->cur;
@@ -179,7 +185,7 @@
 		*(out++) = disp;
 	} else {
 		disp = dest-(out+5);
-		if (disp <= 0x7FFFFFFF && disp >= -2147483648) {
+		if (CHECK_DISP(disp)) {
 			*(out++) = OP_JMP;
 			*(out++) = disp;
 			disp >>= 8;
@@ -1917,7 +1923,7 @@
 		*(out++) = disp;
 	} else {
 		disp = dest-(out+6);
-		if (disp <= 0x7FFFFFFF && disp >= -2147483648) {
+		if (CHECK_DISP(disp)) {
 			*(out++) = PRE_2BYTE;
 			*(out++) = OP2_JCC | cc;
 			*(out++) = disp;
@@ -1944,7 +1950,7 @@
 		*(out++) = disp;
 	} else {
 		disp = dest-(out+5);
-		if (disp <= 0x7FFFFFFF && disp >= -2147483648) {
+		if (CHECK_DISP(disp)) {
 			*(out++) = OP_JMP;
 			*(out++) = disp;
 			disp >>= 8;
@@ -1991,7 +1997,7 @@
 	check_alloc_code(code, 5);
 	code_ptr out = code->cur;
 	ptrdiff_t disp = fun-(out+5);
-	if (disp <= 0x7FFFFFFF && disp >= -2147483648) {
+	if (CHECK_DISP(disp)) {
 		*(out++) = OP_CALL;
 		*(out++) = disp;
 		disp >>= 8;
@@ -2029,7 +2035,7 @@
 	check_alloc_code(code, 5);
 	code_ptr out = code->cur;
 	ptrdiff_t disp = fun-(out+5);
-	if (disp <= 0x7FFFFFFF && disp >= -2147483648) {
+	if (CHECK_DISP(disp)) {
 		*(out++) = OP_CALL;
 		*(out++) = disp;
 		disp >>= 8;
@@ -2146,6 +2152,7 @@
 	{
 		push_r(code, arg_arr[i]);
 	}
+	free(arg_arr);
 	
 	return stack_args * sizeof(void *) + adjust;
 }
--- a/genesis.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/genesis.c	Sat Jan 05 00:58:08 2019 -0800
@@ -16,6 +16,9 @@
 #include "util.h"
 #include "debug.h"
 #include "gdb_remote.h"
+#include "saves.h"
+#include "bindings.h"
+#include "jcart.h"
 #define MCLKS_NTSC 53693175
 #define MCLKS_PAL  53203395
 
@@ -29,7 +32,7 @@
 
 //TODO: Figure out the exact value for this
 #define LINES_NTSC 262
-#define LINES_PAL 312
+#define LINES_PAL 313
 
 #define MAX_SOUND_CYCLES 100000	
 
@@ -110,7 +113,7 @@
 
 static void update_z80_bank_pointer(genesis_context *gen)
 {
-	if (gen->z80->bank_reg < 0x100) {
+	if (gen->z80->bank_reg < 0x140) {
 		gen->z80->mem_pointers[1] = get_native_pointer(gen->z80->bank_reg << 15, (void **)gen->m68k->mem_pointers, &gen->m68k->options->gen);
 	} else {
 		gen->z80->mem_pointers[1] = NULL;
@@ -125,6 +128,7 @@
 	gen->z80->bank_reg = load_int16(buf) & 0x1FF;
 }
 
+static void adjust_int_cycle(m68k_context * context, vdp_context * v_context);
 void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen)
 {
 	register_section_handler(buf, (section_handler){.fun = m68k_deserialize, .data = gen->m68k}, SECTION_68000);
@@ -144,6 +148,7 @@
 		load_section(buf);
 	}
 	update_z80_bank_pointer(gen);
+	adjust_int_cycle(gen->m68k, gen->vdp);
 }
 
 uint16_t read_dma_value(uint32_t address)
@@ -243,6 +248,7 @@
 	genesis_context * gen = z_context->system;
 	z_context->int_pulse_start = vdp_next_vint_z80(gen->vdp);
 	z_context->int_pulse_end = z_context->int_pulse_start + Z80_INT_PULSE_MCLKS;
+	z_context->im2_vector = 0xFF;
 }
 
 static void sync_z80(z80_context * z_context, uint32_t mclks)
@@ -307,6 +313,11 @@
 	sync_z80(z_context, mclks);
 	sync_sound(gen, mclks);
 	vdp_run_context(v_context, mclks);
+	if (mclks >= gen->reset_cycle) {
+		gen->reset_requested = 1;
+		context->should_return = 1;
+		gen->reset_cycle = CYCLE_NEVER;
+	}
 	if (v_context->frame != last_frame_num) {
 		//printf("reached frame end %d | MCLK Cycles: %d, Target: %d, VDP cycles: %d, vcounter: %d, hslot: %d\n", last_frame_num, mclks, gen->frame_end, v_context->cycles, v_context->vcounter, v_context->hslot);
 		last_frame_num = v_context->frame;
@@ -323,6 +334,9 @@
 			io_adjust_cycles(gen->io.ports, context->current_cycle, deduction);
 			io_adjust_cycles(gen->io.ports+1, context->current_cycle, deduction);
 			io_adjust_cycles(gen->io.ports+2, context->current_cycle, deduction);
+			if (gen->mapper_type == MAPPER_JCART) {
+				jcart_adjust_cycles(gen, deduction);
+			}
 			context->current_cycle -= deduction;
 			z80_adjust_cycles(z_context, deduction);
 			gen->ym->current_cycle -= deduction;
@@ -330,6 +344,9 @@
 			if (gen->ym->write_cycle != CYCLE_NEVER) {
 				gen->ym->write_cycle = gen->ym->write_cycle >= deduction ? gen->ym->write_cycle - deduction : 0;
 			}
+			if (gen->reset_cycle != CYCLE_NEVER) {
+				gen->reset_cycle -= deduction;
+			}
 		}
 	}
 	gen->frame_end = vdp_cycles_to_frame_end(v_context);
@@ -344,6 +361,9 @@
 		context->sync_cycle = context->current_cycle + 1;
 	}
 	adjust_int_cycle(context, v_context);
+	if (gen->reset_cycle < context->target_cycle) {
+		context->target_cycle = gen->reset_cycle;
+	}
 	if (address) {
 		if (gen->header.enter_debugger) {
 			gen->header.enter_debugger = 0;
@@ -359,18 +379,7 @@
 					sync_z80(z_context, z_context->current_cycle + MCLKS_PER_Z80);
 				}
 			}
-			char *save_path;
-			if (slot == QUICK_SAVE_SLOT) {
-				save_path = save_state_path;
-			} else {
-				char slotname[] = "slot_0.state";
-				slotname[5] = '0' + slot;
-				if (!use_native_states) {
-					strcpy(slotname + 7, "gst");
-				}
-				char const *parts[] = {gen->header.save_dir, PATH_SEP, slotname};
-				save_path = alloc_concat_m(3, parts);
-			}
+			char *save_path = get_slot_name(&gen->header, slot, use_native_states ? "state" : "gst");
 			if (use_native_states) {
 				serialize_buffer state;
 				init_serialize(&state);
@@ -381,9 +390,7 @@
 				save_gst(gen, save_path, address);
 			}
 			printf("Saved state to %s\n", save_path);
-			if (slot != QUICK_SAVE_SLOT) {
-				free(save_path);
-			}
+			free(save_path);
 		} else if(gen->header.save_state) {
 			context->sync_cycle = context->current_cycle + 1;
 		}
@@ -1008,37 +1015,24 @@
 	gen->master_clock = gen->normal_clock;
 }
 
-static void handle_reset_requests(genesis_context *gen)
-{
-	while (gen->reset_requested)
-	{
-		gen->reset_requested = 0;
-		z80_assert_reset(gen->z80, gen->m68k->current_cycle);
-		z80_clear_busreq(gen->z80, gen->m68k->current_cycle);
-		ym_reset(gen->ym);
-		//Is there any sort of VDP reset?
-		m68k_reset(gen->m68k);
-	}
-	vdp_release_framebuffer(gen->vdp);
-}
-
 #include "m68k_internal.h" //needed for get_native_address_trans, should be eliminated once handling of PC is cleaned up
 static uint8_t load_state(system_header *system, uint8_t slot)
 {
 	genesis_context *gen = (genesis_context *)system;
-	char numslotname[] = "slot_0.state";
-	char *slotname;
-	if (slot == QUICK_SAVE_SLOT) {
-		slotname = "quicksave.state";
-	} else {
-		numslotname[5] = '0' + slot;
-		slotname = numslotname;
-	}
-	char const *parts[] = {gen->header.save_dir, PATH_SEP, slotname};
-	char *statepath = alloc_concat_m(3, parts);
+	char *statepath = get_slot_name(system, slot, "state");
 	deserialize_buffer state;
 	uint32_t pc = 0;
 	uint8_t ret;
+	if (!gen->m68k->resume_pc) {
+		system->delayed_load_slot = slot + 1;
+		gen->m68k->should_return = 1;
+		ret = get_modification_time(statepath) != 0;
+		if (!ret) {
+			strcpy(statepath + strlen(statepath)-strlen("state"), "gst");
+			ret = get_modification_time(statepath) != 0;
+		}
+		goto done;
+	}
 	if (load_from_file(&state, statepath)) {
 		genesis_deserialize(&state, gen);
 		free(state.data);
@@ -1053,15 +1047,39 @@
 	if (ret) {
 		gen->m68k->resume_pc = get_native_address_trans(gen->m68k, pc);
 	}
+done:
 	free(statepath);
 	return ret;
 }
 
+static void handle_reset_requests(genesis_context *gen)
+{
+	while (gen->reset_requested || gen->header.delayed_load_slot)
+	{
+		if (gen->reset_requested) {
+			gen->reset_requested = 0;
+			gen->m68k->should_return = 0;
+			z80_assert_reset(gen->z80, gen->m68k->current_cycle);
+			z80_clear_busreq(gen->z80, gen->m68k->current_cycle);
+			ym_reset(gen->ym);
+			//Is there any sort of VDP reset?
+			m68k_reset(gen->m68k);
+		}
+		if (gen->header.delayed_load_slot) {
+			load_state(&gen->header, gen->header.delayed_load_slot - 1);
+			gen->header.delayed_load_slot = 0;
+			resume_68k(gen->m68k);
+		}
+	}
+	bindings_release_capture();
+	vdp_release_framebuffer(gen->vdp);
+	render_pause_source(gen->ym->audio);
+	render_pause_source(gen->psg->audio);
+}
+
 static void start_genesis(system_header *system, char *statefile)
 {
 	genesis_context *gen = (genesis_context *)system;
-	set_keybindings(&gen->io);
-	render_set_video_standard((gen->version_reg & HZ50) ? VID_PAL : VID_NTSC);
 	if (statefile) {
 		//first try loading as a native format savestate
 		deserialize_buffer state;
@@ -1100,9 +1118,11 @@
 static void resume_genesis(system_header *system)
 {
 	genesis_context *gen = (genesis_context *)system;
-	map_all_bindings(&gen->io);
 	render_set_video_standard((gen->version_reg & HZ50) ? VID_PAL : VID_NTSC);
+	bindings_reacquire_capture();
 	vdp_reacquire_framebuffer(gen->vdp);
+	render_resume_source(gen->ym->audio);
+	render_resume_source(gen->psg->audio);
 	resume_68k(gen->m68k);
 	handle_reset_requests(gen);
 }
@@ -1110,19 +1130,7 @@
 static void inc_debug_mode(system_header *system)
 {
 	genesis_context *gen = (genesis_context *)system;
-	gen->vdp->debug++;
-	if (gen->vdp->debug == 7) {
-		gen->vdp->debug = 0;
-	}
-}
-
-static void inc_debug_pal(system_header *system)
-{
-	genesis_context *gen = (genesis_context *)system;
-	gen->vdp->debug_pal++;
-	if (gen->vdp->debug_pal == 4) {
-		gen->vdp->debug_pal = 0;
-	}
+	vdp_inc_debug_mode(gen->vdp);
 }
 
 static void request_exit(system_header *system)
@@ -1163,14 +1171,20 @@
 static void soft_reset(system_header *system)
 {
 	genesis_context *gen = (genesis_context *)system;
-	gen->m68k->should_return = 1;
-	gen->reset_requested = 1;
+	if (gen->reset_cycle == CYCLE_NEVER) {
+		double random = (double)rand()/(double)RAND_MAX;
+		gen->reset_cycle = gen->m68k->current_cycle + random * MCLKS_LINE * (gen->version_reg & HZ50 ? LINES_PAL : LINES_NTSC);
+		if (gen->reset_cycle < gen->m68k->target_cycle) {
+			gen->m68k->target_cycle = gen->reset_cycle;
+		}
+	}
 }
 
 static void free_genesis(system_header *system)
 {
 	genesis_context *gen = (genesis_context *)system;
 	vdp_free(gen->vdp);
+	memmap_chunk *map = (memmap_chunk *)gen->m68k->options->gen.memmap;
 	m68k_options_free(gen->m68k->options);
 	free(gen->cart);
 	free(gen->m68k);
@@ -1180,12 +1194,72 @@
 	free(gen->zram);
 	ym_free(gen->ym);
 	psg_free(gen->psg);
-	free(gen->save_storage);
 	free(gen->header.save_dir);
+	free_rom_info(&gen->header.info);
 	free(gen->lock_on);
 	free(gen);
 }
 
+static void gamepad_down(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_gamepad_down(&gen->io, gamepad_num, button);
+	if (gen->mapper_type == MAPPER_JCART) {
+		jcart_gamepad_down(gen, gamepad_num, button);
+	}
+}
+
+static void gamepad_up(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_gamepad_up(&gen->io, gamepad_num, button);
+	if (gen->mapper_type == MAPPER_JCART) {
+		jcart_gamepad_up(gen, gamepad_num, button);
+	}
+}
+
+static void mouse_down(system_header *system, uint8_t mouse_num, uint8_t button)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_mouse_down(&gen->io, mouse_num, button);
+}
+
+static void mouse_up(system_header *system, uint8_t mouse_num, uint8_t button)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_mouse_up(&gen->io, mouse_num, button);
+}
+
+static void mouse_motion_absolute(system_header *system, uint8_t mouse_num, uint16_t x, uint16_t y)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_mouse_motion_absolute(&gen->io, mouse_num, x, y);
+}
+
+static void mouse_motion_relative(system_header *system, uint8_t mouse_num, int32_t x, int32_t y)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_mouse_motion_relative(&gen->io, mouse_num, x, y);
+}
+
+static void keyboard_down(system_header *system, uint8_t scancode)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_keyboard_down(&gen->io, scancode);
+}
+
+static void keyboard_up(system_header *system, uint8_t scancode)
+{
+	genesis_context *gen = (genesis_context *)system;
+	io_keyboard_up(&gen->io, scancode);
+}
+
+static void config_updated(system_header *system)
+{
+	genesis_context *gen = (genesis_context *)system;
+	setup_io_devices(config, &system->info, &gen->io);
+}
+
 static genesis_context *shared_init(uint32_t system_opts, rom_info *rom, uint8_t force_region)
 {
 	static memmap_chunk z80_map[] = {
@@ -1217,13 +1291,20 @@
 	gen->header.get_open_bus_value = get_open_bus_value;
 	gen->header.request_exit = request_exit;
 	gen->header.inc_debug_mode = inc_debug_mode;
-	gen->header.inc_debug_pal = inc_debug_pal;
+	gen->header.gamepad_down = gamepad_down;
+	gen->header.gamepad_up = gamepad_up;
+	gen->header.mouse_down = mouse_down;
+	gen->header.mouse_up = mouse_up;
+	gen->header.mouse_motion_absolute = mouse_motion_absolute;
+	gen->header.mouse_motion_relative = mouse_motion_relative;
+	gen->header.keyboard_down = keyboard_down;
+	gen->header.keyboard_up = keyboard_up;
+	gen->header.config_updated = config_updated;
 	gen->header.type = SYSTEM_GENESIS;
-	
+	gen->header.info = *rom;
 	set_region(gen, rom, force_region);
 	
-	gen->vdp = malloc(sizeof(vdp_context));
-	init_vdp_context(gen->vdp, gen->version_reg & 0x40);
+	gen->vdp = init_vdp_context(gen->version_reg & 0x40);
 	gen->vdp->system = &gen->header;
 	gen->frame_end = vdp_cycles_to_frame_end(gen->vdp);
 	char * config_cycles = tern_find_path(config, "clocks\0max_cycles\0", TVAL_PTR).ptrval;
@@ -1231,16 +1312,14 @@
 	gen->int_latency_prev1 = MCLKS_PER_68K * 32;
 	gen->int_latency_prev2 = MCLKS_PER_68K * 16;
 	
-	char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval;
-	uint32_t lowpass_cutoff = lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : DEFAULT_LOWPASS_CUTOFF;
+	render_set_video_standard((gen->version_reg & HZ50) ? VID_PAL : VID_NTSC);
 	
 	gen->ym = malloc(sizeof(ym2612_context));
-	ym_init(gen->ym, render_sample_rate(), gen->master_clock, MCLKS_PER_YM, render_audio_buffer(), system_opts, lowpass_cutoff);
+	ym_init(gen->ym, gen->master_clock, MCLKS_PER_YM, system_opts);
 
 	gen->psg = malloc(sizeof(psg_context));
-	psg_init(gen->psg, render_sample_rate(), gen->master_clock, MCLKS_PER_PSG, render_audio_buffer(), lowpass_cutoff);
+	psg_init(gen->psg, gen->master_clock, MCLKS_PER_PSG);
 
-	gen->zram = calloc(1, Z80_RAM_BYTES);
 	z80_map[0].buffer = gen->zram = calloc(1, Z80_RAM_BYTES);
 #ifndef NO_Z80
 	z80_options *z_opts = malloc(sizeof(z80_options));
@@ -1298,6 +1377,7 @@
 	gen->lock_on = lock_on;
 	
 	setup_io_devices(config, rom, &gen->io);
+	gen->header.has_keyboard = io_has_keyboard(&gen->io);
 	gen->mapper_type = rom->mapper_type;
 	gen->save_type = rom->save_type;
 	if (gen->save_type != SAVE_NONE) {
@@ -1309,7 +1389,8 @@
 		if (gen->save_type == SAVE_I2C) {
 			eeprom_init(&gen->eeprom, gen->save_storage, gen->save_size);
 		} else if (gen->save_type == SAVE_NOR) {
-			nor_flash_init(&gen->nor, gen->save_storage, gen->save_size, rom->save_page_size, rom->save_product_id, rom->save_bus);
+			memcpy(&gen->nor, rom->nor, sizeof(gen->nor));
+			//nor_flash_init(&gen->nor, gen->save_storage, gen->save_size, rom->save_page_size, rom->save_product_id, rom->save_bus);
 		}
 	} else {
 		gen->save_storage = NULL;
@@ -1363,32 +1444,43 @@
 };
 const size_t base_chunks = sizeof(base_map)/sizeof(*base_map); 
 
-genesis_context *alloc_config_genesis(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t ym_opts, uint8_t force_region, rom_info *info_out)
+genesis_context *alloc_config_genesis(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t ym_opts, uint8_t force_region)
 {
 	tern_node *rom_db = get_rom_db();
-	*info_out = configure_rom(rom_db, rom, rom_size, lock_on, lock_on_size, base_map, base_chunks);
-	rom = info_out->rom;
-	rom_size = info_out->rom_size;
+	rom_info info = configure_rom(rom_db, rom, rom_size, lock_on, lock_on_size, base_map, base_chunks);
+	rom = info.rom;
+	rom_size = info.rom_size;
 #ifndef BLASTEM_BIG_ENDIAN
 	byteswap_rom(rom_size, rom);
 	if (lock_on) {
 		byteswap_rom(lock_on_size, lock_on);
 	}
 #endif
-	return alloc_init_genesis(info_out, rom, lock_on, ym_opts, force_region);
+	char *m68k_divider = tern_find_path(config, "clocks\0m68k_divider\0", TVAL_PTR).ptrval;
+	if (!m68k_divider) {
+		m68k_divider = "7";
+	}
+	MCLKS_PER_68K = atoi(m68k_divider);
+	if (!MCLKS_PER_68K) {
+		MCLKS_PER_68K = 7;
+	}
+	return alloc_init_genesis(&info, rom, lock_on, ym_opts, force_region);
 }
 
-genesis_context *alloc_config_genesis_cdboot(system_media *media, uint32_t system_opts, uint8_t force_region, rom_info *info_out)
+genesis_context *alloc_config_genesis_cdboot(system_media *media, uint32_t system_opts, uint8_t force_region)
 {
-	segacd_context *cd = alloc_configure_segacd(media, system_opts, force_region, info_out);
-	genesis_context *gen = shared_init(system_opts, info_out, force_region);
+	tern_node *rom_db = get_rom_db();
+	rom_info info = configure_rom(rom_db, media->buffer, media->size, NULL, 0, base_map, base_chunks);
+	
+	segacd_context *cd = alloc_configure_segacd(media, system_opts, force_region, &info);
+	genesis_context *gen = shared_init(system_opts, &info, force_region);
 	gen->cart = gen->lock_on = NULL;
 	gen->save_storage = NULL;
 	gen->save_type = SAVE_NONE;
 	gen->version_reg &= ~NO_DISK;
 	
 	gen->expansion = cd;
-	setup_io_devices(config, info_out, &gen->io);
+	setup_io_devices(config, &info, &gen->io);
 	
 	uint32_t cd_chunks;
 	memmap_chunk *cd_map = segacd_main_cpu_map(gen->expansion, &cd_chunks);
--- a/genesis.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/genesis.h	Sat Jan 05 00:58:08 2019 -0800
@@ -45,6 +45,7 @@
 	uint32_t        max_cycles;
 	uint32_t        int_latency_prev1;
 	uint32_t        int_latency_prev2;
+	uint32_t        reset_cycle;
 	uint8_t         bank_regs[8];
 	uint16_t        mapper_start_index;
 	uint8_t         mapper_type;
@@ -60,10 +61,9 @@
 #define RAM_WORDS 32 * 1024
 #define Z80_RAM_BYTES 8 * 1024
 
-uint16_t read_dma_value(uint32_t address);
 m68k_context * sync_components(m68k_context *context, uint32_t address);
-genesis_context *alloc_config_genesis(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t system_opts, uint8_t force_region, rom_info *info_out);
-genesis_context *alloc_config_genesis_cdboot(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info_out);
+genesis_context *alloc_config_genesis(void *rom, uint32_t rom_size, void *lock_on, uint32_t lock_on_size, uint32_t system_opts, uint8_t force_region);
+genesis_context *alloc_config_genesis_cdboot(system_media *media, uint32_t system_opts, uint8_t force_region);
 void genesis_serialize(genesis_context *gen, serialize_buffer *buf, uint32_t m68k_pc);
 void genesis_deserialize(deserialize_buffer *buf, genesis_context *gen);
 
Binary file images/360.png has changed
Binary file images/ps4.png has changed
Binary file images/ps4_6b.png has changed
--- a/io.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/io.c	Sat Jan 05 00:58:08 2019 -0800
@@ -18,16 +18,15 @@
 #include "serialize.h"
 #include "io.h"
 #include "blastem.h"
-#include "genesis.h"
-#include "sms.h"
 #include "render.h"
 #include "util.h"
-#include "menu.h"
+#include "bindings.h"
 
 #define CYCLE_NEVER 0xFFFFFFFF
 #define MIN_POLL_INTERVAL 6840
 
 const char * device_type_names[] = {
+	"None",
 	"SMS gamepad",
 	"3-button gamepad",
 	"6-button gamepad",
@@ -40,308 +39,150 @@
 	"EA 4-way Play cable A",
 	"EA 4-way Play cable B",
 	"Sega Parallel Transfer Board",
-	"Generic Device",
-	"None"
-};
-
-enum {
-	BIND_NONE,
-	BIND_UI,
-	BIND_GAMEPAD1,
-	BIND_GAMEPAD2,
-	BIND_GAMEPAD3,
-	BIND_GAMEPAD4,
-	BIND_GAMEPAD5,
-	BIND_GAMEPAD6,
-	BIND_GAMEPAD7,
-	BIND_GAMEPAD8,
-	BIND_MOUSE1,
-	BIND_MOUSE2,
-	BIND_MOUSE3,
-	BIND_MOUSE4,
-	BIND_MOUSE5,
-	BIND_MOUSE6,
-	BIND_MOUSE7,
-	BIND_MOUSE8
+	"Generic Device"
 };
 
-typedef enum {
-	UI_DEBUG_MODE_INC,
-	UI_DEBUG_PAL_INC,
-	UI_ENTER_DEBUGGER,
-	UI_SAVE_STATE,
-	UI_SET_SPEED,
-	UI_NEXT_SPEED,
-	UI_PREV_SPEED,
-	UI_RELEASE_MOUSE,
-	UI_TOGGLE_KEYBOARD_CAPTURE,
-	UI_TOGGLE_FULLSCREEN,
-	UI_SOFT_RESET,
-	UI_RELOAD,
-	UI_SMS_PAUSE,
-	UI_SCREENSHOT,
-	UI_EXIT
-} ui_action;
+#define GAMEPAD_TH0 0
+#define GAMEPAD_TH1 1
+#define GAMEPAD_EXTRA 2
+#define GAMEPAD_NONE 0xF
 
-typedef enum {
-	MOUSE_NONE,     //mouse is ignored
-	MOUSE_ABSOLUTE, //really only useful for menu ROM
-	MOUSE_RELATIVE, //for full screen
-	MOUSE_CAPTURE   //for windowed mode
-} mouse_modes;
-
+#define IO_TH0 0
+#define IO_TH1 1
+#define IO_STATE 2
 
-typedef struct {
-	io_port *port;
-	uint8_t bind_type;
-	uint8_t subtype_a;
-	uint8_t subtype_b;
-	uint8_t value;
-} keybinding;
-
-typedef struct {
-	keybinding bindings[4];
-	uint8_t    state;
-} joydpad;
-
-typedef struct {
-	keybinding positive;
-	keybinding negative;
-	int16_t    value;
-} joyaxis;
+enum {
+	IO_WRITE_PENDING,
+	IO_WRITTEN,
+	IO_READ_PENDING,
+	IO_READ
+};
 
 typedef struct {
-	keybinding *buttons;
-	joydpad    *dpads;
-	joyaxis    *axes;
-	uint32_t   num_buttons; //number of entries in the buttons array, not necessarily the number of buttons on the device
-	uint32_t   num_dpads;   //number of entries in the dpads array, not necessarily the number of dpads on the device
-	uint32_t   num_axes;    //number of entries in the axes array, not necessarily the number of dpads on the device
-} joystick;
+	uint8_t states[2], value;
+} gp_button_def;
 
-typedef struct {
-	io_port    *motion_port;
-	keybinding buttons[MAX_MOUSE_BUTTONS];
-	uint8_t    bind_type;
-} mousebinding;
 
-#define DEFAULT_JOYBUTTON_ALLOC 12
+static gp_button_def button_defs[NUM_GAMEPAD_BUTTONS] = {
+	[DPAD_UP] = {.states = {GAMEPAD_TH0, GAMEPAD_TH1}, .value = 0x1},
+	[DPAD_DOWN] = {.states = {GAMEPAD_TH0, GAMEPAD_TH1}, .value = 0x2},
+	[DPAD_LEFT] = {.states = {GAMEPAD_TH1, GAMEPAD_NONE}, .value = 0x4},
+	[DPAD_RIGHT] = {.states = {GAMEPAD_TH1, GAMEPAD_NONE}, .value = 0x8},
+	[BUTTON_A] = {.states = {GAMEPAD_TH0, GAMEPAD_NONE}, .value = 0x10},
+	[BUTTON_B] = {.states = {GAMEPAD_TH1, GAMEPAD_NONE}, .value = 0x10},
+	[BUTTON_C] = {.states = {GAMEPAD_TH1, GAMEPAD_NONE}, .value = 0x20},
+	[BUTTON_START] = {.states = {GAMEPAD_TH0, GAMEPAD_NONE}, .value = 0x20},
+	[BUTTON_X] = {.states = {GAMEPAD_EXTRA, GAMEPAD_NONE}, .value = 0x4},
+	[BUTTON_Y] = {.states = {GAMEPAD_EXTRA, GAMEPAD_NONE}, .value = 0x2},
+	[BUTTON_Z] = {.states = {GAMEPAD_EXTRA, GAMEPAD_NONE}, .value = 0x1},
+	[BUTTON_MODE] = {.states = {GAMEPAD_EXTRA, GAMEPAD_NONE}, .value = 0x8},
+};
 
-static sega_io *current_io;
-static keybinding *bindings[0x10000];
-static joystick joysticks[MAX_JOYSTICKS];
-static mousebinding mice[MAX_MICE];
-static io_port *keyboard_port;
-const uint8_t dpadbits[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
-
-static void do_bind(keybinding *binding, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+static io_port *find_gamepad(sega_io *io, uint8_t gamepad_num)
 {
-	binding->bind_type = bind_type;
-	binding->subtype_a = subtype_a;
-	binding->subtype_b = subtype_b;
-	binding->value = value;
+	for (int i = 0; i < 3; i++)
+	{
+		io_port *port = io->ports + i;
+		if (port->device_type < IO_MOUSE && port->device.pad.gamepad_num == gamepad_num) {
+			return port;
+		}
+	}
+	return NULL;
 }
 
-void bind_key(int keycode, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+static io_port *find_mouse(sega_io *io, uint8_t mouse_num)
 {
-	int bucket = keycode >> 15 & 0xFFFF;
-	if (!bindings[bucket]) {
-		bindings[bucket] = malloc(sizeof(keybinding) * 0x8000);
-		memset(bindings[bucket], 0, sizeof(keybinding) * 0x8000);
+	for (int i = 0; i < 3; i++)
+	{
+		io_port *port = io->ports + i;
+		if (port->device_type == IO_MOUSE && port->device.mouse.mouse_num == mouse_num) {
+			return port;
+		}
 	}
-	int idx = keycode & 0x7FFF;
-	do_bind(bindings[bucket] + idx, bind_type, subtype_a, subtype_b, value);
+	return NULL;
 }
 
-void bind_button(int joystick, int button, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+static io_port *find_keyboard(sega_io *io)
 {
-	if (joystick >= MAX_JOYSTICKS) {
-		return;
+	for (int i = 0; i < 3; i++)
+	{
+		io_port *port = io->ports + i;
+		if (port->device_type == IO_SATURN_KEYBOARD || port->device_type == IO_XBAND_KEYBOARD) {
+			return port;
+		}
 	}
-	if (!joysticks[joystick].buttons) {
-		joysticks[joystick].num_buttons = button < DEFAULT_JOYBUTTON_ALLOC ? DEFAULT_JOYBUTTON_ALLOC : button + 1;
-		joysticks[joystick].buttons = calloc(joysticks[joystick].num_buttons, sizeof(keybinding));
-	} else if (joysticks[joystick].num_buttons <= button) {
-		uint32_t old_capacity = joysticks[joystick].num_buttons;
-		joysticks[joystick].num_buttons *= 2;
-		joysticks[joystick].buttons = realloc(joysticks[joystick].buttons, sizeof(keybinding) * joysticks[joystick].num_buttons);
-		memset(joysticks[joystick].buttons + old_capacity, 0, joysticks[joystick].num_buttons - old_capacity);
-	}
-	do_bind(joysticks[joystick].buttons + button, bind_type, subtype_a, subtype_b, value);
+	return NULL;
 }
 
-void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+void io_port_gamepad_down(io_port *port, uint8_t button)
 {
-	if (joystick >= MAX_JOYSTICKS) {
-		return;
-	}
-	if (!joysticks[joystick].dpads) {
-		//multiple D-pads/hats are not common, so don't allocate any extra space
-		joysticks[joystick].dpads = calloc(dpad+1, sizeof(joydpad));
-		joysticks[joystick].num_dpads = dpad+1;
-	} else if (joysticks[joystick].num_dpads <= dpad) {
-		uint32_t old_capacity = joysticks[joystick].num_dpads;
-		joysticks[joystick].num_dpads *= 2;
-		joysticks[joystick].dpads = realloc(joysticks[joystick].dpads, sizeof(joydpad) * joysticks[joystick].num_dpads);
-		memset(joysticks[joystick].dpads + old_capacity, 0, (joysticks[joystick].num_dpads - old_capacity) * sizeof(joydpad));
-	}
-	for (int i = 0; i < 4; i ++) {
-		if (dpadbits[i] & direction) {
-			do_bind(joysticks[joystick].dpads[dpad].bindings + i, bind_type, subtype_a, subtype_b, value);
-			break;
-		}
+	gp_button_def *def = button_defs + button;
+	port->input[def->states[0]] |= def->value;
+	if (def->states[1] != GAMEPAD_NONE) {
+		port->input[def->states[1]] |= def->value;
 	}
 }
 
-void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+void io_port_gamepad_up(io_port *port, uint8_t button)
 {
-	if (joystick >= MAX_JOYSTICKS) {
-		return;
-	}
-	if (!joysticks[joystick].axes) {
-		//typical gamepad has 4 axes
-		joysticks[joystick].num_axes = axis+1 > 4 ? axis+1 : 4;
-		joysticks[joystick].axes = calloc(joysticks[joystick].num_axes, sizeof(joyaxis));
-	} else if (joysticks[joystick].num_axes <= axis) {
-		uint32_t old_capacity = joysticks[joystick].num_axes;
-		joysticks[joystick].num_axes *= 2;
-		joysticks[joystick].axes = realloc(joysticks[joystick].axes, sizeof(joyaxis) * joysticks[joystick].num_axes);
-		memset(joysticks[joystick].axes + old_capacity, 0, (joysticks[joystick].num_axes - old_capacity) * sizeof(joyaxis));
-	}
-	if (positive) {
-		do_bind(&joysticks[joystick].axes[axis].positive, bind_type, subtype_a, subtype_b, value);
-	} else {
-		do_bind(&joysticks[joystick].axes[axis].negative, bind_type, subtype_a, subtype_b, value);
+	gp_button_def *def = button_defs + button;
+	port->input[def->states[0]] &= ~def->value;
+	if (def->states[1] != GAMEPAD_NONE) {
+		port->input[def->states[1]] &= ~def->value;
 	}
 }
 
-void reset_joystick_bindings(int joystick)
+void io_gamepad_down(sega_io *io, uint8_t gamepad_num, uint8_t button)
 {
-	if (joystick >= MAX_JOYSTICKS) {
-		return;
-	}
-	if (joysticks[joystick].buttons) {
-		for (int i = 0; i < joysticks[joystick].num_buttons; i++)
-		{
-			joysticks[joystick].buttons[i].bind_type = BIND_NONE;
-		}
+	io_port *port = find_gamepad(io, gamepad_num);
+	if (port) {
+		io_port_gamepad_down(port, button);
 	}
-	if (joysticks[joystick].dpads) {
-		for (int i = 0; i < joysticks[joystick].num_dpads; i++)
-		{
-			for (int dir = 0; dir < 4; dir++)
-			{
-				joysticks[joystick].dpads[i].bindings[dir].bind_type = BIND_NONE;
-			}
-		}
-	}
-	if (joysticks[joystick].axes) {
-		for (int i = 0; i < joysticks[joystick].num_axes; i++)
-		{
-			joysticks[joystick].axes[i].positive.bind_type = BIND_NONE;
-			joysticks[joystick].axes[i].negative.bind_type = BIND_NONE;
-		}
+}
+
+void io_gamepad_up(sega_io *io, uint8_t gamepad_num, uint8_t button)
+{
+	io_port *port = find_gamepad(io, gamepad_num);
+	if (port) {
+		io_port_gamepad_up(port, button);
 	}
 }
 
-#define GAMEPAD_BUTTON(PRI_SLOT, SEC_SLOT, VALUE)  (PRI_SLOT << 12 | SEC_SLOT << 8 | VALUE)
-
-#define DPAD_UP      GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_TH1, 0x01)
-#define BUTTON_Z     GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x01)
-#define DPAD_DOWN    GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_TH1, 0x02)
-#define BUTTON_Y     GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x02)
-#define DPAD_LEFT    GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x04)
-#define BUTTON_X     GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x04)
-#define DPAD_RIGHT   GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x08)
-#define BUTTON_MODE  GAMEPAD_BUTTON(GAMEPAD_EXTRA, GAMEPAD_NONE, 0x08)
-#define BUTTON_A     GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_NONE, 0x10)
-#define BUTTON_B     GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x10)
-#define BUTTON_START GAMEPAD_BUTTON(GAMEPAD_TH0, GAMEPAD_NONE, 0x20)
-#define BUTTON_C     GAMEPAD_BUTTON(GAMEPAD_TH1, GAMEPAD_NONE, 0x20)
-
-#define PSEUDO_BUTTON_MOTION 0xFFFF
-#define MOUSE_LEFT           1
-#define MOUSE_RIGHT          2
-#define MOUSE_MIDDLE         4
-#define MOUSE_START          8
-
-void bind_gamepad(int keycode, int gamepadnum, int button)
+void io_mouse_down(sega_io *io, uint8_t mouse_num, uint8_t button)
 {
-
-	if (gamepadnum < 1 || gamepadnum > 8) {
-		return;
+	io_port *port = find_mouse(io, mouse_num);
+	if (port) {
+		port->input[0] |= button;
 	}
-	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
-	bind_key(keycode, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
-}
-
-void bind_button_gamepad(int joystick, int joybutton, int gamepadnum, int padbutton)
-{
-	if (gamepadnum < 1 || gamepadnum > 8) {
-		return;
-	}
-	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
-	bind_button(joystick, joybutton, bind_type, padbutton >> 12, padbutton >> 8 & 0xF, padbutton & 0xFF);
 }
 
-void bind_dpad_gamepad(int joystick, int dpad, uint8_t direction, int gamepadnum, int button)
-{
-	if (gamepadnum < 1 || gamepadnum > 8) {
-		return;
-	}
-	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
-	bind_dpad(joystick, dpad, direction, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
-}
-
-void bind_axis_gamepad(int joystick, int axis, uint8_t positive, int gamepadnum, int button)
-{
-	if (gamepadnum < 1 || gamepadnum > 8) {
-		return;
-	}
-	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
-	bind_axis(joystick, axis, positive, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
-}
-
-void bind_ui(int keycode, ui_action action, uint8_t param)
-{
-	bind_key(keycode, BIND_UI, action, 0, param);
-}
-
-void bind_button_ui(int joystick, int joybutton, ui_action action, uint8_t param)
+void io_mouse_up(sega_io *io, uint8_t mouse_num, uint8_t button)
 {
-	bind_button(joystick, joybutton, BIND_UI, action, 0, param);
-}
-
-void bind_dpad_ui(int joystick, int dpad, uint8_t direction, ui_action action, uint8_t param)
-{
-	bind_dpad(joystick, dpad, direction, BIND_UI, action, 0, param);
-}
-
-void bind_axis_ui(int joystick, int axis, uint8_t positive, ui_action action, uint8_t param)
-{
-	bind_axis(joystick, axis, positive, BIND_UI, action, 0, param);
-}
-
-void handle_binding_down(keybinding * binding)
-{
-	if (binding->bind_type >= BIND_GAMEPAD1 && binding->bind_type <= BIND_GAMEPAD8)
-	{
-		if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
-			binding->port->input[binding->subtype_a] |= binding->value;
-		}
-		if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
-			binding->port->input[binding->subtype_b] |= binding->value;
-		}
-	}
-	else if (binding->bind_type >= BIND_MOUSE1 && binding->bind_type <= BIND_MOUSE8)
-	{
-		if (binding->port) {
-			binding->port->input[0] |= binding->value;
-		}
+	io_port *port = find_mouse(io, mouse_num);
+	if (port) {
+		port->input[0] &= ~button;
 	}
 }
 
-void store_key_event(uint16_t code)
+void io_mouse_motion_absolute(sega_io *io, uint8_t mouse_num, uint16_t x, uint16_t y)
+{
+	io_port *port = find_mouse(io, mouse_num);
+	if (port) {
+		port->device.mouse.cur_x = x;
+		port->device.mouse.cur_y = y;
+	}
+}
+
+void io_mouse_motion_relative(sega_io *io, uint8_t mouse_num, int32_t x, int32_t y)
+{
+	io_port *port = find_mouse(io, mouse_num);
+	if (port) {
+		port->device.mouse.cur_x += x;
+		port->device.mouse.cur_y += y;
+	}
+}
+
+void store_key_event(io_port *keyboard_port, uint16_t code)
 {
 	if (keyboard_port && keyboard_port->device.keyboard.write_pos != keyboard_port->device.keyboard.read_pos) {
 		//there's room in the buffer, record this event
@@ -354,482 +195,24 @@
 	}
 }
 
-void handle_keydown(int keycode, uint8_t scancode)
-{
-	int bucket = keycode >> 15 & 0xFFFF;
-	int idx = keycode & 0x7FFF;
-	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
-	if (binding && (!current_io->keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
-		handle_binding_down(binding);
-	} else if (current_io->keyboard_captured) {
-		store_key_event(scancode);
-	}
-}
-
-void handle_joydown(int joystick, int button)
-{
-	if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons) {
-		return;
-	}
-	keybinding * binding = joysticks[joystick].buttons + button;
-	handle_binding_down(binding);
-}
-
-void handle_mousedown(int mouse, int button)
-{
-	if (current_io->mouse_mode == MOUSE_CAPTURE && !current_io->mouse_captured) {
-		current_io->mouse_captured = 1;
-		render_relative_mouse(1);
-		return;
-	}
-	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
-		return;
-	}
-	keybinding * binding = mice[mouse].buttons + button - 1;
-	handle_binding_down(binding);
-}
-
-uint8_t ui_debug_mode = 0;
-uint8_t ui_debug_pal = 0;
-
-int current_speed = 0;
-int num_speeds = 1;
-uint32_t * speeds = NULL;
-
-uint8_t is_keyboard(io_port *port)
-{
-	return port->device_type == IO_SATURN_KEYBOARD || port->device_type == IO_XBAND_KEYBOARD;
-}
-
-uint8_t keyboard_connected(sega_io *io)
-{
-	return is_keyboard(io->ports) || is_keyboard(io->ports+1) || is_keyboard(io->ports+2);
-}
-
-#ifdef _WIN32
-#define localtime_r(a,b) localtime(a)
-#endif
-
-void handle_binding_up(keybinding * binding)
+void io_keyboard_down(sega_io *io, uint8_t scancode)
 {
-	switch(binding->bind_type)
-	{
-	case BIND_GAMEPAD1:
-	case BIND_GAMEPAD2:
-	case BIND_GAMEPAD3:
-	case BIND_GAMEPAD4:
-	case BIND_GAMEPAD5:
-	case BIND_GAMEPAD6:
-	case BIND_GAMEPAD7:
-	case BIND_GAMEPAD8:
-		if (binding->subtype_a <= GAMEPAD_EXTRA && binding->port) {
-			binding->port->input[binding->subtype_a] &= ~binding->value;
-		}
-		if (binding->subtype_b <= GAMEPAD_EXTRA && binding->port) {
-			binding->port->input[binding->subtype_b] &= ~binding->value;
-		}
-		break;
-	case BIND_MOUSE1:
-	case BIND_MOUSE2:
-	case BIND_MOUSE3:
-	case BIND_MOUSE4:
-	case BIND_MOUSE5:
-	case BIND_MOUSE6:
-	case BIND_MOUSE7:
-	case BIND_MOUSE8:
-		if (binding->port) {
-			binding->port->input[0] &= ~binding->value;
-		}
-		break;
-	case BIND_UI:
-		switch (binding->subtype_a)
-		{
-		case UI_DEBUG_MODE_INC:
-			current_system->inc_debug_mode(current_system);
-			break;
-		case UI_DEBUG_PAL_INC:
-			current_system->inc_debug_pal(current_system);
-			break;
-		case UI_ENTER_DEBUGGER:
-			current_system->enter_debugger = 1;
-			break;
-		case UI_SAVE_STATE:
-			current_system->save_state = QUICK_SAVE_SLOT+1;
-			break;
-		case UI_NEXT_SPEED:
-			current_speed++;
-			if (current_speed >= num_speeds) {
-				current_speed = 0;
-			}
-			printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
-			current_system->set_speed_percent(current_system, speeds[current_speed]);
-			break;
-		case UI_PREV_SPEED:
-			current_speed--;
-			if (current_speed < 0) {
-				current_speed = num_speeds - 1;
-			}
-			printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
-			current_system->set_speed_percent(current_system, speeds[current_speed]);
-			break;
-		case UI_SET_SPEED:
-			if (binding->value < num_speeds) {
-				current_speed = binding->value;
-				printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]);
-				current_system->set_speed_percent(current_system, speeds[current_speed]);
-			} else {
-				printf("Setting speed to %d\n", speeds[current_speed]);
-				current_system->set_speed_percent(current_system, speeds[current_speed]);
-			}
-			break;
-		case UI_RELEASE_MOUSE:
-			if (current_io->mouse_captured) {
-				current_io->mouse_captured = 0;
-				render_relative_mouse(0);
-			}
-			break;
-		case UI_TOGGLE_KEYBOARD_CAPTURE:
-			if (keyboard_connected(current_io)) {
-				current_io->keyboard_captured = !current_io->keyboard_captured;
-			}
-			break;
-		case UI_TOGGLE_FULLSCREEN:
-			render_toggle_fullscreen();
-			break;
-		case UI_SOFT_RESET:
-			current_system->soft_reset(current_system);
-			break;
-		case UI_RELOAD:
-			reload_media();
-			break;
-		case UI_SMS_PAUSE:
-			if (current_system->type == SYSTEM_SMS) {
-				sms_context *sms = (sms_context *)current_system;
-				vdp_pbc_pause(sms->vdp);
-			}
-			break;
-		case UI_SCREENSHOT: {
-			char *screenshot_base = tern_find_path(config, "ui\0screenshot_path\0", TVAL_PTR).ptrval;
-			if (!screenshot_base) {
-				screenshot_base = "$HOME";
-			}
-			tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
-			vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
-			screenshot_base = replace_vars(screenshot_base, vars, 1);
-			tern_free(vars);
-			time_t now = time(NULL);
-			struct tm local_store;
-			char fname_part[256];
-			char *template = tern_find_path(config, "ui\0screenshot_template\0", TVAL_PTR).ptrval;
-			if (!template) {
-				template = "blastem_%c.ppm";
-			}
-			strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store));
-			char const *parts[] = {screenshot_base, PATH_SEP, fname_part};
-			char *path = alloc_concat_m(3, parts);
-			free(screenshot_base);
-			render_save_screenshot(path);
-			break;
-		}
-		case UI_EXIT:
-			current_system->request_exit(current_system);
-			if (current_system->type == SYSTEM_GENESIS) {
-				genesis_context *gen = (genesis_context *)current_system;
-				if (gen->extra) {
-					//TODO: More robust mechanism for detecting menu
-					menu_context *menu = gen->extra;
-					menu->external_game_load = 1;
-				}
-			}
-			break;
-		}
-		break;
-	}
-}
-
-void handle_keyup(int keycode, uint8_t scancode)
-{
-	int bucket = keycode >> 15 & 0xFFFF;
-	int idx = keycode & 0x7FFF;
-	keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL;
-	if (binding && (!current_io->keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) {
-		handle_binding_up(binding);
-	} else if (current_io->keyboard_captured) {
-		store_key_event(0xF000 | scancode);
-	}
-}
-
-void handle_joyup(int joystick, int button)
-{
-	if (joystick >= MAX_JOYSTICKS  || button >= joysticks[joystick].num_buttons) {
-		return;
-	}
-	keybinding * binding = joysticks[joystick].buttons + button;
-	handle_binding_up(binding);
-}
-
-void handle_joy_dpad(int joystick, int dpadnum, uint8_t value)
-{
-	if (joystick >= MAX_JOYSTICKS  || dpadnum >= joysticks[joystick].num_dpads) {
-		return;
-	}
-	joydpad * dpad = joysticks[joystick].dpads + dpadnum;
-	uint8_t newdown = (value ^ dpad->state) & value;
-	uint8_t newup = ((~value) ^ (~dpad->state)) & (~value);
-	dpad->state = value;
-	for (int i = 0; i < 4; i++) {
-		if (newdown & dpadbits[i]) {
-			handle_binding_down(dpad->bindings + i);
-		} else if(newup & dpadbits[i]) {
-			handle_binding_up(dpad->bindings + i);
-		}
-	}
+	store_key_event(find_keyboard(io), scancode);
 }
 
-#define JOY_AXIS_THRESHOLD 2000
-
-void handle_joy_axis(int joystick, int axis, int16_t value)
-{
-	if (joystick >= MAX_JOYSTICKS  || axis >= joysticks[joystick].num_axes) {
-		return;
-	}
-	joyaxis *jaxis = joysticks[joystick].axes + axis;
-	int old_active = abs(jaxis->value) > JOY_AXIS_THRESHOLD;
-	int new_active = abs(value) > JOY_AXIS_THRESHOLD;
-	int old_pos = jaxis->value > 0;
-	int new_pos = value > 0;
-	jaxis->value = value;
-	if (old_active && (!new_active || old_pos != new_pos)) {
-		//previously activated direction is no longer active
-		handle_binding_up(old_pos ? &jaxis->positive : &jaxis->negative);
-	}
-	if (new_active && (!old_active || old_pos != new_pos)) {
-		//previously unactivated direction is now active
-		handle_binding_down(new_pos ? &jaxis->positive : &jaxis->negative);
-	}
-}
-
-void handle_mouseup(int mouse, int button)
+void io_keyboard_up(sega_io *io, uint8_t scancode)
 {
-	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
-		return;
-	}
-	keybinding * binding = mice[mouse].buttons + button - 1;
-	handle_binding_up(binding);
-}
-
-void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay)
-{
-	if (mouse >= MAX_MICE || !mice[mouse].motion_port) {
-		return;
-	}
-	switch(current_io->mouse_mode)
-	{
-	case MOUSE_NONE:
-		break;
-	case MOUSE_ABSOLUTE: {
-		float scale_x = (render_emulated_width() * 2.0f) / ((float)render_width());
-		float scale_y = (render_emulated_height() * 2.0f) / ((float)render_height());
-		int32_t adj_x = x * scale_x + 2 * render_overscan_left() - 2 * BORDER_LEFT;
-		int32_t adj_y = y * scale_y + 2 * render_overscan_top() - 4;
-		if (adj_x >= 0 && adj_y >= 0) {
-			mice[mouse].motion_port->device.mouse.cur_x = adj_x;
-			mice[mouse].motion_port->device.mouse.cur_y = adj_y;
-		}
-		break;
-	}
-	case MOUSE_RELATIVE: {
-		mice[mouse].motion_port->device.mouse.cur_x += deltax;
-		mice[mouse].motion_port->device.mouse.cur_y += deltay;
-		break;
-	}
-	case MOUSE_CAPTURE: {
-		if (current_io->mouse_captured) {
-			mice[mouse].motion_port->device.mouse.cur_x += deltax;
-			mice[mouse].motion_port->device.mouse.cur_y += deltay;
-		}
-		break;
-	}
-	}
+	store_key_event(find_keyboard(io), 0xF000 | scancode);
 }
 
-int parse_binding_target(char * target, tern_node * padbuttons, tern_node *mousebuttons, int * ui_out, int * padnum_out, int * padbutton_out)
-{
-	const int gpadslen = strlen("gamepads.");
-	const int mouselen = strlen("mouse.");
-	if (!strncmp(target, "gamepads.", gpadslen)) {
-		if (target[gpadslen] >= '1' && target[gpadslen] <= '8') {
-			int padnum = target[gpadslen] - '0';
-			int button = tern_find_int(padbuttons, target + gpadslen + 1, 0);
-			if (button) {
-				*padnum_out = padnum;
-				*padbutton_out = button;
-				return BIND_GAMEPAD1;
-			} else {
-				if (target[gpadslen+1]) {
-					warning("Gamepad mapping string '%s' refers to an invalid button '%s'\n", target, target + gpadslen + 1);
-				} else {
-					warning("Gamepad mapping string '%s' has no button component\n", target);
-				}
-			}
-		} else {
-			warning("Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]);
-		}
-	} else if(!strncmp(target, "mouse.", mouselen)) {
-		if (target[mouselen] >= '1' && target[mouselen] <= '8') {
-			int mousenum = target[mouselen] - '0';
-			int button = tern_find_int(mousebuttons, target + mouselen + 1, 0);
-			if (button) {
-				*padnum_out = mousenum;
-				*padbutton_out = button;
-				return BIND_MOUSE1;
-			} else {
-				if (target[mouselen+1]) {
-					warning("Mouse mapping string '%s' refers to an invalid button '%s'\n", target, target + mouselen + 1);
-				} else {
-					warning("Mouse mapping string '%s' has no button component\n", target);
-				}
-			}
-		} else {
-			warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]);
-		}
-	} else if(!strncmp(target, "ui.", strlen("ui."))) {
-		*padbutton_out = 0;
-		if (!strcmp(target + 3, "vdp_debug_mode")) {
-			*ui_out = UI_DEBUG_MODE_INC;
-		} else if(!strcmp(target + 3, "vdp_debug_pal")) {
-			*ui_out = UI_DEBUG_PAL_INC;
-		} else if(!strcmp(target + 3, "enter_debugger")) {
-			*ui_out = UI_ENTER_DEBUGGER;
-		} else if(!strcmp(target + 3, "save_state")) {
-			*ui_out = UI_SAVE_STATE;
-		} else if(!strncmp(target + 3, "set_speed.", strlen("set_speed."))) {
-			*ui_out = UI_SET_SPEED;
-			*padbutton_out = atoi(target + 3 + strlen("set_speed."));
-		} else if(!strcmp(target + 3, "next_speed")) {
-			*ui_out = UI_NEXT_SPEED;
-		} else if(!strcmp(target + 3, "prev_speed")) {
-			*ui_out = UI_PREV_SPEED;
-		} else if(!strcmp(target + 3, "release_mouse")) {
-			*ui_out = UI_RELEASE_MOUSE;
-		} else if(!strcmp(target + 3, "toggle_keyboard_captured")) {
-			*ui_out = UI_TOGGLE_KEYBOARD_CAPTURE;
-		} else if (!strcmp(target + 3, "toggle_fullscreen")) {
-			*ui_out = UI_TOGGLE_FULLSCREEN;
-		} else if (!strcmp(target + 3, "soft_reset")) {
-			*ui_out = UI_SOFT_RESET;
-		} else if (!strcmp(target + 3, "reload")) {
-			*ui_out = UI_RELOAD;
-		} else if (!strcmp(target + 3, "sms_pause")) {
-			*ui_out = UI_SMS_PAUSE;
-		} else if (!strcmp(target + 3, "screenshot")) {
-			*ui_out = UI_SCREENSHOT;
-		} else if(!strcmp(target + 3, "exit")) {
-			*ui_out = UI_EXIT;
-		} else {
-			warning("Unreconized UI binding type %s\n", target);
-			return 0;
-		}
-		return BIND_UI;
-	} else {
-		warning("Unrecognized binding type %s\n", target);
-	}
-	return 0;
-}
-
-void process_keys(tern_node * cur, tern_node * special, tern_node * padbuttons, tern_node *mousebuttons, char * prefix)
+uint8_t io_has_keyboard(sega_io *io)
 {
-	char * curstr = NULL;
-	int len;
-	if (!cur) {
-		return;
-	}
-	char onec[2];
-	if (prefix) {
-		len = strlen(prefix);
-		curstr = malloc(len + 2);
-		memcpy(curstr, prefix, len);
-	} else {
-		curstr = onec;
-		len = 0;
-	}
-	curstr[len] = cur->el;
-	curstr[len+1] = 0;
-	if (cur->el) {
-		process_keys(cur->straight.next, special, padbuttons, mousebuttons, curstr);
-	} else {
-		int keycode = tern_find_int(special, curstr, 0);
-		if (!keycode) {
-			keycode = curstr[0];
-			if (curstr[1] != 0) {
-				warning("%s is not recognized as a key identifier, truncating to %c\n", curstr, curstr[0]);
-			}
-		}
-		char * target = cur->straight.value.ptrval;
-		int ui_func, padnum, button;
-		int bindtype = parse_binding_target(target, padbuttons, mousebuttons, &ui_func, &padnum, &button);
-		if (bindtype == BIND_GAMEPAD1) {
-			bind_gamepad(keycode, padnum, button);
-		} else if(bindtype == BIND_UI) {
-			bind_ui(keycode, ui_func, button);
-		}
-	}
-	process_keys(cur->left, special, padbuttons, mousebuttons, prefix);
-	process_keys(cur->right, special, padbuttons, mousebuttons, prefix);
-	if (curstr && len) {
-		free(curstr);
-	}
-}
-
-void process_speeds(tern_node * cur, char * prefix)
-{
-	char * curstr = NULL;
-	int len;
-	if (!cur) {
-		return;
-	}
-	char onec[2];
-	if (prefix) {
-		len = strlen(prefix);
-		curstr = malloc(len + 2);
-		memcpy(curstr, prefix, len);
-	} else {
-		curstr = onec;
-		len = 0;
-	}
-	curstr[len] = cur->el;
-	curstr[len+1] = 0;
-	if (cur->el) {
-		process_speeds(cur->straight.next, curstr);
-	} else {
-		char *end;
-		long speed_index = strtol(curstr, &end, 10);
-		if (speed_index < 0 || end == curstr || *end) {
-			warning("%s is not a valid speed index", curstr);
-		} else {
-			if (speed_index >= num_speeds) {
-				speeds = realloc(speeds, sizeof(uint32_t) * (speed_index+1));
-				for(; num_speeds < speed_index + 1; num_speeds++) {
-					speeds[num_speeds] = 0;
-				}
-			}
-			speeds[speed_index] = atoi(cur->straight.value.ptrval);
-			if (speeds[speed_index] < 1) {
-				warning("%s is not a valid speed percentage, setting speed %d to 100", cur->straight.value.ptrval, speed_index);
-				speeds[speed_index] = 100;
-			}
-		}
-	}
-	process_speeds(cur->left, prefix);
-	process_speeds(cur->right, prefix);
-	if (curstr && len) {
-		free(curstr);
-	}
+	return find_keyboard(io) != NULL;
 }
 
 void process_device(char * device_type, io_port * port)
 {
-	port->device_type = IO_NONE;
+	//assuming that the io_port struct has been zeroed if this is the first time this has been called
 	if (!device_type)
 	{
 		return;
@@ -852,34 +235,44 @@
 		} else {
 			port->device_type = IO_GAMEPAD6;
 		}
-		port->device.pad.gamepad_num = device_type[gamepad_len+2] - '1';
+		port->device.pad.gamepad_num = device_type[gamepad_len+2] - '0';
 	} else if(!strncmp(device_type, "mouse", mouse_len)) {
-		port->device_type = IO_MOUSE;
-		port->device.mouse.mouse_num = device_type[mouse_len+1] - '1';
-		port->device.mouse.last_read_x = 0;
-		port->device.mouse.last_read_y = 0;
-		port->device.mouse.cur_x = 0;
-		port->device.mouse.cur_y = 0;
-		port->device.mouse.latched_x = 0;
-		port->device.mouse.latched_y = 0;
-		port->device.mouse.ready_cycle = CYCLE_NEVER;
-		port->device.mouse.tr_counter = 0;
+		if (port->device_type != IO_MOUSE) {
+			port->device_type = IO_MOUSE;
+			port->device.mouse.mouse_num = device_type[mouse_len+1] - '0';
+			port->device.mouse.last_read_x = 0;
+			port->device.mouse.last_read_y = 0;
+			port->device.mouse.cur_x = 0;
+			port->device.mouse.cur_y = 0;
+			port->device.mouse.latched_x = 0;
+			port->device.mouse.latched_y = 0;
+			port->device.mouse.ready_cycle = CYCLE_NEVER;
+			port->device.mouse.tr_counter = 0;
+		}
 	} else if(!strcmp(device_type, "saturn keyboard")) {
-		port->device_type = IO_SATURN_KEYBOARD;
-		port->device.keyboard.read_pos = 0xFF;
-		port->device.keyboard.write_pos = 0;
+		if (port->device_type != IO_SATURN_KEYBOARD) {
+			port->device_type = IO_SATURN_KEYBOARD;
+			port->device.keyboard.read_pos = 0xFF;
+			port->device.keyboard.write_pos = 0;
+		}
 	} else if(!strcmp(device_type, "xband keyboard")) {
-		port->device_type = IO_XBAND_KEYBOARD;
-		port->device.keyboard.read_pos = 0xFF;
-		port->device.keyboard.write_pos = 0;
+		if (port->device_type != IO_XBAND_KEYBOARD) {
+			port->device_type = IO_XBAND_KEYBOARD;
+			port->device.keyboard.read_pos = 0xFF;
+			port->device.keyboard.write_pos = 0;
+		}
 	} else if(!strcmp(device_type, "sega_parallel")) {
-		port->device_type = IO_SEGA_PARALLEL;
-		port->device.stream.data_fd = -1;
-		port->device.stream.listen_fd = -1;
+		if (port->device_type != IO_SEGA_PARALLEL) {
+			port->device_type = IO_SEGA_PARALLEL;
+			port->device.stream.data_fd = -1;
+			port->device.stream.listen_fd = -1;
+		}
 	} else if(!strcmp(device_type, "generic")) {
-		port->device_type = IO_GENERIC;
-		port->device.stream.data_fd = -1;
-		port->device.stream.listen_fd = -1;
+		if (port->device_type != IO_GENERIC) {
+			port->device_type = IO_GENERIC;
+			port->device.stream.data_fd = -1;
+			port->device.stream.listen_fd = -1;
+		}
 	}
 }
 
@@ -906,8 +299,7 @@
 
 void setup_io_devices(tern_node * config, rom_info *rom, sega_io *io)
 {
-	current_io = io;
-	io_port * ports = current_io->ports;
+	io_port * ports = io->ports;
 	tern_node *io_nodes = tern_find_path(config, "io\0devices\0", TVAL_NODE).ptrval;
 	char * io_1 = rom->port1_override ? rom->port1_override : io_nodes ? tern_find_ptr(io_nodes, "1") : NULL;
 	char * io_2 = rom->port2_override ? rom->port2_override : io_nodes ? tern_find_ptr(io_nodes, "2") : NULL;
@@ -917,25 +309,26 @@
 	process_device(io_2, ports+1);
 	process_device(io_ext, ports+2);
 
+	uint8_t mouse_mode;
 	if (ports[0].device_type == IO_MOUSE || ports[1].device_type == IO_MOUSE || ports[2].device_type == IO_MOUSE) {
 		if (render_fullscreen()) {
-				current_io->mouse_mode = MOUSE_RELATIVE;
-				render_relative_mouse(1);
+				mouse_mode = MOUSE_RELATIVE;
 		} else {
 			if (rom->mouse_mode && !strcmp(rom->mouse_mode, "absolute")) {
-				current_io->mouse_mode = MOUSE_ABSOLUTE;
+				mouse_mode = MOUSE_ABSOLUTE;
 			} else {
-				current_io->mouse_mode = MOUSE_CAPTURE;
+				mouse_mode = MOUSE_CAPTURE;
 			}
 		}
 	} else {
-		current_io->mouse_mode = MOUSE_NONE;
+		mouse_mode = MOUSE_NONE;
 	}
+	bindings_set_mouse_mode(mouse_mode);
 
 	for (int i = 0; i < 3; i++)
 	{
 #ifndef _WIN32
-		if (ports[i].device_type == IO_SEGA_PARALLEL)
+		if (ports[i].device_type == IO_SEGA_PARALLEL && ports[i].device.stream.data_fd == -1)
 		{
 			char *pipe_name = tern_find_path(config, "io\0parallel_pipe\0", TVAL_PTR).ptrval;
 			if (!pipe_name)
@@ -962,7 +355,7 @@
 					}
 				}
 			}
-		} else if (ports[i].device_type == IO_GENERIC) {
+		} else if (ports[i].device_type == IO_GENERIC && ports[i].device.stream.data_fd == -1) {
 			char *sock_name = tern_find_path(config, "io\0socket\0", TVAL_PTR).ptrval;
 			if (!sock_name)
 			{
@@ -1006,496 +399,6 @@
 	}
 }
 
-void map_bindings(io_port *ports, keybinding *bindings, int numbindings)
-{
-	for (int i = 0; i < numbindings; i++)
-	{
-		if (bindings[i].bind_type >= BIND_GAMEPAD1 && bindings[i].bind_type <= BIND_GAMEPAD8)
-		{
-			int num = bindings[i].bind_type - BIND_GAMEPAD1;
-			for (int j = 0; j < 3; j++)
-			{
-				if ((ports[j].device_type == IO_GAMEPAD3
-					 || ports[j].device_type == IO_GAMEPAD6
-					 || ports[j].device_type == IO_GAMEPAD2)
-					 && ports[j].device.pad.gamepad_num == num
-				)
-				{
-					memset(ports[j].input, 0, sizeof(ports[j].input));
-					bindings[i].port = ports + j;
-					break;
-				}
-			}
-		}
-		else if (bindings[i].bind_type >= BIND_MOUSE1 && bindings[i].bind_type <= BIND_MOUSE8)
-		{
-			int num = bindings[i].bind_type - BIND_MOUSE1;
-			for (int j = 0; j < 3; j++)
-			{
-				if (ports[j].device_type == IO_MOUSE && ports[j].device.mouse.mouse_num == num)
-				{
-					memset(ports[j].input, 0, sizeof(ports[j].input));
-					bindings[i].port = ports + j;
-					break;
-				}
-			}
-		}
-	}
-}
-
-typedef struct {
-	tern_node *padbuttons;
-	tern_node *mousebuttons;
-	int       mouseidx;
-} pmb_state;
-
-void process_mouse_button(char *buttonstr, tern_val value, uint8_t valtype, void *data)
-{
-	pmb_state *state = data;
-	int buttonnum = atoi(buttonstr);
-	if (buttonnum < 1 || buttonnum > MAX_MOUSE_BUTTONS) {
-		warning("Mouse button %s is out of the supported range of 1-8\n", buttonstr);
-		return;
-	}
-	if (valtype != TVAL_PTR) {
-		warning("Mouse button %s is not a scalar value!\n", buttonstr);
-		return;
-	}
-	buttonnum--;
-	int ui_func, devicenum, button;
-	int bindtype = parse_binding_target(value.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &devicenum, &button);
-	switch (bindtype)
-	{
-	case BIND_UI:
-		mice[state->mouseidx].buttons[buttonnum].subtype_a = ui_func;
-		break;
-	case BIND_GAMEPAD1:
-		mice[state->mouseidx].buttons[buttonnum].subtype_a = button >> 12;
-		mice[state->mouseidx].buttons[buttonnum].subtype_b = button >> 8 & 0xF;
-		mice[state->mouseidx].buttons[buttonnum].value = button & 0xFF;
-		break;
-	case BIND_MOUSE1:
-		mice[state->mouseidx].buttons[buttonnum].value = button & 0xFF;
-		break;
-	}
-	if (bindtype != BIND_UI) {
-		bindtype += devicenum-1;
-	}
-	mice[state->mouseidx].buttons[buttonnum].bind_type = bindtype;
-
-}
-
-void process_mouse(char *mousenum, tern_val value, uint8_t valtype, void *data)
-{
-	tern_node **buttonmaps = data;
-	if (valtype != TVAL_NODE) {
-		warning("Binding for mouse %s is a scalar!\n", mousenum);
-		return;
-	}
-	tern_node *mousedef = value.ptrval;
-	tern_node *padbuttons = buttonmaps[0];
-	tern_node *mousebuttons = buttonmaps[1];
-
-	int mouseidx = atoi(mousenum);
-	if (mouseidx < 0 || mouseidx >= MAX_MICE) {
-		warning("Mouse numbers must be between 0 and %d, but %d is not\n", MAX_MICE, mouseidx);
-		return;
-	}
-	char *motion = tern_find_ptr(mousedef, "motion");
-	if (motion) {
-		int ui_func,devicenum,button;
-		int bindtype = parse_binding_target(motion, padbuttons, mousebuttons, &ui_func, &devicenum, &button);
-		if (bindtype != BIND_UI) {
-			bindtype += devicenum-1;
-		}
-		if (button == PSEUDO_BUTTON_MOTION) {
-			mice[mouseidx].bind_type = bindtype;
-		} else {
-			warning("Mouse motion can't be bound to target %s\n", motion);
-		}
-	}
-	tern_node *buttons = tern_find_path(mousedef, "buttons\0\0", TVAL_NODE).ptrval;
-	if (buttons) {
-		pmb_state state = {padbuttons, mousebuttons, mouseidx};
-		tern_foreach(buttons, process_mouse_button, &state);
-	}
-}
-
-typedef struct {
-	int       padnum;
-	tern_node *padbuttons;
-	tern_node *mousebuttons;
-} pad_button_state;
-
-
-static long map_warning_pad = -1;
-void process_pad_button(char *key, tern_val val, uint8_t valtype, void *data)
-{
-	pad_button_state *state = data;
-	int hostpadnum = state->padnum;
-	int ui_func, padnum, button;
-	if (valtype != TVAL_PTR) {
-		warning("Pad button %s has a non-scalar value\n", key);
-		return;
-	}
-	int bindtype = parse_binding_target(val.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &padnum, &button);
-	char *end;
-	long hostbutton = strtol(key, &end, 10);
-	if (*end) {
-		//key is not a valid base 10 integer
-		hostbutton = render_translate_input_name(hostpadnum, key, 0);
-		if (hostbutton < 0) {
-			if (hostbutton == RENDER_INVALID_NAME) {
-				warning("%s is not a valid gamepad input name\n", key);
-			} else if (hostbutton == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) {
-				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
-				map_warning_pad = hostpadnum;
-			}
-			return;
-		}
-		if (hostbutton & RENDER_DPAD_BIT) {
-			if (bindtype == BIND_GAMEPAD1) {
-				bind_dpad_gamepad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), padnum, button);
-			} else {
-				bind_dpad_ui(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), ui_func, button);
-			}
-			return;
-		} else if (hostbutton & RENDER_AXIS_BIT) {
-			if (bindtype == BIND_GAMEPAD1) {
-				bind_axis_gamepad(hostpadnum, render_axis_part(hostbutton), 1, padnum, button);
-			} else {
-				bind_axis_ui(hostpadnum, render_axis_part(hostbutton), 1, padnum, button);
-			}
-			return;
-		}
-	}
-	if (bindtype == BIND_GAMEPAD1) {
-		bind_button_gamepad(hostpadnum, hostbutton, padnum, button);
-	} else if (bindtype == BIND_UI) {
-		bind_button_ui(hostpadnum, hostbutton, ui_func, button);
-	}
-}
-
-void process_pad_axis(char *key, tern_val val, uint8_t valtype, void *data)
-{
-	key = strdup(key);
-	pad_button_state *state = data;
-	int hostpadnum = state->padnum;
-	int ui_func, padnum, button;
-	if (valtype != TVAL_PTR) {
-		warning("Mapping for axis %s has a non-scalar value", key);
-		return;
-	}
-	int bindtype = parse_binding_target(val.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &padnum, &button);
-	char *modifier = strchr(key, '.');
-	int positive = 1;
-	if (modifier) {
-		*modifier = 0;
-		modifier++;
-		if (!strcmp("negative", modifier)) {
-			positive = 0;
-		} else if(strcmp("positive", modifier)) {
-			warning("Invalid axis modifier %s for axis %s on pad %d\n", modifier, key, hostpadnum);
-		}
-	}
-	char *end;
-	long axis = strtol(key, &end, 10);
-	if (*end) {
-		//key is not a valid base 10 integer
-		axis = render_translate_input_name(hostpadnum, key, 1);
-		if (axis < 0) {
-			if (axis == RENDER_INVALID_NAME) {
-				warning("%s is not a valid gamepad input name\n", key);
-			} else if (axis == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) {
-				warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum);
-				map_warning_pad = hostpadnum;
-			}
-			goto done;
-		}
-		if (axis & RENDER_DPAD_BIT) {
-			if (bindtype == BIND_GAMEPAD1) {
-				bind_dpad_gamepad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), padnum, button);
-			} else {
-				bind_dpad_ui(hostpadnum, render_dpad_part(axis), render_direction_part(axis), ui_func, button);
-			}
-			goto done;
-		} else if (axis & RENDER_AXIS_BIT) {
-			axis = render_axis_part(axis);
-		} else {
-			if (bindtype == BIND_GAMEPAD1) {
-				bind_button_gamepad(hostpadnum, axis, padnum, button);
-			} else if (bindtype == BIND_UI) {
-				bind_button_ui(hostpadnum, axis, ui_func, button);
-			}
-			goto done;
-		}
-	}
-	if (bindtype == BIND_GAMEPAD1) {
-		bind_axis_gamepad(hostpadnum, axis, positive, padnum, button);
-	} else {
-		bind_axis_ui(hostpadnum, axis, positive, ui_func, button);
-	}
-done:
-	free(key);
-	return;
-}
-
-static tern_node *get_pad_buttons()
-{
-	static tern_node *padbuttons;
-	if (!padbuttons) {
-		padbuttons = tern_insert_int(NULL, ".up", DPAD_UP);
-		padbuttons = tern_insert_int(padbuttons, ".down", DPAD_DOWN);
-		padbuttons = tern_insert_int(padbuttons, ".left", DPAD_LEFT);
-		padbuttons = tern_insert_int(padbuttons, ".right", DPAD_RIGHT);
-		padbuttons = tern_insert_int(padbuttons, ".a", BUTTON_A);
-		padbuttons = tern_insert_int(padbuttons, ".b", BUTTON_B);
-		padbuttons = tern_insert_int(padbuttons, ".c", BUTTON_C);
-		padbuttons = tern_insert_int(padbuttons, ".x", BUTTON_X);
-		padbuttons = tern_insert_int(padbuttons, ".y", BUTTON_Y);
-		padbuttons = tern_insert_int(padbuttons, ".z", BUTTON_Z);
-		padbuttons = tern_insert_int(padbuttons, ".start", BUTTON_START);
-		padbuttons = tern_insert_int(padbuttons, ".mode", BUTTON_MODE);
-	}
-	return padbuttons;
-}
-
-static tern_node *get_mouse_buttons()
-{
-	static tern_node *mousebuttons;
-	if (!mousebuttons) {
-		mousebuttons = tern_insert_int(NULL, ".left", MOUSE_LEFT);
-		mousebuttons = tern_insert_int(mousebuttons, ".middle", MOUSE_MIDDLE);
-		mousebuttons = tern_insert_int(mousebuttons, ".right", MOUSE_RIGHT);
-		mousebuttons = tern_insert_int(mousebuttons, ".start", MOUSE_START);
-		mousebuttons = tern_insert_int(mousebuttons, ".motion", PSEUDO_BUTTON_MOTION);
-	}
-	return mousebuttons;
-}
-
-void handle_joy_added(int joystick)
-{
-	if (joystick > MAX_JOYSTICKS) {
-		return;
-	}
-	tern_node * pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
-	if (pads) {
-		char numstr[11];
-		sprintf(numstr, "%d", joystick);
-		tern_node * pad = tern_find_node(pads, numstr);
-		if (pad) {
-			tern_node * dpad_node = tern_find_node(pad, "dpads");
-			if (dpad_node) {
-				for (int dpad = 0; dpad < 10; dpad++)
-				{
-					numstr[0] = dpad + '0';
-					numstr[1] = 0;
-					tern_node * pad_dpad = tern_find_node(dpad_node, numstr);
-					char * dirs[] = {"up", "down", "left", "right"};
-					int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
-					for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) {
-						char * target = tern_find_ptr(pad_dpad, dirs[dir]);
-						if (target) {
-							int ui_func, padnum, button;
-							int bindtype = parse_binding_target(target, get_pad_buttons(), get_mouse_buttons(), &ui_func, &padnum, &button);
-							if (bindtype == BIND_GAMEPAD1) {
-								bind_dpad_gamepad(joystick, dpad, dirnums[dir], padnum, button);
-							} else if (bindtype == BIND_UI) {
-								bind_dpad_ui(joystick, dpad, dirnums[dir], ui_func, button);
-							}
-						}
-					}
-				}
-			}
-			tern_node *button_node = tern_find_node(pad, "buttons");
-			if (button_node) {
-				pad_button_state state = {
-					.padnum = joystick,
-					.padbuttons = get_pad_buttons(),
-					.mousebuttons = get_mouse_buttons()
-				};
-				tern_foreach(button_node, process_pad_button, &state);
-			}
-			tern_node *axes_node = tern_find_node(pad, "axes");
-			if (axes_node) {
-				pad_button_state state = {
-					.padnum = joystick,
-					.padbuttons = get_pad_buttons(),
-					.mousebuttons = get_mouse_buttons()
-				};
-				tern_foreach(axes_node, process_pad_axis, &state);
-			}
-			if (current_io) {
-				if (joysticks[joystick].buttons) {
-					map_bindings(current_io->ports, joysticks[joystick].buttons, joysticks[joystick].num_buttons);
-				}
-				if (joysticks[joystick].dpads)
-				{
-					for (uint32_t i = 0; i < joysticks[joystick].num_dpads; i++)
-					{
-						map_bindings(current_io->ports, joysticks[joystick].dpads[i].bindings, 4);
-					}
-				}
-				if (joysticks[joystick].axes) {
-					for (uint32_t i = 0; i < joysticks[joystick].num_axes; i++)
-					{
-						map_bindings(current_io->ports, &joysticks[joystick].axes[i].positive, 1);
-						map_bindings(current_io->ports, &joysticks[joystick].axes[i].negative, 1);
-					}
-				}
-			}
-		}
-	}
-	
-}
-
-void set_keybindings(sega_io *io)
-{
-	static uint8_t already_done;
-	if (already_done) {
-		map_all_bindings(io);
-		return;
-	}
-	already_done = 1;
-	io_port *ports = io->ports;
-	tern_node * special = tern_insert_int(NULL, "up", RENDERKEY_UP);
-	special = tern_insert_int(special, "down", RENDERKEY_DOWN);
-	special = tern_insert_int(special, "left", RENDERKEY_LEFT);
-	special = tern_insert_int(special, "right", RENDERKEY_RIGHT);
-	special = tern_insert_int(special, "enter", '\r');
-	special = tern_insert_int(special, "space", ' ');
-	special = tern_insert_int(special, "tab", '\t');
-	special = tern_insert_int(special, "backspace", '\b');
-	special = tern_insert_int(special, "esc", RENDERKEY_ESC);
-	special = tern_insert_int(special, "delete", RENDERKEY_DEL);
-	special = tern_insert_int(special, "lshift", RENDERKEY_LSHIFT);
-	special = tern_insert_int(special, "rshift", RENDERKEY_RSHIFT);
-	special = tern_insert_int(special, "lctrl", RENDERKEY_LCTRL);
-	special = tern_insert_int(special, "rctrl", RENDERKEY_RCTRL);
-	special = tern_insert_int(special, "lalt", RENDERKEY_LALT);
-	special = tern_insert_int(special, "ralt", RENDERKEY_RALT);
-	special = tern_insert_int(special, "home", RENDERKEY_HOME);
-	special = tern_insert_int(special, "end", RENDERKEY_END);
-	special = tern_insert_int(special, "pageup", RENDERKEY_PAGEUP);
-	special = tern_insert_int(special, "pagedown", RENDERKEY_PAGEDOWN);
-	special = tern_insert_int(special, "f1", RENDERKEY_F1);
-	special = tern_insert_int(special, "f2", RENDERKEY_F2);
-	special = tern_insert_int(special, "f3", RENDERKEY_F3);
-	special = tern_insert_int(special, "f4", RENDERKEY_F4);
-	special = tern_insert_int(special, "f5", RENDERKEY_F5);
-	special = tern_insert_int(special, "f6", RENDERKEY_F6);
-	special = tern_insert_int(special, "f7", RENDERKEY_F7);
-	special = tern_insert_int(special, "f8", RENDERKEY_F8);
-	special = tern_insert_int(special, "f9", RENDERKEY_F9);
-	special = tern_insert_int(special, "f10", RENDERKEY_F10);
-	special = tern_insert_int(special, "f11", RENDERKEY_F11);
-	special = tern_insert_int(special, "f12", RENDERKEY_F12);
-	special = tern_insert_int(special, "select", RENDERKEY_SELECT);
-	special = tern_insert_int(special, "play", RENDERKEY_PLAY);
-	special = tern_insert_int(special, "search", RENDERKEY_SEARCH);
-	special = tern_insert_int(special, "back", RENDERKEY_BACK);
-
-	tern_node *padbuttons = get_pad_buttons();
-
-	tern_node *mousebuttons = get_mouse_buttons();
-	
-	tern_node * keys = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
-	process_keys(keys, special, padbuttons, mousebuttons, NULL);
-	char numstr[] = "00";
-	tern_node * pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval;
-	if (pads) {
-		for (int i = 0; i < MAX_JOYSTICKS; i++)
-		{
-
-			if (i < 10) {
-				numstr[0] = i + '0';
-				numstr[1] = 0;
-			} else {
-				numstr[0] = i/10 + '0';
-				numstr[1] = i%10 + '0';
-			}
-			
-		}
-	}
-	memset(mice, 0, sizeof(mice));
-	tern_node * mice = tern_find_path(config, "bindings\0mice\0", TVAL_NODE).ptrval;
-	if (mice) {
-		tern_node *buttonmaps[2] = {padbuttons, mousebuttons};
-		tern_foreach(mice, process_mouse, buttonmaps);
-	}
-	tern_node * speed_nodes = tern_find_path(config, "clocks\0speeds\0", TVAL_NODE).ptrval;
-	speeds = malloc(sizeof(uint32_t));
-	speeds[0] = 100;
-	process_speeds(speed_nodes, NULL);
-	for (int i = 0; i < num_speeds; i++)
-	{
-		if (!speeds[i]) {
-			warning("Speed index %d was not set to a valid percentage!", i);
-			speeds[i] = 100;
-		}
-	}
-	map_all_bindings(io);
-}
-
-void map_all_bindings(sega_io *io)
-{
-	current_io = io;
-	io_port *ports = io->ports;
-	
-	for (int bucket = 0; bucket < 0x10000; bucket++)
-	{
-		if (bindings[bucket])
-		{
-			map_bindings(ports, bindings[bucket], 0x8000);
-		}
-	}
-	for (int stick = 0; stick < MAX_JOYSTICKS; stick++)
-	{
-		if (joysticks[stick].buttons) {
-			map_bindings(ports, joysticks[stick].buttons, joysticks[stick].num_buttons);
-		}
-		if (joysticks[stick].dpads)
-		{
-			for (uint32_t i = 0; i < joysticks[stick].num_dpads; i++)
-			{
-				map_bindings(ports, joysticks[stick].dpads[i].bindings, 4);
-			}
-		}
-		for (uint32_t i = 0; i < joysticks[stick].num_axes; i++)
-		{
-			map_bindings(current_io->ports, &joysticks[stick].axes[i].positive, 1);
-			map_bindings(current_io->ports, &joysticks[stick].axes[i].negative, 1);
-		}
-	}
-	for (int mouse = 0; mouse < MAX_MICE; mouse++)
-	{
-		if (mice[mouse].bind_type >= BIND_MOUSE1 && mice[mouse].bind_type <= BIND_MOUSE8) {
-			int num = mice[mouse].bind_type - BIND_MOUSE1;
-			for (int j = 0; j < 3; j++)
-			{
-				if (ports[j].device_type == IO_MOUSE && ports[j].device.mouse.mouse_num == num)
-				{
-					memset(ports[j].input, 0, sizeof(ports[j].input));
-					mice[mouse].motion_port = ports + j;
-					break;
-				}
-			}
-		}
-		map_bindings(ports, mice[mouse].buttons, MAX_MOUSE_BUTTONS);
-	}
-	keyboard_port = NULL;
-	for (int i = 0; i < 3; i++)
-	{
-		if (ports[i].device_type == IO_SATURN_KEYBOARD || ports[i].device_type == IO_XBAND_KEYBOARD) {
-			keyboard_port = ports + i;
-			break;
-		}
-	}
-	//not really related to the intention of this function, but the best place to do this currently
-	if (speeds[0] != 100) {
-		current_system->set_speed_percent(current_system, speeds[0]);
-	}
-}
 
 #define TH 0x40
 #define TR 0x20
@@ -1509,6 +412,7 @@
 		if (port->device.mouse.tr_counter == 3) {
 			port->device.mouse.latched_x = port->device.mouse.cur_x;
 			port->device.mouse.latched_y = port->device.mouse.cur_y;
+			/* FIXME mouse mode owned by bindings now
 			if (current_io->mouse_mode == MOUSE_ABSOLUTE) {
 				//avoid overflow in absolute mode
 				int deltax = port->device.mouse.latched_x - port->device.mouse.last_read_x;
@@ -1519,7 +423,7 @@
 				if (abs(deltay) > 255) {
 					port->device.mouse.latched_y = port->device.mouse.last_read_y + (deltay > 0 ? 255 : -255);
 				}
-			}
+			}*/
 		}
 	}
 }
--- a/io.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/io.h	Sat Jan 05 00:58:08 2019 -0800
@@ -11,6 +11,7 @@
 #include "serialize.h"
 
 enum {
+	IO_NONE,
 	IO_GAMEPAD2,
 	IO_GAMEPAD3,
 	IO_GAMEPAD6,
@@ -23,8 +24,7 @@
 	IO_EA_MULTI_A,
 	IO_EA_MULTI_B,
 	IO_SEGA_PARALLEL,
-	IO_GENERIC,
-	IO_NONE
+	IO_GENERIC
 };
 
 typedef struct {
@@ -70,48 +70,61 @@
 
 typedef struct {
 	io_port	ports[3];
-	uint8_t mouse_mode;
-	uint8_t mouse_captured;
-	uint8_t keyboard_captured;
 } sega_io;
 
-#define GAMEPAD_TH0 0
-#define GAMEPAD_TH1 1
-#define GAMEPAD_EXTRA 2
-#define GAMEPAD_NONE 0xF
-
-#define IO_TH0 0
-#define IO_TH1 1
-#define IO_STATE 2
+//pseudo gamepad for buttons on main console unit
+#define GAMEPAD_MAIN_UNIT 255
 
 enum {
-	IO_WRITE_PENDING,
-	IO_WRITTEN,
-	IO_READ_PENDING,
-	IO_READ
+	BUTTON_INVALID,
+	DPAD_UP,
+	DPAD_DOWN,
+	DPAD_LEFT,
+	DPAD_RIGHT,
+	BUTTON_A,
+	BUTTON_B,
+	BUTTON_C,
+	BUTTON_START,
+	BUTTON_X,
+	BUTTON_Y,
+	BUTTON_Z,
+	BUTTON_MODE,
+	NUM_GAMEPAD_BUTTONS
 };
 
-typedef struct genesis_context genesis_context;
+enum {
+	MAIN_UNIT_PAUSE
+};
 
-void set_keybindings(sega_io *io);
-void map_all_bindings(sega_io *io);
+enum {
+	MOUSE_LEFT = 1,
+	MOUSE_RIGHT = 2,
+	MOUSE_MIDDLE = 4,
+	MOUSE_START = 8,
+	PSEUDO_BUTTON_MOTION=0xFF
+};
+
 void setup_io_devices(tern_node * config, rom_info *rom, sega_io *io);
 void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction);
 void io_control_write(io_port *port, uint8_t value, uint32_t current_cycle);
 void io_data_write(io_port * pad, uint8_t value, uint32_t current_cycle);
 uint8_t io_data_read(io_port * pad, uint32_t current_cycle);
-void handle_keydown(int keycode, uint8_t scancode);
-void handle_keyup(int keycode, uint8_t scancode);
-void handle_joydown(int joystick, int button);
-void handle_joyup(int joystick, int button);
-void handle_joy_dpad(int joystick, int dpad, uint8_t state);
-void handle_joy_axis(int joystick, int axis, int16_t value);
-void handle_joy_added(int joystick);
-void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay);
-void handle_mousedown(int mouse, int button);
-void handle_mouseup(int mouse, int button);
 void io_serialize(io_port *port, serialize_buffer *buf);
 void io_deserialize(deserialize_buffer *buf, void *vport);
 
+void io_port_gamepad_down(io_port *port, uint8_t button);
+void io_port_gamepad_up(io_port *port, uint8_t button);
+void io_gamepad_down(sega_io *io, uint8_t gamepad_num, uint8_t button);
+void io_gamepad_up(sega_io *io, uint8_t gamepad_num, uint8_t button);
+void io_mouse_down(sega_io *io, uint8_t mouse_num, uint8_t button);
+void io_mouse_up(sega_io *io, uint8_t mouse_num, uint8_t button);
+void io_mouse_motion_absolute(sega_io *io, uint8_t mouse_num, uint16_t x, uint16_t y);
+void io_mouse_motion_relative(sega_io *io, uint8_t mouse_num, int32_t x, int32_t y);
+void io_keyboard_down(sega_io *io, uint8_t scancode);
+void io_keyboard_up(sega_io *io, uint8_t scancode);
+uint8_t io_has_keyboard(sega_io *io);
+
+extern const char * device_type_names[];
+
 #endif //IO_H_
 
--- a/jagcpu_x86.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/jagcpu_x86.c	Sat Jan 05 00:58:08 2019 -0800
@@ -102,7 +102,7 @@
 	code_ptr no_delay = code-.cur + 1;
 	jcc(code, CC_NZ, no_delay + 1);
 	ccylces(code, 1);
-	*no_delay = code->cur - (no_delay = 1);
+	*no_delay = code->cur - (no_delay + 1);
 	*no_result = code->cur - (no_result + 1);
 	mov_rr(code, opts->resultreg, opts->writeback, SZ_B);
 }
@@ -122,7 +122,7 @@
 
 uint16_t *translate_jag_inst(uint16_t *stream, jag_cpu_options *opts, uint32_t address)
 {
-	uint16_t inst = *stream
+	uint16_t inst = *stream;
 	++stream;
 	uint16_t opcode = jag_opcode(inst, opts->is_gpu);
 	check_cycles_int(&opts->gen, address);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcart.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,87 @@
+#include "genesis.h"
+
+static io_port *get_ports(m68k_context *m68k)
+{
+	genesis_context *gen = m68k->system;
+	if (!gen->extra) {
+		io_port *ports = calloc(2, sizeof(io_port));
+		ports[0].device_type = IO_GAMEPAD3;
+		ports[0].device.pad.gamepad_num = 3;
+		ports[1].device_type = IO_GAMEPAD3;
+		ports[1].device.pad.gamepad_num = 4;
+		io_control_write(ports, 0x40, 0);
+		io_control_write(ports + 1, 0x40, 0);
+		gen->extra = ports;
+	}
+		
+	return gen->extra;
+}
+
+void *jcart_write_w(uint32_t address, void *context, uint16_t value)
+{
+	m68k_context *m68k= context;
+	io_port *ports = get_ports(m68k);
+	value = value << 6 & 0x40;
+	io_data_write(ports, value, m68k->current_cycle);
+	io_data_write(ports + 1, value, m68k->current_cycle);
+	return context;
+}
+
+void *jcart_write_b(uint32_t address, void *context, uint8_t value)
+{
+	if (address & 1) {
+		return jcart_write_w(address, context, value);
+	}
+	return context;
+}
+
+uint16_t jcart_read_w(uint32_t address, void *context)
+{
+	m68k_context *m68k= context;
+	io_port *ports = get_ports(m68k);
+	//according to Eke, bit 14 is forced low, at least on the Micro Machines 2 cart
+	//TODO: Test behavior of actual cart
+	uint16_t value = io_data_read(ports, m68k->current_cycle) << 8;
+	value |= io_data_read(ports + 1, m68k->current_cycle);
+	return value;
+}
+
+uint8_t jcart_read_b(uint32_t address, void *context)
+{
+	m68k_context *m68k= context;
+	io_port *ports = get_ports(m68k);
+	return io_data_read(ports + (address & 1), m68k->current_cycle);
+}
+
+void jcart_adjust_cycles(genesis_context *context, uint32_t deduction)
+{
+	io_port *ports = get_ports(context->m68k);
+	io_adjust_cycles(ports, context->m68k->current_cycle, deduction);
+	io_adjust_cycles(ports + 1, context->m68k->current_cycle, deduction);
+}
+
+void jcart_gamepad_down(genesis_context *context, uint8_t gamepad_num, uint8_t button)
+{
+	io_port *ports = get_ports(context->m68k);
+	if (gamepad_num == ports[1].device.pad.gamepad_num) {
+		ports++;
+	} else if (gamepad_num != ports[0].device.pad.gamepad_num) {
+		ports = NULL;
+	}
+	if (ports) {
+		io_port_gamepad_down(ports, button);
+	}
+}
+
+void jcart_gamepad_up(genesis_context *context, uint8_t gamepad_num, uint8_t button)
+{
+	io_port *ports = get_ports(context->m68k);
+	if (gamepad_num == ports[1].device.pad.gamepad_num) {
+		ports++;
+	} else if (gamepad_num != ports[0].device.pad.gamepad_num) {
+		ports = NULL;
+	}
+	if (ports) {
+		io_port_gamepad_up(ports, button);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jcart.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,12 @@
+#ifndef JCART_H_
+#define JCART_H_
+
+void *jcart_write_w(uint32_t address, void *context, uint16_t value);
+void *jcart_write_b(uint32_t address, void *context, uint8_t value);
+uint16_t jcart_read_w(uint32_t address, void *context);
+uint8_t jcart_read_b(uint32_t address, void *context);
+void jcart_adjust_cycles(genesis_context *context, uint32_t deduction);
+void jcart_gamepad_down(genesis_context *context, uint8_t gamepad_num, uint8_t button);
+void jcart_gamepad_up(genesis_context *context, uint8_t gamepad_num, uint8_t button);
+
+#endif //JCART_H_
--- a/m68k_core.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/m68k_core.c	Sat Jan 05 00:58:08 2019 -0800
@@ -468,7 +468,11 @@
 		}
 	}
 	if (opts->num_movem == opts->movem_storage) {
-		opts->movem_storage *= 2;
+		if (!opts->movem_storage) {
+			opts->movem_storage = 4;
+		} else {
+			opts->movem_storage *= 2;
+		}
 		opts->big_movem = realloc(opts->big_movem, sizeof(movem_fun) * opts->movem_storage);
 	}
 	if (!opts->extra_code.cur) {
@@ -902,7 +906,7 @@
 	RAW_IMPL(M68K_MOVE_USP, translate_m68k_move_usp),
 	RAW_IMPL(M68K_LEA, translate_m68k_lea_pea),
 	RAW_IMPL(M68K_PEA, translate_m68k_lea_pea),
-	RAW_IMPL(M68K_CLR, translate_m68k_clr),
+	UNARY_IMPL(M68K_CLR, N0|V0|C0|Z1),
 	OP_IMPL(M68K_EXG, translate_m68k_exg),
 	RAW_IMPL(M68K_SCC, translate_m68k_scc),
 
@@ -1168,7 +1172,6 @@
 {
 	code_ptr addr = get_native_address_trans(context, address);
 	m68k_options * options = context->options;
-	context->should_return = 0;
 	options->start_context(addr, context);
 }
 
@@ -1192,8 +1195,21 @@
 
 void m68k_options_free(m68k_options *opts)
 {
+	for (uint32_t address = 0; address < opts->gen.address_mask; address += NATIVE_CHUNK_SIZE)
+	{
+		uint32_t chunk = address / NATIVE_CHUNK_SIZE;
+		if (opts->gen.native_code_map[chunk].base) {
+			free(opts->gen.native_code_map[chunk].offsets);
+		}
+	}
 	free(opts->gen.native_code_map);
+	uint32_t ram_inst_slots = ram_size(&opts->gen) / 1024;
+	for (uint32_t i = 0; i < ram_inst_slots; i++)
+	{
+		free(opts->gen.ram_inst_sizes[i]);
+	}
 	free(opts->gen.ram_inst_sizes);
+	free(opts->big_movem);
 	free(opts);
 }
 
--- a/m68k_core_x86.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/m68k_core_x86.c	Sat Jan 05 00:58:08 2019 -0800
@@ -742,27 +742,6 @@
 	cycles(&opts->gen, BUS);
 }
 
-void translate_m68k_clr(m68k_options * opts, m68kinst * inst)
-{
-	code_info *code = &opts->gen.code;
-	update_flags(opts, N0|V0|C0|Z1);
-	int8_t reg = native_reg(&(inst->dst), opts);
-	if (reg >= 0) {
-		cycles(&opts->gen, (inst->extra.size == OPSIZE_LONG ? 6 : 4));
-		xor_rr(code, reg, reg, inst->extra.size);
-		return;
-	}
-	host_ea dst_op;
-	//TODO: fix timing
-	translate_m68k_op(inst, &dst_op, opts, 1);
-	if (dst_op.mode == MODE_REG_DIRECT) {
-		xor_rr(code, dst_op.base, dst_op.base, inst->extra.size);
-	} else {
-		mov_irdisp(code, 0, dst_op.base, dst_op.disp, inst->extra.size);
-	}
-	m68k_save_result(inst, opts);
-}
-
 void translate_m68k_ext(m68k_options * opts, m68kinst * inst)
 {
 	code_info *code = &opts->gen.code;
@@ -780,6 +759,7 @@
 	}
 	inst->extra.size = dst_size;
 	update_flags(opts, N|V0|C0|Z);
+	cycles(&opts->gen, BUS);
 	//M68K EXT only operates on registers so no need for a call to save result here
 }
 
@@ -1210,7 +1190,8 @@
 void translate_m68k_reset(m68k_options *opts, m68kinst *inst)
 {
 	code_info *code = &opts->gen.code;
-	cycles(&opts->gen, BUS);
+	//RESET instructions take a long time to give peripherals time to reset themselves
+	cycles(&opts->gen, 132);
 	mov_rdispr(code, opts->gen.context_reg, offsetof(m68k_context, reset_handler), opts->gen.scratch1, SZ_PTR);
 	cmp_ir(code, 0, opts->gen.scratch1, SZ_PTR);
 	code_ptr no_reset_handler = code->cur + 1;
@@ -1437,6 +1418,7 @@
 {
 	switch(inst->op)
 	{
+	case M68K_CLR:   xor_rr(code, dst, dst, size); break;
 	case M68K_NEG:   neg_r(code, dst, size); break;
 	case M68K_NOT:   not_r(code, dst, size); cmp_ir(code, 0, dst, size); break;
 	case M68K_ROL:   rol_clr(code, dst, size); break;
@@ -1452,6 +1434,7 @@
 {
 	switch(inst->op)
 	{
+	case M68K_CLR:   mov_irdisp(code, 0, dst, disp, size); break;
 	case M68K_NEG:   neg_rdisp(code, dst, disp, size); break;
 	case M68K_NOT:   not_rdisp(code, dst, disp, size); cmp_irdisp(code, 0, dst, disp, size); break;
 	case M68K_ROL:   rol_clrdisp(code, dst, disp, size); break;
@@ -1466,7 +1449,11 @@
 void translate_m68k_unary(m68k_options *opts, m68kinst *inst, uint32_t flag_mask, host_ea *dst_op)
 {
 	code_info *code = &opts->gen.code;
-	cycles(&opts->gen, BUS);
+	uint32_t num_cycles = BUS;
+	if (inst->extra.size == OPSIZE_LONG && (inst->dst.addr_mode == MODE_REG || inst->dst.addr_mode == MODE_AREG)) {
+		num_cycles += 2;
+	}
+	cycles(&opts->gen, num_cycles);
 	if (dst_op->mode == MODE_REG_DIRECT) {
 		op_r(code, inst, dst_op->base, inst->extra.size);
 	} else {
@@ -1508,6 +1495,8 @@
 		//destination is in memory so we need to preserve scratch2 for the write at the end
 		push_r(code, opts->gen.scratch2);
 	}
+	//MC68000 User's Manual suggests NBCD hides the 2 cycle penalty during the write cycle somehow
+	cycles(&opts->gen, inst->op == M68K_NBCD && inst->dst.addr_mode != MODE_REG_DIRECT ? BUS : BUS + 2);
 	uint8_t other_reg;
 	//WARNING: This may need adjustment if register assignments change
 	if (opts->gen.scratch2 > RBX) {
@@ -2365,7 +2354,7 @@
 	}
 	code_ptr loop_top = code->cur;
 		call(code, opts->do_sync);
-		cmp_rr(code, opts->gen.limit, opts->gen.cycles, SZ_D);
+		cmp_rr(code, opts->gen.cycles, opts->gen.limit, SZ_D);
 		code_ptr normal_cycle_up = code->cur + 1;
 		jcc(code, CC_A, code->cur + 2);
 			cycles(&opts->gen, BUS);
@@ -2454,6 +2443,7 @@
 void translate_m68k_move_from_sr(m68k_options *opts, m68kinst *inst, host_ea *src_op, host_ea *dst_op)
 {
 	code_info *code = &opts->gen.code;
+	cycles(&opts->gen, inst->dst.addr_mode == MODE_REG_DIRECT ? BUS+2 : BUS);
 	call(code, opts->get_sr);
 	if (dst_op->mode == MODE_REG_DIRECT) {
 		mov_rr(code, opts->gen.scratch1, dst_op->base, SZ_W);
--- a/m68k_internal.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/m68k_internal.h	Sat Jan 05 00:58:08 2019 -0800
@@ -66,7 +66,6 @@
 void translate_m68k_unary(m68k_options *opts, m68kinst *inst, uint32_t flag_mask, host_ea *dst_op);
 void translate_m68k_cmp(m68k_options * opts, m68kinst * inst);
 void translate_m68k_tas(m68k_options * opts, m68kinst * inst);
-void translate_m68k_clr(m68k_options * opts, m68kinst * inst);
 void translate_m68k_ext(m68k_options * opts, m68kinst * inst);
 void translate_m68k_abcd_sbcd(m68k_options *opts, m68kinst *inst, host_ea *src_op, host_ea *dst_op);
 void translate_m68k_sl(m68k_options *opts, m68kinst *inst, host_ea *src_op, host_ea *dst_op);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/megawifi.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,439 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#ifdef _WIN32
+#define WINVER 0x501
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include "genesis.h"
+#include "net.h"
+
+enum {
+	TX_IDLE,
+	TX_LEN1,
+	TX_LEN2,
+	TX_PAYLOAD,
+	TX_WAIT_ETX
+};
+#define STX 0x7E
+#define ETX 0x7E
+#define MAX_RECV_SIZE 1440
+
+#define E(N) N
+enum {
+#include "mw_commands.c"
+	CMD_ERROR = 255
+};
+#undef E
+#define E(N) #N
+static const char *cmd_names[] = {
+#include "mw_commands.c"
+	[255] = "CMD_ERROR"
+};
+
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+
+enum {
+	STATE_IDLE=1,
+	STATE_AP_JOIN,
+	STATE_SCAN,
+	STATE_READY,
+	STATE_TRANSPARENT
+};
+
+#define FLAG_ONLINE 
+
+typedef struct {
+	uint32_t transmit_bytes;
+	uint32_t expected_bytes;
+	uint32_t receive_bytes;
+	uint32_t receive_read;
+	int      sock_fds[15];
+	uint16_t channel_flags;
+	uint8_t  channel_state[15];
+	uint8_t  scratchpad;
+	uint8_t  transmit_channel;
+	uint8_t  transmit_state;
+	uint8_t  module_state;
+	uint8_t  flags;
+	uint8_t  transmit_buffer[4096];
+	uint8_t  receive_buffer[4096];
+} megawifi;
+
+static megawifi *get_megawifi(void *context)
+{
+	m68k_context *m68k = context;
+	genesis_context *gen = m68k->system;
+	if (!gen->extra) {
+		gen->extra = calloc(1, sizeof(megawifi));
+		megawifi *mw = gen->extra;
+		mw->module_state = STATE_IDLE;
+		for (int i = 0; i < 15; i++)
+		{
+			mw->sock_fds[i] = -1;
+		}
+	}
+	return gen->extra;
+}
+
+static void mw_putc(megawifi *mw, uint8_t v)
+{
+	if (mw->receive_bytes == sizeof(mw->receive_buffer)) {
+		return;
+	}
+	mw->receive_buffer[mw->receive_bytes++] = v;
+}
+
+static void mw_set(megawifi *mw, uint8_t val, uint32_t count)
+{
+	if (count + mw->receive_bytes > sizeof(mw->receive_buffer)) {
+		count = sizeof(mw->receive_buffer) - mw->receive_bytes;
+	}
+	memset(mw->receive_buffer + mw->receive_bytes, val, count);
+	mw->receive_bytes += count;
+}
+
+static void mw_copy(megawifi *mw, const uint8_t *src, uint32_t count)
+{
+	if (count + mw->receive_bytes > sizeof(mw->receive_buffer)) {
+		count = sizeof(mw->receive_buffer) - mw->receive_bytes;
+	}
+	memcpy(mw->receive_buffer + mw->receive_bytes, src, count);
+	mw->receive_bytes += count;
+}
+
+static void mw_puts(megawifi *mw, char *s)
+{
+	uint32_t len = strlen(s);
+	if ((mw->receive_bytes + len) > sizeof(mw->receive_buffer)) {
+		return;
+	}
+	memcpy(mw->receive_buffer + mw->receive_bytes, s, len);
+	mw->receive_bytes += len;
+}
+
+static void poll_socket(megawifi *mw, uint8_t channel)
+{
+	if (mw->sock_fds[channel] < 0) {
+		return;
+	}
+	if (mw->channel_state[channel] == 1) {
+		int res = accept(mw->sock_fds[channel], NULL, NULL);
+		if (res >= 0) {
+			close(mw->sock_fds[channel]);
+#ifndef _WIN32
+//FIXME: Set nonblocking on Windows too
+			fcntl(res, F_SETFL, O_NONBLOCK);
+#endif
+			mw->sock_fds[channel] = res;
+			mw->channel_state[channel] = 2;
+			mw->channel_flags |= 1 << (channel + 1);
+		} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
+			close(mw->sock_fds[channel]);
+			mw->channel_state[channel] = 0;
+			mw->channel_flags |= 1 << (channel + 1);
+		}
+	} else if (mw->channel_state[channel] == 2 && mw->receive_bytes < sizeof(mw->receive_buffer) - 4) {
+		size_t max = sizeof(mw->receive_buffer) - 4 - mw->receive_bytes;
+		if (max > MAX_RECV_SIZE) {
+			max = MAX_RECV_SIZE;
+		}
+		int bytes = recv(mw->sock_fds[channel], mw->receive_buffer + mw->receive_bytes + 3, max, 0);
+		if (bytes > 0) {
+			mw_putc(mw, STX);
+			mw_putc(mw, bytes >> 8 | (channel+1) << 4);
+			mw_putc(mw, bytes);
+			mw->receive_bytes += bytes;
+			mw_putc(mw, ETX);
+			//should this set the channel flag?
+		} else if (bytes < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
+			close(mw->sock_fds[channel]);
+			mw->channel_state[channel] = 0;
+			mw->channel_flags |= 1 << (channel + 1);
+		}
+	}
+}
+
+static void poll_all_sockets(megawifi *mw)
+{
+	for (int i = 0; i < 15; i++)
+	{
+		poll_socket(mw, i);
+	}
+}
+
+static void start_reply(megawifi *mw, uint8_t cmd)
+{
+	mw_putc(mw, STX);
+	//reserve space for length
+	mw->receive_bytes += 2;
+	//cmd
+	mw_putc(mw, 0);
+	mw_putc(mw, cmd);
+	//reserve space for length
+	mw->receive_bytes += 2;
+}
+
+static void end_reply(megawifi *mw)
+{
+	uint32_t len = mw->receive_bytes - 3;
+	//LSD packet length
+	mw->receive_buffer[1] = len >> 8;
+	mw->receive_buffer[2] = len;
+	//command length
+	len -= 4;
+	mw->receive_buffer[5] = len >> 8;
+	mw->receive_buffer[6] = len;
+	mw_putc(mw, ETX);
+}
+
+static void process_packet(megawifi *mw)
+{
+	if (mw->transmit_channel == 0) {
+		uint32_t command = mw->transmit_buffer[0] << 8 | mw->transmit_buffer[1];
+		uint32_t size = mw->transmit_buffer[2] << 8 | mw->transmit_buffer[3];
+		if (size > mw->transmit_bytes - 4) {
+			size = mw->transmit_bytes - 4;
+		}
+		int orig_receive_bytes = mw->receive_bytes;
+		switch (command)
+		{
+		case CMD_VERSION:
+			start_reply(mw, CMD_OK);
+			mw_putc(mw, 1);
+			mw_putc(mw, 0);
+			mw_puts(mw, "blastem");
+			end_reply(mw);
+			break;
+		case CMD_ECHO:
+			mw->receive_bytes = mw->transmit_bytes;
+			memcpy(mw->receive_buffer, mw->transmit_buffer, mw->transmit_bytes);
+			break;
+		case CMD_IP_CURRENT: {
+			iface_info i;
+			if (get_host_address(&i)) {
+				start_reply(mw, CMD_OK);
+				//config number and reserved bytes
+				mw_set(mw, 0, 4);
+				//ip
+				mw_copy(mw, i.ip, sizeof(i.ip));
+				//net mask
+				mw_copy(mw, i.net_mask, sizeof(i.net_mask));
+				//gateway guess
+				mw_putc(mw, i.ip[0] & i.net_mask[0]);
+				mw_putc(mw, i.ip[1] & i.net_mask[1]);
+				mw_putc(mw, i.ip[2] & i.net_mask[2]);
+				mw_putc(mw, (i.ip[3] & i.net_mask[3]) + 1);
+				//dns
+				static const uint8_t localhost[] = {127,0,0,1};
+				mw_copy(mw, localhost, sizeof(localhost));
+				mw_copy(mw, localhost, sizeof(localhost));
+				
+			} else {
+				start_reply(mw, CMD_ERROR);
+			}
+			end_reply(mw);
+			break;
+		}
+		case CMD_AP_JOIN:
+			mw->module_state = STATE_READY;
+			start_reply(mw, CMD_OK);
+			end_reply(mw);
+			break;
+		case CMD_TCP_BIND:{
+			if (size < 7){
+				start_reply(mw, CMD_ERROR);
+				end_reply(mw);
+				break;
+			}
+			uint8_t channel = mw->transmit_buffer[10];
+			if (!channel || channel > 15) {
+				start_reply(mw, CMD_ERROR);
+				end_reply(mw);
+				break;
+			}
+			channel--;
+			if (mw->sock_fds[channel] >= 0) {
+				close(mw->sock_fds[channel]);
+			}
+			mw->sock_fds[channel] = socket(AF_INET, SOCK_STREAM, 0);
+			if (mw->sock_fds[channel] < 0) {
+				start_reply(mw, CMD_ERROR);
+				end_reply(mw);
+				break;
+			}
+			int value = 1;
+			setsockopt(mw->sock_fds[channel], SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
+			struct sockaddr_in bind_addr;
+			memset(&bind_addr, 0, sizeof(bind_addr));
+			bind_addr.sin_family = AF_INET;
+			bind_addr.sin_port = htons(mw->transmit_buffer[8] << 8 | mw->transmit_buffer[9]);
+			if (bind(mw->sock_fds[channel], (struct sockaddr *)&bind_addr, sizeof(bind_addr)) != 0) {
+				close(mw->sock_fds[channel]);
+				mw->sock_fds[channel] = -1;
+				start_reply(mw, CMD_ERROR);
+				end_reply(mw);
+				break;
+			}
+			int res = listen(mw->sock_fds[channel], 2);
+			start_reply(mw, res ? CMD_ERROR : CMD_OK);
+			if (res) {
+				close(mw->sock_fds[channel]);
+				mw->sock_fds[channel] = -1;
+			} else {
+				mw->channel_flags |= 1 << (channel + 1);
+				mw->channel_state[channel] = 1;
+#ifndef _WIN32
+//FIXME: Set nonblocking on Windows too
+				fcntl(mw->sock_fds[channel], F_SETFL, O_NONBLOCK);
+#endif
+			}
+			end_reply(mw);
+			break;
+		}
+		case CMD_SOCK_STAT: {
+			uint8_t channel = mw->transmit_buffer[4];
+			if (!channel || channel > 15) {
+				start_reply(mw, CMD_ERROR);
+				end_reply(mw);
+				break;
+			}
+			mw->channel_flags &= ~(1 << channel);
+			channel--;
+			poll_socket(mw, channel);
+			start_reply(mw, CMD_OK);
+			mw_putc(mw, mw->channel_state[channel]);
+			end_reply(mw);
+			break;
+		}
+		case CMD_SYS_STAT:
+			poll_all_sockets(mw);
+			start_reply(mw, CMD_OK);
+			mw_putc(mw, mw->module_state);
+			mw_putc(mw, mw->flags);
+			mw_putc(mw, mw->channel_flags >> 8);
+			mw_putc(mw, mw->channel_flags);
+			end_reply(mw);
+			break;
+		default:
+			printf("Unhandled MegaWiFi command %s(%d) with length %X\n", cmd_names[command], command, size);
+			break;
+		}
+	} else if (mw->sock_fds[mw->transmit_channel - 1] >= 0 && mw->channel_state[mw->transmit_channel - 1] == 2) {
+		uint8_t channel = mw->transmit_channel - 1;
+		int sent = send(mw->sock_fds[channel], mw->transmit_buffer, mw->transmit_bytes, MSG_NOSIGNAL);
+		if (sent < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
+			close(mw->sock_fds[channel]);
+			mw->sock_fds[channel] = -1;
+			mw->channel_state[channel] = 0;
+			mw->channel_flags |= 1 << mw->transmit_channel;
+		} else if (sent < mw->transmit_bytes) {
+			//TODO: save this data somewhere so it can be sent in poll_socket
+			printf("Sent %d bytes on channel %d, but %d were requested\n", sent, mw->transmit_channel, mw->transmit_bytes);
+		}
+	} else {
+		printf("Unhandled receive of MegaWiFi data on channel %d\n", mw->transmit_channel);
+	}
+	mw->transmit_bytes = mw->expected_bytes = 0;
+}
+
+void *megawifi_write_b(uint32_t address, void *context, uint8_t value)
+{
+	if (!(address & 1)) {
+		return context;
+	}
+	megawifi *mw = get_megawifi(context);
+	address = address >> 1 & 7;
+	switch (address)
+	{
+	case 0:
+		switch (mw->transmit_state)
+		{
+		case TX_IDLE:
+			if (value == STX) {
+				mw->transmit_state = TX_LEN1;
+			}
+			break;
+		case TX_LEN1:
+			mw->transmit_channel = value >> 4;
+			mw->expected_bytes = value << 8 & 0xF00;
+			mw->transmit_state = TX_LEN2;
+			break;
+		case TX_LEN2:
+			mw->expected_bytes |= value;
+			mw->transmit_state = TX_PAYLOAD;
+			break;
+		case TX_PAYLOAD:
+			mw->transmit_buffer[mw->transmit_bytes++] = value;
+			if (mw->transmit_bytes == mw->expected_bytes) {
+				mw->transmit_state = TX_WAIT_ETX;
+			}
+			break;
+		case TX_WAIT_ETX:
+			if (value == ETX) {
+				mw->transmit_state = TX_IDLE;
+				process_packet(mw);
+			}
+			break;
+		}
+		break;
+	case 7:
+		mw->scratchpad = value;
+		break;
+	default:
+		printf("Unhandled write to MegaWiFi UART register %X: %X\n", address, value);
+	}
+	return context;
+}
+
+void *megawifi_write_w(uint32_t address, void *context, uint16_t value)
+{
+	return megawifi_write_b(address | 1, context, value);
+}
+
+uint8_t megawifi_read_b(uint32_t address, void *context)
+{
+	
+	if (!(address & 1)) {
+		return 0xFF;
+	}
+	megawifi *mw = get_megawifi(context);
+	address = address >> 1 & 7;
+	switch (address)
+	{
+	case 0:
+		poll_all_sockets(mw);
+		if (mw->receive_read < mw->receive_bytes) {
+			uint8_t ret = mw->receive_buffer[mw->receive_read++];
+			if (mw->receive_read == mw->receive_bytes) {
+				mw->receive_read = mw->receive_bytes = 0;
+			}
+			return ret;
+		}
+		return 0xFF;
+	case 5:
+		poll_all_sockets(mw);
+		//line status
+		return 0x60 | (mw->receive_read < mw->receive_bytes);
+	case 7:
+		return mw->scratchpad;
+	default:
+		printf("Unhandled read from MegaWiFi UART register %X\n", address);
+		return 0xFF;
+	}
+}
+
+uint16_t megawifi_read_w(uint32_t address, void *context)
+{
+	return 0xFF00 | megawifi_read_b(address | 1, context);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/megawifi.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,9 @@
+#ifndef MEGAWIFI_H_
+#define MEGAWIFI_H_
+
+void *megawifi_write_w(uint32_t address, void *context, uint16_t value);
+void *megawifi_write_b(uint32_t address, void *context, uint8_t value);
+uint16_t megawifi_read_w(uint32_t address, void *context);
+uint8_t megawifi_read_b(uint32_t address, void *context);
+
+#endif //MEGAWIFI_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/memmap.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,41 @@
+#ifndef MEMMAP_H_
+#define MEMMAP_H_
+
+typedef enum {
+	READ_16,
+	READ_8,
+	WRITE_16,
+	WRITE_8
+} ftype;
+
+#define MMAP_READ      0x01
+#define MMAP_WRITE     0x02
+#define MMAP_CODE      0x04
+#define MMAP_PTR_IDX   0x08
+#define MMAP_ONLY_ODD  0x10
+#define MMAP_ONLY_EVEN 0x20
+#define MMAP_FUNC_NULL 0x40
+#define MMAP_BYTESWAP  0x80
+#define MMAP_AUX_BUFF  0x100
+#define MMAP_READ_CODE 0x200
+
+typedef uint16_t (*read_16_fun)(uint32_t address, void * context);
+typedef uint8_t (*read_8_fun)(uint32_t address, void * context);
+typedef void * (*write_16_fun)(uint32_t address, void * context, uint16_t value);
+typedef void * (*write_8_fun)(uint32_t address, void * context, uint8_t value);
+
+typedef struct {
+	uint32_t     start;
+	uint32_t     end;
+	uint32_t     mask;
+	uint32_t     aux_mask;
+	uint16_t     ptr_index;
+	uint16_t     flags;
+	void *       buffer;
+	read_16_fun  read_16;
+	write_16_fun write_16;
+	read_8_fun   read_8;
+	write_8_fun  write_8;
+} memmap_chunk;
+
+#endif //MEMMAP_H_
--- a/menu.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/menu.c	Sat Jan 05 00:58:08 2019 -0800
@@ -8,71 +8,16 @@
 #include "backend.h"
 #include "util.h"
 #include "gst.h"
-#include "m68k_internal.h" //needed for get_native_address_trans, should be eliminated once handling of PC is cleaned up
-
-static menu_context *persist_path_menu;
-static void persist_path(void)
-{
-	char const *parts[] = {get_userdata_dir(), PATH_SEP, "sticky_path"};
-	char *pathfname = alloc_concat_m(3, parts);
-	FILE *f = fopen(pathfname, "wb");
-	if (f) {
-		if (fwrite(persist_path_menu->curpath, 1, strlen(persist_path_menu->curpath), f) != strlen(persist_path_menu->curpath)) {
-			warning("Failed to save menu path");
-		}
-		fclose(f);
-	} else {
-		warning("Failed to save menu path: Could not open %s for writing\n", pathfname);
-		
-	}
-	free(pathfname);
-}
+#include "paths.h"
+#include "saves.h"
+#include "config.h"
 
 static menu_context *get_menu(genesis_context *gen)
 {
 	menu_context *menu = gen->extra;
 	if (!menu) {
 		gen->extra = menu = calloc(1, sizeof(menu_context));
-		menu->curpath = NULL;
-		char *remember_path = tern_find_path(config, "ui\0remember_path\0", TVAL_PTR).ptrval;
-		if (!remember_path || !strcmp("on", remember_path)) {
-			char const *parts[] = {get_userdata_dir(), PATH_SEP, "sticky_path"};
-			char *pathfname = alloc_concat_m(3, parts);
-			FILE *f = fopen(pathfname, "rb");
-			if (f) {
-				long pathsize = file_size(f);
-				if (pathsize > 0) {
-					menu->curpath = malloc(pathsize + 1);
-					if (fread(menu->curpath, 1, pathsize, f) != pathsize) {
-						warning("Error restoring saved menu path");
-						free(menu->curpath);
-						menu->curpath = NULL;
-					} else {
-						menu->curpath[pathsize] = 0;
-					}
-				}
-				fclose(f);
-			}
-			free(pathfname);
-			if (!persist_path_menu) {
-				atexit(persist_path);
-			}
-			persist_path_menu = menu;
-		}
-		if (!menu->curpath) {
-			menu->curpath = tern_find_path(config, "ui\0initial_path\0", TVAL_PTR).ptrval;
-		}
-		if (!menu->curpath){
-#ifdef __ANDROID__
-			menu->curpath = get_external_storage_path();
-#else
-			menu->curpath = "$HOME";
-#endif
-		}
-		tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
-		vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
-		menu->curpath = replace_vars(menu->curpath, vars, 1);
-		tern_free(vars);
+		get_initial_browse_path(&menu->curpath);
 	}
 	return menu;
 }
@@ -97,17 +42,6 @@
 	}
 }
 
-int menu_dir_sort(const void *a, const void *b)
-{
-	const dir_entry *da, *db;
-	da = a;
-	db = b;
-	if (da->is_dir != db->is_dir) {
-		return db->is_dir - da->is_dir;
-	}
-	return strcasecmp(((dir_entry *)a)->name, ((dir_entry *)b)->name);
-}
-
 void copy_string_from_guest(m68k_context *m68k, uint32_t guest_addr, char *buf, size_t maxchars)
 {
 	char *cur;
@@ -150,54 +84,6 @@
 
 #define SAVE_INFO_BUFFER_SIZE (11*40)
 
-#ifdef __ANDROID__
-#include <SDL.h>
-#include <jni.h>
-char *get_external_storage_path()
-{
-	static char *ret;
-	if (ret) {
-		return ret;
-	}
-	JNIEnv *env = SDL_AndroidGetJNIEnv();
-	if ((*env)->PushLocalFrame(env, 8) < 0) {
-		return NULL;
-	}
-
-	jclass Environment = (*env)->FindClass(env, "android/os/Environment");
-	jmethodID getExternalStorageDirectory =
-		(*env)->GetStaticMethodID(env, Environment, "getExternalStorageDirectory", "()Ljava/io/File;");
-	jobject file = (*env)->CallStaticObjectMethod(env, Environment, getExternalStorageDirectory);
-	if (!file) {
-		goto cleanup;
-	}
-
-	jmethodID getAbsolutePath = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, file),
-		"getAbsolutePath", "()Ljava/lang/String;");
-	jstring path = (*env)->CallObjectMethod(env, file, getAbsolutePath);
-
-	char const *tmp = (*env)->GetStringUTFChars(env, path, NULL);
-	ret = strdup(tmp);
-	(*env)->ReleaseStringUTFChars(env, path, tmp);
-
-cleanup:
-	(*env)->PopLocalFrame(env, NULL);
-	return ret;
-}
-#endif
-
-#ifdef _WIN32
-#define localtime_r(a,b) localtime(a)
-//windows inclues seem not to like certain single letter defines from m68k_internal.h
-//get rid of them here
-#undef X
-#undef N
-#undef Z
-#undef V
-#undef C
-#include <windows.h>
-#endif
-
 uint32_t copy_dir_entry_to_guest(uint32_t dst, m68k_context *m68k, char *name, uint8_t is_dir)
 {
 	uint8_t *dest = get_native_pointer(dst, (void **)m68k->mem_pointers, &m68k->options->gen);
@@ -235,7 +121,9 @@
 	}
 	return dst;
 }
-
+#ifdef _WIN32
+#include <windows.h>
+#endif
 void * menu_write_w(uint32_t address, void * context, uint16_t value)
 {
 	m68k_context *m68k = context;
@@ -246,28 +134,10 @@
 		switch (address >> 2)
 		{
 		case 0: {
-#ifdef _WIN32
-			//handle virtual "drives" directory
-			if (menu->curpath[0] == PATH_SEP[0]) {
-				char drivestrings[4096];
-				if (sizeof(drivestrings) >= GetLogicalDriveStrings(sizeof(drivestrings), drivestrings)) {
-					for (char *cur = drivestrings; *cur; cur += strlen(cur) + 1)
-					{
-						dst = copy_dir_entry_to_guest(dst, m68k, cur, 1);
-					}
-				}
-				//terminate list
-				uint8_t *dest = get_native_pointer(dst, (void **)m68k->mem_pointers, &m68k->options->gen);
-				if (dest) {
-					*dest = dest[1] = 0;
-				}
-				break;
-			}
-#endif
 			size_t num_entries;
 			dir_entry *entries = get_dir_list(menu->curpath, &num_entries);
 			if (entries) {
-				qsort(entries, num_entries, sizeof(dir_entry), menu_dir_sort);
+				sort_dir_list(entries, num_entries);
 			} else {
 				warning("Failed to open directory %s: %s\n", menu->curpath, strerror(errno));
 				entries = malloc(sizeof(dir_entry));
@@ -275,46 +145,17 @@
 				entries->is_dir = 1;
 				num_entries = 1;
 			}
-#ifdef _WIN32
-			if (menu->curpath[1] == ':' && !menu->curpath[2]) {
-				//Add fake .. entry to allow navigation to virtual "drives" directory
-				dst = copy_dir_entry_to_guest(dst, m68k, "..", 1);
-			}
-#endif
-			char *ext_filter = strdup(tern_find_path_default(config, "ui\0extensions\0", (tern_val){.ptrval = "bin gen md smd sms gg"}, TVAL_PTR).ptrval);
-			uint32_t num_exts = 0, ext_storage = 5;
-			char **ext_list = malloc(sizeof(char *) * ext_storage);
-			char *cur_filter = ext_filter;
-			while (*cur_filter)
-			{
-				if (num_exts == ext_storage) {
-					ext_storage *= 2;
-					ext_list = realloc(ext_list, sizeof(char *) * ext_storage);
-				}
-				ext_list[num_exts++] = cur_filter;
-				cur_filter = split_keyval(cur_filter);
-			}
+			uint32_t num_exts;
+			char **ext_list = get_extension_list(config, &num_exts);
 			for (size_t i = 0; dst && i < num_entries; i++)
 			{
 				if (num_exts && !entries[i].is_dir) {
-					char *ext = path_extension(entries[i].name);
-					if (!ext) {
-						continue;
-					}
-					uint32_t extidx;
-					for (extidx = 0; extidx < num_exts; extidx++)
-					{
-						if (!strcasecmp(ext, ext_list[extidx])) {
-							break;
-						}
-					}
-					if (extidx == num_exts) {
+					if (!path_matches_extensions(entries[i].name, ext_list, num_exts)) {
 						continue;
 					}
 				}
 				dst = copy_dir_entry_to_guest(dst,  m68k, entries[i].name, entries[i].is_dir);
 			}
-			free(ext_filter);
 			free(ext_list);
 			//terminate list
 			uint8_t *dest = get_native_pointer(dst, (void **)m68k->mem_pointers, &m68k->options->gen);
@@ -327,42 +168,10 @@
 		case 1: {
 			char buf[4096];
 			copy_string_from_guest(m68k, dst, buf, sizeof(buf));
-			if (!strcmp(buf, "..")) {
-#ifdef _WIN32
-				if (menu->curpath[1] == ':' && !menu->curpath[2]) {
-					menu->curpath[0] = PATH_SEP[0];
-					menu->curpath[1] = 0;
-					break;
-				}
-#endif
-				size_t len = strlen(menu->curpath);
-				while (len > 0) {
-					--len;
-					if (is_path_sep(menu->curpath[len])) {
-						if (!len) {
-							//special handling for /
-							menu->curpath[len+1] = 0;
-						} else {
-							menu->curpath[len] = 0;
-						}
-						break;
-					}
-				}
-			} else {
-				char *tmp = menu->curpath;
-#ifdef _WIN32
-				if (menu->curpath[0] == PATH_SEP[0] && !menu->curpath[1]) {
-					menu->curpath = strdup(buf);
-				} else
-#endif
-				if (is_path_sep(menu->curpath[strlen(menu->curpath) - 1])) {
-					menu->curpath = alloc_concat(menu->curpath, buf);
-				} else {
-					char const *pieces[] = {menu->curpath, PATH_SEP, buf};
-					menu->curpath = alloc_concat_m(3, pieces);
-				}
-				free(tmp);
-			}
+			buf[sizeof(buf)-1] = 0;
+			char *tmp = menu->curpath;
+			menu->curpath = path_append(tmp, buf);
+			free(tmp);
 			break;
 		}
 		case 2:
@@ -399,63 +208,19 @@
 			char *cur = buffer;
 			if (gen->header.next_context && gen->header.next_context->save_dir) {
 				char *end = buffer + SAVE_INFO_BUFFER_SIZE;
-				char slotfile[] = "slot_0.state";
-				char slotfilegst[] = "slot_0.gst";
-				char const * parts[3] = {gen->header.next_context->save_dir, PATH_SEP, slotfile};
-				char const * partsgst[3] = {gen->header.next_context->save_dir, PATH_SEP, slotfilegst};
-				struct tm ltime;
-				char *fname;
-				time_t modtime;
-				for (int i = 0; i < 10 && cur < end; i++)
+				uint32_t num_slots;
+				save_slot_info *slots = get_slot_info(gen->header.next_context, &num_slots);
+				for (uint32_t i = 0; i < num_slots; i++)
 				{
-					slotfile[5] = i + '0';
-					fname = alloc_concat_m(3, parts);
-					modtime = get_modification_time(fname);
-					free(fname);
-					if (modtime) {
-						cur += snprintf(cur, end-cur, "Slot %d - ", i);
-						cur += strftime(cur, end-cur, "%c", localtime_r(&modtime, &ltime));
-						
-					} else {
-						slotfilegst[5] = i + '0';
-						fname = alloc_concat_m(3, partsgst);
-						modtime = get_modification_time(fname);
-						free(fname);
-						if (modtime) {
-							cur += snprintf(cur, end-cur, "Slot %d - ", i);
-							cur += strftime(cur, end-cur, "%c", localtime_r(&modtime, &ltime));
-						} else {
-							cur += snprintf(cur, end-cur, "Slot %d - EMPTY", i);
-						}
+					size_t desc_len = strlen(slots[i].desc) + 1;//+1 for string terminator
+					char *after = cur + desc_len + 1;//+1 for list terminator
+					if (after > cur) {
+						desc_len -= after - cur;
 					}
-					//advance past the null terminator for this entry
-					cur++;
+					memcpy(cur, slots[i].desc, desc_len);
+					cur = after;
 				}
-				if (cur < end) {
-					parts[2] = "quicksave.state";
-					fname = alloc_concat_m(3, parts);
-					modtime = get_modification_time(fname);
-					free(fname);
-					if (modtime) {
-						cur += strftime(cur, end-cur, "Quick  - %c", localtime_r(&modtime, &ltime));
-					} else {
-						parts[2] = "quicksave.gst";
-						fname = alloc_concat_m(3, parts);
-						modtime = get_modification_time(fname);
-						free(fname);
-						if (modtime) {
-							cur += strftime(cur, end-cur, "Quick  - %c", localtime_r(&modtime, &ltime));
-						} else if ((end-cur) > strlen("Quick  - EMPTY")){
-							cur += strlen(strcpy(cur, "Quick  - EMPTY"));
-						}
-					}
-					//advance past the null terminator for this entry
-					cur++;
-					if (cur < end) {
-						//terminate the list
-						*(cur++) = 0;
-					}
-				}
+				*cur = 0;//terminate list
 			} else {
 				*(cur++) = 0;
 				*(cur++) = 0;
@@ -475,36 +240,7 @@
 			if (gen->header.next_context && gen->header.next_context->save_dir) {
 				if (!gen->header.next_context->load_state(gen->header.next_context, dst)) {
 					break;
-				}/*
-				char numslotname[] = "slot_0.state";
-				char *slotname;
-				if (dst == QUICK_SAVE_SLOT) {
-					slotname = "quicksave.state";
-				} else {
-					numslotname[5] = '0' + dst;
-					slotname = numslotname;
 				}
-				char const *parts[] = {gen->header.next_context->save_dir, PATH_SEP, slotname};
-				char *statepath = alloc_concat_m(3, parts);
-				gen->header.next_context->load_state
-				genesis_context *next = (genesis_context *)gen->header.next_context;
-				deserialize_buffer state;
-				uint32_t pc = 0;
-				if (load_from_file(&state, statepath)) {
-					genesis_deserialize(&state, next);
-					free(state.data);
-					//HACK
-					pc = next->m68k->last_prefetch_address;
-				} else {
-					strcpy(statepath + strlen(statepath)-strlen("state"), "gst");
-					pc = load_gst(next, statepath);
-				}
-				free(statepath);
-				if (!pc) {
-					break;
-				}
-				next->m68k->resume_pc = get_native_address_trans(next->m68k, pc);
-				*/
 			}
 			m68k->should_return = 1;
 			break;
--- a/menu.s68	Tue Dec 19 00:49:13 2017 -0800
+++ b/menu.s68	Sat Jan 05 00:58:08 2019 -0800
@@ -1251,7 +1251,7 @@
 	dc.b "Prev", 0
 	
 about_text:
-	dc.b "BlastEm v0.5.0", 0
+	dc.b "BlastEm v0.6.1", 0
 	dc.b "Copyright 2011-2017 Michael Pavone", 0
 	dc.b " ", 0
 	dc.b "BlastEm is a high performance, open", 0
@@ -1269,6 +1269,8 @@
 	dc.b "Bart Trzynadlowski: Documentation", 0
 	dc.b "KanedaFR: Hosting the best Sega forum", 0
 	dc.b "Titan: Awesome demos and documentation", 0
+	dc.b "flamewing: BCD info and test ROM", 0
+	dc.b "r57shell: Opcode size test ROM", 0
 	dc.b "micky: Testing", 0
 	dc.b "Sasha: Testing", 0
 	dc.b "lol-frank: Testing", 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mw_commands.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,33 @@
+	E(CMD_OK),
+	E(CMD_VERSION),
+	E(CMD_ECHO),
+	E(CMD_AP_SCAN),
+	E(CMD_AP_CFG),
+	E(CMD_AP_CFG_GET),
+	E(CMD_IP_CURRENT),
+	E(CMD_RESERVED),
+	E(CMD_IP_CFG),
+	E(CMD_IP_CFG_GET),
+	E(CMD_DEF_AP_CFG),
+	E(CMD_DEF_AP_CFG_GET),
+	E(CMD_AP_JOIN),
+	E(CMD_AP_LEAVE),
+	E(CMD_TCP_CON),
+	E(CMD_TCP_BIND),
+	E(CMD_TCP_ACCEPT),
+	E(CMD_TCP_DISC),
+	E(CMD_UDP_SET),
+	E(CMD_UDP_CLR),
+	E(CMD_SOCK_STAT),
+	E(CMD_PING),
+	E(CMD_SNTP_CFG),
+	E(CMD_SNTP_CFG_GET),
+	E(CMD_DATETIME),
+	E(CMD_DT_SET),
+	E(CMD_FLASH_WRITE),
+	E(CMD_FLASH_READ),
+	E(CMD_FLASH_ERASE),
+	E(CMD_FLASH_ID),
+	E(CMD_SYS_STAT),
+	E(CMD_DEF_CFG_SET),
+	E(CMD_HRNG_GET),
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,49 @@
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include "net.h"
+
+static uint8_t is_loopback(struct sockaddr_in *addr)
+{
+	return (addr->sin_addr.s_addr & 0xFF) == 127;
+}
+
+static void format_address(uint8_t *dst, struct sockaddr_in *addr)
+{
+	long ip = addr->sin_addr.s_addr;
+	dst[0] = ip;
+	dst[1] = ip >> 8;
+	dst[2] = ip >> 16;
+	dst[3] = ip >> 24;
+}
+
+uint8_t get_host_address(iface_info *out)
+{
+	struct ifaddrs *entries, *current, *localhost;
+	if (getifaddrs(&entries)) {
+		return 0;
+	}
+	
+	for (current = entries; current; current = current->ifa_next)
+	{
+		if (current->ifa_addr && current->ifa_addr->sa_family == AF_INET) {
+			struct sockaddr_in *addr = (struct sockaddr_in *)current->ifa_addr;
+			if (is_loopback(addr)) {
+				localhost = current;
+			} else {
+				break;
+			}
+		}
+	}
+	if (!current && localhost) {
+		current = localhost;
+	}
+	uint8_t ret = 0;
+	if (current) {
+		ret = 1;
+		format_address(out->ip, (struct sockaddr_in *)current->ifa_addr);
+		format_address(out->net_mask, (struct sockaddr_in *)current->ifa_netmask);
+	}
+	freeifaddrs(entries);
+	return ret;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,12 @@
+#ifndef NET_H_
+#define NET_H_
+#include <stdint.h>
+
+typedef struct {
+	uint8_t ip[4];
+	uint8_t net_mask[4];
+} iface_info;
+
+uint8_t get_host_address(iface_info *out);
+
+#endif //NET_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net_win.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,14 @@
+#include "net.h"
+
+uint8_t get_host_address(iface_info *out)
+{
+	out->ip[0] = 127;
+	out->ip[1] = 0;
+	out->ip[2] = 0;
+	out->ip[3] = 1;
+	out->net_mask[0] = 255;
+	out->net_mask[0] = 255;
+	out->net_mask[0] = 255;
+	out->net_mask[0] = 0;
+	return 1;
+}
--- a/nor.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/nor.c	Sat Jan 05 00:58:08 2019 -0800
@@ -1,6 +1,7 @@
 #include "genesis.h"
 #include <stdlib.h>
 #include <string.h>
+#include "util.h"
 
 enum {
 	NOR_NORMAL,
@@ -31,9 +32,11 @@
 	state->cmd_state = NOR_CMD_IDLE;
 	state->alt_cmd = 0;
 	state->bus_flags = bus_flags;
+	state->cmd_address1 = 0x5555;
+	state->cmd_address2 = 0x2AAA;
 }
 
-void nor_run(nor_state *state, uint32_t cycle)
+void nor_run(nor_state *state, m68k_context *m68k, uint32_t cycle)
 {
 	if (state->last_write_cycle == 0xFFFFFFFF) {
 		return;
@@ -44,6 +47,10 @@
 			state->buffer[state->current_page + i] = state->page_buffer[i];
 		}
 		memset(state->page_buffer, 0xFF, state->page_size);
+		if (state->bus_flags == RAM_FLAG_BOTH) {
+			//TODO: add base address of NOR device to start and end addresses
+			m68k_invalidate_code_range(m68k, state->current_page, state->current_page + state->page_size);
+		}
 	}
 }
 
@@ -62,10 +69,13 @@
 		address = address >> 1;
 	}
 	
-	nor_run(state, m68k->current_cycle);
+	nor_run(state, m68k, m68k->current_cycle);
 	switch (state->mode)
 	{
 	case NOR_NORMAL:
+		if (state->bus_flags == RAM_FLAG_BOTH) {
+			address ^= 1;
+		}
 		return state->buffer[address & (state->size-1)];
 		break;
 	case NOR_PRODUCTID:
@@ -80,7 +90,7 @@
 			return 0xFE;
 		default:
 			return 0xFE;
-		}
+		}			//HERE
 		break;
 	case NOR_BOOTBLOCK:
 		break;
@@ -103,6 +113,9 @@
 		if (state->last_write_cycle != 0xFFFFFFFF) {
 			state->current_page = address & (state->size - 1) & ~(state->page_size - 1);
 		}
+		if (state->bus_flags == RAM_FLAG_BOTH) {
+			address ^= 1;
+		}
 		state->page_buffer[address & (state->page_size - 1)] = value;
 		break;
 	case NOR_PRODUCTID:
@@ -129,11 +142,11 @@
 		address = address >> 1;
 	}
 	
-	nor_run(state, m68k->current_cycle);
+	nor_run(state, m68k, m68k->current_cycle);
 	switch (state->cmd_state)
 	{
 	case NOR_CMD_IDLE:
-		if (value == 0xAA && (address & (state->size - 1)) == 0x5555) {
+		if (value == 0xAA && (address & (state->size - 1)) == state->cmd_address1) {
 			state->cmd_state = NOR_CMD_AA;
 		} else {
 			nor_write_byte(state, address, value, m68k->current_cycle);
@@ -141,16 +154,16 @@
 		}
 		break;
 	case NOR_CMD_AA:
-		if (value == 0x55 && (address & (state->size - 1)) == 0x2AAA) {
+		if (value == 0x55 && (address & (state->size - 1)) == state->cmd_address2) {
 			state->cmd_state = NOR_CMD_55;
 		} else {
-			nor_write_byte(state, 0x5555, 0xAA, m68k->current_cycle);
+			nor_write_byte(state, state->cmd_address1, 0xAA, m68k->current_cycle);
 			nor_write_byte(state, address, value, m68k->current_cycle);
 			state->cmd_state = NOR_CMD_IDLE;
 		}
 		break;
 	case NOR_CMD_55:
-		if ((address & (state->size - 1)) == 0x5555) {
+		if ((address & (state->size - 1)) == state->cmd_address1) {
 			if (state->alt_cmd) {
 				switch(value)
 				{
@@ -187,8 +200,8 @@
 				}
 			}
 		} else {
-			nor_write_byte(state, 0x5555, 0xAA, m68k->current_cycle);
-			nor_write_byte(state, 0x2AAA, 0x55, m68k->current_cycle);
+			nor_write_byte(state, state->cmd_address1, 0xAA, m68k->current_cycle);
+			nor_write_byte(state, state->cmd_address2, 0x55, m68k->current_cycle);
 			nor_write_byte(state, address, value, m68k->current_cycle);
 		}
 		state->cmd_state = NOR_CMD_IDLE;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/blastem_nuklear.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,2017 @@
+#define NK_IMPLEMENTATION
+#define NK_SDL_GLES2_IMPLEMENTATION
+
+#include <stdlib.h>
+#include <limits.h>
+#include "blastem_nuklear.h"
+#include "font.h"
+#include "../render.h"
+#include "../render_sdl.h"
+#include "../util.h"
+#include "../paths.h"
+#include "../saves.h"
+#include "../blastem.h"
+#include "../config.h"
+#include "../io.h"
+#include "../png.h"
+#include "../controller_info.h"
+#include "../bindings.h"
+
+static struct nk_context *context;
+
+typedef struct
+{
+	uint32_t         *image_data;
+	uint32_t         width, height;
+	struct nk_image  ui;
+} ui_image;
+
+static ui_image **ui_images, *controller_360, *controller_ps4, *controller_ps4_6b;
+static uint32_t num_ui_images, ui_image_storage;
+
+typedef void (*view_fun)(struct nk_context *);
+static view_fun current_view;
+static view_fun *previous_views;
+static uint32_t view_storage;
+static uint32_t num_prev;
+static struct nk_font *def_font;
+static uint8_t config_dirty;
+
+static void push_view(view_fun new_view)
+{
+	if (num_prev == view_storage) {
+		view_storage = view_storage ? 2*view_storage : 2;
+		previous_views = realloc(previous_views, view_storage*sizeof(view_fun));
+	}
+	previous_views[num_prev++] = current_view;
+	current_view = new_view;
+}
+
+static void pop_view()
+{
+	if (num_prev) {
+		current_view = previous_views[--num_prev];
+	}
+}
+
+static void clear_view_stack()
+{
+	num_prev = 0;
+}
+
+void view_play(struct nk_context *context)
+{
+	
+}
+
+void view_file_browser(struct nk_context *context, uint8_t normal_open)
+{
+	static char *current_path;
+	static dir_entry *entries;
+	static size_t num_entries;
+	static int32_t selected_entry = -1;
+	static char **ext_list;
+	static uint32_t num_exts;
+	static uint8_t got_ext_list;
+	if (!current_path) {
+		get_initial_browse_path(&current_path);
+	}
+	if (!entries) {
+		entries = get_dir_list(current_path, &num_entries);
+		if (entries) {
+			sort_dir_list(entries, num_entries);
+		}
+	}
+	if (!got_ext_list) {
+		ext_list = get_extension_list(config, &num_exts);
+		got_ext_list = 1;
+	}
+	uint32_t width = render_width();
+	uint32_t height = render_height();
+	if (nk_begin(context, "Load ROM", nk_rect(0, 0, width, height), 0)) {
+		nk_layout_row_static(context, height - context->style.font->height * 3, width - 60, 1);
+		int32_t old_selected = selected_entry;
+		if (nk_group_begin(context, "Select ROM", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) {
+			nk_layout_row_static(context, context->style.font->height - 2, width-100, 1);
+			for (int32_t i = 0; i < num_entries; i++)
+			{
+				if (entries[i].name[0] == '.' && entries[i].name[1] != '.') {
+					continue;
+				}
+				if (num_exts && !entries[i].is_dir && !path_matches_extensions(entries[i].name, ext_list, num_exts)) {
+					continue;
+				}
+				int selected = i == selected_entry;
+				nk_selectable_label(context, entries[i].name, NK_TEXT_ALIGN_LEFT, &selected);
+				if (selected) {
+					selected_entry = i;
+				} else if (i == selected_entry) {
+					selected_entry = -1;
+				}
+			}
+			nk_group_end(context);
+		}
+		nk_layout_row_static(context, context->style.font->height * 1.75, width > 600 ? 300 : width / 2, 2);
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+		}
+		if (nk_button_label(context, "Open") || (old_selected >= 0 && selected_entry < 0)) {
+			if (selected_entry < 0) {
+				selected_entry = old_selected;
+			}
+			char *full_path = path_append(current_path, entries[selected_entry].name);
+			if (entries[selected_entry].is_dir) {
+				free(current_path);
+				current_path = full_path;
+				free_dir_list(entries, num_entries);
+				entries = NULL;
+			} else {
+				if(normal_open) {
+					if (current_system) {
+						current_system->next_rom = full_path;
+						current_system->request_exit(current_system);
+					} else {
+						init_system_with_media(full_path, SYSTEM_UNKNOWN);
+						free(full_path);
+					}
+				} else {
+					lockon_media(full_path);
+					free(full_path);
+				}
+				clear_view_stack();
+				show_play_view();
+			}
+			selected_entry = -1;
+		}
+		nk_end(context);
+	}
+}
+
+void view_load(struct nk_context *context)
+{
+	view_file_browser(context, 1);
+}
+
+void view_lock_on(struct nk_context *context)
+{
+	view_file_browser(context, 0);
+}
+
+void view_about(struct nk_context *context)
+{
+	const char *lines[] = {
+		"BlastEm v0.6.1",
+		"Copyright 2012-2017 Michael Pavone",
+		"",
+		"BlastEm is a high performance open source",
+		"(GPLv3) Genesis/Megadrive emulator",
+	};
+	const uint32_t NUM_LINES = sizeof(lines)/sizeof(*lines);
+	const char *thanks[] = {
+		"Nemesis: Documentation and test ROMs",
+		"Charles MacDonald: Documentation",
+		"Eke-Eke: Documentation",
+		"Bart Trzynadlowski: Documentation",
+		"KanedaFR: Hosting the best Sega forum",
+		"Titan: Awesome demos and documentation",
+		"flamewing: BCD info and test ROM",
+		"r57shell: Opcode size test ROM",
+		"micky: Testing",
+		"Sasha: Testing",
+		"lol-frank: Testing",
+		"Sik: Testing",
+		"Tim Lawrence : Testing",
+		"ComradeOj: Testing",
+		"Vladikcomper: Testing"
+	};
+	const uint32_t NUM_THANKS = sizeof(thanks)/sizeof(*thanks);
+	uint32_t width = render_width();
+	uint32_t height = render_height();
+	if (nk_begin(context, "About", nk_rect(0, 0, width, height), 0)) {
+		nk_layout_row_static(context, context->style.font->height, width-40, 1);
+		for (uint32_t i = 0; i < NUM_LINES; i++)
+		{
+			nk_label(context, lines[i], NK_TEXT_LEFT);
+		}
+		nk_layout_row_static(context, height - (context->style.font->height * 2 + 20) - (context->style.font->height +4)*NUM_LINES, width-40, 1);
+		if (nk_group_begin(context, "Special Thanks", NK_WINDOW_TITLE)) {
+			nk_layout_row_static(context, context->style.font->height, width - 80, 1);
+			for (uint32_t i = 0; i < NUM_THANKS; i++)
+			{
+				nk_label(context, thanks[i], NK_TEXT_LEFT);
+			}
+			nk_group_end(context);
+		}
+		nk_layout_row_static(context, context->style.font->height * 1.75, width/3, 1);
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+		}
+		nk_end(context);
+	}
+}
+
+typedef struct {
+	const char *title;
+	view_fun   next_view;
+} menu_item;
+
+static save_slot_info *slots;
+static uint32_t num_slots, selected_slot;
+
+void view_choose_state(struct nk_context *context, uint8_t is_load)
+{
+	uint32_t width = render_width();
+	uint32_t height = render_height();
+	if (nk_begin(context, "Slot Picker", nk_rect(0, 0, width, height), 0)) {
+		nk_layout_row_static(context, height - context->style.font->height * 3, width - 60, 1);
+		if (nk_group_begin(context, "Select Save Slot", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) {
+			nk_layout_row_static(context, context->style.font->height - 2, width-100, 1);
+			if (!slots) {
+				slots = get_slot_info(current_system, &num_slots);
+			}
+			for (uint32_t i = 0; i < num_slots; i++)
+			{
+				int selected = i == selected_slot;
+				nk_selectable_label(context, slots[i].desc, NK_TEXT_ALIGN_LEFT, &selected);
+				if (selected && (slots[i].modification_time || !is_load)) {
+					selected_slot = i;
+				}
+			}
+			nk_group_end(context);
+		}
+		nk_layout_row_static(context, context->style.font->height * 1.75, width > 600 ? 300 : width / 2, 2);
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+		}
+		if (is_load) {
+			if (nk_button_label(context, "Load")) {
+				current_system->load_state(current_system, selected_slot);
+				show_play_view();
+			}
+		} else {
+			if (nk_button_label(context, "Save")) {
+				current_system->save_state = selected_slot + 1;
+				show_play_view();
+			}
+		}
+		nk_end(context);
+	}
+}
+
+void view_save_state(struct nk_context *context)
+{
+	view_choose_state(context, 0);
+}
+
+void view_load_state(struct nk_context *context)
+{
+	view_choose_state(context, 1);
+}
+
+typedef void (*menu_handler)(uint32_t index);
+
+static void menu(struct nk_context *context, uint32_t num_entries, const menu_item *items, menu_handler handler)
+{
+	const uint32_t button_height = context->style.font->height * 1.75;
+	const uint32_t ideal_button_width = context->style.font->height * 10;
+	const uint32_t button_space = 6;
+	
+	uint32_t width = render_width();
+	uint32_t height = render_height();
+	uint32_t top = height/2 - (button_height * num_entries)/2;
+	uint32_t button_width = width > ideal_button_width ? ideal_button_width : width;
+	uint32_t left = width/2 - button_width/2;
+	
+	nk_layout_space_begin(context, NK_STATIC, top + button_height * num_entries, num_entries);
+	for (uint32_t i = 0; i < num_entries; i++)
+	{
+		nk_layout_space_push(context, nk_rect(left, top + i * button_height, button_width, button_height-button_space));
+		if (nk_button_label(context, items[i].title)) {
+			if (items[i].next_view) {
+				push_view(items[i].next_view);
+				if (current_view == view_save_state || current_view == view_load_state) {
+					free_slot_info(slots);
+					slots = NULL;
+				} else if (current_view == view_play) {
+					set_content_binding_state(1);
+				}
+			} else {
+				handler(i);
+			}
+		}
+	}
+	nk_layout_space_end(context);
+}
+
+void binding_loop(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	if (valtype != TVAL_PTR) {
+		return;
+	}
+	tern_node **binding_lookup = data;
+	*binding_lookup = tern_insert_ptr(*binding_lookup, val.ptrval, strdup(key));
+}
+
+static int32_t keycode;
+static const char *set_binding;
+char *set_label;
+void binding_group(struct nk_context *context, char *name, const char **binds, const char **bind_names, uint32_t num_binds, tern_node *binding_lookup)
+{
+	nk_layout_row_static(context, (context->style.font->height + 4)*num_binds+context->style.font->height+30, render_width() - 80, 1);
+	if (nk_group_begin(context, name, NK_WINDOW_TITLE)) {
+		nk_layout_row_static(context, context->style.font->height, render_width()/2 - 80, 2);
+		
+		for (int i = 0; i < num_binds; i++)
+		{
+			char *label_alloc = bind_names ? NULL : path_extension(binds[i]);
+			const char *label = label_alloc;
+			if (!label) {
+				label = bind_names ? bind_names[i] : binds[i];
+			}
+			nk_label(context, label, NK_TEXT_LEFT);
+			if (nk_button_label(context, tern_find_ptr_default(binding_lookup, binds[i], "Not Set"))) {
+				set_binding = binds[i];
+				set_label = strdup(label);
+				keycode = 0;
+			}
+			if (label_alloc) {
+				free(label_alloc);
+			}
+		}
+		nk_group_end(context);
+	}
+}
+
+static char *get_key_name(int32_t keycode)
+{
+	char *name = NULL;
+	if (keycode > ' ' && keycode < 0x80) {
+		//key corresponds to a printable non-whitespace character
+		name = malloc(2);
+		name[0] = keycode;
+		name[1] = 0;
+	} else {
+		switch (keycode)
+		{
+		case RENDERKEY_UP: name = "up"; break;
+		case RENDERKEY_DOWN: name = "down"; break;
+		case RENDERKEY_LEFT: name = "left"; break;
+		case RENDERKEY_RIGHT: name = "right"; break;
+		case '\r': name = "enter"; break;
+		case ' ': name = "space"; break;
+		case '\t': name = "tab"; break;
+		case '\b': name = "backspace"; break;
+		case RENDERKEY_ESC: name = "esc"; break;
+		case RENDERKEY_DEL: name = "delete"; break;
+		case RENDERKEY_LSHIFT: name = "lshift"; break;
+		case RENDERKEY_RSHIFT: name = "rshift"; break;
+		case RENDERKEY_LCTRL: name = "lctrl"; break;
+		case RENDERKEY_RCTRL: name = "rctrl"; break;
+		case RENDERKEY_LALT: name = "lalt"; break;
+		case RENDERKEY_RALT: name = "ralt"; break;
+		case RENDERKEY_HOME: name = "home"; break;
+		case RENDERKEY_END: name = "end"; break;
+		case RENDERKEY_PAGEUP: name = "pageup"; break;
+		case RENDERKEY_PAGEDOWN: name = "pagedown"; break;
+		case RENDERKEY_F1: name = "f1"; break;
+		case RENDERKEY_F2: name = "f2"; break;
+		case RENDERKEY_F3: name = "f3"; break;
+		case RENDERKEY_F4: name = "f4"; break;
+		case RENDERKEY_F5: name = "f5"; break;
+		case RENDERKEY_F6: name = "f6"; break;
+		case RENDERKEY_F7: name = "f7"; break;
+		case RENDERKEY_F8: name = "f8"; break;
+		case RENDERKEY_F9: name = "f9"; break;
+		case RENDERKEY_F10: name = "f10"; break;
+		case RENDERKEY_F11: name = "f11"; break;
+		case RENDERKEY_F12: name = "f12"; break;
+		case RENDERKEY_SELECT: name = "select"; break;
+		case RENDERKEY_PLAY: name = "play"; break;
+		case RENDERKEY_SEARCH: name = "search"; break;
+		case RENDERKEY_BACK: name = "back"; break;
+		case RENDERKEY_NP0: name = "np0"; break;
+		case RENDERKEY_NP1: name = "np1"; break;
+		case RENDERKEY_NP2: name = "np2"; break;
+		case RENDERKEY_NP3: name = "np3"; break;
+		case RENDERKEY_NP4: name = "np4"; break;
+		case RENDERKEY_NP5: name = "np5"; break;
+		case RENDERKEY_NP6: name = "np6"; break;
+		case RENDERKEY_NP7: name = "np7"; break;
+		case RENDERKEY_NP8: name = "np8"; break;
+		case RENDERKEY_NP9: name = "np9"; break;
+		case RENDERKEY_NP_DIV: name = "np/"; break;
+		case RENDERKEY_NP_MUL: name = "np*"; break;
+		case RENDERKEY_NP_MIN: name = "np-"; break;
+		case RENDERKEY_NP_PLUS: name = "np+"; break;
+		case RENDERKEY_NP_ENTER: name = "npenter"; break;
+		case RENDERKEY_NP_STOP: name = "np."; break;
+		}
+		if (name) {
+			name = strdup(name);
+		}
+	}
+	return name;
+}
+
+void view_key_bindings(struct nk_context *context)
+{
+	const char *controller1_binds[] = {
+		"gamepads.1.up", "gamepads.1.down", "gamepads.1.left", "gamepads.1.right",
+		"gamepads.1.a", "gamepads.1.b", "gamepads.1.c",
+		"gamepads.1.x", "gamepads.1.y", "gamepads.1.z",
+		"gamepads.1.start", "gamepads.1.mode"
+	};
+	const char *controller2_binds[] = {
+		"gamepads.2.up", "gamepads.2.down", "gamepads.2.left", "gamepads.2.right",
+		"gamepads.2.a", "gamepads.2.b", "gamepads.2.c",
+		"gamepads.2.x", "gamepads.2.y", "gamepads.2.z",
+		"gamepads.2.start", "gamepads.2.mode"
+	};
+	const char *general_binds[] = {
+		"ui.exit", "ui.save_state", "ui.toggle_fullscreen", "ui.soft_reset", "ui.reload",
+		"ui.screenshot", "ui.sms_pause", "ui.toggle_keyboard_cpatured", "ui.release_mouse"
+	};
+	const char *general_names[] = {
+		"Show Menu", "Quick Save", "Toggle Fullscreen", "Soft Reset", "Reload Media",
+		"Internal Screenshot", "SMS Pause", "Capture Keyboard", "Release Mouse"
+	};
+	const char *speed_binds[] = {
+		"ui.next_speed", "ui.prev_speed",
+		"ui.set_speed.0", "ui.set_speed.1", "ui.set_speed.2" ,"ui.set_speed.3", "ui.set_speed.4",
+		"ui.set_speed.5", "ui.set_speed.6", "ui.set_speed.7" ,"ui.set_speed.8", "ui.set_speed.9",
+	};
+	const char *speed_names[] = {
+		"Next", "Previous",
+		"Default Speed", "Set Speed 1", "Set Speed 2", "Set Speed 3", "Set Speed 4",
+		"Set Speed 5", "Set Speed 6", "Set Speed 7", "Set Speed 8", "Set Speed 9"
+	};
+	const char *debug_binds[] = {
+		"ui.enter_debugger", "ui.plane_debug", "ui.vram_debug", "ui.cram_debug",
+		"ui.compositing_debug", "ui.vdp_debug_mode"
+	};
+	const char *debug_names[] = {
+		"CPU Debugger", "Plane Debugger", "VRAM Debugger", "CRAM Debugger", 
+		"Layer Debugger", "Cycle Mode/Pal"
+	};
+	const uint32_t NUM_C1_BINDS = sizeof(controller1_binds)/sizeof(*controller1_binds);
+	const uint32_t NUM_C2_BINDS = sizeof(controller2_binds)/sizeof(*controller2_binds);
+	const uint32_t NUM_SPEED_BINDS = sizeof(speed_binds)/sizeof(*speed_binds);
+	const uint32_t NUM_GEN_BINDS = sizeof(general_binds)/sizeof(*general_binds);
+	const uint32_t NUM_DBG_BINDS = sizeof(debug_binds)/sizeof(*debug_binds);
+	static tern_node *binding_lookup;
+	if (!binding_lookup) {
+		tern_node *bindings = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
+		if (bindings) {
+			tern_foreach(bindings, binding_loop, &binding_lookup);
+		}
+	}
+	uint32_t width = render_width();
+	uint32_t height = render_height();
+	if (nk_begin(context, "Keyboard Bindings", nk_rect(0, 0, width, height), 0)) {
+		binding_group(context, "Controller 1", controller1_binds, NULL, NUM_C1_BINDS, binding_lookup);
+		binding_group(context, "Controller 2", controller2_binds, NULL, NUM_C2_BINDS, binding_lookup);
+		binding_group(context, "General", general_binds, general_names, NUM_GEN_BINDS, binding_lookup);
+		binding_group(context, "Speed Control", speed_binds, speed_names, NUM_SPEED_BINDS, binding_lookup);
+		binding_group(context, "Debug", debug_binds, debug_names, NUM_DBG_BINDS, binding_lookup);
+		nk_layout_row_static(context, context->style.font->height * 1.1333, (render_width() - 80) / 2, 1);
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+		}
+		nk_end(context);
+	}
+	if (set_binding && nk_begin(context, "Set Binding", nk_rect(width/4, height/4, width/2/*width*3/4*/, height/2), NK_WINDOW_TITLE | NK_WINDOW_BORDER)) {
+		nk_layout_row_static(context, 30, width/2-30, 1);
+		nk_label(context, "Press new key for", NK_TEXT_CENTERED);
+		nk_label(context, set_label, NK_TEXT_CENTERED);
+		if (nk_button_label(context, "Cancel")) {
+			free(set_label);
+			set_binding = set_label = NULL;
+		} else if (keycode) {
+			char *name = get_key_name(keycode);
+			if (name) {
+				uint32_t prefix_len = strlen("bindings") + strlen("keys") + 2;
+				char * old = tern_find_ptr(binding_lookup, set_binding);
+				if (old) {
+					uint32_t suffix_len = strlen(old) + 1;
+					char *old_path = malloc(prefix_len + suffix_len + 1);
+					memcpy(old_path, "bindings\0keys\0", prefix_len);
+					memcpy(old_path + prefix_len, old, suffix_len);
+					old_path[prefix_len + suffix_len] = 0;
+					tern_val old_val;
+					if (tern_delete_path(&config, old_path, &old_val) == TVAL_PTR) {
+						free(old_val.ptrval);
+					}
+				}
+				uint32_t suffix_len = strlen(name) + 1;
+				char *path = malloc(prefix_len + suffix_len + 1);
+				memcpy(path, "bindings\0keys\0", prefix_len);
+				memcpy(path + prefix_len, name, suffix_len);
+				path[prefix_len + suffix_len] = 0;
+				
+				config_dirty = 1;
+				config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(set_binding)}, TVAL_PTR);
+				free(path);
+				free(name);
+				tern_free(binding_lookup);
+				binding_lookup = NULL;
+			}
+			free(set_label);
+			set_binding = set_label = NULL;
+		}
+		nk_end(context);
+	}
+}
+
+static int selected_controller;
+static controller_info selected_controller_info;
+//#define MIN_BIND_BOX_WIDTH 140
+#define MAX_BIND_BOX_WIDTH 350
+
+#define AXIS       0x40000000
+#define STICKDIR   0x30000000
+#define LEFTSTICK  0x10000000
+#define RIGHTSTICK 0x20000000
+enum {
+	UP,DOWN,RIGHT,LEFT,NUM_AXIS_DIRS
+};
+
+static char * config_ps_names[] = {
+	[SDL_CONTROLLER_BUTTON_A] = "cross",
+	[SDL_CONTROLLER_BUTTON_B] = "circle",
+	[SDL_CONTROLLER_BUTTON_X] = "square",
+	[SDL_CONTROLLER_BUTTON_Y] = "triangle",
+	[SDL_CONTROLLER_BUTTON_BACK] = "share",
+	[SDL_CONTROLLER_BUTTON_START] = "options",
+	[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = "l1",
+	[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = "r1",
+	[SDL_CONTROLLER_BUTTON_LEFTSTICK] = "l3",
+	[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = "r3",
+};
+
+typedef struct {	
+	const char *button_binds[SDL_CONTROLLER_BUTTON_MAX];
+	const char *left_stick[NUM_AXIS_DIRS];
+	const char *right_stick[NUM_AXIS_DIRS];
+	const char *triggers[2];
+} pad_bind_config;
+
+static const char **current_bind_dest;
+
+const char *translate_binding_option(const char *option)
+{
+	static tern_node *conf_names;
+	if (!conf_names) {
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.up", "Pad Up");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.down", "Pad Down");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.left", "Pad Left");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.right", "Pad Right");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.a", "Pad A");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.b", "Pad B");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.c", "Pad C");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.x", "Pad X");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.y", "Pad Y");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.z", "Pad Z");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.start", "Pad Start");
+		conf_names = tern_insert_ptr(conf_names, "gamepads.n.mode", "Pad Mode");
+		conf_names = tern_insert_ptr(conf_names, "ui.release_mouse", "Release Mouse");
+		conf_names = tern_insert_ptr(conf_names, "ui.vdp_debug_mode", "VDP Debug Mode");
+		conf_names = tern_insert_ptr(conf_names, "ui.vdp_debug_pal", "VDP Debug Palette");
+		conf_names = tern_insert_ptr(conf_names, "ui.enter_debugger", "Enter CPU Debugger");
+		conf_names = tern_insert_ptr(conf_names, "ui.screenshot", "Take Screenshot");
+		conf_names = tern_insert_ptr(conf_names, "ui.exit", "Show Menu");
+		conf_names = tern_insert_ptr(conf_names, "ui.save_state", "Quick Save");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.0", "Set Speed 0");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.1", "Set Speed 1");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.2", "Set Speed 2");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.3", "Set Speed 3");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.4", "Set Speed 4");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.5", "Set Speed 5");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.6", "Set Speed 6");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.7", "Set Speed 7");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.8", "Set Speed 8");
+		conf_names = tern_insert_ptr(conf_names, "ui.set_speed.9", "Set Speed 9");
+		conf_names = tern_insert_ptr(conf_names, "ui.next_speed", "Next Speed");
+		conf_names = tern_insert_ptr(conf_names, "ui.prev_speed", "Prev. Speed");
+		conf_names = tern_insert_ptr(conf_names, "ui.toggle_fullscreen", "Toggle Fullscreen");
+		conf_names = tern_insert_ptr(conf_names, "ui.soft_reset", "Soft Reset");
+		conf_names = tern_insert_ptr(conf_names, "ui.reload", "Reload ROM");
+		conf_names = tern_insert_ptr(conf_names, "ui.sms_pause", "SMS Pause");
+		conf_names = tern_insert_ptr(conf_names, "ui.toggle_keyboard_captured", "Toggle Keyboard Capture");
+	}
+	return tern_find_ptr_default(conf_names, option, (void *)option);
+}
+
+static uint8_t controller_binding_changed;
+static void bind_option_group(struct nk_context *context, char *name, const char **options, uint32_t num_options)
+{
+	float margin = context->style.font->height * 2;
+	nk_layout_row_static(context, (context->style.font->height + 3) * ((num_options + 2) / 3) + context->style.font->height*2.1, render_width() - margin, 1);
+	if (nk_group_begin(context, name, NK_WINDOW_TITLE|NK_WINDOW_NO_SCROLLBAR)) {
+		nk_layout_row_static(context, context->style.font->height, (render_width() - margin - context->style.font->height) / 3, 3);
+		for (int i = 0; i < num_options; i++)
+		{
+			if (nk_button_label(context, translate_binding_option(options[i]))) {
+				*current_bind_dest = options[i];
+				controller_binding_changed = 1;
+				pop_view();
+			}
+		}
+		nk_group_end(context);
+	}
+}
+
+static void view_button_binding(struct nk_context *context)
+{
+	static const char *pad_opts[] = {
+		"gamepads.n.up",
+		"gamepads.n.down",
+		"gamepads.n.left",
+		"gamepads.n.right",
+		"gamepads.n.a",
+		"gamepads.n.b",
+		"gamepads.n.c",
+		"gamepads.n.x",
+		"gamepads.n.y",
+		"gamepads.n.z",
+		"gamepads.n.start",
+		"gamepads.n.mode"
+	};
+	static const char *system_buttons[] = {
+		"ui.soft_reset",
+		"ui.reload",
+		"ui.sms_pause"
+	};
+	static const char *emu_control[] = {
+		"ui.save_state",
+		"ui.exit",
+		"ui.toggle_fullscreen",
+		"ui.screenshot",
+		"ui.release_mouse",
+		"ui.toggle_keyboard_captured"
+	};
+	static const char *debugger[] = {
+		"ui.vdp_debug_mode",
+		"ui.vdp_debug_pal",
+		"ui.enter_debugger"
+	};
+	static const char *speeds[] = {
+		"ui.next_speed",
+		"ui.prev_speed",
+		"ui.set_speed.0",
+		"ui.set_speed.1",
+		"ui.set_speed.2",
+		"ui.set_speed.3",
+		"ui.set_speed.4",
+		"ui.set_speed.5",
+		"ui.set_speed.6",
+		"ui.set_speed.7",
+		"ui.set_speed.8",
+		"ui.set_speed.9"
+	};
+		
+	if (nk_begin(context, "Button Binding", nk_rect(0, 0, render_width(), render_height()), 0)) {
+		bind_option_group(context, "Controller Buttons", pad_opts, sizeof(pad_opts)/sizeof(*pad_opts));
+		bind_option_group(context, "System Buttons", system_buttons, sizeof(system_buttons)/sizeof(*system_buttons));
+		bind_option_group(context, "Emulator Control", emu_control, sizeof(emu_control)/sizeof(*emu_control));
+		bind_option_group(context, "Debugging", debugger, sizeof(debugger)/sizeof(*debugger));
+		bind_option_group(context, "Speed Control", speeds, sizeof(speeds)/sizeof(*speeds));
+		
+		nk_layout_row_static(context, context->style.font->height, (render_width() - 80)/4, 1);
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+		}
+		nk_end(context);
+	}
+}
+
+static void binding_box(struct nk_context *context, pad_bind_config *bindings, char *name, float x, float y, float width, int num_binds, int *binds)
+{
+	const struct nk_user_font *font = context->style.font;
+	float row_height = font->height * 2;
+	
+	char const **labels = calloc(sizeof(char *), num_binds);
+	char const ***conf_vals = calloc(sizeof(char *), num_binds);
+	float max_width = 0.0f;
+	
+	int skipped = 0;
+	for (int i = 0; i < num_binds; i++)
+	{
+		if (binds[i] & AXIS) {
+			labels[i] = get_axis_label(&selected_controller_info, binds[i] & ~AXIS);
+			conf_vals[i] = &bindings->triggers[(binds[i] & ~AXIS) - SDL_CONTROLLER_AXIS_TRIGGERLEFT];
+		} else if (binds[i] & STICKDIR) {
+			static char const * dirs[] = {"Up", "Down", "Right", "Left"};
+			labels[i] = dirs[binds[i] & 3];
+			conf_vals[i] = &(binds[i] & LEFTSTICK ? bindings->left_stick : bindings->right_stick)[binds[i] & 3];
+		} else {
+			labels[i] = get_button_label(&selected_controller_info, binds[i]);
+			conf_vals[i] = &bindings->button_binds[binds[i]];
+		}
+		if (!labels[i]) {
+			skipped++;
+			continue;
+		}
+		float lb_width = font->width(font->userdata, font->height, labels[i], strlen(labels[i]));
+		max_width = max_width < lb_width ? lb_width : max_width;
+	}
+	nk_layout_space_push(context, nk_rect(x, y, width, (num_binds - skipped) * (row_height + 4) + 4));
+	nk_group_begin(context, name, NK_WINDOW_BORDER | NK_WINDOW_NO_SCROLLBAR);
+	
+	float widths[] = {max_width + 3, width - (max_width + 6)};
+	nk_layout_row(context, NK_STATIC, row_height, 2, widths);
+	for (int i = 0; i < num_binds; i++)
+	{
+		if (!labels[i]) {
+			continue;
+		}
+		nk_label(context, labels[i], NK_TEXT_LEFT);
+		const char *name = *conf_vals[i] ? translate_binding_option(*conf_vals[i]) : "None";
+		if (nk_button_label(context, name)) {
+			current_bind_dest = conf_vals[i];
+			push_view(view_button_binding);
+		}
+	}
+	free(labels);
+	free(conf_vals);
+	nk_group_end(context);
+}
+
+static void button_iter(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	pad_bind_config *bindings = data;
+	if (valtype != TVAL_PTR) {
+		return;
+	}
+	int button = render_lookup_button(key);
+	if (button != SDL_CONTROLLER_BUTTON_INVALID) {
+		bindings->button_binds[button] = val.ptrval;
+	}
+}
+
+static void axis_iter(char *key, tern_val val, uint8_t valtype, void *data)
+{
+	pad_bind_config *bindings = data;
+	if (valtype != TVAL_PTR) {
+		return;
+	}
+	int axis;
+	uint8_t is_negative = 0;
+	char *period = strchr(key, '.');
+	if (period) {
+		char *tmp = malloc(period-key + 1);
+		memcpy(tmp, key, period-key);
+		tmp[period-key] = 0;
+		axis = render_lookup_axis(tmp);
+		free(tmp);
+		is_negative = strcmp(period+1, "negative") == 0;
+	} else {
+		axis = render_lookup_axis(key);
+	}
+	switch (axis)
+	{
+	case SDL_CONTROLLER_AXIS_LEFTX:
+	case SDL_CONTROLLER_AXIS_LEFTY:
+		bindings->left_stick[(axis - SDL_CONTROLLER_AXIS_LEFTX) * 2 + is_negative] = val.ptrval;
+		break;
+	case SDL_CONTROLLER_AXIS_RIGHTX:
+	case SDL_CONTROLLER_AXIS_RIGHTY:
+		bindings->right_stick[(axis - SDL_CONTROLLER_AXIS_RIGHTX) * 2 + is_negative] = val.ptrval;
+		break;
+	case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
+	case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
+		bindings->triggers[axis-SDL_CONTROLLER_AXIS_TRIGGERLEFT] = val.ptrval;
+		break;
+	}
+}
+
+enum {
+	SIMILAR_CONTROLLERS,
+	IDENTICAL_CONTROLLERS,
+	BY_INDEX,
+	DEFAULT,
+	NUM_DEST_TYPES
+};
+
+//it would be cleaner to generate this algorithmically for 4th and up,
+//but BlastEm only supports 8 controllers currently so it's not worth the effort
+static const char *by_index_names[] = {
+	"Use for 1st controller",
+	"Use for 2nd controller",
+	"Use for 3rd controller",
+	"Use for 4th controller",
+	"Use for 5th controller",
+	"Use for 6th controller",
+	"Use for 7th controller",
+	"Use for 8th controller",
+};
+
+static void save_stick_binds(char *axes_key, size_t axes_key_size, const char **bindings, char *prefix)
+{
+	for (int i = 0; i < NUM_AXIS_DIRS; i++)
+	{
+		char axis = (i / 2) ? 'x' : 'y';
+		char *suffix = (i % 2) ? ".negative" : ".positive";
+		size_t prefix_len = strlen(prefix), suffix_len = strlen(suffix);
+		size_t full_key_size = axes_key_size + prefix_len + 1 + suffix_len + 2;
+		char *full_key = malloc(full_key_size);
+		memcpy(full_key, axes_key, axes_key_size);
+		memcpy(full_key + axes_key_size, prefix, prefix_len);
+		full_key[axes_key_size+prefix_len] = axis;
+		memcpy(full_key + axes_key_size + prefix_len + 1, suffix, suffix_len  +1);
+		full_key[axes_key_size + prefix_len + 1 + suffix_len + 1] = 0;
+		
+		if (bindings[i]) {
+			tern_insert_path(config, full_key, (tern_val){.ptrval = strdup(bindings[i])}, TVAL_PTR);
+		} else {
+			tern_val prev_val;
+			uint8_t prev_type = tern_delete_path(&config, full_key, &prev_val);
+			if (prev_type == TVAL_PTR) {
+				free(prev_val.ptrval);
+			}
+		}
+		
+		free(full_key);
+	}
+}
+
+static pad_bind_config *bindings;
+static void handle_dest_clicked(uint32_t dest)
+{
+	char key_buf[12];
+	char *key;
+	switch (dest)
+	{
+	case SIMILAR_CONTROLLERS:
+		key = make_controller_type_key(&selected_controller_info);
+		break;
+	case IDENTICAL_CONTROLLERS:
+		key = render_joystick_type_id(selected_controller);
+		break;
+	case BY_INDEX:
+		snprintf(key_buf, sizeof(key_buf), "%d", selected_controller);
+		key = key_buf;
+		break;
+	default:
+		key = "default";
+		break;
+	}
+	static const char base_path[] = "bindings\0pads";
+	size_t pad_key_size = sizeof(base_path) + strlen(key) + 1;
+	char *pad_key = malloc(pad_key_size);
+	memcpy(pad_key, base_path, sizeof(base_path));
+	strcpy(pad_key + sizeof(base_path), key);
+	static const char dpad_base[] = "dpads\0""0";
+	size_t dpad_key_size = pad_key_size + sizeof(dpad_base);
+	char *dpad_key = malloc(dpad_key_size);
+	memcpy(dpad_key, pad_key, pad_key_size);
+	memcpy(dpad_key + pad_key_size, dpad_base, sizeof(dpad_base));
+	static const char button_base[] = "buttons";
+	size_t button_key_size = pad_key_size + sizeof(button_base);
+	char *button_key = malloc(button_key_size);
+	memcpy(button_key, pad_key, pad_key_size);
+	memcpy(button_key + pad_key_size, button_base, sizeof(button_base));
+	
+	char *final_key;
+	for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++)
+	{
+		char *base;
+		const char *suffix;
+		size_t base_key_len;
+		if ( i < SDL_CONTROLLER_BUTTON_DPAD_UP) {
+			suffix = SDL_GameControllerGetStringForButton(i);
+			base_key_len = button_key_size;
+			base = button_key;
+			
+			
+		} else {
+			static const char *dir_keys[] = {"up", "down", "left", "right"};
+			suffix = dir_keys[i - SDL_CONTROLLER_BUTTON_DPAD_UP];
+			base = dpad_key;
+			base_key_len = dpad_key_size;
+		}
+		size_t suffix_len = strlen(suffix);
+		final_key = malloc(base_key_len + suffix_len + 2);
+		memcpy(final_key, base, base_key_len);
+		memcpy(final_key + base_key_len, suffix, suffix_len + 1);
+		final_key[base_key_len + suffix_len + 1] = 0;
+		if (bindings->button_binds[i]) {
+			tern_insert_path(config, final_key, (tern_val){.ptrval = strdup(bindings->button_binds[i])}, TVAL_PTR);
+		} else {
+			tern_val prev_val;
+			uint8_t prev_type = tern_delete_path(&config, final_key, &prev_val);
+			if (prev_type == TVAL_PTR) {
+				free(prev_val.ptrval);
+			}
+		}
+		free(final_key);
+	}
+	free(button_key);
+	free(dpad_key);
+	
+	static const char axes_base[] = "axes";
+	size_t axes_key_size = pad_key_size + sizeof(axes_base);
+	char *axes_key = malloc(axes_key_size);
+	memcpy(axes_key, pad_key, pad_key_size);
+	memcpy(axes_key + pad_key_size, axes_base, sizeof(axes_base));
+	
+	save_stick_binds(axes_key, axes_key_size,bindings->left_stick, "left");
+	save_stick_binds(axes_key, axes_key_size,bindings->right_stick, "right");
+	for (int i = SDL_CONTROLLER_AXIS_TRIGGERLEFT; i < SDL_CONTROLLER_AXIS_MAX; i++)
+	{
+		const char *suffix = SDL_GameControllerGetStringForAxis(i);
+		size_t suffix_len = strlen(suffix);
+		final_key = malloc(axes_key_size + suffix_len + 2);
+		memcpy(final_key, axes_key, axes_key_size);
+		memcpy(final_key + axes_key_size, suffix, suffix_len + 1);
+		final_key[axes_key_size + suffix_len + 1] = 0;
+		if (bindings->triggers[i - SDL_CONTROLLER_AXIS_TRIGGERLEFT]) {
+			tern_insert_path(config, final_key, (tern_val){.ptrval = strdup(bindings->triggers[i - SDL_CONTROLLER_AXIS_TRIGGERLEFT])}, TVAL_PTR);
+		} else {
+			tern_val prev_val;
+			uint8_t prev_type = tern_delete_path(&config, final_key, &prev_val);
+			if (prev_type == TVAL_PTR) {
+				free(prev_val.ptrval);
+			}
+		}
+		free(final_key);
+	}
+	free(axes_key);
+	
+	free(pad_key);
+	if (dest == SIMILAR_CONTROLLERS) {
+		free(key);
+	}
+	pop_view();
+	config_dirty = 1;
+}
+
+void view_select_binding_dest(struct nk_context *context)
+{
+	static menu_item options[NUM_DEST_TYPES];
+	options[IDENTICAL_CONTROLLERS].title = "Use for identical controllers";
+	options[DEFAULT].title = "Use as default";
+	options[BY_INDEX].title = by_index_names[selected_controller];
+	options[SIMILAR_CONTROLLERS].title = make_human_readable_type_name(&selected_controller_info);
+	
+	if (nk_begin(context, "Select Binding Dest", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) {
+		menu(context, NUM_DEST_TYPES, options, handle_dest_clicked);
+		nk_end(context);
+	}
+	free((char *)options[SIMILAR_CONTROLLERS].title);
+}
+
+static ui_image *select_best_image(controller_info *info)
+{
+	if (info->variant != VARIANT_NORMAL) {
+		return controller_ps4_6b;
+	} else if (info->type == TYPE_PSX) {
+		return controller_ps4;
+	} else {
+		return controller_360;
+	}
+}
+
+void view_controller_bindings(struct nk_context *context)
+{
+	if (nk_begin(context, "Controller Bindings", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) {
+		if (!bindings) {
+			bindings = calloc(1, sizeof(*bindings));
+			tern_node *pad = get_binding_node_for_pad(selected_controller);
+			if (pad) {
+				tern_foreach(tern_find_node(pad, "buttons"), button_iter, bindings);
+				tern_foreach(tern_find_node(pad, "axes"), axis_iter, bindings);
+				tern_node *dpad = tern_find_path(pad, "dpads\0" "0\0", TVAL_NODE).ptrval;
+				const char *dir_keys[] = {"up", "down", "right", "left"};
+				const int button_idx[] = {SDL_CONTROLLER_BUTTON_DPAD_UP, SDL_CONTROLLER_BUTTON_DPAD_DOWN, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_LEFT};
+				for (int i = 0; i < NUM_AXIS_DIRS; i++)
+				{
+					bindings->button_binds[button_idx[i]] = tern_find_ptr(dpad, dir_keys[i]);
+				}
+			}
+		}
+	
+		float orig_height = def_font->handle.height;
+		def_font->handle.height *= 0.5f;
+		
+		uint32_t avail_height = render_height() - 2 * orig_height;
+		float desired_width = render_width() * 0.5f, desired_height = avail_height * 0.5f;
+		ui_image *controller_image = select_best_image(&selected_controller_info);
+		
+		float controller_ratio = (float)controller_image->width / (float)controller_image->height;
+		
+		const struct nk_user_font *font = context->style.font;
+		int MIN_BIND_BOX_WIDTH = font->width(font->userdata, font->height, "Right", strlen("Right"))
+			+ def_font->handle.width(font->userdata, font->height, "Internal Screenshot", strlen("Internal Screenshot"));
+		
+		if (render_width() - desired_width < 2.5f*MIN_BIND_BOX_WIDTH) {
+			desired_width = render_width() - 2.5f*MIN_BIND_BOX_WIDTH;
+		}
+		
+		if (desired_width / desired_height > controller_ratio) {
+			desired_width = desired_height * controller_ratio;
+		} else {
+			desired_height = desired_width / controller_ratio;
+		}
+		float img_left = render_width() / 2.0f - desired_width / 2.0f;
+		float img_top = avail_height / 2.0f - desired_height / 2.0f;
+		float img_right = img_left + desired_width;
+		float img_bot = img_top + desired_height;
+		nk_layout_space_begin(context, NK_STATIC, avail_height, INT_MAX);
+		nk_layout_space_push(context, nk_rect(img_left, img_top, desired_width, desired_height));
+		nk_image(context, controller_image->ui);
+		
+		float bind_box_width = (render_width() - img_right) * 0.8f;
+		if (bind_box_width < MIN_BIND_BOX_WIDTH) {
+			bind_box_width = render_width() - img_right;
+			if (bind_box_width > MIN_BIND_BOX_WIDTH) {
+				bind_box_width = MIN_BIND_BOX_WIDTH;
+			}
+		} else if (bind_box_width > MAX_BIND_BOX_WIDTH) {
+			bind_box_width = MAX_BIND_BOX_WIDTH;
+		}
+		float bind_box_left;
+		if (bind_box_width >= (render_width() - img_right)) {
+			bind_box_left = img_right;
+		} else {
+			bind_box_left = img_right + (render_width() - img_right) / 2.0f - bind_box_width / 2.0f;
+		}
+		
+		if (selected_controller_info.variant == VARIANT_NORMAL) {
+			binding_box(context, bindings, "Action Buttons", bind_box_left, img_top, bind_box_width, 4, (int[]){
+				SDL_CONTROLLER_BUTTON_A,
+				SDL_CONTROLLER_BUTTON_B,
+				SDL_CONTROLLER_BUTTON_X,
+				SDL_CONTROLLER_BUTTON_Y
+			});
+		} else {
+			binding_box(context, bindings, "Action Buttons", bind_box_left, img_top, bind_box_width, 6, (int[]){
+				SDL_CONTROLLER_BUTTON_A,
+				SDL_CONTROLLER_BUTTON_B,
+				selected_controller_info.variant == VARIANT_6B_RIGHT ? AXIS | SDL_CONTROLLER_AXIS_TRIGGERRIGHT : SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
+				SDL_CONTROLLER_BUTTON_X,
+				SDL_CONTROLLER_BUTTON_Y,
+				selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_RIGHTSHOULDER : SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
+			});
+		}
+		
+		binding_box(context, bindings, "Right Shoulder", bind_box_left, font->height/2, bind_box_width,
+			selected_controller_info.variant == VARIANT_6B_BUMPERS ? 1 : 2, 
+			(int[]){
+			selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_LEFTSHOULDER : SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
+			AXIS | SDL_CONTROLLER_AXIS_TRIGGERLEFT
+		});
+		
+		binding_box(context, bindings, "Misc Buttons", (render_width() - bind_box_width) / 2, font->height/2, bind_box_width, 3, (int[]){
+			SDL_CONTROLLER_BUTTON_BACK,
+			SDL_CONTROLLER_BUTTON_GUIDE,
+			SDL_CONTROLLER_BUTTON_START
+		});
+		
+		if (selected_controller_info.variant == VARIANT_NORMAL)
+		{
+			binding_box(context, bindings, "Right Stick", img_right - desired_width/3, img_bot, bind_box_width, 5, (int[]){
+				RIGHTSTICK | UP,
+				RIGHTSTICK | DOWN,
+				RIGHTSTICK | LEFT,
+				RIGHTSTICK | RIGHT,
+				SDL_CONTROLLER_BUTTON_RIGHTSTICK
+			});
+		}
+		
+		bind_box_left -= img_right;
+		float dpad_left, dpad_top;
+		if (selected_controller_info.variant == VARIANT_NORMAL)
+		{
+			binding_box(context, bindings, "Left Stick", bind_box_left, img_top, bind_box_width, 5, (int[]){
+				LEFTSTICK | UP,
+				LEFTSTICK | DOWN,
+				LEFTSTICK | LEFT,
+				LEFTSTICK | RIGHT,
+				SDL_CONTROLLER_BUTTON_LEFTSTICK
+			});
+			dpad_left = img_left - desired_width/6;
+			dpad_top = img_bot + font->height * 1.5;
+		} else {
+			dpad_left = bind_box_left;
+			dpad_top = img_top;
+		}
+		
+		binding_box(context, bindings, "Left Shoulder", bind_box_left, font->height/2, bind_box_width, 
+			selected_controller_info.variant == VARIANT_6B_BUMPERS ? 1 : 2, 
+			(int[]){
+			selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_LEFTSTICK : SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
+			SDL_CONTROLLER_BUTTON_RIGHTSTICK
+		});
+		
+		binding_box(context, bindings, "D-pad", dpad_left, dpad_top, bind_box_width, 4, (int[]){
+			SDL_CONTROLLER_BUTTON_DPAD_UP,
+			SDL_CONTROLLER_BUTTON_DPAD_DOWN,
+			SDL_CONTROLLER_BUTTON_DPAD_LEFT,
+			SDL_CONTROLLER_BUTTON_DPAD_RIGHT
+		});
+		
+		nk_layout_space_end(context);
+		
+		def_font->handle.height = orig_height;
+		nk_layout_row_static(context, orig_height + 4, (render_width() - 2*orig_height) / 4, 1);
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+			if (controller_binding_changed) {
+				push_view(view_select_binding_dest);
+			}
+		}
+		nk_end(context);
+	}
+}
+
+static int current_button;
+static int current_axis;
+static int button_pressed, last_button;
+static int hat_moved, hat_value, last_hat, last_hat_value;
+static int axis_moved, axis_value, last_axis;
+static char *mapping_string;
+static size_t mapping_pos;
+
+static void start_mapping(void)
+{
+	const char *name;
+	mapping_string[mapping_pos++] = ',';
+	if (current_button != SDL_CONTROLLER_BUTTON_MAX) {
+		name = SDL_GameControllerGetStringForButton(current_button);
+	} else {
+		name = SDL_GameControllerGetStringForAxis(current_axis);
+	}
+	size_t namesz = strlen(name);
+	memcpy(mapping_string + mapping_pos, name, namesz);
+	mapping_pos += namesz;
+	mapping_string[mapping_pos++] = ':';
+}
+
+#define QUIET_FRAMES 9
+static void view_controller_mappings(struct nk_context *context)
+{
+	char buffer[512];
+	static int quiet, button_a = -1, button_a_axis = -1;
+	uint8_t added_mapping = 0;
+	if (nk_begin(context, "Controllers", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) {
+		
+		nk_layout_space_begin(context, NK_STATIC, render_height() - context->style.font->height, 3);
+		
+		if (current_button < SDL_CONTROLLER_BUTTON_MAX) {
+			snprintf(buffer, sizeof(buffer), "Press Button %s", get_button_label(&selected_controller_info, current_button));
+		} else {
+			snprintf(buffer, sizeof(buffer), "Move Axis %s", get_axis_label(&selected_controller_info, current_axis));
+		}
+		
+		float height = context->style.font->height * 1.25;
+		float top = render_height()/2 - 1.5 * height;
+		float width = render_width() - context->style.font->height;
+		
+		nk_layout_space_push(context, nk_rect(0, top, width, height));
+		nk_label(context, buffer, NK_TEXT_CENTERED);
+		if (current_button > SDL_CONTROLLER_BUTTON_B) {
+			nk_layout_space_push(context, nk_rect(0, top + height, width, height));
+			nk_label(context, "OR", NK_TEXT_CENTERED);
+		
+			nk_layout_space_push(context, nk_rect(0, top + 2.0 * height, width, height));
+			snprintf(buffer, sizeof(buffer), "Press Button %s to skip", get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_A));
+			nk_label(context, buffer, NK_TEXT_CENTERED);
+		}
+		
+		nk_layout_space_end(context);
+		if (quiet) {
+			--quiet;
+		} else {
+			if (button_pressed >= 0 && button_pressed != last_button) {
+				if (current_button <= SDL_CONTROLLER_BUTTON_B || button_pressed != button_a) {
+					start_mapping();
+					mapping_string[mapping_pos++] = 'b';
+					if (button_pressed > 9) {
+						mapping_string[mapping_pos++] = '0' + button_pressed / 10;
+					}
+					mapping_string[mapping_pos++] = '0' + button_pressed % 10;
+					last_button = button_pressed;
+					if (current_button == SDL_CONTROLLER_BUTTON_A) {
+						button_a = button_pressed;
+					}
+				}
+				added_mapping = 1;
+			} else if (hat_moved >= 0 && hat_value && (hat_moved != last_hat || hat_value != last_hat_value)) {
+				start_mapping();
+				mapping_string[mapping_pos++] = 'h';
+				mapping_string[mapping_pos++] = '0' + hat_moved;
+				mapping_string[mapping_pos++] = '.';
+				mapping_string[mapping_pos++] = '0' + hat_value;
+				added_mapping = 1;
+				
+				last_hat = hat_moved;
+				last_hat_value = hat_value;
+			} else if (axis_moved >= 0 && abs(axis_value) > 1000 && axis_moved != last_axis) {
+				if (current_button <= SDL_CONTROLLER_BUTTON_B || axis_moved != button_a_axis) {
+					start_mapping();
+					mapping_string[mapping_pos++] = 'a';
+					if (axis_moved > 9) {
+						mapping_string[mapping_pos++] = '0' + axis_moved / 10;
+					}
+					mapping_string[mapping_pos++] = '0' + axis_moved % 10;
+					last_axis = axis_moved;
+				}
+				added_mapping = 1;
+			}
+		}
+			
+		if (added_mapping) {
+			quiet = QUIET_FRAMES;
+			if (current_button < SDL_CONTROLLER_BUTTON_MAX) {
+				current_button++;
+				if (current_button == SDL_CONTROLLER_BUTTON_MAX) {
+					current_axis = 0;
+				}
+			} else {
+				current_axis++;
+				if (current_axis == SDL_CONTROLLER_AXIS_MAX) {
+					button_a = -1;
+					button_a_axis = -1;
+					mapping_string[mapping_pos] = 0;
+					save_controller_mapping(selected_controller, mapping_string);
+					free(mapping_string);
+					pop_view();
+					push_view(view_controller_bindings);
+					controller_binding_changed = 0;
+				}
+			}
+		}
+		button_pressed = -1;
+		hat_moved = -1;
+		axis_moved = -1;
+		nk_end(context);
+	}
+}
+
+static void view_controller_variant(struct nk_context *context)
+{
+	uint8_t selected = 0;
+	if (nk_begin(context, "Controller Type", nk_rect(0, 0, render_width(), render_height()), 0)) {
+		nk_layout_row_static(context, context->style.font->height*1.25, render_width() - context->style.font->height * 2, 1);
+		nk_label(context, "", NK_TEXT_CENTERED);
+		nk_label(context, "Select the layout that", NK_TEXT_CENTERED);
+		nk_label(context, "best matches your controller", NK_TEXT_CENTERED);
+		nk_label(context, "", NK_TEXT_CENTERED);
+		if (nk_button_label(context, "4 face buttons")) {
+			selected_controller_info.variant = VARIANT_NORMAL;
+			selected = 1;
+		}
+		char buffer[512];
+		snprintf(buffer, sizeof(buffer), "6 face buttons including %s and %s", 
+			get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), 
+			get_axis_label(&selected_controller_info, SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
+		);
+		if (nk_button_label(context, buffer)) {
+			selected_controller_info.variant = VARIANT_6B_RIGHT;
+			selected = 1;
+		}
+		snprintf(buffer, sizeof(buffer), "6 face buttons including %s and %s", 
+			get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_LEFTSHOULDER), 
+			get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
+		);
+		if (nk_button_label(context, buffer)) {
+			selected_controller_info.variant = VARIANT_6B_BUMPERS;
+			selected = 1;
+		}
+		nk_end(context);
+	}
+	if (selected) {
+		save_controller_info(selected_controller, &selected_controller_info);
+		pop_view();
+		SDL_GameController *controller = render_get_controller(selected_controller);
+		if (controller) {
+			push_view(view_controller_bindings);
+			controller_binding_changed = 0;
+			SDL_GameControllerClose(controller);
+		} else {
+			current_button = SDL_CONTROLLER_BUTTON_A;
+			button_pressed = -1;
+			last_button = -1;
+			last_hat = -1;
+			axis_moved = -1;
+			last_axis = -1;
+			SDL_Joystick *joy = render_get_joystick(selected_controller);
+			const char *name = SDL_JoystickName(joy);
+			size_t namesz = strlen(name);
+			mapping_string = malloc(512 + namesz);
+			for (mapping_pos = 0; mapping_pos < namesz; mapping_pos++)
+			{
+				char c = name[mapping_pos];
+				if (c == ',' || c == '\n' || c == '\r') {
+					c = ' ';
+				}
+				mapping_string[mapping_pos] = c;
+			}
+			
+			push_view(view_controller_mappings);
+		}
+	}
+}
+
+static void controller_type_group(struct nk_context *context, char *name, int type_id, int first_subtype_id, const char **types, uint32_t num_types)
+{
+	nk_layout_row_static(context, (context->style.font->height + 3) * num_types + context->style.font->height, render_width() - 80, 1);
+	if (nk_group_begin(context, name, NK_WINDOW_TITLE)) {
+		nk_layout_row_static(context, context->style.font->height, render_width()/2 - 80, 2);
+		for (int i = 0; i < num_types; i++)
+		{
+			if (nk_button_label(context, types[i])) {
+				selected_controller_info.type = type_id;
+				selected_controller_info.subtype = first_subtype_id + i;
+				pop_view();
+				push_view(view_controller_variant);
+			}
+		}
+		nk_group_end(context);
+	}
+}
+
+void view_controller_type(struct nk_context *context)
+{
+	if (nk_begin(context, "Controller Type", nk_rect(0, 0, render_width(), render_height()), 0)) {
+		controller_type_group(context, "Xbox", TYPE_XBOX, SUBTYPE_XBOX, (const char *[]){
+			"Original", "Xbox 360", "Xbox One"
+		}, 3);
+		controller_type_group(context, "Playstation", TYPE_PSX, SUBTYPE_PS3, (const char *[]){
+			"PS3", "PS4"
+		}, 2);
+		controller_type_group(context, "Sega", TYPE_SEGA, SUBTYPE_GENESIS, (const char *[]){
+			"Genesis", "Saturn"
+		}, 2);
+		controller_type_group(context, "Nintendo", TYPE_NINTENDO, SUBTYPE_WIIU, (const char *[]){
+			"WiiU", "Switch"
+		}, 2);
+		nk_end(context);
+	}
+}
+
+void view_controllers(struct nk_context *context)
+{
+	if (nk_begin(context, "Controllers", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) {
+		int height = (render_width() - 2*context->style.font->height) / MAX_JOYSTICKS;
+		for (int i = 0; i < MAX_JOYSTICKS; i++)
+		{
+			SDL_Joystick *joy = render_get_joystick(i);
+			if (joy) {
+				controller_info info = get_controller_info(i);
+				ui_image *controller_image = select_best_image(&info);
+				int image_width = height * controller_image->width / controller_image->height;
+				nk_layout_row_begin(context, NK_STATIC, height, 2);
+				nk_layout_row_push(context, image_width);
+				if (info.type == TYPE_UNKNOWN || info.type == TYPE_GENERIC_MAPPING) {
+					nk_label(context, "?", NK_TEXT_CENTERED);
+				} else {
+					nk_image(context, controller_image->ui);
+				}
+				nk_layout_row_push(context, render_width() - image_width - 2 * context->style.font->height);
+				if (nk_button_label(context, info.name)) {
+					selected_controller = i;
+					selected_controller_info = info;
+					if (info.type == TYPE_UNKNOWN || info.type == TYPE_GENERIC_MAPPING) {
+						push_view(view_controller_type);
+					} else {
+						push_view(view_controller_bindings);
+						controller_binding_changed = 0;
+					}
+					
+				}
+				nk_layout_row_end(context);
+			}
+		}
+		nk_layout_row_static(context, context->style.font->height, (render_width() - 2 * context->style.font->height) / 2, 2);
+		nk_label(context, "", NK_TEXT_LEFT);
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+		}
+		nk_end(context);
+	}
+}
+
+void settings_toggle(struct nk_context *context, char *label, char *path, uint8_t def)
+{
+	uint8_t curval = !strcmp("on", tern_find_path_default(config, path, (tern_val){.ptrval = def ? "on": "off"}, TVAL_PTR).ptrval);
+	nk_label(context, label, NK_TEXT_LEFT);
+	uint8_t newval = nk_check_label(context, "", curval);
+	if (newval != curval) {
+		config_dirty = 1;
+		config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(newval ? "on" : "off")}, TVAL_PTR);
+	}
+}
+
+void settings_int_input(struct nk_context *context, char *label, char *path, char *def)
+{
+	char buffer[12];
+	nk_label(context, label, NK_TEXT_LEFT);
+	uint32_t curval;
+	char *curstr = tern_find_path_default(config, path, (tern_val){.ptrval = def}, TVAL_PTR).ptrval;
+	uint32_t len = strlen(curstr);
+	if (len > 11) {
+		len = 11;
+	}
+	memcpy(buffer, curstr, len);
+	memset(buffer+len, 0, sizeof(buffer)-len);
+	nk_edit_string(context, NK_EDIT_SIMPLE, buffer, &len, sizeof(buffer)-1, nk_filter_decimal);
+	buffer[len] = 0;
+	if (strcmp(buffer, curstr)) {
+		config_dirty = 1;
+		config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR);
+	}
+}
+
+void settings_int_property(struct nk_context *context, char *label, char *name, char *path, int def, int min, int max)
+{
+	char *curstr = tern_find_path(config, path, TVAL_PTR).ptrval;
+	int curval = curstr ? atoi(curstr) : def;
+	nk_label(context, label, NK_TEXT_LEFT);
+	int val = curval;
+	nk_property_int(context, name, min, &val, max, 1, 1.0f);
+	if (val != curval) {
+		char buffer[12];
+		sprintf(buffer, "%d", val);
+		config_dirty = 1;
+		config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR);
+	}
+}
+
+typedef struct {
+	char *fragment;
+	char *vertex;
+} shader_prog;
+
+shader_prog *get_shader_progs(dir_entry *entries, size_t num_entries, shader_prog *progs, uint32_t *num_existing, uint32_t *storage)
+{
+	uint32_t num_progs = *num_existing;
+	uint32_t prog_storage = *storage;
+	uint32_t starting = num_progs;
+	
+	for (uint32_t i = 0; i < num_entries; i++) {
+		if (entries[i].is_dir) {
+			continue;
+		}
+		char *no_ext = basename_no_extension(entries[i].name);
+		uint32_t len = strlen(no_ext);
+		if (no_ext[len-1] == 'f' && no_ext[len-2] == '.') {
+			uint8_t dupe = 0;;
+			for (uint32_t j = 0; j < starting; j++) {
+				if (!strcmp(entries[i].name, progs[j].fragment)) {
+					dupe = 1;
+					break;
+				}
+			}
+			if (!dupe) {
+				if (num_progs == prog_storage) {
+					prog_storage = prog_storage ? prog_storage*2 : 4;
+					progs = realloc(progs, sizeof(progs) * prog_storage);
+				}
+				progs[num_progs].vertex = NULL;
+				progs[num_progs++].fragment = strdup(entries[i].name); 
+			}
+		}
+		free(no_ext);
+	}
+	
+	for (uint32_t i = 0; i < num_entries; i++) {
+		if (entries[i].is_dir) {
+			continue;
+		}
+		char *no_ext = basename_no_extension(entries[i].name);
+		uint32_t len = strlen(no_ext);
+		if (no_ext[len-1] == 'v' && no_ext[len-2] == '.') {
+			for (uint32_t j = 0; j < num_progs; j++) {
+				if (!strncmp(no_ext, progs[j].fragment, len-1) && progs[j].fragment[len-1] == 'f' && progs[j].fragment[len] == '.') {
+					progs[j].vertex = strdup(entries[i].name);
+				}
+			}
+		}
+		free(no_ext);
+	}
+	free_dir_list(entries, num_entries);
+	*num_existing = num_progs;
+	*storage = prog_storage;
+	return progs;
+}
+
+shader_prog *get_shader_list(uint32_t *num_out)
+{
+	char *shader_dir = path_append(get_config_dir(), "shaders");
+	size_t num_entries;
+	dir_entry *entries = get_dir_list(shader_dir, &num_entries);
+	free(shader_dir);
+	shader_prog *progs;
+	uint32_t num_progs = 0, prog_storage;
+	if (num_entries) {
+		progs = calloc(num_entries, sizeof(shader_prog));
+		prog_storage = num_entries;
+		progs = get_shader_progs(entries, num_entries, progs, &num_progs, &prog_storage);
+	} else {
+		progs = NULL;
+		prog_storage = 0;
+	}
+	shader_dir = path_append(get_exe_dir(), "shaders");
+	entries = get_dir_list(shader_dir, &num_entries);
+	progs = get_shader_progs(entries, num_entries, progs, &num_progs, &prog_storage);
+	*num_out = num_progs;
+	return progs;
+}
+
+int32_t find_match(const char **options, uint32_t num_options, char *path, char *def)
+{
+	char *setting = tern_find_path_default(config, path, (tern_val){.ptrval = def}, TVAL_PTR).ptrval;
+	int32_t selected = -1;
+	for (uint32_t i = 0; i < num_options; i++)
+	{
+		if (!strcmp(setting, options[i])) {
+			selected = i;
+			break;
+		}
+	}
+	if (selected == -1) {
+		for (uint32_t i = 0; i < num_options; i++)
+		{
+			if (!strcmp(def, options[i])) {
+				selected = i;
+				break;
+			}
+		}
+	}
+	return selected;
+}
+
+int32_t settings_dropdown_ex(struct nk_context *context, char *label, const char **options, const char **opt_display, uint32_t num_options, int32_t current, char *path)
+{
+	nk_label(context, label, NK_TEXT_LEFT);
+	int32_t next = nk_combo(context, opt_display, num_options, current, 30, nk_vec2(300, 300));
+	if (next != current) {
+		config_dirty = 1;
+		config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(options[next])}, TVAL_PTR);
+	}
+	return next;
+}
+
+int32_t settings_dropdown(struct nk_context *context, char *label, const char **options, uint32_t num_options, int32_t current, char *path)
+{
+	return settings_dropdown_ex(context, label, options, options, num_options, current, path);
+}
+
+void view_video_settings(struct nk_context *context)
+{
+	const char *vsync_opts[] = {"on", "off", "tear"};
+	const char *vsync_opt_names[] = {
+		"On",
+		"Off",
+		"On, tear if late"
+	};
+	const uint32_t num_vsync_opts = sizeof(vsync_opts)/sizeof(*vsync_opts);
+	static shader_prog *progs;
+	static char **prog_names;
+	static uint32_t num_progs;
+	static uint32_t selected_prog;
+	static int32_t selected_vsync = -1;
+	if (selected_vsync < 0) {
+		selected_vsync = find_match(vsync_opts, num_vsync_opts, "video\0vsync\0", "off");
+	}
+	if(!progs) {
+		progs = get_shader_list(&num_progs);
+		prog_names = calloc(num_progs, sizeof(char*));
+		for (uint32_t i = 0; i < num_progs; i++)
+		{
+			prog_names[i] = basename_no_extension(progs[i].fragment);;
+			uint32_t len = strlen(prog_names[i]);
+			if (len > 2) {
+				prog_names[i][len-2] = 0;
+			}
+			if (!progs[i].vertex) {
+				progs[i].vertex = strdup("default.v.glsl");
+			}
+			if (!strcmp(
+				progs[i].fragment,
+				tern_find_path_default(config, "video\0fragment_shader\0", (tern_val){.ptrval = "default.f.glsl"}, TVAL_PTR).ptrval
+			)) {
+				selected_prog = i;
+			}
+		}
+	}
+	uint32_t width = render_width();
+	uint32_t height = render_height();
+	uint32_t desired_width = context->style.font->height * 10;
+	if (desired_width > width) {
+		desired_width = width;
+	}
+	if (nk_begin(context, "Video Settings", nk_rect(0, 0, width, height), 0)) {
+		nk_layout_row_static(context, context->style.font->height, desired_width, 2);
+		settings_toggle(context, "Fullscreen", "video\0fullscreen\0", 0);
+		settings_toggle(context, "Open GL", "video\0gl\0", 1);
+		settings_toggle(context, "Scanlines", "video\0scanlines\0", 0);
+		selected_vsync = settings_dropdown_ex(context, "VSync", vsync_opts, vsync_opt_names, num_vsync_opts, selected_vsync, "video\0vsync\0");
+		settings_int_input(context, "Windowed Width", "video\0width\0", "640");
+		nk_label(context, "Shader", NK_TEXT_LEFT);
+		uint32_t next_selected = nk_combo(context, (const char **)prog_names, num_progs, selected_prog, context->style.font->height, nk_vec2(desired_width, desired_width));
+		if (next_selected != selected_prog) {
+			selected_prog = next_selected;
+			config_dirty = 1;
+			config = tern_insert_path(config, "video\0fragment_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].fragment)}, TVAL_PTR);
+			config = tern_insert_path(config, "video\0vertex_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].vertex)}, TVAL_PTR);
+		}
+		settings_int_property(context, "NTSC Overscan", "Top", "video\0ntsc\0overscan\0top\0", 2, 0, 32);
+		settings_int_property(context, "", "Bottom", "video\0ntsc\0overscan\0bottom\0", 17, 0, 32);
+		settings_int_property(context, "", "Left", "video\0ntsc\0overscan\0left\0", 13, 0, 32);
+		settings_int_property(context, "", "Right", "video\0ntsc\0overscan\0right\0", 14, 0, 32);
+		settings_int_property(context, "PAL Overscan", "Top", "video\0pal\0overscan\0top\0", 2, 0, 32);
+		settings_int_property(context, "", "Bottom", "video\0pal\0overscan\0bottom\0", 17, 0, 32);
+		settings_int_property(context, "", "Left", "video\0pal\0overscan\0left\0", 13, 0, 32);
+		settings_int_property(context, "", "Right", "video\0pal\0overscan\0right\0", 14, 0, 32);
+		
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+		}
+		nk_end(context);
+	}
+}
+
+void view_audio_settings(struct nk_context *context)
+{
+	const char *rates[] = {
+		"192000",
+		"96000",
+		"48000",
+		"44100",
+		"22050"
+	};
+	const char *sizes[] = {
+		"1024",
+		"512",
+		"256",
+		"128",
+		"64"
+	};
+	const uint32_t num_rates = sizeof(rates)/sizeof(*rates);
+	const uint32_t num_sizes = sizeof(sizes)/sizeof(*sizes);
+	static int32_t selected_rate = -1;
+	static int32_t selected_size = -1;
+	if (selected_rate < 0 || selected_size < 0) {
+		selected_rate = find_match(rates, num_rates, "autio\0rate\0", "48000");
+		selected_size = find_match(sizes, num_sizes, "audio\0buffer\0", "512");
+	}
+	uint32_t width = render_width();
+	uint32_t height = render_height();
+	uint32_t desired_width = context->style.font->height * 10;
+	if (desired_width > width) {
+		desired_width = width;
+	}
+	if (nk_begin(context, "Audio Settings", nk_rect(0, 0, width, height), 0)) {
+		nk_layout_row_static(context, context->style.font->height , desired_width, 2);
+		selected_rate = settings_dropdown(context, "Rate in Hz", rates, num_rates, selected_rate, "audio\0rate\0");
+		selected_size = settings_dropdown(context, "Buffer Samples", sizes, num_sizes, selected_size, "audio\0buffer\0");
+		settings_int_input(context, "Lowpass Cutoff Hz", "audio\0lowpass_cutoff\0", "3390");
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+		}
+		nk_end(context);
+	}
+}
+void view_system_settings(struct nk_context *context)
+{
+	const char *sync_opts[] = {
+		"video",
+		"audio"
+	};
+	const uint32_t num_sync_opts = sizeof(sync_opts)/sizeof(*sync_opts);
+	static int32_t selected_sync = -1;
+	if (selected_sync < 0) {
+		selected_sync = find_match(sync_opts, num_sync_opts, "system\0sync_source\0", "video");
+	}
+	const char *regions[] = {
+		"J - Japan",
+		"U - Americas",
+		"E - Europe"
+	};
+	const char *region_codes[] = {"J", "U", "E"};
+	const uint32_t num_regions = sizeof(regions)/sizeof(*regions);
+	static int32_t selected_region = -1;
+	if (selected_region < 0) {
+		selected_region = find_match(region_codes, num_regions, "system\0default_region\0", "U");
+	}
+	const char *formats[] = {
+		"native",
+		"gst"
+	};
+	const uint32_t num_formats = sizeof(formats)/sizeof(*formats);
+	int32_t selected_format = -1;
+	if (selected_format < 0) {
+		selected_format = find_match(formats, num_formats, "ui\0state_format\0", "native");
+	}
+	const char *ram_inits[] = {
+		"zero",
+		"random"
+	};
+	const uint32_t num_inits = sizeof(ram_inits)/sizeof(*ram_inits);
+	static int32_t selected_init = -1;
+	if (selected_init < 0) {
+		selected_init = find_match(ram_inits, num_inits, "system\0ram_init\0", "zero");
+	}
+	const char *io_opts_1[] = {
+		"gamepad2.1",
+		"gamepad3.1",
+		"gamepad6.1",
+		"mouse.1",
+		"saturn keyboard",
+		"xband keyboard"
+	};
+	const char *io_opts_2[] = {
+		"gamepad2.2",
+		"gamepad3.2",
+		"gamepad6.2",
+		"mouse.1",
+		"saturn keyboard",
+		"xband keyboard"
+	};
+	static int32_t selected_io_1 = -1;
+	static int32_t selected_io_2 = -1;
+	const uint32_t num_io = sizeof(io_opts_1)/sizeof(*io_opts_1);
+	if (selected_io_1 < 0 || selected_io_2 < 0) {
+		selected_io_1 = find_match(io_opts_1, num_io, "io\0devices\0""1\0", "gamepad6.1");
+		selected_io_2 = find_match(io_opts_2, num_io, "io\0devices\0""2\0", "gamepad6.2");
+	}
+	
+	uint32_t width = render_width();
+	uint32_t height = render_height();
+	uint32_t desired_width = context->style.font->height * 10;
+	if (nk_begin(context, "System Settings", nk_rect(0, 0, width, height), 0)) {
+		nk_layout_row_static(context, context->style.font->height, desired_width, 2);
+		selected_sync = settings_dropdown(context, "Sync Source", sync_opts, num_sync_opts, selected_sync, "system\0sync_source\0");
+		settings_int_property(context, "68000 Clock Divider", "", "clocks\0m68k_divider\0", 7, 1, 53);
+		settings_toggle(context, "Remember ROM Path", "ui\0remember_path\0", 1);
+		selected_region = settings_dropdown_ex(context, "Default Region", region_codes, regions, num_regions, selected_region, "system\0default_region\0");
+		selected_format = settings_dropdown(context, "Save State Format", formats, num_formats, selected_format, "ui\0state_format\0");
+		selected_init = settings_dropdown(context, "Initial RAM Value", ram_inits, num_inits, selected_init, "system\0ram_init\0");
+		selected_io_1 = settings_dropdown_ex(context, "IO Port 1 Device", io_opts_1, device_type_names, num_io, selected_io_1, "io\0devices\0""1\0");
+		selected_io_2 = settings_dropdown_ex(context, "IO Port 2 Device", io_opts_2, device_type_names, num_io, selected_io_2, "io\0devices\0""2\0");
+		if (nk_button_label(context, "Back")) {
+			pop_view();
+		}
+		nk_end(context);
+	}
+}
+
+void view_back(struct nk_context *context)
+{
+	pop_view();
+	pop_view();
+	current_view(context);
+}
+
+void view_settings(struct nk_context *context)
+{
+	static menu_item items[] = {
+		{"Key Bindings", view_key_bindings},
+		{"Controllers", view_controllers},
+		{"Video", view_video_settings},
+		{"Audio", view_audio_settings},
+		{"System", view_system_settings},
+		{"Back", view_back}
+	};
+	
+	if (nk_begin(context, "Settings Menu", nk_rect(0, 0, render_width(), render_height()), 0)) {
+		menu(context, sizeof(items)/sizeof(*items), items, NULL);
+		nk_end(context);
+	}
+}
+
+void exit_handler(uint32_t index)
+{
+	exit(0);
+}
+
+void view_pause(struct nk_context *context)
+{
+	static menu_item items[] = {
+		{"Resume", view_play},
+		{"Load ROM", view_load},
+		{"Lock On", view_lock_on},
+		{"Save State", view_save_state},
+		{"Load State", view_load_state},
+		{"Settings", view_settings},
+		{"Exit", NULL}
+	};
+	
+	if (nk_begin(context, "Main Menu", nk_rect(0, 0, render_width(), render_height()), 0)) {
+		menu(context, sizeof(items)/sizeof(*items), items, exit_handler);
+		nk_end(context);
+	}
+}
+
+void view_menu(struct nk_context *context)
+{
+	static menu_item items[] = {
+		{"Load ROM", view_load},
+		{"Settings", view_settings},
+		{"About", view_about},
+		{"Exit", NULL}
+	};
+	
+	if (nk_begin(context, "Main Menu", nk_rect(0, 0, render_width(), render_height()), 0)) {
+		menu(context, sizeof(items)/sizeof(*items), items, exit_handler);
+		nk_end(context);
+	}
+}
+
+void blastem_nuklear_render(void)
+{
+	if (current_view != view_play) {
+		nk_input_end(context);
+		current_view(context);
+		nk_sdl_render(NK_ANTI_ALIASING_ON, 512 * 1024, 128 * 1024);
+		nk_input_begin(context);
+	}
+}
+
+void ui_idle_loop(void)
+{
+	const uint32_t MIN_UI_DELAY = 15;
+	static uint32_t last;
+	while (current_view != view_play)
+	{
+		uint32_t current = render_elapsed_ms();
+		if ((current - last) < MIN_UI_DELAY) {
+			render_sleep_ms(MIN_UI_DELAY - (current - last) - 1);
+		}
+		last = current;
+		render_update_display();
+	}
+	if (config_dirty) {
+		apply_updated_config();
+		persist_config(config);
+		config_dirty = 0;
+	}
+}
+static void handle_event(SDL_Event *event)
+{
+	if (event->type == SDL_KEYDOWN) {
+		keycode = event->key.keysym.sym;
+	}
+	else if (event->type == SDL_JOYBUTTONDOWN) {
+		button_pressed = event->jbutton.button;
+	}
+	else if (event->type == SDL_JOYHATMOTION) {
+		hat_moved = event->jhat.hat;
+		hat_value = event->jhat.value;
+	}
+	else if (event->type == SDL_JOYAXISMOTION) {
+		if (event->jaxis.axis == axis_moved || abs(event->jaxis.value) > abs(axis_value) || abs(event->jaxis.value) > 1000) {
+			axis_moved = event->jaxis.axis;
+			axis_value = event->jaxis.value;
+		}
+	}
+	nk_sdl_handle_event(event);
+}
+
+static void context_destroyed(void)
+{
+	nk_sdl_shutdown();
+}
+
+static struct nk_image load_image_texture(uint32_t *buf, uint32_t width, uint32_t height)
+{
+	GLuint tex;
+	glGenTextures(1, &tex);
+	glBindTexture(GL_TEXTURE_2D, tex);
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+#ifdef USE_GLES
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
+#else
+	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, buf);
+#endif
+	return nk_image_id((int)tex);
+}
+
+static void texture_init(void)
+{
+	struct nk_font_atlas *atlas;
+	nk_sdl_font_stash_begin(&atlas);
+	uint32_t font_size;
+	uint8_t *font = default_font(&font_size);
+	if (!font) {
+		fatal_error("Failed to find default font path\n");
+	}
+	def_font = nk_font_atlas_add_from_memory(atlas, font, font_size, render_height() / 16, NULL);
+	free(font);
+	nk_sdl_font_stash_end();
+	nk_style_set_font(context, &def_font->handle);
+	for (uint32_t i = 0; i < num_ui_images; i++)
+	{
+		ui_images[i]->ui = load_image_texture(ui_images[i]->image_data, ui_images[i]->width, ui_images[i]->height);
+	}
+}
+
+static void context_created(void)
+{
+	context = nk_sdl_init(render_get_window());
+	texture_init();
+}
+
+void show_pause_menu(void)
+{
+	set_content_binding_state(0);
+	context->style.window.background = nk_rgba(0, 0, 0, 128);
+	context->style.window.fixed_background = nk_style_item_color(nk_rgba(0, 0, 0, 128));
+	current_view = view_pause;
+	current_system->request_exit(current_system);
+}
+
+void show_play_view(void)
+{
+	set_content_binding_state(1);
+	current_view = view_play;
+}
+
+static uint8_t active;
+uint8_t is_nuklear_active(void)
+{
+	return active;
+}
+
+uint8_t is_nuklear_available(void)
+{
+	if (!render_has_gl()) {
+		//currently no fallback if GL2 unavailable
+		return 0;
+	}
+	char *style = tern_find_path(config, "ui\0style\0", TVAL_PTR).ptrval;
+	if (!style) {
+		return 1;
+	}
+	return strcmp(style, "rom") != 0;
+}
+
+static void persist_config_exit(void)
+{
+	if (config_dirty) {
+		persist_config(config);
+	}
+}
+
+ui_image *load_ui_image(char *name)
+{
+	uint32_t buf_size;
+	uint8_t *buf = (uint8_t *)read_bundled_file(name, &buf_size);
+	if (buf) {
+		num_ui_images++;
+		if (num_ui_images > ui_image_storage) {
+			ui_image_storage = (ui_image_storage + 1) * 2;
+			ui_images = realloc(ui_images, ui_image_storage * sizeof(*ui_images));
+		}
+		ui_image *this_image = ui_images[num_ui_images-1] = calloc(1, sizeof(ui_image));
+		this_image->image_data = load_png(buf, buf_size, &this_image->width, &this_image->height);
+#ifdef USE_GLES
+		uint32_t *cur = this_image->image_data;
+		for (int i = 0; i < this_image->width*this_image->height; i++, cur++)
+		{
+			uint32_t pixel = *cur;
+			*cur = (pixel & 0xFF00FF00) | (pixel << 16 & 0xFF0000) | (pixel >> 16 & 0xFF);
+		}
+#endif
+		free(buf);
+		if (!this_image->image_data) {
+			num_ui_images--;
+			free(this_image);
+			return NULL;
+		}
+		return this_image;
+	} else {
+		return NULL;
+	}
+}
+
+void blastem_nuklear_init(uint8_t file_loaded)
+{
+	context = nk_sdl_init(render_get_window());
+	
+	controller_360 = load_ui_image("images/360.png");
+	controller_ps4 = load_ui_image("images/ps4.png");
+	controller_ps4_6b = load_ui_image("images/ps4_6b.png");
+	
+	texture_init();
+	
+	if (file_loaded) {
+		current_view = view_play;
+	} else {
+		current_view = view_menu;
+		set_content_binding_state(0);
+	}
+	render_set_ui_render_fun(blastem_nuklear_render);
+	render_set_event_handler(handle_event);
+	render_set_gl_context_handlers(context_destroyed, context_created);
+	
+	atexit(persist_config_exit);
+	
+	active = 1;
+	ui_idle_loop();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/blastem_nuklear.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,20 @@
+#ifndef BLASTEM_NUKLEAR_H_
+#define BLASTEM_NUKLEAR_H_
+
+#define NK_INCLUDE_FIXED_TYPES
+#define NK_INCLUDE_STANDARD_IO
+#define NK_INCLUDE_STANDARD_VARARGS
+#define NK_INCLUDE_DEFAULT_ALLOCATOR
+#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+#define NK_INCLUDE_FONT_BAKING
+#include "nuklear.h"
+#include "nuklear_sdl_gles2.h"
+
+void blastem_nuklear_init(uint8_t file_loaded);
+void show_pause_menu(void);
+void show_play_view(void);
+uint8_t is_nuklear_active(void);
+uint8_t is_nuklear_available(void);
+void ui_idle_loop(void);
+
+#endif //BLASTEM_NUKLEAR_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/font.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include "../util.h"
+#include "sfnt.h"
+
+char *default_font_path(void)
+{
+	FILE *fc_pipe = popen("fc-match -f '%{file}'", "r");
+	if (!fc_pipe) {
+		return NULL;
+	}
+	size_t buf_size = 128;
+	char *buffer = NULL;
+	size_t total = 0, read = 0;
+	do {
+		total += read;
+		buf_size *= 2;
+		buffer = realloc(buffer, buf_size);
+		if (!buffer) {
+			return NULL;
+		}
+		read = fread(buffer, 1, buf_size - total, fc_pipe);
+	} while (read == (buf_size - total));
+	total += read;
+	buffer[total] = 0;
+	
+	return buffer;
+}
+
+uint8_t *default_font(uint32_t *size_out)
+{
+	char *path = default_font_path();
+	if (!path) {
+		goto error;
+	}
+	FILE *f = fopen(path, "rb");
+	free(path);
+	if (!f) {
+		goto error;
+	}
+	long size = file_size(f);
+	uint8_t *buffer = malloc(size);
+	if (size != fread(buffer, 1, size, f)) {
+		fclose(f);
+		goto error;
+	}
+	fclose(f);
+	sfnt_container *sfnt = load_sfnt(buffer, size);
+	if (!sfnt) {
+		free(buffer);
+		goto error;
+	}
+	return sfnt_flatten(sfnt->tables, size_out);
+error:
+	//TODO: try to find a suitable font in /usr/share/fonts as a fallback
+	return NULL;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/font.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,6 @@
+#ifndef FONT_H_
+#define FONT_H_
+
+uint8_t *default_font(uint32_t *size_out);
+
+#endif //FONT_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/font_mac.m	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,118 @@
+#import <AppKit/AppKit.h>
+#include <stddef.h>
+#include "../paths.h"
+#include "../util.h"
+#include "sfnt.h"
+
+static sfnt_table *find_font_in_dir(char *path, char *prefix, const char *ps_name)
+{
+	size_t num_entries;
+	dir_entry *entries = get_dir_list(path, &num_entries);
+	size_t prefix_len = prefix ? strlen(prefix) : 0;
+	sfnt_table *selected = NULL;
+	for (size_t i = 0; i < num_entries && !selected; i++)
+	{
+		char *ext = path_extension(entries[i].name);
+		if (!ext || (strcasecmp(ext, "ttf") && strcasecmp(ext, "ttc") && strcasecmp(ext, "dfont"))) {
+			//not a truetype font, ignore
+			printf("Skipping %s because of its extension\n", entries[i].name);
+			free(ext);
+			continue;
+		}
+		free(ext);
+		if (!prefix || !strncasecmp(entries[i].name, prefix, prefix_len)) {
+			char *full_path = path_append(path, entries[i].name);
+			FILE *f = fopen(full_path, "rb");
+			if (f)
+			{
+				long font_size = file_size(f);
+				uint8_t *blob = malloc(font_size);
+				if (font_size == fread(blob, 1, font_size, f))
+				{
+					sfnt_container *sfnt = load_sfnt(blob, font_size);
+					if (sfnt) {
+						printf("Examining font file %s\n", entries[i].name);
+						for (uint8_t j = 0; j < sfnt->num_fonts && !selected; j++)
+						{
+							char *cur_ps = sfnt_name(sfnt->tables + j, SFNT_POSTSCRIPT);
+							printf("\t%s\n", cur_ps);
+							if (!strcmp(cur_ps, ps_name)) {
+								selected = sfnt->tables + j;
+							}
+							free(cur_ps);
+						}
+					} else {
+						printf("Failed to load %s as sfnt containern\n", entries[i].name);
+						free(blob);
+					}
+				} else {
+					free(blob);
+				}
+				fclose(f);
+			}
+			free(full_path);
+		}
+	}
+	return selected;
+}
+
+static sfnt_table *find_font_by_ps_name(const char*ps_name, uint8_t exhaustive)
+{
+	const unsigned char *prefix_start = (const unsigned char *)ps_name;
+	while(*prefix_start && (
+		*prefix_start < '0' || 
+		(*prefix_start > 'z' && *prefix_start <= 0x80) || 
+		(*prefix_start > 'Z' && *prefix_start < 'a') || 
+		(*prefix_start > '9' && *prefix_start < 'A')
+	))
+	{
+		prefix_start++;
+	}
+	if (!*prefix_start) {
+		//Didn't find a suitable starting character, just start from the beginning
+		prefix_start = (const unsigned char *)ps_name;
+	}
+	const unsigned char *prefix_end = (const unsigned char *)prefix_start + 1;
+	while (*prefix_end && *prefix_end >= 'a')
+	{
+		prefix_end++;
+	}
+	char *prefix = malloc(prefix_end - prefix_start + 1);
+	memcpy(prefix, prefix_start, prefix_end - prefix_start);
+	prefix[prefix_end-prefix_start] = 0;
+	//check /Library/Fonts first
+	sfnt_table *selected = find_font_in_dir("/Library/Fonts", (char *)prefix, ps_name);
+	if (!selected) {
+		selected = find_font_in_dir("/System/Library/Fonts", (char *)prefix, ps_name);
+	}
+	if (exhaustive) {
+		if (!selected) {
+			puts("Check using prefix failed, exhaustively checking fonts");
+			selected = find_font_in_dir("/Library/Fonts", NULL, ps_name);
+		}
+		if (!selected) {
+			selected = find_font_in_dir("/System/Library/Fonts", NULL, ps_name);
+		}
+	}
+	free(prefix);
+	return selected;
+}
+
+uint8_t *default_font(uint32_t *size_out)
+{
+	NSFont *sys = [NSFont systemFontOfSize:0];
+	NSString *name = [sys fontName];
+	sfnt_table *selected = find_font_by_ps_name([name UTF8String], 1);
+	if (!selected) {
+		selected = find_font_by_ps_name(".HelveticaNeueDeskInterface-Regular", 0);
+	}
+	if (!selected) {
+		selected = find_font_by_ps_name(".LucidaGrandeUI", 0);
+	}
+	
+	if (!selected) {
+		fatal_error("Failed to find system font %s\n", [name UTF8String]);
+	}
+	return sfnt_flatten(selected, size_out);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/font_win.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,146 @@
+#include <windows.h>
+#include <shlobj.h>
+#include <string.h>
+#include "../paths.h"
+#include "../util.h"
+#include "sfnt.h"
+
+uint8_t *default_font(uint32_t *size_out)
+{
+	static const char *thin[] = {"Thin", NULL};
+	static const char *extra_light[] = {"ExtraLight", "UltraLight", NULL};
+	static const char *light[] = {"Light", NULL};
+	static const char *regular[] = {"Regular", "Normal", "Book", NULL};
+	static const char *medium[] = {"Medium", NULL};
+	static const char *semi_bold[] = {"SemiBold", "DemiBold", NULL};
+	static const char *bold[] = {"Bold", NULL};
+	static const char *extra_bold[] = {"ExtraBold", "UltraBold", NULL};
+	static const char *heavy[] = {"Heavy", "Black", NULL};
+	static const char **weight_to_subfamilies[] = {
+		NULL,
+		thin,
+		extra_light,
+		light,
+		regular,
+		medium,
+		semi_bold,
+		bold,
+		extra_bold,
+		heavy
+	};
+
+	NONCLIENTMETRICSA metrics = {
+		.cbSize = sizeof(metrics)
+	};
+	char *pref_name = NULL, *pref_prefix = NULL;
+	const char **pref_sub_families;
+	if (SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, sizeof(metrics), &metrics, 0)) {
+		pref_name = metrics.lfMenuFont.lfFaceName;
+		int32_t weight = metrics.lfMenuFont.lfWeight / 100;
+		if (weight < 1 || weight > 9) {
+			weight = 4;
+		}
+		pref_sub_families = weight_to_subfamilies[weight];
+	}
+	if (pref_name) {
+		uint32_t prefix_len = 0;
+		while (pref_name[prefix_len] && pref_name[prefix_len] != ' ')
+		{
+			prefix_len++;
+		}
+		pref_prefix = malloc(prefix_len + 1);
+		memcpy(pref_prefix, pref_name, prefix_len);
+		pref_prefix[prefix_len] = 0;
+	}
+	sfnt_table *selected = NULL;
+	char windows[MAX_PATH];
+	SHGetFolderPathA(NULL, CSIDL_WINDOWS, NULL, 0, windows);
+	char *fonts = path_append(windows, "Fonts");
+	size_t num_entries;
+	char *tahoma = NULL, *arial = NULL;
+	dir_entry *entries = get_dir_list(fonts, &num_entries);
+	char *path = NULL;
+	for (size_t i = 0; i < num_entries; i++)
+	{
+		if (entries[i].is_dir) {
+			continue;
+		}
+		char *ext = path_extension(entries[i].name);
+		if (!ext || (strcasecmp(ext, "ttf") && strcasecmp(ext, "ttc") && strcasecmp(ext, "dfont"))) {
+			//not a truetype font, ignore
+			free(ext);
+			continue;
+		}
+		free(ext);
+		char *base = basename_no_extension(entries[i].name);
+		if (pref_prefix && !strncasecmp(base, pref_prefix, 6)) {
+			path = path_append(fonts, entries[i].name);
+			FILE *f = fopen(path, "rb");
+			if (f)
+			{
+				long font_size = file_size(f);
+				uint8_t *blob = malloc(font_size);
+				if (font_size == fread(blob, 1, font_size, f))
+				{
+					sfnt_container *sfnt = load_sfnt(blob, font_size);
+					if (sfnt) {
+						selected = sfnt_subfamily_by_names(sfnt, pref_sub_families);
+						if (!selected) {
+							sfnt_free(sfnt);
+						}
+					} else {
+						free(blob);
+					}
+				} else {
+					free(blob);
+				}
+				fclose(f);
+			}
+			free(path);
+			free(base);
+			if (selected) {
+				printf("Found preferred font in %s\n", entries[i].name);
+				break;
+			}
+		} else if (!strcasecmp(base, "tahoma")) {
+			tahoma = entries[i].name;
+		} else if (!strcasecmp(base, "arial")) {
+			arial = entries[i].name;
+		}
+		free(base);
+	}
+	if (!selected) {
+		path = NULL;
+		if (tahoma) {
+			path = path_append(fonts, tahoma);
+		} else if (arial) {
+			path = path_append(fonts, arial);
+		}
+		if (path) {
+			FILE *f = fopen(path, "rb");
+			if (f)
+			{
+				long font_size = file_size(f);
+				uint8_t *blob = malloc(font_size);
+				if (font_size == fread(blob, 1, font_size, f))
+				{
+					sfnt_container *sfnt = load_sfnt(blob, font_size);
+					if (sfnt) {
+						selected = sfnt->tables;
+					} else {
+						free(blob);
+					}
+				}
+				fclose(f);
+			}
+			free(path);
+		}
+	}
+	free(pref_prefix);
+	free(fonts);
+	free_dir_list(entries, num_entries);
+	if (selected) {
+		return sfnt_flatten(selected, size_out);
+	}
+	return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/nuklear.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,23652 @@
+/*
+ Nuklear - 2.00.0 - public domain
+ no warranty implied; use at your own risk.
+ authored from 2015-2017 by Micha Mettke
+
+ABOUT:
+    This is a minimal state graphical user interface single header toolkit
+    written in ANSI C and licensed under public domain.
+    It was designed as a simple embeddable user interface for application and does
+    not have any dependencies, a default renderbackend or OS window and input handling
+    but instead provides a very modular library approach by using simple input state
+    for input and draw commands describing primitive shapes as output.
+    So instead of providing a layered library that tries to abstract over a number
+    of platform and render backends it only focuses on the actual UI.
+
+VALUES:
+    - Graphical user interface toolkit
+    - Single header library
+    - Written in C89 (a.k.a. ANSI C or ISO C90)
+    - Small codebase (~18kLOC)
+    - Focus on portability, efficiency and simplicity
+    - No dependencies (not even the standard library if not wanted)
+    - Fully skinnable and customizable
+    - Low memory footprint with total memory control if needed or wanted
+    - UTF-8 support
+    - No global or hidden state
+    - Customizable library modules (you can compile and use only what you need)
+    - Optional font baker and vertex buffer output
+
+USAGE:
+    This library is self contained in one single header file and can be used either
+    in header only mode or in implementation mode. The header only mode is used
+    by default when included and allows including this header in other headers
+    and does not contain the actual implementation.
+
+    The implementation mode requires to define  the preprocessor macro
+    NK_IMPLEMENTATION in *one* .c/.cpp file before #includeing this file, e.g.:
+
+        #define NK_IMPLEMENTATION
+        #include "nuklear.h"
+
+    Also optionally define the symbols listed in the section "OPTIONAL DEFINES"
+    below in header and implementation mode if you want to use additional functionality
+    or need more control over the library.
+    IMPORTANT:  Every time you include "nuklear.h" you have to define the same flags.
+                This is very important not doing it either leads to compiler errors
+                or even worse stack corruptions.
+
+FEATURES:
+    - Absolutely no platform dependent code
+    - Memory management control ranging from/to
+        - Ease of use by allocating everything from standard library
+        - Control every byte of memory inside the library
+    - Font handling control ranging from/to
+        - Use your own font implementation for everything
+        - Use this libraries internal font baking and handling API
+    - Drawing output control ranging from/to
+        - Simple shapes for more high level APIs which already have drawing capabilities
+        - Hardware accessible anti-aliased vertex buffer output
+    - Customizable colors and properties ranging from/to
+        - Simple changes to color by filling a simple color table
+        - Complete control with ability to use skinning to decorate widgets
+    - Bendable UI library with widget ranging from/to
+        - Basic widgets like buttons, checkboxes, slider, ...
+        - Advanced widget like abstract comboboxes, contextual menus,...
+    - Compile time configuration to only compile what you need
+        - Subset which can be used if you do not want to link or use the standard library
+    - Can be easily modified to only update on user input instead of frame updates
+
+OPTIONAL DEFINES:
+    NK_PRIVATE
+        If defined declares all functions as static, so they can only be accessed
+        inside the file that contains the implementation
+
+    NK_INCLUDE_FIXED_TYPES
+        If defined it will include header <stdint.h> for fixed sized types
+        otherwise nuklear tries to select the correct type. If that fails it will
+        throw a compiler error and you have to select the correct types yourself.
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_INCLUDE_DEFAULT_ALLOCATOR
+        if defined it will include header <stdlib.h> and provide additional functions
+        to use this library without caring for memory allocation control and therefore
+        ease memory management.
+        <!> Adds the standard library with malloc and free so don't define if you
+            don't want to link to the standard library <!>
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_INCLUDE_STANDARD_IO
+        if defined it will include header <stdio.h> and provide
+        additional functions depending on file loading.
+        <!> Adds the standard library with fopen, fclose,... so don't define this
+            if you don't want to link to the standard library <!>
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_INCLUDE_STANDARD_VARARGS
+        if defined it will include header <stdarg.h> and provide
+        additional functions depending on variable arguments
+        <!> Adds the standard library with va_list and  so don't define this if
+            you don't want to link to the standard library<!>
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+        Defining this adds a vertex draw command list backend to this
+        library, which allows you to convert queue commands into vertex draw commands.
+        This is mainly if you need a hardware accessible format for OpenGL, DirectX,
+        Vulkan, Metal,...
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_INCLUDE_FONT_BAKING
+        Defining this adds `stb_truetype` and `stb_rect_pack` implementation
+        to this library and provides font baking and rendering.
+        If you already have font handling or do not want to use this font handler
+        you don't have to define it.
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_INCLUDE_DEFAULT_FONT
+        Defining this adds the default font: ProggyClean.ttf into this library
+        which can be loaded into a font atlas and allows using this library without
+        having a truetype font
+        <!> Enabling this adds ~12kb to global stack memory <!>
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_INCLUDE_COMMAND_USERDATA
+        Defining this adds a userdata pointer into each command. Can be useful for
+        example if you want to provide custom shaders depending on the used widget.
+        Can be combined with the style structures.
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_BUTTON_TRIGGER_ON_RELEASE
+        Different platforms require button clicks occurring either on buttons being
+        pressed (up to down) or released (down to up).
+        By default this library will react on buttons being pressed, but if you
+        define this it will only trigger if a button is released.
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_ZERO_COMMAND_MEMORY
+        Defining this will zero out memory for each drawing command added to a
+        drawing queue (inside nk_command_buffer_push). Zeroing command memory
+        is very useful for fast checking (using memcmp) if command buffers are
+        equal and avoid drawing frames when nothing on screen has changed since
+        previous frame.
+
+    NK_ASSERT
+        If you don't define this, nuklear will use <assert.h> with assert().
+        <!> Adds the standard library so define to nothing of not wanted <!>
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_BUFFER_DEFAULT_INITIAL_SIZE
+        Initial buffer size allocated by all buffers while using the default allocator
+        functions included by defining NK_INCLUDE_DEFAULT_ALLOCATOR. If you don't
+        want to allocate the default 4k memory then redefine it.
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_MAX_NUMBER_BUFFER
+        Maximum buffer size for the conversion buffer between float and string
+        Under normal circumstances this should be more than sufficient.
+        <!> If used needs to be defined for implementation and header <!>
+
+    NK_INPUT_MAX
+        Defines the max number of bytes which can be added as text input in one frame.
+        Under normal circumstances this should be more than sufficient.
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_MEMSET
+        You can define this to 'memset' or your own memset implementation
+        replacement. If not nuklear will use its own version.
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_MEMCPY
+        You can define this to 'memcpy' or your own memcpy implementation
+        replacement. If not nuklear will use its own version.
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_SQRT
+        You can define this to 'sqrt' or your own sqrt implementation
+        replacement. If not nuklear will use its own slow and not highly
+        accurate version.
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_SIN
+        You can define this to 'sinf' or your own sine implementation
+        replacement. If not nuklear will use its own approximation implementation.
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_COS
+        You can define this to 'cosf' or your own cosine implementation
+        replacement. If not nuklear will use its own approximation implementation.
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_STRTOD
+        You can define this to `strtod` or your own string to double conversion
+        implementation replacement. If not defined nuklear will use its own
+        imprecise and possibly unsafe version (does not handle nan or infinity!).
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_DTOA
+        You can define this to `dtoa` or your own double to string conversion
+        implementation replacement. If not defined nuklear will use its own
+        imprecise and possibly unsafe version (does not handle nan or infinity!).
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_VSNPRINTF
+        If you define `NK_INCLUDE_STANDARD_VARARGS` as well as `NK_INCLUDE_STANDARD_IO`
+        and want to be safe define this to `vsnprintf` on compilers supporting
+        later versions of C or C++. By default nuklear will check for your stdlib version
+        in C as well as compiler version in C++. if `vsnprintf` is available
+        it will define it to `vsnprintf` directly. If not defined and if you have
+        older versions of C or C++ it will be defined to `vsprintf` which is unsafe.
+        <!> If used it is only required to be defined for the implementation part <!>
+
+    NK_BYTE
+    NK_INT16
+    NK_UINT16
+    NK_INT32
+    NK_UINT32
+    NK_SIZE_TYPE
+    NK_POINTER_TYPE
+        If you compile without NK_USE_FIXED_TYPE then a number of standard types
+        will be selected and compile time validated. If they are incorrect you can
+        define the correct types by overloading these type defines.
+
+CREDITS:
+    Developed by Micha Mettke and every direct or indirect contributor.
+
+    Embeds stb_texedit, stb_truetype and stb_rectpack by Sean Barret (public domain)
+    Embeds ProggyClean.ttf font by Tristan Grimmer (MIT license).
+
+    Big thank you to Omar Cornut (ocornut@github) for his imgui library and
+    giving me the inspiration for this library, Casey Muratori for handmade hero
+    and his original immediate mode graphical user interface idea and Sean
+    Barret for his amazing single header libraries which restored my faith
+    in libraries and brought me to create some of my own.
+
+LICENSE:
+    This software is dual-licensed to the public domain and under the following
+    license: you are granted a perpetual, irrevocable license to copy, modify,
+    publish and distribute this file as you see fit.
+*/
+#ifndef NK_NUKLEAR_H_
+#define NK_NUKLEAR_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * ==============================================================
+ *
+ *                          CONSTANTS
+ *
+ * ===============================================================
+ */
+#define NK_UNDEFINED (-1.0f)
+#define NK_UTF_INVALID 0xFFFD /* internal invalid utf8 rune */
+#define NK_UTF_SIZE 4 /* describes the number of bytes a glyph consists of*/
+#ifndef NK_INPUT_MAX
+#define NK_INPUT_MAX 16
+#endif
+#ifndef NK_MAX_NUMBER_BUFFER
+#define NK_MAX_NUMBER_BUFFER 64
+#endif
+#ifndef NK_SCROLLBAR_HIDING_TIMEOUT
+#define NK_SCROLLBAR_HIDING_TIMEOUT 4.0f
+#endif
+/*
+ * ==============================================================
+ *
+ *                          HELPER
+ *
+ * ===============================================================
+ */
+#ifndef NK_API
+  #ifdef NK_PRIVATE
+    #if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199409L))
+      #define NK_API static inline
+    #elif defined(__cplusplus)
+      #define NK_API static inline
+    #else
+      #define NK_API static
+    #endif
+  #else
+    #define NK_API extern
+  #endif
+#endif
+
+#define NK_INTERN static
+#define NK_STORAGE static
+#define NK_GLOBAL static
+
+#define NK_FLAG(x) (1 << (x))
+#define NK_STRINGIFY(x) #x
+#define NK_MACRO_STRINGIFY(x) NK_STRINGIFY(x)
+#define NK_STRING_JOIN_IMMEDIATE(arg1, arg2) arg1 ## arg2
+#define NK_STRING_JOIN_DELAY(arg1, arg2) NK_STRING_JOIN_IMMEDIATE(arg1, arg2)
+#define NK_STRING_JOIN(arg1, arg2) NK_STRING_JOIN_DELAY(arg1, arg2)
+
+#ifdef _MSC_VER
+#define NK_UNIQUE_NAME(name) NK_STRING_JOIN(name,__COUNTER__)
+#else
+#define NK_UNIQUE_NAME(name) NK_STRING_JOIN(name,__LINE__)
+#endif
+
+#ifndef NK_STATIC_ASSERT
+#define NK_STATIC_ASSERT(exp) typedef char NK_UNIQUE_NAME(_dummy_array)[(exp)?1:-1]
+#endif
+
+#ifndef NK_FILE_LINE
+#ifdef _MSC_VER
+#define NK_FILE_LINE __FILE__ ":" NK_MACRO_STRINGIFY(__COUNTER__)
+#else
+#define NK_FILE_LINE __FILE__ ":" NK_MACRO_STRINGIFY(__LINE__)
+#endif
+#endif
+
+#define NK_MIN(a,b) ((a) < (b) ? (a) : (b))
+#define NK_MAX(a,b) ((a) < (b) ? (b) : (a))
+#define NK_CLAMP(i,v,x) (NK_MAX(NK_MIN(v,x), i))
+/*
+ * ===============================================================
+ *
+ *                          BASIC
+ *
+ * ===============================================================
+ */
+#ifdef NK_INCLUDE_FIXED_TYPES
+ #include <stdint.h>
+ #define NK_INT8 int8_t
+ #define NK_UINT8 uint8_t
+ #define NK_INT16 int16_t
+ #define NK_UINT16 uint16_t
+ #define NK_INT32 int32_t
+ #define NK_UINT32 uint32_t
+ #define NK_SIZE_TYPE uintptr_t
+ #define NK_POINTER_TYPE uintptr_t
+#else
+  #ifndef NK_INT8
+    #define NK_INT8 char
+  #endif
+  #ifndef NK_UINT8
+    #define NK_UINT8 unsigned char
+  #endif
+  #ifndef NK_INT16
+    #define NK_INT16 signed short
+  #endif
+  #ifndef NK_UINT16
+    #define NK_UINT16 unsigned short
+  #endif
+  #ifndef NK_INT32
+    #if defined(_MSC_VER)
+      #define NK_INT32 __int32
+    #else
+      #define NK_INT32 signed int
+    #endif
+  #endif
+  #ifndef NK_UINT32
+    #if defined(_MSC_VER)
+      #define NK_UINT32 unsigned __int32
+    #else
+      #define NK_UINT32 unsigned int
+    #endif
+  #endif
+  #ifndef NK_SIZE_TYPE
+    #if defined(_WIN64) && defined(_MSC_VER)
+      #define NK_SIZE_TYPE unsigned __int64
+    #elif (defined(_WIN32) || defined(WIN32)) && defined(_MSC_VER)
+      #define NK_SIZE_TYPE unsigned __int32
+    #elif defined(__GNUC__) || defined(__clang__)
+      #if defined(__x86_64__) || defined(__ppc64__)
+        #define NK_SIZE_TYPE unsigned long
+      #else
+        #define NK_SIZE_TYPE unsigned int
+      #endif
+    #else
+      #define NK_SIZE_TYPE unsigned long
+    #endif
+  #endif
+  #ifndef NK_POINTER_TYPE
+    #if defined(_WIN64) && defined(_MSC_VER)
+      #define NK_POINTER_TYPE unsigned __int64
+    #elif (defined(_WIN32) || defined(WIN32)) && defined(_MSC_VER)
+      #define NK_POINTER_TYPE unsigned __int32
+    #elif defined(__GNUC__) || defined(__clang__)
+      #if defined(__x86_64__) || defined(__ppc64__)
+        #define NK_POINTER_TYPE unsigned long
+      #else
+        #define NK_POINTER_TYPE unsigned int
+      #endif
+    #else
+      #define NK_POINTER_TYPE unsigned long
+    #endif
+  #endif
+#endif
+
+typedef NK_INT8 nk_char;
+typedef NK_UINT8 nk_uchar;
+typedef NK_UINT8 nk_byte;
+typedef NK_INT16 nk_short;
+typedef NK_UINT16 nk_ushort;
+typedef NK_INT32 nk_int;
+typedef NK_UINT32 nk_uint;
+typedef NK_SIZE_TYPE nk_size;
+typedef NK_POINTER_TYPE nk_ptr;
+
+typedef nk_uint nk_hash;
+typedef nk_uint nk_flags;
+typedef nk_uint nk_rune;
+
+/* Make sure correct type size:
+ * This will fire with a negative subscript error if the type sizes
+ * are set incorrectly by the compiler, and compile out if not */
+NK_STATIC_ASSERT(sizeof(nk_short) == 2);
+NK_STATIC_ASSERT(sizeof(nk_ushort) == 2);
+NK_STATIC_ASSERT(sizeof(nk_uint) == 4);
+NK_STATIC_ASSERT(sizeof(nk_int) == 4);
+NK_STATIC_ASSERT(sizeof(nk_byte) == 1);
+NK_STATIC_ASSERT(sizeof(nk_flags) >= 4);
+NK_STATIC_ASSERT(sizeof(nk_rune) >= 4);
+NK_STATIC_ASSERT(sizeof(nk_size) >= sizeof(void*));
+NK_STATIC_ASSERT(sizeof(nk_ptr) >= sizeof(void*));
+
+/* ============================================================================
+ *
+ *                                  API
+ *
+ * =========================================================================== */
+struct nk_buffer;
+struct nk_allocator;
+struct nk_command_buffer;
+struct nk_draw_command;
+struct nk_convert_config;
+struct nk_style_item;
+struct nk_text_edit;
+struct nk_draw_list;
+struct nk_user_font;
+struct nk_panel;
+struct nk_context;
+struct nk_draw_vertex_layout_element;
+struct nk_style_button;
+struct nk_style_toggle;
+struct nk_style_selectable;
+struct nk_style_slide;
+struct nk_style_progress;
+struct nk_style_scrollbar;
+struct nk_style_edit;
+struct nk_style_property;
+struct nk_style_chart;
+struct nk_style_combo;
+struct nk_style_tab;
+struct nk_style_window_header;
+struct nk_style_window;
+
+enum {nk_false, nk_true};
+struct nk_color {nk_byte r,g,b,a;};
+struct nk_colorf {float r,g,b,a;};
+struct nk_vec2 {float x,y;};
+struct nk_vec2i {short x, y;};
+struct nk_rect {float x,y,w,h;};
+struct nk_recti {short x,y,w,h;};
+typedef char nk_glyph[NK_UTF_SIZE];
+typedef union {void *ptr; int id;} nk_handle;
+struct nk_image {nk_handle handle;unsigned short w,h;unsigned short region[4];};
+struct nk_cursor {struct nk_image img; struct nk_vec2 size, offset;};
+struct nk_scroll {nk_uint x, y;};
+
+enum nk_heading         {NK_UP, NK_RIGHT, NK_DOWN, NK_LEFT};
+enum nk_button_behavior {NK_BUTTON_DEFAULT, NK_BUTTON_REPEATER};
+enum nk_modify          {NK_FIXED = nk_false, NK_MODIFIABLE = nk_true};
+enum nk_orientation     {NK_VERTICAL, NK_HORIZONTAL};
+enum nk_collapse_states {NK_MINIMIZED = nk_false, NK_MAXIMIZED = nk_true};
+enum nk_show_states     {NK_HIDDEN = nk_false, NK_SHOWN = nk_true};
+enum nk_chart_type      {NK_CHART_LINES, NK_CHART_COLUMN, NK_CHART_MAX};
+enum nk_chart_event     {NK_CHART_HOVERING = 0x01, NK_CHART_CLICKED = 0x02};
+enum nk_color_format    {NK_RGB, NK_RGBA};
+enum nk_popup_type      {NK_POPUP_STATIC, NK_POPUP_DYNAMIC};
+enum nk_layout_format   {NK_DYNAMIC, NK_STATIC};
+enum nk_tree_type       {NK_TREE_NODE, NK_TREE_TAB};
+
+typedef void*(*nk_plugin_alloc)(nk_handle, void *old, nk_size);
+typedef void (*nk_plugin_free)(nk_handle, void *old);
+typedef int(*nk_plugin_filter)(const struct nk_text_edit*, nk_rune unicode);
+typedef void(*nk_plugin_paste)(nk_handle, struct nk_text_edit*);
+typedef void(*nk_plugin_copy)(nk_handle, const char*, int len);
+
+struct nk_allocator {
+    nk_handle userdata;
+    nk_plugin_alloc alloc;
+    nk_plugin_free free;
+};
+enum nk_symbol_type {
+    NK_SYMBOL_NONE,
+    NK_SYMBOL_X,
+    NK_SYMBOL_UNDERSCORE,
+    NK_SYMBOL_CIRCLE_SOLID,
+    NK_SYMBOL_CIRCLE_OUTLINE,
+    NK_SYMBOL_RECT_SOLID,
+    NK_SYMBOL_RECT_OUTLINE,
+    NK_SYMBOL_TRIANGLE_UP,
+    NK_SYMBOL_TRIANGLE_DOWN,
+    NK_SYMBOL_TRIANGLE_LEFT,
+    NK_SYMBOL_TRIANGLE_RIGHT,
+    NK_SYMBOL_PLUS,
+    NK_SYMBOL_MINUS,
+    NK_SYMBOL_MAX
+};
+/* =============================================================================
+ *
+ *                                  CONTEXT
+ *
+ * =============================================================================*/
+/*  Contexts are the main entry point and the majestro of nuklear and contain all required state.
+ *  They are used for window, memory, input, style, stack, commands and time management and need
+ *  to be passed into all nuklear GUI specific functions.
+ *
+ *  Usage
+ *  -------------------
+ *  To use a context it first has to be initialized which can be achieved by calling
+ *  one of either `nk_init_default`, `nk_init_fixed`, `nk_init`, `nk_init_custom`.
+ *  Each takes in a font handle and a specific way of handling memory. Memory control
+ *  hereby ranges from standard library to just specifying a fixed sized block of memory
+ *  which nuklear has to manage itself from.
+ *
+ *      struct nk_context ctx;
+ *      nk_init_xxx(&ctx, ...);
+ *      while (1) {
+ *          [...]
+ *          nk_clear(&ctx);
+ *      }
+ *      nk_free(&ctx);
+ *
+ *  Reference
+ *  -------------------
+ *  nk_init_default     - Initializes context with standard library memory allocation (malloc,free)
+ *  nk_init_fixed       - Initializes context from single fixed size memory block
+ *  nk_init             - Initializes context with memory allocator callbacks for alloc and free
+ *  nk_init_custom      - Initializes context from two buffers. One for draw commands the other for window/panel/table allocations
+ *  nk_clear            - Called at the end of the frame to reset and prepare the context for the next frame
+ *  nk_free             - Shutdown and free all memory allocated inside the context
+ *  nk_set_user_data    - Utility function to pass user data to draw command
+ */
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+/*  nk_init_default - Initializes a `nk_context` struct with a default standard library allocator.
+ *  Should be used if you don't want to be bothered with memory management in nuklear.
+ *  Parameters:
+ *      @ctx must point to an either stack or heap allocated `nk_context` struct
+ *      @font must point to a previously initialized font handle for more info look at font documentation
+ *  Return values:
+ *      true(1) on success
+ *      false(0) on failure */
+NK_API int nk_init_default(struct nk_context*, const struct nk_user_font*);
+#endif
+/*  nk_init_fixed - Initializes a `nk_context` struct from a single fixed size memory block
+ *  Should be used if you want complete control over nuklear's memory management.
+ *  Especially recommended for system with little memory or systems with virtual memory.
+ *  For the later case you can just allocate for example 16MB of virtual memory
+ *  and only the required amount of memory will actually be committed.
+ *  IMPORTANT: make sure the passed memory block is aligned correctly for `nk_draw_commands`
+ *  Parameters:
+ *      @ctx must point to an either stack or heap allocated `nk_context` struct
+ *      @memory must point to a previously allocated memory block
+ *      @size must contain the total size of @memory
+ *      @font must point to a previously initialized font handle for more info look at font documentation
+ *  Return values:
+ *      true(1) on success
+ *      false(0) on failure */
+NK_API int nk_init_fixed(struct nk_context*, void *memory, nk_size size, const struct nk_user_font*);
+/*  nk_init - Initializes a `nk_context` struct with memory allocation callbacks for nuklear to allocate
+ *  memory from. Used internally for `nk_init_default` and provides a kitchen sink allocation
+ *  interface to nuklear. Can be useful for cases like monitoring memory consumption.
+ *  Parameters:
+ *      @ctx must point to an either stack or heap allocated `nk_context` struct
+ *      @alloc must point to a previously allocated memory allocator
+ *      @font must point to a previously initialized font handle for more info look at font documentation
+ *  Return values:
+ *      true(1) on success
+ *      false(0) on failure */
+NK_API int nk_init(struct nk_context*, struct nk_allocator*, const struct nk_user_font*);
+/*  nk_init_custom - Initializes a `nk_context` struct from two different either fixed or growing
+ *  buffers. The first buffer is for allocating draw commands while the second buffer is
+ *  used for allocating windows, panels and state tables.
+ *  Parameters:
+ *      @ctx must point to an either stack or heap allocated `nk_context` struct
+ *      @cmds must point to a previously initialized memory buffer either fixed or dynamic to store draw commands into
+ *      @pool must point to a previously initialized memory buffer either fixed or dynamic to store windows, panels and tables
+ *      @font must point to a previously initialized font handle for more info look at font documentation
+ *  Return values:
+ *      true(1) on success
+ *      false(0) on failure */
+NK_API int nk_init_custom(struct nk_context*, struct nk_buffer *cmds, struct nk_buffer *pool, const struct nk_user_font*);
+/*  nk_clear - Resets the context state at the end of the frame. This includes mostly
+ *  garbage collector tasks like removing windows or table not called and therefore
+ *  used anymore.
+ *  Parameters:
+ *      @ctx must point to a previously initialized `nk_context` struct */
+NK_API void nk_clear(struct nk_context*);
+/*  nk_free - Frees all memory allocated by nuklear. Not needed if context was
+ *  initialized with `nk_init_fixed`.
+ *  Parameters:
+ *      @ctx must point to a previously initialized `nk_context` struct */
+NK_API void nk_free(struct nk_context*);
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+/*  nk_set_user_data - Sets the currently passed userdata passed down into each draw command.
+ *  Parameters:
+ *      @ctx must point to a previously initialized `nk_context` struct
+ *      @data handle with either pointer or index to be passed into every draw commands */
+NK_API void nk_set_user_data(struct nk_context*, nk_handle handle);
+#endif
+/* =============================================================================
+ *
+ *                                  INPUT
+ *
+ * =============================================================================*/
+/*  The input API is responsible for holding the current input state composed of
+ *  mouse, key and text input states.
+ *  It is worth noting that no direct os or window handling is done in nuklear.
+ *  Instead all input state has to be provided by platform specific code. This in one hand
+ *  expects more work from the user and complicates usage but on the other hand
+ *  provides simple abstraction over a big number of platforms, libraries and other
+ *  already provided functionality.
+ *
+ *  Usage
+ *  -------------------
+ *  Input state needs to be provided to nuklear by first calling `nk_input_begin`
+ *  which resets internal state like delta mouse position and button transistions.
+ *  After `nk_input_begin` all current input state needs to be provided. This includes
+ *  mouse motion, button and key pressed and released, text input and scrolling.
+ *  Both event- or state-based input handling are supported by this API
+ *  and should work without problems. Finally after all input state has been
+ *  mirrored `nk_input_end` needs to be called to finish input process.
+ *
+ *      struct nk_context ctx;
+ *      nk_init_xxx(&ctx, ...);
+ *      while (1) {
+ *          Event evt;
+ *          nk_input_begin(&ctx);
+ *          while (GetEvent(&evt)) {
+ *              if (evt.type == MOUSE_MOVE)
+ *                  nk_input_motion(&ctx, evt.motion.x, evt.motion.y);
+ *              else if (evt.type == ...) {
+ *                  ...
+ *              }
+ *          }
+ *          nk_input_end(&ctx);
+ *          [...]
+ *          nk_clear(&ctx);
+ *      }
+ *      nk_free(&ctx);
+ *
+ *  Reference
+ *  -------------------
+ *  nk_input_begin      - Begins the input mirroring process. Needs to be called before all other `nk_input_xxx` calls
+ *  nk_input_motion     - Mirrors mouse cursor position
+ *  nk_input_key        - Mirrors key state with either pressed or released
+ *  nk_input_button     - Mirrors mouse button state with either pressed or released
+ *  nk_input_scroll     - Mirrors mouse scroll values
+ *  nk_input_char       - Adds a single ASCII text character into an internal text buffer
+ *  nk_input_glyph      - Adds a single multi-byte UTF-8 character into an internal text buffer
+ *  nk_input_unicode    - Adds a single unicode rune into an internal text buffer
+ *  nk_input_end        - Ends the input mirroring process by calculating state changes. Don't call any `nk_input_xxx` function referenced above after this call
+ */
+enum nk_keys {
+    NK_KEY_NONE,
+    NK_KEY_SHIFT,
+    NK_KEY_CTRL,
+    NK_KEY_DEL,
+    NK_KEY_ENTER,
+    NK_KEY_TAB,
+    NK_KEY_BACKSPACE,
+    NK_KEY_COPY,
+    NK_KEY_CUT,
+    NK_KEY_PASTE,
+    NK_KEY_UP,
+    NK_KEY_DOWN,
+    NK_KEY_LEFT,
+    NK_KEY_RIGHT,
+    /* Shortcuts: text field */
+    NK_KEY_TEXT_INSERT_MODE,
+    NK_KEY_TEXT_REPLACE_MODE,
+    NK_KEY_TEXT_RESET_MODE,
+    NK_KEY_TEXT_LINE_START,
+    NK_KEY_TEXT_LINE_END,
+    NK_KEY_TEXT_START,
+    NK_KEY_TEXT_END,
+    NK_KEY_TEXT_UNDO,
+    NK_KEY_TEXT_REDO,
+    NK_KEY_TEXT_SELECT_ALL,
+    NK_KEY_TEXT_WORD_LEFT,
+    NK_KEY_TEXT_WORD_RIGHT,
+    /* Shortcuts: scrollbar */
+    NK_KEY_SCROLL_START,
+    NK_KEY_SCROLL_END,
+    NK_KEY_SCROLL_DOWN,
+    NK_KEY_SCROLL_UP,
+    NK_KEY_MAX
+};
+enum nk_buttons {
+    NK_BUTTON_LEFT,
+    NK_BUTTON_MIDDLE,
+    NK_BUTTON_RIGHT,
+    NK_BUTTON_DOUBLE,
+    NK_BUTTON_MAX
+};
+/*  nk_input_begin - Begins the input mirroring process by resetting text, scroll
+ *  mouse previous mouse position and movement as well as key state transitions,
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct */
+NK_API void nk_input_begin(struct nk_context*);
+/*  nk_input_motion - Mirrors current mouse position to nuklear
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @x must contain an integer describing the current mouse cursor x-position
+ *      @y must contain an integer describing the current mouse cursor y-position */
+NK_API void nk_input_motion(struct nk_context*, int x, int y);
+/*  nk_input_key - Mirrors state of a specific key to nuklear
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @key must be any value specified in enum `nk_keys` that needs to be mirrored
+ *      @down must be 0 for key is up and 1 for key is down */
+NK_API void nk_input_key(struct nk_context*, enum nk_keys, int down);
+/*  nk_input_button - Mirrors the state of a specific mouse button to nuklear
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @nk_buttons must be any value specified in enum `nk_buttons` that needs to be mirrored
+ *      @x must contain an integer describing mouse cursor x-position on click up/down
+ *      @y must contain an integer describing mouse cursor y-position on click up/down
+ *      @down must be 0 for key is up and 1 for key is down */
+NK_API void nk_input_button(struct nk_context*, enum nk_buttons, int x, int y, int down);
+/*  nk_input_scroll - Copies the last mouse scroll value to nuklear. Is generally
+ *  a  scroll value. So does not have to come from mouse and could also originate
+ *  from touch for example.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @val vector with both X- as well as Y-scroll value */
+NK_API void nk_input_scroll(struct nk_context*, struct nk_vec2 val);
+/*  nk_input_char - Copies a single ASCII character into an internal text buffer
+ *  This is basically a helper function to quickly push ASCII characters into
+ *  nuklear. Note that you can only push up to NK_INPUT_MAX bytes into
+ *  struct `nk_input` between `nk_input_begin` and `nk_input_end`.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @c must be a single ASCII character preferable one that can be printed */
+NK_API void nk_input_char(struct nk_context*, char);
+/*  nk_input_unicode - Converts a encoded unicode rune into UTF-8 and copies the result
+ *  into an internal text buffer.
+ *  Note that you can only push up to NK_INPUT_MAX bytes into
+ *  struct `nk_input` between `nk_input_begin` and `nk_input_end`.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @glyph UTF-32 unicode codepoint */
+NK_API void nk_input_glyph(struct nk_context*, const nk_glyph);
+/*  nk_input_unicode - Converts a unicode rune into UTF-8 and copies the result
+ *  into an internal text buffer.
+ *  Note that you can only push up to NK_INPUT_MAX bytes into
+ *  struct `nk_input` between `nk_input_begin` and `nk_input_end`.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @glyph UTF-32 unicode codepoint */
+NK_API void nk_input_unicode(struct nk_context*, nk_rune);
+/*  nk_input_end - End the input mirroring process by resetting mouse grabbing
+ *  state to ensure the mouse cursor is not grabbed indefinitely.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct */
+NK_API void nk_input_end(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  DRAWING
+ *
+ * =============================================================================*/
+/*  This library was designed to be render backend agnostic so it does
+ *  not draw anything to screen directly. Instead all drawn shapes, widgets
+ *  are made of, are buffered into memory and make up a command queue.
+ *  Each frame therefore fills the command buffer with draw commands
+ *  that then need to be executed by the user and his own render backend.
+ *  After that the command buffer needs to be cleared and a new frame can be
+ *  started. It is probably important to note that the command buffer is the main
+ *  drawing API and the optional vertex buffer API only takes this format and
+ *  converts it into a hardware accessible format.
+ *
+ *  Usage
+ *  -------------------
+ *  To draw all draw commands accumulated over a frame you need your own render
+ *  backend able to draw a number of 2D primitives. This includes at least
+ *  filled and stroked rectangles, circles, text, lines, triangles and scissors.
+ *  As soon as this criterion is met you can iterate over each draw command
+ *  and execute each draw command in a interpreter like fashion:
+ *
+ *      const struct nk_command *cmd = 0;
+ *      nk_foreach(cmd, &ctx) {
+ *      switch (cmd->type) {
+ *      case NK_COMMAND_LINE:
+ *          your_draw_line_function(...)
+ *          break;
+ *      case NK_COMMAND_RECT
+ *          your_draw_rect_function(...)
+ *          break;
+ *      case ...:
+ *          [...]
+ *      }
+ *
+ *  In program flow context draw commands need to be executed after input has been
+ *  gathered and the complete UI with windows and their contained widgets have
+ *  been executed and before calling `nk_clear` which frees all previously
+ *  allocated draw commands.
+ *
+ *      struct nk_context ctx;
+ *      nk_init_xxx(&ctx, ...);
+ *      while (1) {
+ *          Event evt;
+ *          nk_input_begin(&ctx);
+ *          while (GetEvent(&evt)) {
+ *              if (evt.type == MOUSE_MOVE)
+ *                  nk_input_motion(&ctx, evt.motion.x, evt.motion.y);
+ *              else if (evt.type == [...]) {
+ *                  [...]
+ *              }
+ *          }
+ *          nk_input_end(&ctx);
+ *
+ *          [...]
+ *
+ *          const struct nk_command *cmd = 0;
+ *          nk_foreach(cmd, &ctx) {
+ *          switch (cmd->type) {
+ *          case NK_COMMAND_LINE:
+ *              your_draw_line_function(...)
+ *              break;
+ *          case NK_COMMAND_RECT
+ *              your_draw_rect_function(...)
+ *              break;
+ *          case ...:
+ *              [...]
+ *          }
+ *          nk_clear(&ctx);
+ *      }
+ *      nk_free(&ctx);
+ *
+ *  You probably noticed that you have to draw all of the UI each frame which is
+ *  quite wasteful. While the actual UI updating loop is quite fast rendering
+ *  without actually needing it is not. So there are multiple things you could do.
+ *
+ *  First is only update on input. This of course is only an option if your
+ *  application only depends on the UI and does not require any outside calculations.
+ *  If you actually only update on input make sure to update the UI two times each
+ *  frame and call `nk_clear` directly after the first pass and only draw in
+ *  the second pass. In addition it is recommended to also add additional timers
+ *  to make sure the UI is not drawn more than a fixed number of frames per second.
+ *
+ *      struct nk_context ctx;
+ *      nk_init_xxx(&ctx, ...);
+ *      while (1) {
+ *          [...wait for input ]
+ *
+ *          [...do two UI passes ...]
+ *          do_ui(...)
+ *          nk_clear(&ctx);
+ *          do_ui(...)
+ *
+ *          const struct nk_command *cmd = 0;
+ *          nk_foreach(cmd, &ctx) {
+ *          switch (cmd->type) {
+ *          case NK_COMMAND_LINE:
+ *              your_draw_line_function(...)
+ *              break;
+ *          case NK_COMMAND_RECT
+ *              your_draw_rect_function(...)
+ *              break;
+ *          case ...:
+ *              [...]
+ *          }
+ *          nk_clear(&ctx);
+ *      }
+ *      nk_free(&ctx);
+ *
+ *  The second probably more applicable trick is to only draw if anything changed.
+ *  It is not really useful for applications with continuous draw loop but
+ *  quite useful for desktop applications. To actually get nuklear to only
+ *  draw on changes you first have to define `NK_ZERO_COMMAND_MEMORY` and
+ *  allocate a memory buffer that will store each unique drawing output.
+ *  After each frame you compare the draw command memory inside the library
+ *  with your allocated buffer by memcmp. If memcmp detects differences
+ *  you have to copy the command buffer into the allocated buffer
+ *  and then draw like usual (this example uses fixed memory but you could
+ *  use dynamically allocated memory).
+ *
+ *      [... other defines ...]
+ *      #define NK_ZERO_COMMAND_MEMORY
+ *      #include "nuklear.h"
+ *
+ *      struct nk_context ctx;
+ *      void *last = calloc(1,64*1024);
+ *      void *buf = calloc(1,64*1024);
+ *      nk_init_fixed(&ctx, buf, 64*1024);
+ *      while (1) {
+ *          [...input...]
+ *          [...ui...]
+ *
+ *          void *cmds = nk_buffer_memory(&ctx.memory);
+ *          if (memcmp(cmds, last, ctx.memory.allocated)) {
+ *              memcpy(last,cmds,ctx.memory.allocated);
+ *              const struct nk_command *cmd = 0;
+ *              nk_foreach(cmd, &ctx) {
+ *                  switch (cmd->type) {
+ *                  case NK_COMMAND_LINE:
+ *                      your_draw_line_function(...)
+ *                      break;
+ *                  case NK_COMMAND_RECT
+ *                      your_draw_rect_function(...)
+ *                      break;
+ *                  case ...:
+ *                      [...]
+ *                  }
+ *              }
+ *          }
+ *          nk_clear(&ctx);
+ *      }
+ *      nk_free(&ctx);
+ *
+ *  Finally while using draw commands makes sense for higher abstracted platforms like
+ *  X11 and Win32 or drawing libraries it is often desirable to use graphics
+ *  hardware directly. Therefore it is possible to just define
+ *  `NK_INCLUDE_VERTEX_BUFFER_OUTPUT` which includes optional vertex output.
+ *  To access the vertex output you first have to convert all draw commands into
+ *  vertexes by calling `nk_convert` which takes in your preferred vertex format.
+ *  After successfully converting all draw commands just iterate over and execute all
+ *  vertex draw commands:
+ *
+ *      struct nk_convert_config cfg = {};
+ *      static const struct nk_draw_vertex_layout_element vertex_layout[] = {
+ *          {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct your_vertex, pos)},
+ *          {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct your_vertex, uv)},
+ *          {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct your_vertex, col)},
+ *          {NK_VERTEX_LAYOUT_END}
+ *      };
+ *      cfg.shape_AA = NK_ANTI_ALIASING_ON;
+ *      cfg.line_AA = NK_ANTI_ALIASING_ON;
+ *      cfg.vertex_layout = vertex_layout;
+ *      cfg.vertex_size = sizeof(struct your_vertex);
+ *      cfg.vertex_alignment = NK_ALIGNOF(struct your_vertex);
+ *      cfg.circle_segment_count = 22;
+ *      cfg.curve_segment_count = 22;
+ *      cfg.arc_segment_count = 22;
+ *      cfg.global_alpha = 1.0f;
+ *      cfg.null = dev->null;
+ *
+ *      struct nk_buffer cmds, verts, idx;
+ *      nk_buffer_init_default(&cmds);
+ *      nk_buffer_init_default(&verts);
+ *      nk_buffer_init_default(&idx);
+ *      nk_convert(&ctx, &cmds, &verts, &idx, &cfg);
+ *      nk_draw_foreach(cmd, &ctx, &cmds) {
+ *          if (!cmd->elem_count) continue;
+ *          [...]
+ *      }
+ *      nk_buffer_free(&cms);
+ *      nk_buffer_free(&verts);
+ *      nk_buffer_free(&idx);
+ *
+ *  Reference
+ *  -------------------
+ *  nk__begin           - Returns the first draw command in the context draw command list to be drawn
+ *  nk__next            - Increments the draw command iterator to the next command inside the context draw command list
+ *  nk_foreach          - Iterates over each draw command inside the context draw command list
+ *
+ *  nk_convert          - Converts from the abstract draw commands list into a hardware accessible vertex format
+ *  nk__draw_begin      - Returns the first vertex command in the context vertex draw list to be executed
+ *  nk__draw_next       - Increments the vertex command iterator to the next command inside the context vertex command list
+ *  nk__draw_end        - Returns the end of the vertex draw list
+ *  nk_draw_foreach     - Iterates over each vertex draw command inside the vertex draw list
+ */
+enum nk_anti_aliasing {NK_ANTI_ALIASING_OFF, NK_ANTI_ALIASING_ON};
+enum nk_convert_result {
+    NK_CONVERT_SUCCESS = 0,
+    NK_CONVERT_INVALID_PARAM = 1,
+    NK_CONVERT_COMMAND_BUFFER_FULL = NK_FLAG(1),
+    NK_CONVERT_VERTEX_BUFFER_FULL = NK_FLAG(2),
+    NK_CONVERT_ELEMENT_BUFFER_FULL = NK_FLAG(3)
+};
+struct nk_draw_null_texture {
+    nk_handle texture; /* texture handle to a texture with a white pixel */
+    struct nk_vec2 uv; /* coordinates to a white pixel in the texture  */
+};
+struct nk_convert_config {
+    float global_alpha; /* global alpha value */
+    enum nk_anti_aliasing line_AA; /* line anti-aliasing flag can be turned off if you are tight on memory */
+    enum nk_anti_aliasing shape_AA; /* shape anti-aliasing flag can be turned off if you are tight on memory */
+    unsigned circle_segment_count; /* number of segments used for circles: default to 22 */
+    unsigned arc_segment_count; /* number of segments used for arcs: default to 22 */
+    unsigned curve_segment_count; /* number of segments used for curves: default to 22 */
+    struct nk_draw_null_texture null; /* handle to texture with a white pixel for shape drawing */
+    const struct nk_draw_vertex_layout_element *vertex_layout; /* describes the vertex output format and packing */
+    nk_size vertex_size; /* sizeof one vertex for vertex packing */
+    nk_size vertex_alignment; /* vertex alignment: Can be obtained by NK_ALIGNOF */
+};
+/*  nk__begin - Returns a draw command list iterator to iterate all draw
+ *  commands accumulated over one frame.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct at the end of a frame
+ *  Return values:
+ *      draw command pointer pointing to the first command inside the draw command list  */
+NK_API const struct nk_command* nk__begin(struct nk_context*);
+/*  nk__next - Returns a draw command list iterator to iterate all draw
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct at the end of a frame
+ *      @cmd must point to an previously a draw command either returned by `nk__begin` or `nk__next`
+ *  Return values:
+ *      draw command pointer pointing to the next command inside the draw command list  */
+NK_API const struct nk_command* nk__next(struct nk_context*, const struct nk_command*);
+/*  nk_foreach - Iterates over each draw command inside the context draw command list
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct at the end of a frame
+ *      @cmd pointer initialized to NULL */
+#define nk_foreach(c, ctx) for((c) = nk__begin(ctx); (c) != 0; (c) = nk__next(ctx,c))
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+/*  nk_convert - converts all internal draw command into vertex draw commands and fills
+ *  three buffers with vertexes, vertex draw commands and vertex indices. The vertex format
+ *  as well as some other configuration values have to be configured by filling out a
+ *  `nk_convert_config` struct.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct at the end of a frame
+ *      @cmds must point to a previously initialized buffer to hold converted vertex draw commands
+ *      @vertices must point to a previously initialized buffer to hold all produced vertices
+ *      @elements must point to a previously initialized buffer to hold all produced vertex indices
+ *      @config must point to a filled out `nk_config` struct to configure the conversion process
+ *  Returns:
+ *      returns NK_CONVERT_SUCCESS on success and a enum nk_convert_result error values if not */
+NK_API nk_flags nk_convert(struct nk_context*, struct nk_buffer *cmds, struct nk_buffer *vertices, struct nk_buffer *elements, const struct nk_convert_config*);
+/*  nk__draw_begin - Returns a draw vertex command buffer iterator to iterate each the vertex draw command buffer
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct at the end of a frame
+ *      @buf must point to an previously by `nk_convert` filled out vertex draw command buffer
+ *  Return values:
+ *      vertex draw command pointer pointing to the first command inside the vertex draw command buffer  */
+NK_API const struct nk_draw_command* nk__draw_begin(const struct nk_context*, const struct nk_buffer*);
+/*  nk__draw_end - Returns the vertex draw command  at the end of the vertex draw command buffer
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct at the end of a frame
+ *      @buf must point to an previously by `nk_convert` filled out vertex draw command buffer
+ *  Return values:
+ *      vertex draw command pointer pointing to the end of the last vertex draw command inside the vertex draw command buffer  */
+NK_API const struct nk_draw_command* nk__draw_end(const struct nk_context*, const struct nk_buffer*);
+/*  nk__draw_next - Increments the vertex draw command buffer iterator
+ *  Parameters:
+ *      @cmd must point to an previously either by `nk__draw_begin` or `nk__draw_next` returned vertex draw command
+ *      @buf must point to an previously by `nk_convert` filled out vertex draw command buffer
+ *      @ctx must point to an previously initialized `nk_context` struct at the end of a frame
+ *  Return values:
+ *      vertex draw command pointer pointing to the end of the last vertex draw command inside the vertex draw command buffer  */
+NK_API const struct nk_draw_command* nk__draw_next(const struct nk_draw_command*, const struct nk_buffer*, const struct nk_context*);
+/*  nk_draw_foreach - Iterates over each vertex draw command inside a vertex draw command buffer
+ *  Parameters:
+ *      @cmd nk_draw_command pointer set to NULL
+ *      @buf must point to an previously by `nk_convert` filled out vertex draw command buffer
+ *      @ctx must point to an previously initialized `nk_context` struct at the end of a frame */
+#define nk_draw_foreach(cmd,ctx, b) for((cmd)=nk__draw_begin(ctx, b); (cmd)!=0; (cmd)=nk__draw_next(cmd, b, ctx))
+#endif
+/* =============================================================================
+ *
+ *                                  WINDOW
+ *
+ * =============================================================================
+ * Windows are the main persistent state used inside nuklear and are life time
+ * controlled by simply "retouching" (i.e. calling) each window each frame.
+ * All widgets inside nuklear can only be added inside function pair `nk_begin_xxx`
+ * and `nk_end`. Calling any widgets outside these two functions will result in an
+ * assert in debug or no state change in release mode.
+ *
+ * Each window holds frame persistent state like position, size, flags, state tables,
+ * and some garbage collected internal persistent widget state. Each window
+ * is linked into a window stack list which determines the drawing and overlapping
+ * order. The topmost window thereby is the currently active window.
+ *
+ * To change window position inside the stack occurs either automatically by
+ * user input by being clicked on or programmatically by calling `nk_window_focus`.
+ * Windows by default are visible unless explicitly being defined with flag
+ * `NK_WINDOW_HIDDEN`, the user clicked the close button on windows with flag
+ * `NK_WINDOW_CLOSABLE` or if a window was explicitly hidden by calling
+ * `nk_window_show`. To explicitly close and destroy a window call `nk_window_close`.
+ *
+ * Usage
+ * -------------------
+ * To create and keep a window you have to call one of the two `nk_begin_xxx`
+ * functions to start window declarations and `nk_end` at the end. Furthermore it
+ * is recommended to check the return value of `nk_begin_xxx` and only process
+ * widgets inside the window if the value is not 0. Either way you have to call
+ * `nk_end` at the end of window declarations. Furthermore, do not attempt to
+ * nest `nk_begin_xxx` calls which will hopefully result in an assert or if not
+ * in a segmentation fault.
+ *
+ *      if (nk_begin_xxx(...) {
+ *          [... widgets ...]
+ *      }
+ *      nk_end(ctx);
+ *
+ * In the grand concept window and widget declarations need to occur after input
+ * handling and before drawing to screen. Not doing so can result in higher
+ * latency or at worst invalid behavior. Furthermore make sure that `nk_clear`
+ * is called at the end of the frame. While nuklear's default platform backends
+ * already call `nk_clear` for you if you write your own backend not calling
+ * `nk_clear` can cause asserts or even worse undefined behavior.
+ *
+ *      struct nk_context ctx;
+ *      nk_init_xxx(&ctx, ...);
+ *      while (1) {
+ *          Event evt;
+ *          nk_input_begin(&ctx);
+ *          while (GetEvent(&evt)) {
+ *              if (evt.type == MOUSE_MOVE)
+ *                  nk_input_motion(&ctx, evt.motion.x, evt.motion.y);
+ *              else if (evt.type == [...]) {
+ *                  nk_input_xxx(...);
+ *              }
+ *          }
+ *          nk_input_end(&ctx);
+ *
+ *          if (nk_begin_xxx(...) {
+ *              [...]
+ *          }
+ *          nk_end(ctx);
+ *
+ *          const struct nk_command *cmd = 0;
+ *          nk_foreach(cmd, &ctx) {
+ *          case NK_COMMAND_LINE:
+ *              your_draw_line_function(...)
+ *              break;
+ *          case NK_COMMAND_RECT
+ *              your_draw_rect_function(...)
+ *              break;
+ *          case ...:
+ *              [...]
+ *          }
+ *          nk_clear(&ctx);
+ *      }
+ *      nk_free(&ctx);
+ *
+ *  Reference
+ *  -------------------
+ *  nk_begin                            - starts a new window; needs to be called every frame for every window (unless hidden) or otherwise the window gets removed
+ *  nk_begin_titled                     - extended window start with separated title and identifier to allow multiple windows with same name but not title
+ *  nk_end                              - needs to be called at the end of the window building process to process scaling, scrollbars and general cleanup
+ *
+ *  nk_window_find                      - finds and returns the window with give name
+ *  nk_window_get_bounds                - returns a rectangle with screen position and size of the currently processed window.
+ *  nk_window_get_position              - returns the position of the currently processed window
+ *  nk_window_get_size                  - returns the size with width and height of the currently processed window
+ *  nk_window_get_width                 - returns the width of the currently processed window
+ *  nk_window_get_height                - returns the height of the currently processed window
+ *  nk_window_get_panel                 - returns the underlying panel which contains all processing state of the current window
+ *  nk_window_get_content_region        - returns the position and size of the currently visible and non-clipped space inside the currently processed window
+ *  nk_window_get_content_region_min    - returns the upper rectangle position of the currently visible and non-clipped space inside the currently processed window
+ *  nk_window_get_content_region_max    - returns the upper rectangle position of the currently visible and non-clipped space inside the currently processed window
+ *  nk_window_get_content_region_size   - returns the size of the currently visible and non-clipped space inside the currently processed window
+ *  nk_window_get_canvas                - returns the draw command buffer. Can be used to draw custom widgets
+ *
+ *  nk_window_has_focus                 - returns if the currently processed window is currently active
+ *  nk_window_is_collapsed              - returns if the window with given name is currently minimized/collapsed
+ *  nk_window_is_closed                 - returns if the currently processed window was closed
+ *  nk_window_is_hidden                 - returns if the currently processed window was hidden
+ *  nk_window_is_active                 - same as nk_window_has_focus for some reason
+ *  nk_window_is_hovered                - returns if the currently processed window is currently being hovered by mouse
+ *  nk_window_is_any_hovered            - return if any window currently hovered
+ *  nk_item_is_any_active               - returns if any window or widgets is currently hovered or active
+ *
+ *  nk_window_set_bounds                - updates position and size of the currently processed window
+ *  nk_window_set_position              - updates position of the currently process window
+ *  nk_window_set_size                  - updates the size of the currently processed window
+ *  nk_window_set_focus                 - set the currently processed window as active window
+ *
+ *  nk_window_close                     - closes the window with given window name which deletes the window at the end of the frame
+ *  nk_window_collapse                  - collapses the window with given window name
+ *  nk_window_collapse_if               - collapses the window with given window name if the given condition was met
+ *  nk_window_show                      - hides a visible or reshows a hidden window
+ *  nk_window_show_if                   - hides/shows a window depending on condition
+ */
+enum nk_panel_flags {
+    NK_WINDOW_BORDER            = NK_FLAG(0), /* Draws a border around the window to visually separate window from the background */
+    NK_WINDOW_MOVABLE           = NK_FLAG(1), /* The movable flag indicates that a window can be moved by user input or by dragging the window header */
+    NK_WINDOW_SCALABLE          = NK_FLAG(2), /* The scalable flag indicates that a window can be scaled by user input by dragging a scaler icon at the button of the window */
+    NK_WINDOW_CLOSABLE          = NK_FLAG(3), /* adds a closable icon into the header */
+    NK_WINDOW_MINIMIZABLE       = NK_FLAG(4), /* adds a minimize icon into the header */
+    NK_WINDOW_NO_SCROLLBAR      = NK_FLAG(5), /* Removes the scrollbar from the window */
+    NK_WINDOW_TITLE             = NK_FLAG(6), /* Forces a header at the top at the window showing the title */
+    NK_WINDOW_SCROLL_AUTO_HIDE  = NK_FLAG(7), /* Automatically hides the window scrollbar if no user interaction: also requires delta time in `nk_context` to be set each frame */
+    NK_WINDOW_BACKGROUND        = NK_FLAG(8), /* Always keep window in the background */
+    NK_WINDOW_SCALE_LEFT        = NK_FLAG(9), /* Puts window scaler in the left-ottom corner instead right-bottom*/
+    NK_WINDOW_NO_INPUT          = NK_FLAG(10) /* Prevents window of scaling, moving or getting focus */
+};
+/*  nk_begin - starts a new window; needs to be called every frame for every window (unless hidden) or otherwise the window gets removed
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @title window title and identifier. Needs to be persistent over frames to identify the window
+ *      @bounds initial position and window size. However if you do not define `NK_WINDOW_SCALABLE` or `NK_WINDOW_MOVABLE` you can set window position and size every frame
+ *      @flags window flags defined in `enum nk_panel_flags` with a number of different window behaviors
+ *  Return values:
+ *      returns 1 if the window can be filled up with widgets from this point until `nk_end or 0 otherwise for example if minimized `*/
+NK_API int nk_begin(struct nk_context *ctx, const char *title, struct nk_rect bounds, nk_flags flags);
+/*  nk_begin_titled - extended window start with separated title and identifier to allow multiple windows with same name but not title
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name window identifier. Needs to be persistent over frames to identify the window
+ *      @title window title displayed inside header if flag `NK_WINDOW_TITLE` or either `NK_WINDOW_CLOSABLE` or `NK_WINDOW_MINIMIZED` was set
+ *      @bounds initial position and window size. However if you do not define `NK_WINDOW_SCALABLE` or `NK_WINDOW_MOVABLE` you can set window position and size every frame
+ *      @flags window flags defined in `enum nk_panel_flags` with a number of different window behaviors
+ *  Return values:
+ *      returns 1 if the window can be filled up with widgets from this point until `nk_end or 0 otherwise `*/
+NK_API int nk_begin_titled(struct nk_context *ctx, const char *name, const char *title, struct nk_rect bounds, nk_flags flags);
+/*  nk_end - needs to be called at the end of the window building process to process scaling, scrollbars and general cleanup.
+ *  All widget calls after this functions will result in asserts or no state changes
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct */
+NK_API void nk_end(struct nk_context *ctx);
+/*  nk_window_find - finds and returns the window with give name
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name window identifier
+ *  Return values:
+ *      returns a `nk_window` struct pointing to the identified window or 0 if no window with given name was found */
+NK_API struct nk_window *nk_window_find(struct nk_context *ctx, const char *name);
+/*  nk_window_get_bounds - returns a rectangle with screen position and size of the currently processed window.
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns a `nk_rect` struct with window upper left position and size */
+NK_API struct nk_rect nk_window_get_bounds(const struct nk_context *ctx);
+/*  nk_window_get_position - returns the position of the currently processed window.
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns a `nk_vec2` struct with window upper left position */
+NK_API struct nk_vec2 nk_window_get_position(const struct nk_context *ctx);
+/*  nk_window_get_size - returns the size with width and height of the currently processed window.
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns a `nk_vec2` struct with window size */
+NK_API struct nk_vec2 nk_window_get_size(const struct nk_context*);
+/*  nk_window_get_width - returns the width of the currently processed window.
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns the window width */
+NK_API float nk_window_get_width(const struct nk_context*);
+/*  nk_window_get_height - returns the height of the currently processed window.
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns the window height */
+NK_API float nk_window_get_height(const struct nk_context*);
+/*  nk_window_get_panel - returns the underlying panel which contains all processing state of the current window.
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns a pointer to window internal `nk_panel` state. DO NOT keep this pointer around it is only valid until `nk_end` */
+NK_API struct nk_panel* nk_window_get_panel(struct nk_context*);
+/*  nk_window_get_content_region - returns the position and size of the currently visible and non-clipped space inside the currently processed window.
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns `nk_rect` struct with screen position and size (no scrollbar offset) of the visible space inside the current window */
+NK_API struct nk_rect nk_window_get_content_region(struct nk_context*);
+/*  nk_window_get_content_region_min - returns the upper left position of the currently visible and non-clipped space inside the currently processed window.
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns `nk_vec2` struct with  upper left screen position (no scrollbar offset) of the visible space inside the current window */
+NK_API struct nk_vec2 nk_window_get_content_region_min(struct nk_context*);
+/*  nk_window_get_content_region_max - returns the lower right screen position of the currently visible and non-clipped space inside the currently processed window.
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns `nk_vec2` struct with lower right screen position (no scrollbar offset) of the visible space inside the current window */
+NK_API struct nk_vec2 nk_window_get_content_region_max(struct nk_context*);
+/*  nk_window_get_content_region_size - returns the size of the currently visible and non-clipped space inside the currently processed window
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns `nk_vec2` struct with size the visible space inside the current window */
+NK_API struct nk_vec2 nk_window_get_content_region_size(struct nk_context*);
+/*  nk_window_get_canvas - returns the draw command buffer. Can be used to draw custom widgets
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns a pointer to window internal `nk_command_buffer` struct used as drawing canvas. Can be used to do custom drawing */
+NK_API struct nk_command_buffer* nk_window_get_canvas(struct nk_context*);
+/*  nk_window_has_focus - returns if the currently processed window is currently active
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns 0 if current window is not active or 1 if it is */
+NK_API int nk_window_has_focus(const struct nk_context*);
+/*  nk_window_is_collapsed - returns if the window with given name is currently minimized/collapsed
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of window you want to check is collapsed
+ *  Return values:
+ *      returns 1 if current window is minimized and 0 if window not found or is not minimized */
+NK_API int nk_window_is_collapsed(struct nk_context *ctx, const char *name);
+/*  nk_window_is_closed - returns if the window with given name was closed by calling `nk_close`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of window you want to check is closed
+ *  Return values:
+ *      returns 1 if current window was closed or 0 window not found or not closed */
+NK_API int nk_window_is_closed(struct nk_context*, const char*);
+/*  nk_window_is_hidden - returns if the window with given name is hidden
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of window you want to check is hidden
+ *  Return values:
+ *      returns 1 if current window is hidden or 0 window not found or visible */
+NK_API int nk_window_is_hidden(struct nk_context*, const char*);
+/*  nk_window_is_active - same as nk_window_has_focus for some reason
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of window you want to check is hidden
+ *  Return values:
+ *      returns 1 if current window is active or 0 window not found or not active */
+NK_API int nk_window_is_active(struct nk_context*, const char*);
+/*  nk_window_is_hovered - return if the current window is being hovered
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns 1 if current window is hovered or 0 otherwise */
+NK_API int nk_window_is_hovered(struct nk_context*);
+/*  nk_window_is_any_hovered - returns if the any window is being hovered
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns 1 if any window is hovered or 0 otherwise */
+NK_API int nk_window_is_any_hovered(struct nk_context*);
+/*  nk_item_is_any_active - returns if the any window is being hovered or any widget is currently active.
+ *  Can be used to decide if input should be processed by UI or your specific input handling.
+ *  Example could be UI and 3D camera to move inside a 3D space.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *  Return values:
+ *      returns 1 if any window is hovered or any item is active or 0 otherwise */
+NK_API int nk_item_is_any_active(struct nk_context*);
+/*  nk_window_set_bounds - updates position and size of the currently processed window
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of the window to modify both position and size
+ *      @bounds points to a `nk_rect` struct with the new position and size of currently active window */
+NK_API void nk_window_set_bounds(struct nk_context*, const char *name, struct nk_rect bounds);
+/*  nk_window_set_position - updates position of the currently processed window
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of the window to modify position of
+ *      @pos points to a `nk_vec2` struct with the new position of currently active window */
+NK_API void nk_window_set_position(struct nk_context*, const char *name, struct nk_vec2 pos);
+/*  nk_window_set_size - updates size of the currently processed window
+ *  IMPORTANT: only call this function between calls `nk_begin_xxx` and `nk_end`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of the window to modify size of
+ *      @size points to a `nk_vec2` struct with the new size of currently active window */
+NK_API void nk_window_set_size(struct nk_context*, const char *name, struct nk_vec2);
+/*  nk_window_set_focus - sets the window with given name as active
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of the window to be set active */
+NK_API void nk_window_set_focus(struct nk_context*, const char *name);
+/*  nk_window_close - closed a window and marks it for being freed at the end of the frame
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of the window to be closed */
+NK_API void nk_window_close(struct nk_context *ctx, const char *name);
+/*  nk_window_collapse - updates collapse state of a window with given name
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of the window to be either collapse or maximize */
+NK_API void nk_window_collapse(struct nk_context*, const char *name, enum nk_collapse_states state);
+/*  nk_window_collapse - updates collapse state of a window with given name if given condition is met
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of the window to be either collapse or maximize
+ *      @state the window should be put into
+ *      @condition that has to be true to actually commit the collapse state change */
+NK_API void nk_window_collapse_if(struct nk_context*, const char *name, enum nk_collapse_states, int cond);
+/*  nk_window_show - updates visibility state of a window with given name
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of the window to be either collapse or maximize
+ *      @state with either visible or hidden to modify the window with */
+NK_API void nk_window_show(struct nk_context*, const char *name, enum nk_show_states);
+/*  nk_window_show_if - updates visibility state of a window with given name if a given condition is met
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @name of the window to be either collapse or maximize
+ *      @state with either visible or hidden to modify the window with
+ *      @condition that has to be true to actually commit the visible state change */
+NK_API void nk_window_show_if(struct nk_context*, const char *name, enum nk_show_states, int cond);
+/* =============================================================================
+ *
+ *                                  LAYOUT
+ *
+ * ============================================================================= */
+/*  Layouting in general describes placing widget inside a window with position and size.
+ *  While in this particular implementation there are five different APIs for layouting
+ *  each with different trade offs between control and ease of use.
+ *
+ *  All layouting methods in this library are based around the concept of a row.
+ *  A row has a height the window content grows by and a number of columns and each
+ *  layouting method specifies how each widget is placed inside the row.
+ *  After a row has been allocated by calling a layouting functions and then
+ *  filled with widgets will advance an internal pointer over the allocated row.
+ *
+ *  To actually define a layout you just call the appropriate layouting function
+ *  and each subsequent widget call will place the widget as specified. Important
+ *  here is that if you define more widgets then columns defined inside the layout
+ *  functions it will allocate the next row without you having to make another layouting
+ *  call.
+ *
+ *  Biggest limitation with using all these APIs outside the `nk_layout_space_xxx` API
+ *  is that you have to define the row height for each. However the row height
+ *  often depends on the height of the font.
+ *
+ *  To fix that internally nuklear uses a minimum row height that is set to the
+ *  height plus padding of currently active font and overwrites the row height
+ *  value if zero.
+ *
+ *  If you manually want to change the minimum row height then
+ *  use nk_layout_set_min_row_height, and use nk_layout_reset_min_row_height to
+ *  reset it back to be derived from font height.
+ *
+ *  Also if you change the font in nuklear it will automatically change the minimum
+ *  row height for you and. This means if you change the font but still want
+ *  a minimum row height smaller than the font you have to repush your value.
+ *
+ *  For actually more advanced UI I would even recommend using the `nk_layout_space_xxx`
+ *  layouting method in combination with a cassowary constraint solver (there are
+ *  some versions on github with permissive license model) to take over all control over widget
+ *  layouting yourself. However for quick and dirty layouting using all the other layouting
+ *  functions should be fine.
+ *
+ *  Usage
+ *  -------------------
+ *  1.) nk_layout_row_dynamic
+ *  The easiest layouting function is `nk_layout_row_dynamic`. It provides each
+ *  widgets with same horizontal space inside the row and dynamically grows
+ *  if the owning window grows in width. So the number of columns dictates
+ *  the size of each widget dynamically by formula:
+ *
+ *      widget_width = (window_width - padding - spacing) * (1/colum_count)
+ *
+ *  Just like all other layouting APIs if you define more widget than columns this
+ *  library will allocate a new row and keep all layouting parameters previously
+ *  defined.
+ *
+ *      if (nk_begin_xxx(...) {
+ *          // first row with height: 30 composed of two widgets
+ *          nk_layout_row_dynamic(&ctx, 30, 2);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *
+ *          // second row with same parameter as defined above
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *
+ *          // third row uses 0 for height which will use auto layouting
+ *          nk_layout_row_dynamic(&ctx, 0, 2);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *      }
+ *      nk_end(...);
+ *
+ *  2.) nk_layout_row_static
+ *  Another easy layouting function is `nk_layout_row_static`. It provides each
+ *  widget with same horizontal pixel width inside the row and does not grow
+ *  if the owning window scales smaller or bigger.
+ *
+ *      if (nk_begin_xxx(...) {
+ *          // first row with height: 30 composed of two widgets with width: 80
+ *          nk_layout_row_static(&ctx, 30, 80, 2);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *
+ *          // second row with same parameter as defined above
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *
+ *          // third row uses 0 for height which will use auto layouting
+ *          nk_layout_row_static(&ctx, 0, 80, 2);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *      }
+ *      nk_end(...);
+ *
+ *  3.) nk_layout_row_xxx
+ *  A little bit more advanced layouting API are functions `nk_layout_row_begin`,
+ *  `nk_layout_row_push` and `nk_layout_row_end`. They allow to directly
+ *  specify each column pixel or window ratio in a row. It supports either
+ *  directly setting per column pixel width or widget window ratio but not
+ *  both. Furthermore it is a immediate mode API so each value is directly
+ *  pushed before calling a widget. Therefore the layout is not automatically
+ *  repeating like the last two layouting functions.
+ *
+ *      if (nk_begin_xxx(...) {
+ *          // first row with height: 25 composed of two widgets with width 60 and 40
+ *          nk_layout_row_begin(ctx, NK_STATIC, 25, 2);
+ *          nk_layout_row_push(ctx, 60);
+ *          nk_widget(...);
+ *          nk_layout_row_push(ctx, 40);
+ *          nk_widget(...);
+ *          nk_layout_row_end(ctx);
+ *
+ *          // second row with height: 25 composed of two widgets with window ratio 0.25 and 0.75
+ *          nk_layout_row_begin(ctx, NK_DYNAMIC, 25, 2);
+ *          nk_layout_row_push(ctx, 0.25f);
+ *          nk_widget(...);
+ *          nk_layout_row_push(ctx, 0.75f);
+ *          nk_widget(...);
+ *          nk_layout_row_end(ctx);
+ *
+ *          // third row with auto generated height: composed of two widgets with window ratio 0.25 and 0.75
+ *          nk_layout_row_begin(ctx, NK_DYNAMIC, 0, 2);
+ *          nk_layout_row_push(ctx, 0.25f);
+ *          nk_widget(...);
+ *          nk_layout_row_push(ctx, 0.75f);
+ *          nk_widget(...);
+ *          nk_layout_row_end(ctx);
+ *      }
+ *      nk_end(...);
+ *
+ *  4.) nk_layout_row
+ *  The array counterpart to API nk_layout_row_xxx is the single nk_layout_row
+ *  functions. Instead of pushing either pixel or window ratio for every widget
+ *  it allows to define it by array. The trade of for less control is that
+ *  `nk_layout_row` is automatically repeating. Otherwise the behavior is the
+ *  same.
+ *
+ *      if (nk_begin_xxx(...) {
+ *          // two rows with height: 30 composed of two widgets with width 60 and 40
+ *          const float size[] = {60,40};
+ *          nk_layout_row(ctx, NK_STATIC, 30, 2, ratio);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *
+ *          // two rows with height: 30 composed of two widgets with window ratio 0.25 and 0.75
+ *          const float ratio[] = {0.25, 0.75};
+ *          nk_layout_row(ctx, NK_DYNAMIC, 30, 2, ratio);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *
+ *          // two rows with auto generated height composed of two widgets with window ratio 0.25 and 0.75
+ *          const float ratio[] = {0.25, 0.75};
+ *          nk_layout_row(ctx, NK_DYNAMIC, 30, 2, ratio);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *      }
+ *      nk_end(...);
+ *
+ *  5.) nk_layout_row_template_xxx
+ *  The most complex and second most flexible API is a simplified flexbox version without
+ *  line wrapping and weights for dynamic widgets. It is an immediate mode API but
+ *  unlike `nk_layout_row_xxx` it has auto repeat behavior and needs to be called
+ *  before calling the templated widgets.
+ *  The row template layout has three different per widget size specifier. The first
+ *  one is the static widget size specifier with fixed widget pixel width. They do
+ *  not grow if the row grows and will always stay the same. The second size
+ *  specifier is nk_layout_row_template_push_variable which defines a
+ *  minimum widget size but it also can grow if more space is available not taken
+ *  by other widgets. Finally there are dynamic widgets which are completely flexible
+ *  and unlike variable widgets can even shrink to zero if not enough space
+ *  is provided.
+ *
+ *      if (nk_begin_xxx(...) {
+ *          // two rows with height: 30 composed of three widgets
+ *          nk_layout_row_template_begin(ctx, 30);
+ *          nk_layout_row_template_push_dynamic(ctx);
+ *          nk_layout_row_template_push_variable(ctx, 80);
+ *          nk_layout_row_template_push_static(ctx, 80);
+ *          nk_layout_row_template_end(ctx);
+ *
+ *          nk_widget(...); // dynamic widget can go to zero if not enough space
+ *          nk_widget(...); // variable widget with min 80 pixel but can grow bigger if enough space
+ *          nk_widget(...); // static widget with fixed 80 pixel width
+ *
+ *          // second row same layout
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *          nk_widget(...);
+ *      }
+ *      nk_end(...);
+ *
+ *  6.) nk_layout_space_xxx
+ *  Finally the most flexible API directly allows you to place widgets inside the
+ *  window. The space layout API is an immediate mode API which does not support
+ *  row auto repeat and directly sets position and size of a widget. Position
+ *  and size hereby can be either specified as ratio of allocated space or
+ *  allocated space local position and pixel size. Since this API is quite
+ *  powerful there are a number of utility functions to get the available space
+ *  and convert between local allocated space and screen space.
+ *
+ *      if (nk_begin_xxx(...) {
+ *          // static row with height: 500 (you can set column count to INT_MAX if you don't want to be bothered)
+ *          nk_layout_space_begin(ctx, NK_STATIC, 500, INT_MAX);
+ *          nk_layout_space_push(ctx, nk_rect(0,0,150,200));
+ *          nk_widget(...);
+ *          nk_layout_space_push(ctx, nk_rect(200,200,100,200));
+ *          nk_widget(...);
+ *          nk_layout_space_end(ctx);
+ *
+ *          // dynamic row with height: 500 (you can set column count to INT_MAX if you don't want to be bothered)
+ *          nk_layout_space_begin(ctx, NK_DYNAMIC, 500, INT_MAX);
+ *          nk_layout_space_push(ctx, nk_rect(0.5,0.5,0.1,0.1));
+ *          nk_widget(...);
+ *          nk_layout_space_push(ctx, nk_rect(0.7,0.6,0.1,0.1));
+ *          nk_widget(...);
+ *      }
+ *      nk_end(...);
+ *
+ *  Reference
+ *  -------------------
+ *  nk_layout_set_min_row_height            - set the currently used minimum row height to a specified value
+ *  nk_layout_reset_min_row_height          - resets the currently used minimum row height to font height
+ *
+ *  nk_layout_widget_bounds                 - calculates current width a static layout row can fit inside a window
+ *  nk_layout_ratio_from_pixel              - utility functions to calculate window ratio from pixel size
+ *
+ *  nk_layout_row_dynamic                   - current layout is divided into n same sized growing columns
+ *  nk_layout_row_static                    - current layout is divided into n same fixed sized columns
+ *  nk_layout_row_begin                     - starts a new row with given height and number of columns
+ *  nk_layout_row_push                      - pushes another column with given size or window ratio
+ *  nk_layout_row_end                       - finished previously started row
+ *  nk_layout_row                           - specifies row columns in array as either window ratio or size
+ *
+ *  nk_layout_row_template_begin            - begins the row template declaration
+ *  nk_layout_row_template_push_dynamic     - adds a dynamic column that dynamically grows and can go to zero if not enough space
+ *  nk_layout_row_template_push_variable    - adds a variable column that dynamically grows but does not shrink below specified pixel width
+ *  nk_layout_row_template_push_static      - adds a static column that does not grow and will always have the same size
+ *  nk_layout_row_template_end              - marks the end of the row template
+ *
+ *  nk_layout_space_begin                   - begins a new layouting space that allows to specify each widgets position and size
+ *  nk_layout_space_push                    - pushes position and size of the next widget in own coordinate space either as pixel or ratio
+ *  nk_layout_space_end                     - marks the end of the layouting space
+ *
+ *  nk_layout_space_bounds                  - callable after nk_layout_space_begin and returns total space allocated
+ *  nk_layout_space_to_screen               - converts vector from nk_layout_space coordinate space into screen space
+ *  nk_layout_space_to_local                - converts vector from screen space into nk_layout_space coordinates
+ *  nk_layout_space_rect_to_screen          - converts rectangle from nk_layout_space coordinate space into screen space
+ *  nk_layout_space_rect_to_local           - converts rectangle from screen space into nk_layout_space coordinates
+ */
+/*  nk_layout_set_min_row_height - sets the currently used minimum row height.
+ *  IMPORTANT: The passed height needs to include both your preferred row height
+ *  as well as padding. No internal padding is added.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_begin_xxx`
+ *      @height new minimum row height to be used for auto generating the row height */
+NK_API void nk_layout_set_min_row_height(struct nk_context*, float height);
+/*  nk_layout_reset_min_row_height - Reset the currently used minimum row height
+ *  back to font height + text padding + additional padding (style_window.min_row_height_padding)
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` */
+NK_API void nk_layout_reset_min_row_height(struct nk_context*);
+/*  nk_layout_widget_bounds - returns the width of the next row allocate by one of the layouting functions
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` */
+NK_API struct nk_rect nk_layout_widget_bounds(struct nk_context*);
+/*  nk_layout_ratio_from_pixel - utility functions to calculate window ratio from pixel size
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context`
+ *      @pixel_width to convert to window ratio */
+NK_API float nk_layout_ratio_from_pixel(struct nk_context*, float pixel_width);
+/*  nk_layout_row_dynamic - Sets current row layout to share horizontal space
+ *  between @cols number of widgets evenly. Once called all subsequent widget
+ *  calls greater than @cols will allocate a new row with same layout.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_begin_xxx`
+ *      @row_height holds height of each widget in row or zero for auto layouting
+ *      @cols number of widget inside row */
+NK_API void nk_layout_row_dynamic(struct nk_context *ctx, float height, int cols);
+/*  nk_layout_row_static - Sets current row layout to fill @cols number of widgets
+ *  in row with same @item_width horizontal size. Once called all subsequent widget
+ *  calls greater than @cols will allocate a new row with same layout.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_begin_xxx`
+ *      @height holds row height to allocate from panel for widget height
+ *      @item_width holds width of each widget in row
+ *      @cols number of widget inside row */
+NK_API void nk_layout_row_static(struct nk_context *ctx, float height, int item_width, int cols);
+/*  nk_layout_row_begin - Starts a new dynamic or fixed row with given height and columns.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_begin_xxx`
+ *      @fmt either `NK_DYNAMIC` for window ratio or `NK_STATIC` for fixed size columns
+ *      @row_height holds height of each widget in row or zero for auto layouting
+ *      @cols number of widget inside row */
+NK_API void nk_layout_row_begin(struct nk_context *ctx, enum nk_layout_format fmt, float row_height, int cols);
+/*  nk_layout_row_push - Specifies either window ratio or width of a single column
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_row_begin`
+ *      @value either a window ratio or fixed width depending on @fmt in previous `nk_layout_row_begin` call */
+NK_API void nk_layout_row_push(struct nk_context*, float value);
+/*  nk_layout_row_end - finished previously started row
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_row_begin` */
+NK_API void nk_layout_row_end(struct nk_context*);
+/*  nk_layout_row - specifies row columns in array as either window ratio or size
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context`
+ *      @fmt either `NK_DYNAMIC` for window ratio or `NK_STATIC` for fixed size columns
+ *      @row_height holds height of each widget in row or zero for auto layouting
+ *      @cols number of widget inside row */
+NK_API void nk_layout_row(struct nk_context*, enum nk_layout_format, float height, int cols, const float *ratio);
+/*  nk_layout_row_template_begin - Begins the row template declaration
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @row_height holds height of each widget in row or zero for auto layouting */
+NK_API void nk_layout_row_template_begin(struct nk_context*, float row_height);
+/*  nk_layout_row_template_push_dynamic - adds a dynamic column that dynamically grows and can go to zero if not enough space
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_row_template_begin` */
+NK_API void nk_layout_row_template_push_dynamic(struct nk_context*);
+/*  nk_layout_row_template_push_variable - adds a variable column that dynamically grows but does not shrink below specified pixel width
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_row_template_begin`
+ *      @min_width holds the minimum pixel width the next column must be */
+NK_API void nk_layout_row_template_push_variable(struct nk_context*, float min_width);
+/*  nk_layout_row_template_push_static - adds a static column that does not grow and will always have the same size
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_row_template_begin`
+ *      @width holds the absolute pixel width value the next column must be */
+NK_API void nk_layout_row_template_push_static(struct nk_context*, float width);
+/*  nk_layout_row_template_end - marks the end of the row template
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_row_template_begin` */
+NK_API void nk_layout_row_template_end(struct nk_context*);
+/*  nk_layout_space_begin - begins a new layouting space that allows to specify each widgets position and size.
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct
+ *      @fmt either `NK_DYNAMIC` for window ratio or `NK_STATIC` for fixed size columns
+ *      @row_height holds height of each widget in row or zero for auto layouting
+ *      @widget_count number of widgets inside row */
+NK_API void nk_layout_space_begin(struct nk_context*, enum nk_layout_format, float height, int widget_count);
+/*  nk_layout_space_push - pushes position and size of the next widget in own coordinate space either as pixel or ratio
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin`
+ *      @bounds position and size in laoyut space local coordinates */
+NK_API void nk_layout_space_push(struct nk_context*, struct nk_rect);
+/*  nk_layout_space_end - marks the end of the layout space
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin` */
+NK_API void nk_layout_space_end(struct nk_context*);
+/*  nk_layout_space_bounds - returns total space allocated for `nk_layout_space`
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin` */
+NK_API struct nk_rect nk_layout_space_bounds(struct nk_context*);
+/*  nk_layout_space_to_screen - converts vector from nk_layout_space coordinate space into screen space
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin`
+ *      @vec position to convert from layout space into screen coordinate space */
+NK_API struct nk_vec2 nk_layout_space_to_screen(struct nk_context*, struct nk_vec2);
+/*  nk_layout_space_to_screen - converts vector from layout space into screen space
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin`
+ *      @vec position to convert from screen space into layout coordinate space */
+NK_API struct nk_vec2 nk_layout_space_to_local(struct nk_context*, struct nk_vec2);
+/*  nk_layout_space_rect_to_screen - converts rectangle from screen space into layout space
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin`
+ *      @bounds rectangle to convert from layout space into screen space */
+NK_API struct nk_rect nk_layout_space_rect_to_screen(struct nk_context*, struct nk_rect);
+/*  nk_layout_space_rect_to_local - converts rectangle from layout space into screen space
+ *  Parameters:
+ *      @ctx must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin`
+ *      @bounds rectangle to convert from screen space into layout space */
+NK_API struct nk_rect nk_layout_space_rect_to_local(struct nk_context*, struct nk_rect);
+/* =============================================================================
+ *
+ *                                  GROUP
+ *
+ * ============================================================================= */
+NK_API int nk_group_begin(struct nk_context*, const char *title, nk_flags);
+NK_API int nk_group_scrolled_offset_begin(struct nk_context*, nk_uint *x_offset, nk_uint *y_offset, const char*, nk_flags);
+NK_API int nk_group_scrolled_begin(struct nk_context*, struct nk_scroll*, const char *title, nk_flags);
+NK_API void nk_group_scrolled_end(struct nk_context*);
+NK_API void nk_group_end(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  LIST VIEW
+ *
+ * ============================================================================= */
+struct nk_list_view {
+/* public: */
+    int begin, end, count;
+/* private: */
+    int total_height;
+    struct nk_context *ctx;
+    nk_uint *scroll_pointer;
+    nk_uint scroll_value;
+};
+NK_API int nk_list_view_begin(struct nk_context*, struct nk_list_view *out, const char *id, nk_flags, int row_height, int row_count);
+NK_API void nk_list_view_end(struct nk_list_view*);
+/* =============================================================================
+ *
+ *                                  TREE
+ *
+ * ============================================================================= */
+#define nk_tree_push(ctx, type, title, state) nk_tree_push_hashed(ctx, type, title, state, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),__LINE__)
+#define nk_tree_push_id(ctx, type, title, state, id) nk_tree_push_hashed(ctx, type, title, state, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),id)
+NK_API int nk_tree_push_hashed(struct nk_context*, enum nk_tree_type, const char *title, enum nk_collapse_states initial_state, const char *hash, int len,int seed);
+#define nk_tree_image_push(ctx, type, img, title, state) nk_tree_image_push_hashed(ctx, type, img, title, state, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),__LINE__)
+#define nk_tree_image_push_id(ctx, type, img, title, state, id) nk_tree_image_push_hashed(ctx, type, img, title, state, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),id)
+NK_API int nk_tree_image_push_hashed(struct nk_context*, enum nk_tree_type, struct nk_image, const char *title, enum nk_collapse_states initial_state, const char *hash, int len,int seed);
+NK_API void nk_tree_pop(struct nk_context*);
+NK_API int nk_tree_state_push(struct nk_context*, enum nk_tree_type, const char *title, enum nk_collapse_states *state);
+NK_API int nk_tree_state_image_push(struct nk_context*, enum nk_tree_type, struct nk_image, const char *title, enum nk_collapse_states *state);
+NK_API void nk_tree_state_pop(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  WIDGET
+ *
+ * ============================================================================= */
+enum nk_widget_layout_states {
+    NK_WIDGET_INVALID, /* The widget cannot be seen and is completely out of view */
+    NK_WIDGET_VALID, /* The widget is completely inside the window and can be updated and drawn */
+    NK_WIDGET_ROM /* The widget is partially visible and cannot be updated */
+};
+enum nk_widget_states {
+    NK_WIDGET_STATE_MODIFIED    = NK_FLAG(1),
+    NK_WIDGET_STATE_INACTIVE    = NK_FLAG(2), /* widget is neither active nor hovered */
+    NK_WIDGET_STATE_ENTERED     = NK_FLAG(3), /* widget has been hovered on the current frame */
+    NK_WIDGET_STATE_HOVER       = NK_FLAG(4), /* widget is being hovered */
+    NK_WIDGET_STATE_ACTIVED     = NK_FLAG(5),/* widget is currently activated */
+    NK_WIDGET_STATE_LEFT        = NK_FLAG(6), /* widget is from this frame on not hovered anymore */
+    NK_WIDGET_STATE_HOVERED     = NK_WIDGET_STATE_HOVER|NK_WIDGET_STATE_MODIFIED, /* widget is being hovered */
+    NK_WIDGET_STATE_ACTIVE      = NK_WIDGET_STATE_ACTIVED|NK_WIDGET_STATE_MODIFIED /* widget is currently activated */
+};
+NK_API enum nk_widget_layout_states nk_widget(struct nk_rect*, const struct nk_context*);
+NK_API enum nk_widget_layout_states nk_widget_fitting(struct nk_rect*, struct nk_context*, struct nk_vec2);
+NK_API struct nk_rect nk_widget_bounds(struct nk_context*);
+NK_API struct nk_vec2 nk_widget_position(struct nk_context*);
+NK_API struct nk_vec2 nk_widget_size(struct nk_context*);
+NK_API float nk_widget_width(struct nk_context*);
+NK_API float nk_widget_height(struct nk_context*);
+NK_API int nk_widget_is_hovered(struct nk_context*);
+NK_API int nk_widget_is_mouse_clicked(struct nk_context*, enum nk_buttons);
+NK_API int nk_widget_has_mouse_click_down(struct nk_context*, enum nk_buttons, int down);
+NK_API void nk_spacing(struct nk_context*, int cols);
+/* =============================================================================
+ *
+ *                                  TEXT
+ *
+ * ============================================================================= */
+enum nk_text_align {
+    NK_TEXT_ALIGN_LEFT        = 0x01,
+    NK_TEXT_ALIGN_CENTERED    = 0x02,
+    NK_TEXT_ALIGN_RIGHT       = 0x04,
+    NK_TEXT_ALIGN_TOP         = 0x08,
+    NK_TEXT_ALIGN_MIDDLE      = 0x10,
+    NK_TEXT_ALIGN_BOTTOM      = 0x20
+};
+enum nk_text_alignment {
+    NK_TEXT_LEFT        = NK_TEXT_ALIGN_MIDDLE|NK_TEXT_ALIGN_LEFT,
+    NK_TEXT_CENTERED    = NK_TEXT_ALIGN_MIDDLE|NK_TEXT_ALIGN_CENTERED,
+    NK_TEXT_RIGHT       = NK_TEXT_ALIGN_MIDDLE|NK_TEXT_ALIGN_RIGHT
+};
+NK_API void nk_text(struct nk_context*, const char*, int, nk_flags);
+NK_API void nk_text_colored(struct nk_context*, const char*, int, nk_flags, struct nk_color);
+NK_API void nk_text_wrap(struct nk_context*, const char*, int);
+NK_API void nk_text_wrap_colored(struct nk_context*, const char*, int, struct nk_color);
+NK_API void nk_label(struct nk_context*, const char*, nk_flags align);
+NK_API void nk_label_colored(struct nk_context*, const char*, nk_flags align, struct nk_color);
+NK_API void nk_label_wrap(struct nk_context*, const char*);
+NK_API void nk_label_colored_wrap(struct nk_context*, const char*, struct nk_color);
+NK_API void nk_image(struct nk_context*, struct nk_image);
+#ifdef NK_INCLUDE_STANDARD_VARARGS
+NK_API void nk_labelf(struct nk_context*, nk_flags, const char*, ...);
+NK_API void nk_labelf_colored(struct nk_context*, nk_flags align, struct nk_color, const char*,...);
+NK_API void nk_labelf_wrap(struct nk_context*, const char*,...);
+NK_API void nk_labelf_colored_wrap(struct nk_context*, struct nk_color, const char*,...);
+NK_API void nk_value_bool(struct nk_context*, const char *prefix, int);
+NK_API void nk_value_int(struct nk_context*, const char *prefix, int);
+NK_API void nk_value_uint(struct nk_context*, const char *prefix, unsigned int);
+NK_API void nk_value_float(struct nk_context*, const char *prefix, float);
+NK_API void nk_value_color_byte(struct nk_context*, const char *prefix, struct nk_color);
+NK_API void nk_value_color_float(struct nk_context*, const char *prefix, struct nk_color);
+NK_API void nk_value_color_hex(struct nk_context*, const char *prefix, struct nk_color);
+#endif
+/* =============================================================================
+ *
+ *                                  BUTTON
+ *
+ * ============================================================================= */
+NK_API int nk_button_text(struct nk_context*, const char *title, int len);
+NK_API int nk_button_label(struct nk_context*, const char *title);
+NK_API int nk_button_color(struct nk_context*, struct nk_color);
+NK_API int nk_button_symbol(struct nk_context*, enum nk_symbol_type);
+NK_API int nk_button_image(struct nk_context*, struct nk_image img);
+NK_API int nk_button_symbol_label(struct nk_context*, enum nk_symbol_type, const char*, nk_flags text_alignment);
+NK_API int nk_button_symbol_text(struct nk_context*, enum nk_symbol_type, const char*, int, nk_flags alignment);
+NK_API int nk_button_image_label(struct nk_context*, struct nk_image img, const char*, nk_flags text_alignment);
+NK_API int nk_button_image_text(struct nk_context*, struct nk_image img, const char*, int, nk_flags alignment);
+NK_API int nk_button_text_styled(struct nk_context*, const struct nk_style_button*, const char *title, int len);
+NK_API int nk_button_label_styled(struct nk_context*, const struct nk_style_button*, const char *title);
+NK_API int nk_button_symbol_styled(struct nk_context*, const struct nk_style_button*, enum nk_symbol_type);
+NK_API int nk_button_image_styled(struct nk_context*, const struct nk_style_button*, struct nk_image img);
+NK_API int nk_button_symbol_text_styled(struct nk_context*,const struct nk_style_button*, enum nk_symbol_type, const char*, int, nk_flags alignment);
+NK_API int nk_button_symbol_label_styled(struct nk_context *ctx, const struct nk_style_button *style, enum nk_symbol_type symbol, const char *title, nk_flags align);
+NK_API int nk_button_image_label_styled(struct nk_context*,const struct nk_style_button*, struct nk_image img, const char*, nk_flags text_alignment);
+NK_API int nk_button_image_text_styled(struct nk_context*,const struct nk_style_button*, struct nk_image img, const char*, int, nk_flags alignment);
+NK_API void nk_button_set_behavior(struct nk_context*, enum nk_button_behavior);
+NK_API int nk_button_push_behavior(struct nk_context*, enum nk_button_behavior);
+NK_API int nk_button_pop_behavior(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  CHECKBOX
+ *
+ * ============================================================================= */
+NK_API int nk_check_label(struct nk_context*, const char*, int active);
+NK_API int nk_check_text(struct nk_context*, const char*, int,int active);
+NK_API unsigned nk_check_flags_label(struct nk_context*, const char*, unsigned int flags, unsigned int value);
+NK_API unsigned nk_check_flags_text(struct nk_context*, const char*, int, unsigned int flags, unsigned int value);
+NK_API int nk_checkbox_label(struct nk_context*, const char*, int *active);
+NK_API int nk_checkbox_text(struct nk_context*, const char*, int, int *active);
+NK_API int nk_checkbox_flags_label(struct nk_context*, const char*, unsigned int *flags, unsigned int value);
+NK_API int nk_checkbox_flags_text(struct nk_context*, const char*, int, unsigned int *flags, unsigned int value);
+/* =============================================================================
+ *
+ *                                  RADIO BUTTON
+ *
+ * ============================================================================= */
+NK_API int nk_radio_label(struct nk_context*, const char*, int *active);
+NK_API int nk_radio_text(struct nk_context*, const char*, int, int *active);
+NK_API int nk_option_label(struct nk_context*, const char*, int active);
+NK_API int nk_option_text(struct nk_context*, const char*, int, int active);
+/* =============================================================================
+ *
+ *                                  SELECTABLE
+ *
+ * ============================================================================= */
+NK_API int nk_selectable_label(struct nk_context*, const char*, nk_flags align, int *value);
+NK_API int nk_selectable_text(struct nk_context*, const char*, int, nk_flags align, int *value);
+NK_API int nk_selectable_image_label(struct nk_context*,struct nk_image,  const char*, nk_flags align, int *value);
+NK_API int nk_selectable_image_text(struct nk_context*,struct nk_image, const char*, int, nk_flags align, int *value);
+NK_API int nk_select_label(struct nk_context*, const char*, nk_flags align, int value);
+NK_API int nk_select_text(struct nk_context*, const char*, int, nk_flags align, int value);
+NK_API int nk_select_image_label(struct nk_context*, struct nk_image,const char*, nk_flags align, int value);
+NK_API int nk_select_image_text(struct nk_context*, struct nk_image,const char*, int, nk_flags align, int value);
+/* =============================================================================
+ *
+ *                                  SLIDER
+ *
+ * ============================================================================= */
+NK_API float nk_slide_float(struct nk_context*, float min, float val, float max, float step);
+NK_API int nk_slide_int(struct nk_context*, int min, int val, int max, int step);
+NK_API int nk_slider_float(struct nk_context*, float min, float *val, float max, float step);
+NK_API int nk_slider_int(struct nk_context*, int min, int *val, int max, int step);
+/* =============================================================================
+ *
+ *                                  PROGRESSBAR
+ *
+ * ============================================================================= */
+NK_API int nk_progress(struct nk_context*, nk_size *cur, nk_size max, int modifyable);
+NK_API nk_size nk_prog(struct nk_context*, nk_size cur, nk_size max, int modifyable);
+
+/* =============================================================================
+ *
+ *                                  COLOR PICKER
+ *
+ * ============================================================================= */
+NK_API struct nk_color nk_color_picker(struct nk_context*, struct nk_color, enum nk_color_format);
+NK_API int nk_color_pick(struct nk_context*, struct nk_color*, enum nk_color_format);
+/* =============================================================================
+ *
+ *                                  PROPERTIES
+ *
+ * ============================================================================= */
+NK_API void nk_property_int(struct nk_context*, const char *name, int min, int *val, int max, int step, float inc_per_pixel);
+NK_API void nk_property_float(struct nk_context*, const char *name, float min, float *val, float max, float step, float inc_per_pixel);
+NK_API void nk_property_double(struct nk_context*, const char *name, double min, double *val, double max, double step, float inc_per_pixel);
+NK_API int nk_propertyi(struct nk_context*, const char *name, int min, int val, int max, int step, float inc_per_pixel);
+NK_API float nk_propertyf(struct nk_context*, const char *name, float min, float val, float max, float step, float inc_per_pixel);
+NK_API double nk_propertyd(struct nk_context*, const char *name, double min, double val, double max, double step, float inc_per_pixel);
+/* =============================================================================
+ *
+ *                                  TEXT EDIT
+ *
+ * ============================================================================= */
+enum nk_edit_flags {
+    NK_EDIT_DEFAULT                 = 0,
+    NK_EDIT_READ_ONLY               = NK_FLAG(0),
+    NK_EDIT_AUTO_SELECT             = NK_FLAG(1),
+    NK_EDIT_SIG_ENTER               = NK_FLAG(2),
+    NK_EDIT_ALLOW_TAB               = NK_FLAG(3),
+    NK_EDIT_NO_CURSOR               = NK_FLAG(4),
+    NK_EDIT_SELECTABLE              = NK_FLAG(5),
+    NK_EDIT_CLIPBOARD               = NK_FLAG(6),
+    NK_EDIT_CTRL_ENTER_NEWLINE      = NK_FLAG(7),
+    NK_EDIT_NO_HORIZONTAL_SCROLL    = NK_FLAG(8),
+    NK_EDIT_ALWAYS_INSERT_MODE      = NK_FLAG(9),
+    NK_EDIT_MULTILINE               = NK_FLAG(10),
+    NK_EDIT_GOTO_END_ON_ACTIVATE    = NK_FLAG(11)
+};
+enum nk_edit_types {
+    NK_EDIT_SIMPLE  = NK_EDIT_ALWAYS_INSERT_MODE,
+    NK_EDIT_FIELD   = NK_EDIT_SIMPLE|NK_EDIT_SELECTABLE|NK_EDIT_CLIPBOARD,
+    NK_EDIT_BOX     = NK_EDIT_ALWAYS_INSERT_MODE| NK_EDIT_SELECTABLE| NK_EDIT_MULTILINE|NK_EDIT_ALLOW_TAB|NK_EDIT_CLIPBOARD,
+    NK_EDIT_EDITOR  = NK_EDIT_SELECTABLE|NK_EDIT_MULTILINE|NK_EDIT_ALLOW_TAB| NK_EDIT_CLIPBOARD
+};
+enum nk_edit_events {
+    NK_EDIT_ACTIVE      = NK_FLAG(0), /* edit widget is currently being modified */
+    NK_EDIT_INACTIVE    = NK_FLAG(1), /* edit widget is not active and is not being modified */
+    NK_EDIT_ACTIVATED   = NK_FLAG(2), /* edit widget went from state inactive to state active */
+    NK_EDIT_DEACTIVATED = NK_FLAG(3), /* edit widget went from state active to state inactive */
+    NK_EDIT_COMMITED    = NK_FLAG(4) /* edit widget has received an enter and lost focus */
+};
+NK_API nk_flags nk_edit_string(struct nk_context*, nk_flags, char *buffer, int *len, int max, nk_plugin_filter);
+NK_API nk_flags nk_edit_string_zero_terminated(struct nk_context*, nk_flags, char *buffer, int max, nk_plugin_filter);
+NK_API nk_flags nk_edit_buffer(struct nk_context*, nk_flags, struct nk_text_edit*, nk_plugin_filter);
+NK_API void nk_edit_focus(struct nk_context*, nk_flags flags);
+NK_API void nk_edit_unfocus(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  CHART
+ *
+ * ============================================================================= */
+NK_API int nk_chart_begin(struct nk_context*, enum nk_chart_type, int num, float min, float max);
+NK_API int nk_chart_begin_colored(struct nk_context*, enum nk_chart_type, struct nk_color, struct nk_color active, int num, float min, float max);
+NK_API void nk_chart_add_slot(struct nk_context *ctx, const enum nk_chart_type, int count, float min_value, float max_value);
+NK_API void nk_chart_add_slot_colored(struct nk_context *ctx, const enum nk_chart_type, struct nk_color, struct nk_color active, int count, float min_value, float max_value);
+NK_API nk_flags nk_chart_push(struct nk_context*, float);
+NK_API nk_flags nk_chart_push_slot(struct nk_context*, float, int);
+NK_API void nk_chart_end(struct nk_context*);
+NK_API void nk_plot(struct nk_context*, enum nk_chart_type, const float *values, int count, int offset);
+NK_API void nk_plot_function(struct nk_context*, enum nk_chart_type, void *userdata, float(*value_getter)(void* user, int index), int count, int offset);
+/* =============================================================================
+ *
+ *                                  POPUP
+ *
+ * ============================================================================= */
+NK_API int nk_popup_begin(struct nk_context*, enum nk_popup_type, const char*, nk_flags, struct nk_rect bounds);
+NK_API void nk_popup_close(struct nk_context*);
+NK_API void nk_popup_end(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  COMBOBOX
+ *
+ * ============================================================================= */
+NK_API int nk_combo(struct nk_context*, const char **items, int count, int selected, int item_height, struct nk_vec2 size);
+NK_API int nk_combo_separator(struct nk_context*, const char *items_separated_by_separator, int separator, int selected, int count, int item_height, struct nk_vec2 size);
+NK_API int nk_combo_string(struct nk_context*, const char *items_separated_by_zeros, int selected, int count, int item_height, struct nk_vec2 size);
+NK_API int nk_combo_callback(struct nk_context*, void(*item_getter)(void*, int, const char**), void *userdata, int selected, int count, int item_height, struct nk_vec2 size);
+NK_API void nk_combobox(struct nk_context*, const char **items, int count, int *selected, int item_height, struct nk_vec2 size);
+NK_API void nk_combobox_string(struct nk_context*, const char *items_separated_by_zeros, int *selected, int count, int item_height, struct nk_vec2 size);
+NK_API void nk_combobox_separator(struct nk_context*, const char *items_separated_by_separator, int separator,int *selected, int count, int item_height, struct nk_vec2 size);
+NK_API void nk_combobox_callback(struct nk_context*, void(*item_getter)(void*, int, const char**), void*, int *selected, int count, int item_height, struct nk_vec2 size);
+/* =============================================================================
+ *
+ *                                  ABSTRACT COMBOBOX
+ *
+ * ============================================================================= */
+NK_API int nk_combo_begin_text(struct nk_context*, const char *selected, int, struct nk_vec2 size);
+NK_API int nk_combo_begin_label(struct nk_context*, const char *selected, struct nk_vec2 size);
+NK_API int nk_combo_begin_color(struct nk_context*, struct nk_color color, struct nk_vec2 size);
+NK_API int nk_combo_begin_symbol(struct nk_context*,  enum nk_symbol_type,  struct nk_vec2 size);
+NK_API int nk_combo_begin_symbol_label(struct nk_context*, const char *selected, enum nk_symbol_type, struct nk_vec2 size);
+NK_API int nk_combo_begin_symbol_text(struct nk_context*, const char *selected, int, enum nk_symbol_type, struct nk_vec2 size);
+NK_API int nk_combo_begin_image(struct nk_context*, struct nk_image img,  struct nk_vec2 size);
+NK_API int nk_combo_begin_image_label(struct nk_context*, const char *selected, struct nk_image, struct nk_vec2 size);
+NK_API int nk_combo_begin_image_text(struct nk_context*,  const char *selected, int, struct nk_image, struct nk_vec2 size);
+NK_API int nk_combo_item_label(struct nk_context*, const char*, nk_flags alignment);
+NK_API int nk_combo_item_text(struct nk_context*, const char*,int, nk_flags alignment);
+NK_API int nk_combo_item_image_label(struct nk_context*, struct nk_image, const char*, nk_flags alignment);
+NK_API int nk_combo_item_image_text(struct nk_context*, struct nk_image, const char*, int,nk_flags alignment);
+NK_API int nk_combo_item_symbol_label(struct nk_context*, enum nk_symbol_type, const char*, nk_flags alignment);
+NK_API int nk_combo_item_symbol_text(struct nk_context*, enum nk_symbol_type, const char*, int, nk_flags alignment);
+NK_API void nk_combo_close(struct nk_context*);
+NK_API void nk_combo_end(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  CONTEXTUAL
+ *
+ * ============================================================================= */
+NK_API int nk_contextual_begin(struct nk_context*, nk_flags, struct nk_vec2, struct nk_rect trigger_bounds);
+NK_API int nk_contextual_item_text(struct nk_context*, const char*, int,nk_flags align);
+NK_API int nk_contextual_item_label(struct nk_context*, const char*, nk_flags align);
+NK_API int nk_contextual_item_image_label(struct nk_context*, struct nk_image, const char*, nk_flags alignment);
+NK_API int nk_contextual_item_image_text(struct nk_context*, struct nk_image, const char*, int len, nk_flags alignment);
+NK_API int nk_contextual_item_symbol_label(struct nk_context*, enum nk_symbol_type, const char*, nk_flags alignment);
+NK_API int nk_contextual_item_symbol_text(struct nk_context*, enum nk_symbol_type, const char*, int, nk_flags alignment);
+NK_API void nk_contextual_close(struct nk_context*);
+NK_API void nk_contextual_end(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  TOOLTIP
+ *
+ * ============================================================================= */
+NK_API void nk_tooltip(struct nk_context*, const char*);
+NK_API int nk_tooltip_begin(struct nk_context*, float width);
+NK_API void nk_tooltip_end(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  MENU
+ *
+ * ============================================================================= */
+NK_API void nk_menubar_begin(struct nk_context*);
+NK_API void nk_menubar_end(struct nk_context*);
+NK_API int nk_menu_begin_text(struct nk_context*, const char* title, int title_len, nk_flags align, struct nk_vec2 size);
+NK_API int nk_menu_begin_label(struct nk_context*, const char*, nk_flags align, struct nk_vec2 size);
+NK_API int nk_menu_begin_image(struct nk_context*, const char*, struct nk_image, struct nk_vec2 size);
+NK_API int nk_menu_begin_image_text(struct nk_context*, const char*, int,nk_flags align,struct nk_image, struct nk_vec2 size);
+NK_API int nk_menu_begin_image_label(struct nk_context*, const char*, nk_flags align,struct nk_image, struct nk_vec2 size);
+NK_API int nk_menu_begin_symbol(struct nk_context*, const char*, enum nk_symbol_type, struct nk_vec2 size);
+NK_API int nk_menu_begin_symbol_text(struct nk_context*, const char*, int,nk_flags align,enum nk_symbol_type, struct nk_vec2 size);
+NK_API int nk_menu_begin_symbol_label(struct nk_context*, const char*, nk_flags align,enum nk_symbol_type, struct nk_vec2 size);
+NK_API int nk_menu_item_text(struct nk_context*, const char*, int,nk_flags align);
+NK_API int nk_menu_item_label(struct nk_context*, const char*, nk_flags alignment);
+NK_API int nk_menu_item_image_label(struct nk_context*, struct nk_image, const char*, nk_flags alignment);
+NK_API int nk_menu_item_image_text(struct nk_context*, struct nk_image, const char*, int len, nk_flags alignment);
+NK_API int nk_menu_item_symbol_text(struct nk_context*, enum nk_symbol_type, const char*, int, nk_flags alignment);
+NK_API int nk_menu_item_symbol_label(struct nk_context*, enum nk_symbol_type, const char*, nk_flags alignment);
+NK_API void nk_menu_close(struct nk_context*);
+NK_API void nk_menu_end(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  STYLE
+ *
+ * ============================================================================= */
+enum nk_style_colors {
+    NK_COLOR_TEXT,
+    NK_COLOR_WINDOW,
+    NK_COLOR_HEADER,
+    NK_COLOR_BORDER,
+    NK_COLOR_BUTTON,
+    NK_COLOR_BUTTON_HOVER,
+    NK_COLOR_BUTTON_ACTIVE,
+    NK_COLOR_TOGGLE,
+    NK_COLOR_TOGGLE_HOVER,
+    NK_COLOR_TOGGLE_CURSOR,
+    NK_COLOR_SELECT,
+    NK_COLOR_SELECT_ACTIVE,
+    NK_COLOR_SLIDER,
+    NK_COLOR_SLIDER_CURSOR,
+    NK_COLOR_SLIDER_CURSOR_HOVER,
+    NK_COLOR_SLIDER_CURSOR_ACTIVE,
+    NK_COLOR_PROPERTY,
+    NK_COLOR_EDIT,
+    NK_COLOR_EDIT_CURSOR,
+    NK_COLOR_COMBO,
+    NK_COLOR_CHART,
+    NK_COLOR_CHART_COLOR,
+    NK_COLOR_CHART_COLOR_HIGHLIGHT,
+    NK_COLOR_SCROLLBAR,
+    NK_COLOR_SCROLLBAR_CURSOR,
+    NK_COLOR_SCROLLBAR_CURSOR_HOVER,
+    NK_COLOR_SCROLLBAR_CURSOR_ACTIVE,
+    NK_COLOR_TAB_HEADER,
+    NK_COLOR_COUNT
+};
+enum nk_style_cursor {
+    NK_CURSOR_ARROW,
+    NK_CURSOR_TEXT,
+    NK_CURSOR_MOVE,
+    NK_CURSOR_RESIZE_VERTICAL,
+    NK_CURSOR_RESIZE_HORIZONTAL,
+    NK_CURSOR_RESIZE_TOP_LEFT_DOWN_RIGHT,
+    NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT,
+    NK_CURSOR_COUNT
+};
+NK_API void nk_style_default(struct nk_context*);
+NK_API void nk_style_from_table(struct nk_context*, const struct nk_color*);
+NK_API void nk_style_load_cursor(struct nk_context*, enum nk_style_cursor, const struct nk_cursor*);
+NK_API void nk_style_load_all_cursors(struct nk_context*, struct nk_cursor*);
+NK_API const char* nk_style_get_color_by_name(enum nk_style_colors);
+NK_API void nk_style_set_font(struct nk_context*, const struct nk_user_font*);
+NK_API int nk_style_set_cursor(struct nk_context*, enum nk_style_cursor);
+NK_API void nk_style_show_cursor(struct nk_context*);
+NK_API void nk_style_hide_cursor(struct nk_context*);
+
+NK_API int nk_style_push_font(struct nk_context*, const struct nk_user_font*);
+NK_API int nk_style_push_float(struct nk_context*, float*, float);
+NK_API int nk_style_push_vec2(struct nk_context*, struct nk_vec2*, struct nk_vec2);
+NK_API int nk_style_push_style_item(struct nk_context*, struct nk_style_item*, struct nk_style_item);
+NK_API int nk_style_push_flags(struct nk_context*, nk_flags*, nk_flags);
+NK_API int nk_style_push_color(struct nk_context*, struct nk_color*, struct nk_color);
+
+NK_API int nk_style_pop_font(struct nk_context*);
+NK_API int nk_style_pop_float(struct nk_context*);
+NK_API int nk_style_pop_vec2(struct nk_context*);
+NK_API int nk_style_pop_style_item(struct nk_context*);
+NK_API int nk_style_pop_flags(struct nk_context*);
+NK_API int nk_style_pop_color(struct nk_context*);
+/* =============================================================================
+ *
+ *                                  COLOR
+ *
+ * ============================================================================= */
+NK_API struct nk_color nk_rgb(int r, int g, int b);
+NK_API struct nk_color nk_rgb_iv(const int *rgb);
+NK_API struct nk_color nk_rgb_bv(const nk_byte* rgb);
+NK_API struct nk_color nk_rgb_f(float r, float g, float b);
+NK_API struct nk_color nk_rgb_fv(const float *rgb);
+NK_API struct nk_color nk_rgb_hex(const char *rgb);
+
+NK_API struct nk_color nk_rgba(int r, int g, int b, int a);
+NK_API struct nk_color nk_rgba_u32(nk_uint);
+NK_API struct nk_color nk_rgba_iv(const int *rgba);
+NK_API struct nk_color nk_rgba_bv(const nk_byte *rgba);
+NK_API struct nk_color nk_rgba_f(float r, float g, float b, float a);
+NK_API struct nk_color nk_rgba_fv(const float *rgba);
+NK_API struct nk_color nk_rgba_hex(const char *rgb);
+
+NK_API struct nk_color nk_hsv(int h, int s, int v);
+NK_API struct nk_color nk_hsv_iv(const int *hsv);
+NK_API struct nk_color nk_hsv_bv(const nk_byte *hsv);
+NK_API struct nk_color nk_hsv_f(float h, float s, float v);
+NK_API struct nk_color nk_hsv_fv(const float *hsv);
+
+NK_API struct nk_color nk_hsva(int h, int s, int v, int a);
+NK_API struct nk_color nk_hsva_iv(const int *hsva);
+NK_API struct nk_color nk_hsva_bv(const nk_byte *hsva);
+NK_API struct nk_color nk_hsva_f(float h, float s, float v, float a);
+NK_API struct nk_color nk_hsva_fv(const float *hsva);
+
+/* color (conversion nuklear --> user) */
+NK_API void nk_color_f(float *r, float *g, float *b, float *a, struct nk_color);
+NK_API void nk_color_fv(float *rgba_out, struct nk_color);
+NK_API void nk_color_d(double *r, double *g, double *b, double *a, struct nk_color);
+NK_API void nk_color_dv(double *rgba_out, struct nk_color);
+
+NK_API nk_uint nk_color_u32(struct nk_color);
+NK_API void nk_color_hex_rgba(char *output, struct nk_color);
+NK_API void nk_color_hex_rgb(char *output, struct nk_color);
+
+NK_API void nk_color_hsv_i(int *out_h, int *out_s, int *out_v, struct nk_color);
+NK_API void nk_color_hsv_b(nk_byte *out_h, nk_byte *out_s, nk_byte *out_v, struct nk_color);
+NK_API void nk_color_hsv_iv(int *hsv_out, struct nk_color);
+NK_API void nk_color_hsv_bv(nk_byte *hsv_out, struct nk_color);
+NK_API void nk_color_hsv_f(float *out_h, float *out_s, float *out_v, struct nk_color);
+NK_API void nk_color_hsv_fv(float *hsv_out, struct nk_color);
+
+NK_API void nk_color_hsva_i(int *h, int *s, int *v, int *a, struct nk_color);
+NK_API void nk_color_hsva_b(nk_byte *h, nk_byte *s, nk_byte *v, nk_byte *a, struct nk_color);
+NK_API void nk_color_hsva_iv(int *hsva_out, struct nk_color);
+NK_API void nk_color_hsva_bv(nk_byte *hsva_out, struct nk_color);
+NK_API void nk_color_hsva_f(float *out_h, float *out_s, float *out_v, float *out_a, struct nk_color);
+NK_API void nk_color_hsva_fv(float *hsva_out, struct nk_color);
+/* =============================================================================
+ *
+ *                                  IMAGE
+ *
+ * ============================================================================= */
+NK_API nk_handle nk_handle_ptr(void*);
+NK_API nk_handle nk_handle_id(int);
+NK_API struct nk_image nk_image_handle(nk_handle);
+NK_API struct nk_image nk_image_ptr(void*);
+NK_API struct nk_image nk_image_id(int);
+NK_API int nk_image_is_subimage(const struct nk_image* img);
+NK_API struct nk_image nk_subimage_ptr(void*, unsigned short w, unsigned short h, struct nk_rect sub_region);
+NK_API struct nk_image nk_subimage_id(int, unsigned short w, unsigned short h, struct nk_rect sub_region);
+NK_API struct nk_image nk_subimage_handle(nk_handle, unsigned short w, unsigned short h, struct nk_rect sub_region);
+/* =============================================================================
+ *
+ *                                  MATH
+ *
+ * ============================================================================= */
+NK_API nk_hash nk_murmur_hash(const void *key, int len, nk_hash seed);
+NK_API void nk_triangle_from_direction(struct nk_vec2 *result, struct nk_rect r, float pad_x, float pad_y, enum nk_heading);
+
+NK_API struct nk_vec2 nk_vec2(float x, float y);
+NK_API struct nk_vec2 nk_vec2i(int x, int y);
+NK_API struct nk_vec2 nk_vec2v(const float *xy);
+NK_API struct nk_vec2 nk_vec2iv(const int *xy);
+
+NK_API struct nk_rect nk_get_null_rect(void);
+NK_API struct nk_rect nk_rect(float x, float y, float w, float h);
+NK_API struct nk_rect nk_recti(int x, int y, int w, int h);
+NK_API struct nk_rect nk_recta(struct nk_vec2 pos, struct nk_vec2 size);
+NK_API struct nk_rect nk_rectv(const float *xywh);
+NK_API struct nk_rect nk_rectiv(const int *xywh);
+NK_API struct nk_vec2 nk_rect_pos(struct nk_rect);
+NK_API struct nk_vec2 nk_rect_size(struct nk_rect);
+/* =============================================================================
+ *
+ *                                  STRING
+ *
+ * ============================================================================= */
+NK_API int nk_strlen(const char *str);
+NK_API int nk_stricmp(const char *s1, const char *s2);
+NK_API int nk_stricmpn(const char *s1, const char *s2, int n);
+NK_API int nk_strtoi(const char *str, const char **endptr);
+NK_API float nk_strtof(const char *str, const char **endptr);
+NK_API double nk_strtod(const char *str, const char **endptr);
+NK_API int nk_strfilter(const char *text, const char *regexp);
+NK_API int nk_strmatch_fuzzy_string(char const *str, char const *pattern, int *out_score);
+NK_API int nk_strmatch_fuzzy_text(const char *txt, int txt_len, const char *pattern, int *out_score);
+/* =============================================================================
+ *
+ *                                  UTF-8
+ *
+ * ============================================================================= */
+NK_API int nk_utf_decode(const char*, nk_rune*, int);
+NK_API int nk_utf_encode(nk_rune, char*, int);
+NK_API int nk_utf_len(const char*, int byte_len);
+NK_API const char* nk_utf_at(const char *buffer, int length, int index, nk_rune *unicode, int *len);
+/* ===============================================================
+ *
+ *                          FONT
+ *
+ * ===============================================================*/
+/*  Font handling in this library was designed to be quite customizable and lets
+    you decide what you want to use and what you want to provide. There are three
+    different ways to use the font atlas. The first two will use your font
+    handling scheme and only requires essential data to run nuklear. The next
+    slightly more advanced features is font handling with vertex buffer output.
+    Finally the most complex API wise is using nuklear's font baking API.
+
+    1.) Using your own implementation without vertex buffer output
+    --------------------------------------------------------------
+    So first up the easiest way to do font handling is by just providing a
+    `nk_user_font` struct which only requires the height in pixel of the used
+    font and a callback to calculate the width of a string. This way of handling
+    fonts is best fitted for using the normal draw shape command API where you
+    do all the text drawing yourself and the library does not require any kind
+    of deeper knowledge about which font handling mechanism you use.
+    IMPORTANT: the `nk_user_font` pointer provided to nuklear has to persist
+    over the complete life time! I know this sucks but it is currently the only
+    way to switch between fonts.
+
+        float your_text_width_calculation(nk_handle handle, float height, const char *text, int len)
+        {
+            your_font_type *type = handle.ptr;
+            float text_width = ...;
+            return text_width;
+        }
+
+        struct nk_user_font font;
+        font.userdata.ptr = &your_font_class_or_struct;
+        font.height = your_font_height;
+        font.width = your_text_width_calculation;
+
+        struct nk_context ctx;
+        nk_init_default(&ctx, &font);
+
+    2.) Using your own implementation with vertex buffer output
+    --------------------------------------------------------------
+    While the first approach works fine if you don't want to use the optional
+    vertex buffer output it is not enough if you do. To get font handling working
+    for these cases you have to provide two additional parameters inside the
+    `nk_user_font`. First a texture atlas handle used to draw text as subimages
+    of a bigger font atlas texture and a callback to query a character's glyph
+    information (offset, size, ...). So it is still possible to provide your own
+    font and use the vertex buffer output.
+
+        float your_text_width_calculation(nk_handle handle, float height, const char *text, int len)
+        {
+            your_font_type *type = handle.ptr;
+            float text_width = ...;
+            return text_width;
+        }
+        void query_your_font_glyph(nk_handle handle, float font_height, struct nk_user_font_glyph *glyph, nk_rune codepoint, nk_rune next_codepoint)
+        {
+            your_font_type *type = handle.ptr;
+            glyph.width = ...;
+            glyph.height = ...;
+            glyph.xadvance = ...;
+            glyph.uv[0].x = ...;
+            glyph.uv[0].y = ...;
+            glyph.uv[1].x = ...;
+            glyph.uv[1].y = ...;
+            glyph.offset.x = ...;
+            glyph.offset.y = ...;
+        }
+
+        struct nk_user_font font;
+        font.userdata.ptr = &your_font_class_or_struct;
+        font.height = your_font_height;
+        font.width = your_text_width_calculation;
+        font.query = query_your_font_glyph;
+        font.texture.id = your_font_texture;
+
+        struct nk_context ctx;
+        nk_init_default(&ctx, &font);
+
+    3.) Nuklear font baker
+    ------------------------------------
+    The final approach if you do not have a font handling functionality or don't
+    want to use it in this library is by using the optional font baker.
+    The font baker APIs can be used to create a font plus font atlas texture
+    and can be used with or without the vertex buffer output.
+
+    It still uses the `nk_user_font` struct and the two different approaches
+    previously stated still work. The font baker is not located inside
+    `nk_context` like all other systems since it can be understood as more of
+    an extension to nuklear and does not really depend on any `nk_context` state.
+
+    Font baker need to be initialized first by one of the nk_font_atlas_init_xxx
+    functions. If you don't care about memory just call the default version
+    `nk_font_atlas_init_default` which will allocate all memory from the standard library.
+    If you want to control memory allocation but you don't care if the allocated
+    memory is temporary and therefore can be freed directly after the baking process
+    is over or permanent you can call `nk_font_atlas_init`.
+
+    After successfully initializing the font baker you can add Truetype(.ttf) fonts from
+    different sources like memory or from file by calling one of the `nk_font_atlas_add_xxx`.
+    functions. Adding font will permanently store each font, font config and ttf memory block(!)
+    inside the font atlas and allows to reuse the font atlas. If you don't want to reuse
+    the font baker by for example adding additional fonts you can call
+    `nk_font_atlas_cleanup` after the baking process is over (after calling nk_font_atlas_end).
+
+    As soon as you added all fonts you wanted you can now start the baking process
+    for every selected glyph to image by calling `nk_font_atlas_bake`.
+    The baking process returns image memory, width and height which can be used to
+    either create your own image object or upload it to any graphics library.
+    No matter which case you finally have to call `nk_font_atlas_end` which
+    will free all temporary memory including the font atlas image so make sure
+    you created our texture beforehand. `nk_font_atlas_end` requires a handle
+    to your font texture or object and optionally fills a `struct nk_draw_null_texture`
+    which can be used for the optional vertex output. If you don't want it just
+    set the argument to `NULL`.
+
+    At this point you are done and if you don't want to reuse the font atlas you
+    can call `nk_font_atlas_cleanup` to free all truetype blobs and configuration
+    memory. Finally if you don't use the font atlas and any of it's fonts anymore
+    you need to call `nk_font_atlas_clear` to free all memory still being used.
+
+        struct nk_font_atlas atlas;
+        nk_font_atlas_init_default(&atlas);
+        nk_font_atlas_begin(&atlas);
+        nk_font *font = nk_font_atlas_add_from_file(&atlas, "Path/To/Your/TTF_Font.ttf", 13, 0);
+        nk_font *font2 = nk_font_atlas_add_from_file(&atlas, "Path/To/Your/TTF_Font2.ttf", 16, 0);
+        const void* img = nk_font_atlas_bake(&atlas, &img_width, &img_height, NK_FONT_ATLAS_RGBA32);
+        nk_font_atlas_end(&atlas, nk_handle_id(texture), 0);
+
+        struct nk_context ctx;
+        nk_init_default(&ctx, &font->handle);
+        while (1) {
+
+        }
+        nk_font_atlas_clear(&atlas);
+
+    The font baker API is probably the most complex API inside this library and
+    I would suggest reading some of my examples `example/` to get a grip on how
+    to use the font atlas. There are a number of details I left out. For example
+    how to merge fonts, configure a font with `nk_font_config` to use other languages,
+    use another texture coordinate format and a lot more:
+
+        struct nk_font_config cfg = nk_font_config(font_pixel_height);
+        cfg.merge_mode = nk_false or nk_true;
+        cfg.range = nk_font_korean_glyph_ranges();
+        cfg.coord_type = NK_COORD_PIXEL;
+        nk_font *font = nk_font_atlas_add_from_file(&atlas, "Path/To/Your/TTF_Font.ttf", 13, &cfg);
+
+*/
+struct nk_user_font_glyph;
+typedef float(*nk_text_width_f)(nk_handle, float h, const char*, int len);
+typedef void(*nk_query_font_glyph_f)(nk_handle handle, float font_height,
+                                    struct nk_user_font_glyph *glyph,
+                                    nk_rune codepoint, nk_rune next_codepoint);
+
+#if defined(NK_INCLUDE_VERTEX_BUFFER_OUTPUT) || defined(NK_INCLUDE_SOFTWARE_FONT)
+struct nk_user_font_glyph {
+    struct nk_vec2 uv[2];
+    /* texture coordinates */
+    struct nk_vec2 offset;
+    /* offset between top left and glyph */
+    float width, height;
+    /* size of the glyph  */
+    float xadvance;
+    /* offset to the next glyph */
+};
+#endif
+
+struct nk_user_font {
+    nk_handle userdata;
+    /* user provided font handle */
+    float height;
+    /* max height of the font */
+    nk_text_width_f width;
+    /* font string width in pixel callback */
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+    nk_query_font_glyph_f query;
+    /* font glyph callback to query drawing info */
+    nk_handle texture;
+    /* texture handle to the used font atlas or texture */
+#endif
+};
+
+#ifdef NK_INCLUDE_FONT_BAKING
+enum nk_font_coord_type {
+    NK_COORD_UV, /* texture coordinates inside font glyphs are clamped between 0-1 */
+    NK_COORD_PIXEL /* texture coordinates inside font glyphs are in absolute pixel */
+};
+
+struct nk_baked_font {
+    float height;
+    /* height of the font  */
+    float ascent, descent;
+    /* font glyphs ascent and descent  */
+    nk_rune glyph_offset;
+    /* glyph array offset inside the font glyph baking output array  */
+    nk_rune glyph_count;
+    /* number of glyphs of this font inside the glyph baking array output */
+    const nk_rune *ranges;
+    /* font codepoint ranges as pairs of (from/to) and 0 as last element */
+};
+
+struct nk_font_config {
+    struct nk_font_config *next;
+    /* NOTE: only used internally */
+    void *ttf_blob;
+    /* pointer to loaded TTF file memory block.
+     * NOTE: not needed for nk_font_atlas_add_from_memory and nk_font_atlas_add_from_file. */
+    nk_size ttf_size;
+    /* size of the loaded TTF file memory block
+     * NOTE: not needed for nk_font_atlas_add_from_memory and nk_font_atlas_add_from_file. */
+
+    unsigned char ttf_data_owned_by_atlas;
+    /* used inside font atlas: default to: 0*/
+    unsigned char merge_mode;
+    /* merges this font into the last font */
+    unsigned char pixel_snap;
+    /* align every character to pixel boundary (if true set oversample (1,1)) */
+    unsigned char oversample_v, oversample_h;
+    /* rasterize at hight quality for sub-pixel position */
+    unsigned char padding[3];
+
+    float size;
+    /* baked pixel height of the font */
+    enum nk_font_coord_type coord_type;
+    /* texture coordinate format with either pixel or UV coordinates */
+    struct nk_vec2 spacing;
+    /* extra pixel spacing between glyphs  */
+    const nk_rune *range;
+    /* list of unicode ranges (2 values per range, zero terminated) */
+    struct nk_baked_font *font;
+    /* font to setup in the baking process: NOTE: not needed for font atlas */
+    nk_rune fallback_glyph;
+    /* fallback glyph to use if a given rune is not found */
+};
+
+struct nk_font_glyph {
+    nk_rune codepoint;
+    float xadvance;
+    float x0, y0, x1, y1, w, h;
+    float u0, v0, u1, v1;
+};
+
+struct nk_font {
+    struct nk_font *next;
+    struct nk_user_font handle;
+    struct nk_baked_font info;
+    float scale;
+    struct nk_font_glyph *glyphs;
+    const struct nk_font_glyph *fallback;
+    nk_rune fallback_codepoint;
+    nk_handle texture;
+    struct nk_font_config *config;
+};
+
+enum nk_font_atlas_format {
+    NK_FONT_ATLAS_ALPHA8,
+    NK_FONT_ATLAS_RGBA32
+};
+
+struct nk_font_atlas {
+    void *pixel;
+    int tex_width;
+    int tex_height;
+
+    struct nk_allocator permanent;
+    struct nk_allocator temporary;
+
+    struct nk_recti custom;
+    struct nk_cursor cursors[NK_CURSOR_COUNT];
+
+    int glyph_count;
+    struct nk_font_glyph *glyphs;
+    struct nk_font *default_font;
+    struct nk_font *fonts;
+    struct nk_font_config *config;
+    int font_num;
+};
+
+/* some language glyph codepoint ranges */
+NK_API const nk_rune *nk_font_default_glyph_ranges(void);
+NK_API const nk_rune *nk_font_chinese_glyph_ranges(void);
+NK_API const nk_rune *nk_font_cyrillic_glyph_ranges(void);
+NK_API const nk_rune *nk_font_korean_glyph_ranges(void);
+
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+NK_API void nk_font_atlas_init_default(struct nk_font_atlas*);
+#endif
+NK_API void nk_font_atlas_init(struct nk_font_atlas*, struct nk_allocator*);
+NK_API void nk_font_atlas_init_custom(struct nk_font_atlas*, struct nk_allocator *persistent, struct nk_allocator *transient);
+NK_API void nk_font_atlas_begin(struct nk_font_atlas*);
+NK_API struct nk_font_config nk_font_config(float pixel_height);
+NK_API struct nk_font *nk_font_atlas_add(struct nk_font_atlas*, const struct nk_font_config*);
+#ifdef NK_INCLUDE_DEFAULT_FONT
+NK_API struct nk_font* nk_font_atlas_add_default(struct nk_font_atlas*, float height, const struct nk_font_config*);
+#endif
+NK_API struct nk_font* nk_font_atlas_add_from_memory(struct nk_font_atlas *atlas, void *memory, nk_size size, float height, const struct nk_font_config *config);
+#ifdef NK_INCLUDE_STANDARD_IO
+NK_API struct nk_font* nk_font_atlas_add_from_file(struct nk_font_atlas *atlas, const char *file_path, float height, const struct nk_font_config*);
+#endif
+NK_API struct nk_font *nk_font_atlas_add_compressed(struct nk_font_atlas*, void *memory, nk_size size, float height, const struct nk_font_config*);
+NK_API struct nk_font* nk_font_atlas_add_compressed_base85(struct nk_font_atlas*, const char *data, float height, const struct nk_font_config *config);
+NK_API const void* nk_font_atlas_bake(struct nk_font_atlas*, int *width, int *height, enum nk_font_atlas_format);
+NK_API void nk_font_atlas_end(struct nk_font_atlas*, nk_handle tex, struct nk_draw_null_texture*);
+NK_API const struct nk_font_glyph* nk_font_find_glyph(struct nk_font*, nk_rune unicode);
+NK_API void nk_font_atlas_cleanup(struct nk_font_atlas *atlas);
+NK_API void nk_font_atlas_clear(struct nk_font_atlas*);
+
+#endif
+
+/* ==============================================================
+ *
+ *                          MEMORY BUFFER
+ *
+ * ===============================================================*/
+/*  A basic (double)-buffer with linear allocation and resetting as only
+    freeing policy. The buffer's main purpose is to control all memory management
+    inside the GUI toolkit and still leave memory control as much as possible in
+    the hand of the user while also making sure the library is easy to use if
+    not as much control is needed.
+    In general all memory inside this library can be provided from the user in
+    three different ways.
+
+    The first way and the one providing most control is by just passing a fixed
+    size memory block. In this case all control lies in the hand of the user
+    since he can exactly control where the memory comes from and how much memory
+    the library should consume. Of course using the fixed size API removes the
+    ability to automatically resize a buffer if not enough memory is provided so
+    you have to take over the resizing. While being a fixed sized buffer sounds
+    quite limiting, it is very effective in this library since the actual memory
+    consumption is quite stable and has a fixed upper bound for a lot of cases.
+
+    If you don't want to think about how much memory the library should allocate
+    at all time or have a very dynamic UI with unpredictable memory consumption
+    habits but still want control over memory allocation you can use the dynamic
+    allocator based API. The allocator consists of two callbacks for allocating
+    and freeing memory and optional userdata so you can plugin your own allocator.
+
+    The final and easiest way can be used by defining
+    NK_INCLUDE_DEFAULT_ALLOCATOR which uses the standard library memory
+    allocation functions malloc and free and takes over complete control over
+    memory in this library.
+*/
+struct nk_memory_status {
+    void *memory;
+    unsigned int type;
+    nk_size size;
+    nk_size allocated;
+    nk_size needed;
+    nk_size calls;
+};
+
+enum nk_allocation_type {
+    NK_BUFFER_FIXED,
+    NK_BUFFER_DYNAMIC
+};
+
+enum nk_buffer_allocation_type {
+    NK_BUFFER_FRONT,
+    NK_BUFFER_BACK,
+    NK_BUFFER_MAX
+};
+
+struct nk_buffer_marker {
+    int active;
+    nk_size offset;
+};
+
+struct nk_memory {void *ptr;nk_size size;};
+struct nk_buffer {
+    struct nk_buffer_marker marker[NK_BUFFER_MAX];
+    /* buffer marker to free a buffer to a certain offset */
+    struct nk_allocator pool;
+    /* allocator callback for dynamic buffers */
+    enum nk_allocation_type type;
+    /* memory management type */
+    struct nk_memory memory;
+    /* memory and size of the current memory block */
+    float grow_factor;
+    /* growing factor for dynamic memory management */
+    nk_size allocated;
+    /* total amount of memory allocated */
+    nk_size needed;
+    /* totally consumed memory given that enough memory is present */
+    nk_size calls;
+    /* number of allocation calls */
+    nk_size size;
+    /* current size of the buffer */
+};
+
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+NK_API void nk_buffer_init_default(struct nk_buffer*);
+#endif
+NK_API void nk_buffer_init(struct nk_buffer*, const struct nk_allocator*, nk_size size);
+NK_API void nk_buffer_init_fixed(struct nk_buffer*, void *memory, nk_size size);
+NK_API void nk_buffer_info(struct nk_memory_status*, struct nk_buffer*);
+NK_API void nk_buffer_push(struct nk_buffer*, enum nk_buffer_allocation_type type, const void *memory, nk_size size, nk_size align);
+NK_API void nk_buffer_mark(struct nk_buffer*, enum nk_buffer_allocation_type type);
+NK_API void nk_buffer_reset(struct nk_buffer*, enum nk_buffer_allocation_type type);
+NK_API void nk_buffer_clear(struct nk_buffer*);
+NK_API void nk_buffer_free(struct nk_buffer*);
+NK_API void *nk_buffer_memory(struct nk_buffer*);
+NK_API const void *nk_buffer_memory_const(const struct nk_buffer*);
+NK_API nk_size nk_buffer_total(struct nk_buffer*);
+
+/* ==============================================================
+ *
+ *                          STRING
+ *
+ * ===============================================================*/
+/*  Basic string buffer which is only used in context with the text editor
+ *  to manage and manipulate dynamic or fixed size string content. This is _NOT_
+ *  the default string handling method. The only instance you should have any contact
+ *  with this API is if you interact with an `nk_text_edit` object inside one of the
+ *  copy and paste functions and even there only for more advanced cases. */
+struct nk_str {
+    struct nk_buffer buffer;
+    int len; /* in codepoints/runes/glyphs */
+};
+
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+NK_API void nk_str_init_default(struct nk_str*);
+#endif
+NK_API void nk_str_init(struct nk_str*, const struct nk_allocator*, nk_size size);
+NK_API void nk_str_init_fixed(struct nk_str*, void *memory, nk_size size);
+NK_API void nk_str_clear(struct nk_str*);
+NK_API void nk_str_free(struct nk_str*);
+
+NK_API int nk_str_append_text_char(struct nk_str*, const char*, int);
+NK_API int nk_str_append_str_char(struct nk_str*, const char*);
+NK_API int nk_str_append_text_utf8(struct nk_str*, const char*, int);
+NK_API int nk_str_append_str_utf8(struct nk_str*, const char*);
+NK_API int nk_str_append_text_runes(struct nk_str*, const nk_rune*, int);
+NK_API int nk_str_append_str_runes(struct nk_str*, const nk_rune*);
+
+NK_API int nk_str_insert_at_char(struct nk_str*, int pos, const char*, int);
+NK_API int nk_str_insert_at_rune(struct nk_str*, int pos, const char*, int);
+
+NK_API int nk_str_insert_text_char(struct nk_str*, int pos, const char*, int);
+NK_API int nk_str_insert_str_char(struct nk_str*, int pos, const char*);
+NK_API int nk_str_insert_text_utf8(struct nk_str*, int pos, const char*, int);
+NK_API int nk_str_insert_str_utf8(struct nk_str*, int pos, const char*);
+NK_API int nk_str_insert_text_runes(struct nk_str*, int pos, const nk_rune*, int);
+NK_API int nk_str_insert_str_runes(struct nk_str*, int pos, const nk_rune*);
+
+NK_API void nk_str_remove_chars(struct nk_str*, int len);
+NK_API void nk_str_remove_runes(struct nk_str *str, int len);
+NK_API void nk_str_delete_chars(struct nk_str*, int pos, int len);
+NK_API void nk_str_delete_runes(struct nk_str*, int pos, int len);
+
+NK_API char *nk_str_at_char(struct nk_str*, int pos);
+NK_API char *nk_str_at_rune(struct nk_str*, int pos, nk_rune *unicode, int *len);
+NK_API nk_rune nk_str_rune_at(const struct nk_str*, int pos);
+NK_API const char *nk_str_at_char_const(const struct nk_str*, int pos);
+NK_API const char *nk_str_at_const(const struct nk_str*, int pos, nk_rune *unicode, int *len);
+
+NK_API char *nk_str_get(struct nk_str*);
+NK_API const char *nk_str_get_const(const struct nk_str*);
+NK_API int nk_str_len(struct nk_str*);
+NK_API int nk_str_len_char(struct nk_str*);
+
+/*===============================================================
+ *
+ *                      TEXT EDITOR
+ *
+ * ===============================================================*/
+/* Editing text in this library is handled by either `nk_edit_string` or
+ * `nk_edit_buffer`. But like almost everything in this library there are multiple
+ * ways of doing it and a balance between control and ease of use with memory
+ * as well as functionality controlled by flags.
+ *
+ * This library generally allows three different levels of memory control:
+ * First of is the most basic way of just providing a simple char array with
+ * string length. This method is probably the easiest way of handling simple
+ * user text input. Main upside is complete control over memory while the biggest
+ * downside in comparison with the other two approaches is missing undo/redo.
+ *
+ * For UIs that require undo/redo the second way was created. It is based on
+ * a fixed size nk_text_edit struct, which has an internal undo/redo stack.
+ * This is mainly useful if you want something more like a text editor but don't want
+ * to have a dynamically growing buffer.
+ *
+ * The final way is using a dynamically growing nk_text_edit struct, which
+ * has both a default version if you don't care where memory comes from and an
+ * allocator version if you do. While the text editor is quite powerful for its
+ * complexity I would not recommend editing gigabytes of data with it.
+ * It is rather designed for uses cases which make sense for a GUI library not for
+ * an full blown text editor.
+ */
+#ifndef NK_TEXTEDIT_UNDOSTATECOUNT
+#define NK_TEXTEDIT_UNDOSTATECOUNT     99
+#endif
+
+#ifndef NK_TEXTEDIT_UNDOCHARCOUNT
+#define NK_TEXTEDIT_UNDOCHARCOUNT      999
+#endif
+
+struct nk_text_edit;
+struct nk_clipboard {
+    nk_handle userdata;
+    nk_plugin_paste paste;
+    nk_plugin_copy copy;
+};
+
+struct nk_text_undo_record {
+   int where;
+   short insert_length;
+   short delete_length;
+   short char_storage;
+};
+
+struct nk_text_undo_state {
+   struct nk_text_undo_record undo_rec[NK_TEXTEDIT_UNDOSTATECOUNT];
+   nk_rune undo_char[NK_TEXTEDIT_UNDOCHARCOUNT];
+   short undo_point;
+   short redo_point;
+   short undo_char_point;
+   short redo_char_point;
+};
+
+enum nk_text_edit_type {
+    NK_TEXT_EDIT_SINGLE_LINE,
+    NK_TEXT_EDIT_MULTI_LINE
+};
+
+enum nk_text_edit_mode {
+    NK_TEXT_EDIT_MODE_VIEW,
+    NK_TEXT_EDIT_MODE_INSERT,
+    NK_TEXT_EDIT_MODE_REPLACE
+};
+
+struct nk_text_edit {
+    struct nk_clipboard clip;
+    struct nk_str string;
+    nk_plugin_filter filter;
+    struct nk_vec2 scrollbar;
+
+    int cursor;
+    int select_start;
+    int select_end;
+    unsigned char mode;
+    unsigned char cursor_at_end_of_line;
+    unsigned char initialized;
+    unsigned char has_preferred_x;
+    unsigned char single_line;
+    unsigned char active;
+    unsigned char padding1;
+    float preferred_x;
+    struct nk_text_undo_state undo;
+};
+
+/* filter function */
+NK_API int nk_filter_default(const struct nk_text_edit*, nk_rune unicode);
+NK_API int nk_filter_ascii(const struct nk_text_edit*, nk_rune unicode);
+NK_API int nk_filter_float(const struct nk_text_edit*, nk_rune unicode);
+NK_API int nk_filter_decimal(const struct nk_text_edit*, nk_rune unicode);
+NK_API int nk_filter_hex(const struct nk_text_edit*, nk_rune unicode);
+NK_API int nk_filter_oct(const struct nk_text_edit*, nk_rune unicode);
+NK_API int nk_filter_binary(const struct nk_text_edit*, nk_rune unicode);
+
+/* text editor */
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+NK_API void nk_textedit_init_default(struct nk_text_edit*);
+#endif
+NK_API void nk_textedit_init(struct nk_text_edit*, struct nk_allocator*, nk_size size);
+NK_API void nk_textedit_init_fixed(struct nk_text_edit*, void *memory, nk_size size);
+NK_API void nk_textedit_free(struct nk_text_edit*);
+NK_API void nk_textedit_text(struct nk_text_edit*, const char*, int total_len);
+NK_API void nk_textedit_delete(struct nk_text_edit*, int where, int len);
+NK_API void nk_textedit_delete_selection(struct nk_text_edit*);
+NK_API void nk_textedit_select_all(struct nk_text_edit*);
+NK_API int nk_textedit_cut(struct nk_text_edit*);
+NK_API int nk_textedit_paste(struct nk_text_edit*, char const*, int len);
+NK_API void nk_textedit_undo(struct nk_text_edit*);
+NK_API void nk_textedit_redo(struct nk_text_edit*);
+
+/* ===============================================================
+ *
+ *                          DRAWING
+ *
+ * ===============================================================*/
+/*  This library was designed to be render backend agnostic so it does
+    not draw anything to screen. Instead all drawn shapes, widgets
+    are made of, are buffered into memory and make up a command queue.
+    Each frame therefore fills the command buffer with draw commands
+    that then need to be executed by the user and his own render backend.
+    After that the command buffer needs to be cleared and a new frame can be
+    started. It is probably important to note that the command buffer is the main
+    drawing API and the optional vertex buffer API only takes this format and
+    converts it into a hardware accessible format.
+
+    To use the command queue to draw your own widgets you can access the
+    command buffer of each window by calling `nk_window_get_canvas` after
+    previously having called `nk_begin`:
+
+        void draw_red_rectangle_widget(struct nk_context *ctx)
+        {
+            struct nk_command_buffer *canvas;
+            struct nk_input *input = &ctx->input;
+            canvas = nk_window_get_canvas(ctx);
+
+            struct nk_rect space;
+            enum nk_widget_layout_states state;
+            state = nk_widget(&space, ctx);
+            if (!state) return;
+
+            if (state != NK_WIDGET_ROM)
+                update_your_widget_by_user_input(...);
+            nk_fill_rect(canvas, space, 0, nk_rgb(255,0,0));
+        }
+
+        if (nk_begin(...)) {
+            nk_layout_row_dynamic(ctx, 25, 1);
+            draw_red_rectangle_widget(ctx);
+        }
+        nk_end(..)
+
+    Important to know if you want to create your own widgets is the `nk_widget`
+    call. It allocates space on the panel reserved for this widget to be used,
+    but also returns the state of the widget space. If your widget is not seen and does
+    not have to be updated it is '0' and you can just return. If it only has
+    to be drawn the state will be `NK_WIDGET_ROM` otherwise you can do both
+    update and draw your widget. The reason for separating is to only draw and
+    update what is actually necessary which is crucial for performance.
+*/
+enum nk_command_type {
+    NK_COMMAND_NOP,
+    NK_COMMAND_SCISSOR,
+    NK_COMMAND_LINE,
+    NK_COMMAND_CURVE,
+    NK_COMMAND_RECT,
+    NK_COMMAND_RECT_FILLED,
+    NK_COMMAND_RECT_MULTI_COLOR,
+    NK_COMMAND_CIRCLE,
+    NK_COMMAND_CIRCLE_FILLED,
+    NK_COMMAND_ARC,
+    NK_COMMAND_ARC_FILLED,
+    NK_COMMAND_TRIANGLE,
+    NK_COMMAND_TRIANGLE_FILLED,
+    NK_COMMAND_POLYGON,
+    NK_COMMAND_POLYGON_FILLED,
+    NK_COMMAND_POLYLINE,
+    NK_COMMAND_TEXT,
+    NK_COMMAND_IMAGE,
+    NK_COMMAND_CUSTOM
+};
+
+/* command base and header of every command inside the buffer */
+struct nk_command {
+    enum nk_command_type type;
+    nk_size next;
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    nk_handle userdata;
+#endif
+};
+
+struct nk_command_scissor {
+    struct nk_command header;
+    short x, y;
+    unsigned short w, h;
+};
+
+struct nk_command_line {
+    struct nk_command header;
+    unsigned short line_thickness;
+    struct nk_vec2i begin;
+    struct nk_vec2i end;
+    struct nk_color color;
+};
+
+struct nk_command_curve {
+    struct nk_command header;
+    unsigned short line_thickness;
+    struct nk_vec2i begin;
+    struct nk_vec2i end;
+    struct nk_vec2i ctrl[2];
+    struct nk_color color;
+};
+
+struct nk_command_rect {
+    struct nk_command header;
+    unsigned short rounding;
+    unsigned short line_thickness;
+    short x, y;
+    unsigned short w, h;
+    struct nk_color color;
+};
+
+struct nk_command_rect_filled {
+    struct nk_command header;
+    unsigned short rounding;
+    short x, y;
+    unsigned short w, h;
+    struct nk_color color;
+};
+
+struct nk_command_rect_multi_color {
+    struct nk_command header;
+    short x, y;
+    unsigned short w, h;
+    struct nk_color left;
+    struct nk_color top;
+    struct nk_color bottom;
+    struct nk_color right;
+};
+
+struct nk_command_triangle {
+    struct nk_command header;
+    unsigned short line_thickness;
+    struct nk_vec2i a;
+    struct nk_vec2i b;
+    struct nk_vec2i c;
+    struct nk_color color;
+};
+
+struct nk_command_triangle_filled {
+    struct nk_command header;
+    struct nk_vec2i a;
+    struct nk_vec2i b;
+    struct nk_vec2i c;
+    struct nk_color color;
+};
+
+struct nk_command_circle {
+    struct nk_command header;
+    short x, y;
+    unsigned short line_thickness;
+    unsigned short w, h;
+    struct nk_color color;
+};
+
+struct nk_command_circle_filled {
+    struct nk_command header;
+    short x, y;
+    unsigned short w, h;
+    struct nk_color color;
+};
+
+struct nk_command_arc {
+    struct nk_command header;
+    short cx, cy;
+    unsigned short r;
+    unsigned short line_thickness;
+    float a[2];
+    struct nk_color color;
+};
+
+struct nk_command_arc_filled {
+    struct nk_command header;
+    short cx, cy;
+    unsigned short r;
+    float a[2];
+    struct nk_color color;
+};
+
+struct nk_command_polygon {
+    struct nk_command header;
+    struct nk_color color;
+    unsigned short line_thickness;
+    unsigned short point_count;
+    struct nk_vec2i points[1];
+};
+
+struct nk_command_polygon_filled {
+    struct nk_command header;
+    struct nk_color color;
+    unsigned short point_count;
+    struct nk_vec2i points[1];
+};
+
+struct nk_command_polyline {
+    struct nk_command header;
+    struct nk_color color;
+    unsigned short line_thickness;
+    unsigned short point_count;
+    struct nk_vec2i points[1];
+};
+
+struct nk_command_image {
+    struct nk_command header;
+    short x, y;
+    unsigned short w, h;
+    struct nk_image img;
+    struct nk_color col;
+};
+
+typedef void (*nk_command_custom_callback)(void *canvas, short x,short y,
+    unsigned short w, unsigned short h, nk_handle callback_data);
+struct nk_command_custom {
+    struct nk_command header;
+    short x, y;
+    unsigned short w, h;
+    nk_handle callback_data;
+    nk_command_custom_callback callback;
+};
+
+struct nk_command_text {
+    struct nk_command header;
+    const struct nk_user_font *font;
+    struct nk_color background;
+    struct nk_color foreground;
+    short x, y;
+    unsigned short w, h;
+    float height;
+    int length;
+    char string[1];
+};
+
+enum nk_command_clipping {
+    NK_CLIPPING_OFF = nk_false,
+    NK_CLIPPING_ON = nk_true
+};
+
+struct nk_command_buffer {
+    struct nk_buffer *base;
+    struct nk_rect clip;
+    int use_clipping;
+    nk_handle userdata;
+    nk_size begin, end, last;
+};
+
+/* shape outlines */
+NK_API void nk_stroke_line(struct nk_command_buffer *b, float x0, float y0, float x1, float y1, float line_thickness, struct nk_color);
+NK_API void nk_stroke_curve(struct nk_command_buffer*, float, float, float, float, float, float, float, float, float line_thickness, struct nk_color);
+NK_API void nk_stroke_rect(struct nk_command_buffer*, struct nk_rect, float rounding, float line_thickness, struct nk_color);
+NK_API void nk_stroke_circle(struct nk_command_buffer*, struct nk_rect, float line_thickness, struct nk_color);
+NK_API void nk_stroke_arc(struct nk_command_buffer*, float cx, float cy, float radius, float a_min, float a_max, float line_thickness, struct nk_color);
+NK_API void nk_stroke_triangle(struct nk_command_buffer*, float, float, float, float, float, float, float line_thichness, struct nk_color);
+NK_API void nk_stroke_polyline(struct nk_command_buffer*, float *points, int point_count, float line_thickness, struct nk_color col);
+NK_API void nk_stroke_polygon(struct nk_command_buffer*, float*, int point_count, float line_thickness, struct nk_color);
+
+/* filled shades */
+NK_API void nk_fill_rect(struct nk_command_buffer*, struct nk_rect, float rounding, struct nk_color);
+NK_API void nk_fill_rect_multi_color(struct nk_command_buffer*, struct nk_rect, struct nk_color left, struct nk_color top, struct nk_color right, struct nk_color bottom);
+NK_API void nk_fill_circle(struct nk_command_buffer*, struct nk_rect, struct nk_color);
+NK_API void nk_fill_arc(struct nk_command_buffer*, float cx, float cy, float radius, float a_min, float a_max, struct nk_color);
+NK_API void nk_fill_triangle(struct nk_command_buffer*, float x0, float y0, float x1, float y1, float x2, float y2, struct nk_color);
+NK_API void nk_fill_polygon(struct nk_command_buffer*, float*, int point_count, struct nk_color);
+
+/* misc */
+NK_API void nk_draw_image(struct nk_command_buffer*, struct nk_rect, const struct nk_image*, struct nk_color);
+NK_API void nk_draw_text(struct nk_command_buffer*, struct nk_rect, const char *text, int len, const struct nk_user_font*, struct nk_color, struct nk_color);
+NK_API void nk_push_scissor(struct nk_command_buffer*, struct nk_rect);
+NK_API void nk_push_custom(struct nk_command_buffer*, struct nk_rect, nk_command_custom_callback, nk_handle usr);
+
+/* ===============================================================
+ *
+ *                          INPUT
+ *
+ * ===============================================================*/
+struct nk_mouse_button {
+    int down;
+    unsigned int clicked;
+    struct nk_vec2 clicked_pos;
+};
+struct nk_mouse {
+    struct nk_mouse_button buttons[NK_BUTTON_MAX];
+    struct nk_vec2 pos;
+    struct nk_vec2 prev;
+    struct nk_vec2 delta;
+    struct nk_vec2 scroll_delta;
+    unsigned char grab;
+    unsigned char grabbed;
+    unsigned char ungrab;
+};
+
+struct nk_key {
+    int down;
+    unsigned int clicked;
+};
+struct nk_keyboard {
+    struct nk_key keys[NK_KEY_MAX];
+    char text[NK_INPUT_MAX];
+    int text_len;
+};
+
+struct nk_input {
+    struct nk_keyboard keyboard;
+    struct nk_mouse mouse;
+};
+
+NK_API int nk_input_has_mouse_click(const struct nk_input*, enum nk_buttons);
+NK_API int nk_input_has_mouse_click_in_rect(const struct nk_input*, enum nk_buttons, struct nk_rect);
+NK_API int nk_input_has_mouse_click_down_in_rect(const struct nk_input*, enum nk_buttons, struct nk_rect, int down);
+NK_API int nk_input_is_mouse_click_in_rect(const struct nk_input*, enum nk_buttons, struct nk_rect);
+NK_API int nk_input_is_mouse_click_down_in_rect(const struct nk_input *i, enum nk_buttons id, struct nk_rect b, int down);
+NK_API int nk_input_any_mouse_click_in_rect(const struct nk_input*, struct nk_rect);
+NK_API int nk_input_is_mouse_prev_hovering_rect(const struct nk_input*, struct nk_rect);
+NK_API int nk_input_is_mouse_hovering_rect(const struct nk_input*, struct nk_rect);
+NK_API int nk_input_mouse_clicked(const struct nk_input*, enum nk_buttons, struct nk_rect);
+NK_API int nk_input_is_mouse_down(const struct nk_input*, enum nk_buttons);
+NK_API int nk_input_is_mouse_pressed(const struct nk_input*, enum nk_buttons);
+NK_API int nk_input_is_mouse_released(const struct nk_input*, enum nk_buttons);
+NK_API int nk_input_is_key_pressed(const struct nk_input*, enum nk_keys);
+NK_API int nk_input_is_key_released(const struct nk_input*, enum nk_keys);
+NK_API int nk_input_is_key_down(const struct nk_input*, enum nk_keys);
+
+/* ===============================================================
+ *
+ *                          DRAW LIST
+ *
+ * ===============================================================*/
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+/*  The optional vertex buffer draw list provides a 2D drawing context
+    with antialiasing functionality which takes basic filled or outlined shapes
+    or a path and outputs vertexes, elements and draw commands.
+    The actual draw list API is not required to be used directly while using this
+    library since converting the default library draw command output is done by
+    just calling `nk_convert` but I decided to still make this library accessible
+    since it can be useful.
+
+    The draw list is based on a path buffering and polygon and polyline
+    rendering API which allows a lot of ways to draw 2D content to screen.
+    In fact it is probably more powerful than needed but allows even more crazy
+    things than this library provides by default.
+*/
+typedef nk_ushort nk_draw_index;
+enum nk_draw_list_stroke {
+    NK_STROKE_OPEN = nk_false,
+    /* build up path has no connection back to the beginning */
+    NK_STROKE_CLOSED = nk_true
+    /* build up path has a connection back to the beginning */
+};
+
+enum nk_draw_vertex_layout_attribute {
+    NK_VERTEX_POSITION,
+    NK_VERTEX_COLOR,
+    NK_VERTEX_TEXCOORD,
+    NK_VERTEX_ATTRIBUTE_COUNT
+};
+
+enum nk_draw_vertex_layout_format {
+    NK_FORMAT_SCHAR,
+    NK_FORMAT_SSHORT,
+    NK_FORMAT_SINT,
+    NK_FORMAT_UCHAR,
+    NK_FORMAT_USHORT,
+    NK_FORMAT_UINT,
+    NK_FORMAT_FLOAT,
+    NK_FORMAT_DOUBLE,
+
+NK_FORMAT_COLOR_BEGIN,
+    NK_FORMAT_R8G8B8 = NK_FORMAT_COLOR_BEGIN,
+    NK_FORMAT_R16G15B16,
+    NK_FORMAT_R32G32B32,
+
+    NK_FORMAT_R8G8B8A8,
+    NK_FORMAT_B8G8R8A8,
+    NK_FORMAT_R16G15B16A16,
+    NK_FORMAT_R32G32B32A32,
+    NK_FORMAT_R32G32B32A32_FLOAT,
+    NK_FORMAT_R32G32B32A32_DOUBLE,
+
+    NK_FORMAT_RGB32,
+    NK_FORMAT_RGBA32,
+NK_FORMAT_COLOR_END = NK_FORMAT_RGBA32,
+    NK_FORMAT_COUNT
+};
+
+#define NK_VERTEX_LAYOUT_END NK_VERTEX_ATTRIBUTE_COUNT,NK_FORMAT_COUNT,0
+struct nk_draw_vertex_layout_element {
+    enum nk_draw_vertex_layout_attribute attribute;
+    enum nk_draw_vertex_layout_format format;
+    nk_size offset;
+};
+
+struct nk_draw_command {
+    unsigned int elem_count;
+    /* number of elements in the current draw batch */
+    struct nk_rect clip_rect;
+    /* current screen clipping rectangle */
+    nk_handle texture;
+    /* current texture to set */
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    nk_handle userdata;
+#endif
+};
+
+struct nk_draw_list {
+    struct nk_rect clip_rect;
+    struct nk_vec2 circle_vtx[12];
+    struct nk_convert_config config;
+
+    struct nk_buffer *buffer;
+    struct nk_buffer *vertices;
+    struct nk_buffer *elements;
+
+    unsigned int element_count;
+    unsigned int vertex_count;
+    unsigned int cmd_count;
+    nk_size cmd_offset;
+
+    unsigned int path_count;
+    unsigned int path_offset;
+
+    enum nk_anti_aliasing line_AA;
+    enum nk_anti_aliasing shape_AA;
+
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    nk_handle userdata;
+#endif
+};
+
+/* draw list */
+NK_API void nk_draw_list_init(struct nk_draw_list*);
+NK_API void nk_draw_list_setup(struct nk_draw_list*, const struct nk_convert_config*, struct nk_buffer *cmds, struct nk_buffer *vertices, struct nk_buffer *elements, enum nk_anti_aliasing line_aa,enum nk_anti_aliasing shape_aa);
+NK_API void nk_draw_list_clear(struct nk_draw_list*);
+
+/* drawing */
+#define nk_draw_list_foreach(cmd, can, b) for((cmd)=nk__draw_list_begin(can, b); (cmd)!=0; (cmd)=nk__draw_list_next(cmd, b, can))
+NK_API const struct nk_draw_command* nk__draw_list_begin(const struct nk_draw_list*, const struct nk_buffer*);
+NK_API const struct nk_draw_command* nk__draw_list_next(const struct nk_draw_command*, const struct nk_buffer*, const struct nk_draw_list*);
+NK_API const struct nk_draw_command* nk__draw_list_end(const struct nk_draw_list*, const struct nk_buffer*);
+NK_API void nk_draw_list_clear(struct nk_draw_list *list);
+
+/* path */
+NK_API void nk_draw_list_path_clear(struct nk_draw_list*);
+NK_API void nk_draw_list_path_line_to(struct nk_draw_list*, struct nk_vec2 pos);
+NK_API void nk_draw_list_path_arc_to_fast(struct nk_draw_list*, struct nk_vec2 center, float radius, int a_min, int a_max);
+NK_API void nk_draw_list_path_arc_to(struct nk_draw_list*, struct nk_vec2 center, float radius, float a_min, float a_max, unsigned int segments);
+NK_API void nk_draw_list_path_rect_to(struct nk_draw_list*, struct nk_vec2 a, struct nk_vec2 b, float rounding);
+NK_API void nk_draw_list_path_curve_to(struct nk_draw_list*, struct nk_vec2 p2, struct nk_vec2 p3, struct nk_vec2 p4, unsigned int num_segments);
+NK_API void nk_draw_list_path_fill(struct nk_draw_list*, struct nk_color);
+NK_API void nk_draw_list_path_stroke(struct nk_draw_list*, struct nk_color, enum nk_draw_list_stroke closed, float thickness);
+
+/* stroke */
+NK_API void nk_draw_list_stroke_line(struct nk_draw_list*, struct nk_vec2 a, struct nk_vec2 b, struct nk_color, float thickness);
+NK_API void nk_draw_list_stroke_rect(struct nk_draw_list*, struct nk_rect rect, struct nk_color, float rounding, float thickness);
+NK_API void nk_draw_list_stroke_triangle(struct nk_draw_list*, struct nk_vec2 a, struct nk_vec2 b, struct nk_vec2 c, struct nk_color, float thickness);
+NK_API void nk_draw_list_stroke_circle(struct nk_draw_list*, struct nk_vec2 center, float radius, struct nk_color, unsigned int segs, float thickness);
+NK_API void nk_draw_list_stroke_curve(struct nk_draw_list*, struct nk_vec2 p0, struct nk_vec2 cp0, struct nk_vec2 cp1, struct nk_vec2 p1, struct nk_color, unsigned int segments, float thickness);
+NK_API void nk_draw_list_stroke_poly_line(struct nk_draw_list*, const struct nk_vec2 *pnts, const unsigned int cnt, struct nk_color, enum nk_draw_list_stroke, float thickness, enum nk_anti_aliasing);
+
+/* fill */
+NK_API void nk_draw_list_fill_rect(struct nk_draw_list*, struct nk_rect rect, struct nk_color, float rounding);
+NK_API void nk_draw_list_fill_rect_multi_color(struct nk_draw_list*, struct nk_rect rect, struct nk_color left, struct nk_color top, struct nk_color right, struct nk_color bottom);
+NK_API void nk_draw_list_fill_triangle(struct nk_draw_list*, struct nk_vec2 a, struct nk_vec2 b, struct nk_vec2 c, struct nk_color);
+NK_API void nk_draw_list_fill_circle(struct nk_draw_list*, struct nk_vec2 center, float radius, struct nk_color col, unsigned int segs);
+NK_API void nk_draw_list_fill_poly_convex(struct nk_draw_list*, const struct nk_vec2 *points, const unsigned int count, struct nk_color, enum nk_anti_aliasing);
+
+/* misc */
+NK_API void nk_draw_list_add_image(struct nk_draw_list*, struct nk_image texture, struct nk_rect rect, struct nk_color);
+NK_API void nk_draw_list_add_text(struct nk_draw_list*, const struct nk_user_font*, struct nk_rect, const char *text, int len, float font_height, struct nk_color);
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+NK_API void nk_draw_list_push_userdata(struct nk_draw_list*, nk_handle userdata);
+#endif
+
+#endif
+
+/* ===============================================================
+ *
+ *                          GUI
+ *
+ * ===============================================================*/
+enum nk_style_item_type {
+    NK_STYLE_ITEM_COLOR,
+    NK_STYLE_ITEM_IMAGE
+};
+
+union nk_style_item_data {
+    struct nk_image image;
+    struct nk_color color;
+};
+
+struct nk_style_item {
+    enum nk_style_item_type type;
+    union nk_style_item_data data;
+};
+
+struct nk_style_text {
+    struct nk_color color;
+    struct nk_vec2 padding;
+};
+
+struct nk_style_button {
+    /* background */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item active;
+    struct nk_color border_color;
+
+    /* text */
+    struct nk_color text_background;
+    struct nk_color text_normal;
+    struct nk_color text_hover;
+    struct nk_color text_active;
+    nk_flags text_alignment;
+
+    /* properties */
+    float border;
+    float rounding;
+    struct nk_vec2 padding;
+    struct nk_vec2 image_padding;
+    struct nk_vec2 touch_padding;
+
+    /* optional user callbacks */
+    nk_handle userdata;
+    void(*draw_begin)(struct nk_command_buffer*, nk_handle userdata);
+    void(*draw_end)(struct nk_command_buffer*, nk_handle userdata);
+};
+
+struct nk_style_toggle {
+    /* background */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item active;
+    struct nk_color border_color;
+
+    /* cursor */
+    struct nk_style_item cursor_normal;
+    struct nk_style_item cursor_hover;
+
+    /* text */
+    struct nk_color text_normal;
+    struct nk_color text_hover;
+    struct nk_color text_active;
+    struct nk_color text_background;
+    nk_flags text_alignment;
+
+    /* properties */
+    struct nk_vec2 padding;
+    struct nk_vec2 touch_padding;
+    float spacing;
+    float border;
+
+    /* optional user callbacks */
+    nk_handle userdata;
+    void(*draw_begin)(struct nk_command_buffer*, nk_handle);
+    void(*draw_end)(struct nk_command_buffer*, nk_handle);
+};
+
+struct nk_style_selectable {
+    /* background (inactive) */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item pressed;
+
+    /* background (active) */
+    struct nk_style_item normal_active;
+    struct nk_style_item hover_active;
+    struct nk_style_item pressed_active;
+
+    /* text color (inactive) */
+    struct nk_color text_normal;
+    struct nk_color text_hover;
+    struct nk_color text_pressed;
+
+    /* text color (active) */
+    struct nk_color text_normal_active;
+    struct nk_color text_hover_active;
+    struct nk_color text_pressed_active;
+    struct nk_color text_background;
+    nk_flags text_alignment;
+
+    /* properties */
+    float rounding;
+    struct nk_vec2 padding;
+    struct nk_vec2 touch_padding;
+    struct nk_vec2 image_padding;
+
+    /* optional user callbacks */
+    nk_handle userdata;
+    void(*draw_begin)(struct nk_command_buffer*, nk_handle);
+    void(*draw_end)(struct nk_command_buffer*, nk_handle);
+};
+
+struct nk_style_slider {
+    /* background */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item active;
+    struct nk_color border_color;
+
+    /* background bar */
+    struct nk_color bar_normal;
+    struct nk_color bar_hover;
+    struct nk_color bar_active;
+    struct nk_color bar_filled;
+
+    /* cursor */
+    struct nk_style_item cursor_normal;
+    struct nk_style_item cursor_hover;
+    struct nk_style_item cursor_active;
+
+    /* properties */
+    float border;
+    float rounding;
+    float bar_height;
+    struct nk_vec2 padding;
+    struct nk_vec2 spacing;
+    struct nk_vec2 cursor_size;
+
+    /* optional buttons */
+    int show_buttons;
+    struct nk_style_button inc_button;
+    struct nk_style_button dec_button;
+    enum nk_symbol_type inc_symbol;
+    enum nk_symbol_type dec_symbol;
+
+    /* optional user callbacks */
+    nk_handle userdata;
+    void(*draw_begin)(struct nk_command_buffer*, nk_handle);
+    void(*draw_end)(struct nk_command_buffer*, nk_handle);
+};
+
+struct nk_style_progress {
+    /* background */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item active;
+    struct nk_color border_color;
+
+    /* cursor */
+    struct nk_style_item cursor_normal;
+    struct nk_style_item cursor_hover;
+    struct nk_style_item cursor_active;
+    struct nk_color cursor_border_color;
+
+    /* properties */
+    float rounding;
+    float border;
+    float cursor_border;
+    float cursor_rounding;
+    struct nk_vec2 padding;
+
+    /* optional user callbacks */
+    nk_handle userdata;
+    void(*draw_begin)(struct nk_command_buffer*, nk_handle);
+    void(*draw_end)(struct nk_command_buffer*, nk_handle);
+};
+
+struct nk_style_scrollbar {
+    /* background */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item active;
+    struct nk_color border_color;
+
+    /* cursor */
+    struct nk_style_item cursor_normal;
+    struct nk_style_item cursor_hover;
+    struct nk_style_item cursor_active;
+    struct nk_color cursor_border_color;
+
+    /* properties */
+    float border;
+    float rounding;
+    float border_cursor;
+    float rounding_cursor;
+    struct nk_vec2 padding;
+
+    /* optional buttons */
+    int show_buttons;
+    struct nk_style_button inc_button;
+    struct nk_style_button dec_button;
+    enum nk_symbol_type inc_symbol;
+    enum nk_symbol_type dec_symbol;
+
+    /* optional user callbacks */
+    nk_handle userdata;
+    void(*draw_begin)(struct nk_command_buffer*, nk_handle);
+    void(*draw_end)(struct nk_command_buffer*, nk_handle);
+};
+
+struct nk_style_edit {
+    /* background */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item active;
+    struct nk_color border_color;
+    struct nk_style_scrollbar scrollbar;
+
+    /* cursor  */
+    struct nk_color cursor_normal;
+    struct nk_color cursor_hover;
+    struct nk_color cursor_text_normal;
+    struct nk_color cursor_text_hover;
+
+    /* text (unselected) */
+    struct nk_color text_normal;
+    struct nk_color text_hover;
+    struct nk_color text_active;
+
+    /* text (selected) */
+    struct nk_color selected_normal;
+    struct nk_color selected_hover;
+    struct nk_color selected_text_normal;
+    struct nk_color selected_text_hover;
+
+    /* properties */
+    float border;
+    float rounding;
+    float cursor_size;
+    struct nk_vec2 scrollbar_size;
+    struct nk_vec2 padding;
+    float row_padding;
+};
+
+struct nk_style_property {
+    /* background */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item active;
+    struct nk_color border_color;
+
+    /* text */
+    struct nk_color label_normal;
+    struct nk_color label_hover;
+    struct nk_color label_active;
+
+    /* symbols */
+    enum nk_symbol_type sym_left;
+    enum nk_symbol_type sym_right;
+
+    /* properties */
+    float border;
+    float rounding;
+    struct nk_vec2 padding;
+
+    struct nk_style_edit edit;
+    struct nk_style_button inc_button;
+    struct nk_style_button dec_button;
+
+    /* optional user callbacks */
+    nk_handle userdata;
+    void(*draw_begin)(struct nk_command_buffer*, nk_handle);
+    void(*draw_end)(struct nk_command_buffer*, nk_handle);
+};
+
+struct nk_style_chart {
+    /* colors */
+    struct nk_style_item background;
+    struct nk_color border_color;
+    struct nk_color selected_color;
+    struct nk_color color;
+
+    /* properties */
+    float border;
+    float rounding;
+    struct nk_vec2 padding;
+};
+
+struct nk_style_combo {
+    /* background */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item active;
+    struct nk_color border_color;
+
+    /* label */
+    struct nk_color label_normal;
+    struct nk_color label_hover;
+    struct nk_color label_active;
+
+    /* symbol */
+    struct nk_color symbol_normal;
+    struct nk_color symbol_hover;
+    struct nk_color symbol_active;
+
+    /* button */
+    struct nk_style_button button;
+    enum nk_symbol_type sym_normal;
+    enum nk_symbol_type sym_hover;
+    enum nk_symbol_type sym_active;
+
+    /* properties */
+    float border;
+    float rounding;
+    struct nk_vec2 content_padding;
+    struct nk_vec2 button_padding;
+    struct nk_vec2 spacing;
+};
+
+struct nk_style_tab {
+    /* background */
+    struct nk_style_item background;
+    struct nk_color border_color;
+    struct nk_color text;
+
+    /* button */
+    struct nk_style_button tab_maximize_button;
+    struct nk_style_button tab_minimize_button;
+    struct nk_style_button node_maximize_button;
+    struct nk_style_button node_minimize_button;
+    enum nk_symbol_type sym_minimize;
+    enum nk_symbol_type sym_maximize;
+
+    /* properties */
+    float border;
+    float rounding;
+    float indent;
+    struct nk_vec2 padding;
+    struct nk_vec2 spacing;
+};
+
+enum nk_style_header_align {
+    NK_HEADER_LEFT,
+    NK_HEADER_RIGHT
+};
+struct nk_style_window_header {
+    /* background */
+    struct nk_style_item normal;
+    struct nk_style_item hover;
+    struct nk_style_item active;
+
+    /* button */
+    struct nk_style_button close_button;
+    struct nk_style_button minimize_button;
+    enum nk_symbol_type close_symbol;
+    enum nk_symbol_type minimize_symbol;
+    enum nk_symbol_type maximize_symbol;
+
+    /* title */
+    struct nk_color label_normal;
+    struct nk_color label_hover;
+    struct nk_color label_active;
+
+    /* properties */
+    enum nk_style_header_align align;
+    struct nk_vec2 padding;
+    struct nk_vec2 label_padding;
+    struct nk_vec2 spacing;
+};
+
+struct nk_style_window {
+    struct nk_style_window_header header;
+    struct nk_style_item fixed_background;
+    struct nk_color background;
+
+    struct nk_color border_color;
+    struct nk_color popup_border_color;
+    struct nk_color combo_border_color;
+    struct nk_color contextual_border_color;
+    struct nk_color menu_border_color;
+    struct nk_color group_border_color;
+    struct nk_color tooltip_border_color;
+    struct nk_style_item scaler;
+
+    float border;
+    float combo_border;
+    float contextual_border;
+    float menu_border;
+    float group_border;
+    float tooltip_border;
+    float popup_border;
+    float min_row_height_padding;
+
+    float rounding;
+    struct nk_vec2 spacing;
+    struct nk_vec2 scrollbar_size;
+    struct nk_vec2 min_size;
+
+    struct nk_vec2 padding;
+    struct nk_vec2 group_padding;
+    struct nk_vec2 popup_padding;
+    struct nk_vec2 combo_padding;
+    struct nk_vec2 contextual_padding;
+    struct nk_vec2 menu_padding;
+    struct nk_vec2 tooltip_padding;
+};
+
+struct nk_style {
+    const struct nk_user_font *font;
+    const struct nk_cursor *cursors[NK_CURSOR_COUNT];
+    const struct nk_cursor *cursor_active;
+    struct nk_cursor *cursor_last;
+    int cursor_visible;
+
+    struct nk_style_text text;
+    struct nk_style_button button;
+    struct nk_style_button contextual_button;
+    struct nk_style_button menu_button;
+    struct nk_style_toggle option;
+    struct nk_style_toggle checkbox;
+    struct nk_style_selectable selectable;
+    struct nk_style_slider slider;
+    struct nk_style_progress progress;
+    struct nk_style_property property;
+    struct nk_style_edit edit;
+    struct nk_style_chart chart;
+    struct nk_style_scrollbar scrollh;
+    struct nk_style_scrollbar scrollv;
+    struct nk_style_tab tab;
+    struct nk_style_combo combo;
+    struct nk_style_window window;
+};
+
+NK_API struct nk_style_item nk_style_item_image(struct nk_image img);
+NK_API struct nk_style_item nk_style_item_color(struct nk_color);
+NK_API struct nk_style_item nk_style_item_hide(void);
+
+/*==============================================================
+ *                          PANEL
+ * =============================================================*/
+#ifndef NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS
+#define NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS 16
+#endif
+#ifndef NK_CHART_MAX_SLOT
+#define NK_CHART_MAX_SLOT 4
+#endif
+
+enum nk_panel_type {
+    NK_PANEL_WINDOW     = NK_FLAG(0),
+    NK_PANEL_GROUP      = NK_FLAG(1),
+    NK_PANEL_POPUP      = NK_FLAG(2),
+    NK_PANEL_CONTEXTUAL = NK_FLAG(4),
+    NK_PANEL_COMBO      = NK_FLAG(5),
+    NK_PANEL_MENU       = NK_FLAG(6),
+    NK_PANEL_TOOLTIP    = NK_FLAG(7)
+};
+enum nk_panel_set {
+    NK_PANEL_SET_NONBLOCK = NK_PANEL_CONTEXTUAL|NK_PANEL_COMBO|NK_PANEL_MENU|NK_PANEL_TOOLTIP,
+    NK_PANEL_SET_POPUP = NK_PANEL_SET_NONBLOCK|NK_PANEL_POPUP,
+    NK_PANEL_SET_SUB = NK_PANEL_SET_POPUP|NK_PANEL_GROUP
+};
+
+struct nk_chart_slot {
+    enum nk_chart_type type;
+    struct nk_color color;
+    struct nk_color highlight;
+    float min, max, range;
+    int count;
+    struct nk_vec2 last;
+    int index;
+};
+
+struct nk_chart {
+    int slot;
+    float x, y, w, h;
+    struct nk_chart_slot slots[NK_CHART_MAX_SLOT];
+};
+
+enum nk_panel_row_layout_type {
+    NK_LAYOUT_DYNAMIC_FIXED = 0,
+    NK_LAYOUT_DYNAMIC_ROW,
+    NK_LAYOUT_DYNAMIC_FREE,
+    NK_LAYOUT_DYNAMIC,
+    NK_LAYOUT_STATIC_FIXED,
+    NK_LAYOUT_STATIC_ROW,
+    NK_LAYOUT_STATIC_FREE,
+    NK_LAYOUT_STATIC,
+    NK_LAYOUT_TEMPLATE,
+    NK_LAYOUT_COUNT
+};
+struct nk_row_layout {
+    enum nk_panel_row_layout_type type;
+    int index;
+    float height;
+    float min_height;
+    int columns;
+    const float *ratio;
+    float item_width;
+    float item_height;
+    float item_offset;
+    float filled;
+    struct nk_rect item;
+    int tree_depth;
+    float templates[NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS];
+};
+
+struct nk_popup_buffer {
+    nk_size begin;
+    nk_size parent;
+    nk_size last;
+    nk_size end;
+    int active;
+};
+
+struct nk_menu_state {
+    float x, y, w, h;
+    struct nk_scroll offset;
+};
+
+struct nk_panel {
+    enum nk_panel_type type;
+    nk_flags flags;
+    struct nk_rect bounds;
+    nk_uint *offset_x;
+    nk_uint *offset_y;
+    float at_x, at_y, max_x;
+    float footer_height;
+    float header_height;
+    float border;
+    unsigned int has_scrolling;
+    struct nk_rect clip;
+    struct nk_menu_state menu;
+    struct nk_row_layout row;
+    struct nk_chart chart;
+    struct nk_command_buffer *buffer;
+    struct nk_panel *parent;
+};
+
+/*==============================================================
+ *                          WINDOW
+ * =============================================================*/
+#ifndef NK_WINDOW_MAX_NAME
+#define NK_WINDOW_MAX_NAME 64
+#endif
+
+struct nk_table;
+enum nk_window_flags {
+    NK_WINDOW_PRIVATE       = NK_FLAG(11),
+    NK_WINDOW_DYNAMIC       = NK_WINDOW_PRIVATE,
+    /* special window type growing up in height while being filled to a certain maximum height */
+    NK_WINDOW_ROM           = NK_FLAG(12),
+    /* sets window widgets into a read only mode and does not allow input changes */
+    NK_WINDOW_NOT_INTERACTIVE = NK_WINDOW_ROM|NK_WINDOW_NO_INPUT,
+    /* prevents all interaction caused by input to either window or widgets inside */
+    NK_WINDOW_HIDDEN        = NK_FLAG(13),
+    /* Hides window and stops any window interaction and drawing */
+    NK_WINDOW_CLOSED        = NK_FLAG(14),
+    /* Directly closes and frees the window at the end of the frame */
+    NK_WINDOW_MINIMIZED     = NK_FLAG(15),
+    /* marks the window as minimized */
+    NK_WINDOW_REMOVE_ROM    = NK_FLAG(16)
+    /* Removes read only mode at the end of the window */
+};
+
+struct nk_popup_state {
+    struct nk_window *win;
+    enum nk_panel_type type;
+    struct nk_popup_buffer buf;
+    nk_hash name;
+    int active;
+    unsigned combo_count;
+    unsigned con_count, con_old;
+    unsigned active_con;
+    struct nk_rect header;
+};
+
+struct nk_edit_state {
+    nk_hash name;
+    unsigned int seq;
+    unsigned int old;
+    int active, prev;
+    int cursor;
+    int sel_start;
+    int sel_end;
+    struct nk_scroll scrollbar;
+    unsigned char mode;
+    unsigned char single_line;
+};
+
+struct nk_property_state {
+    int active, prev;
+    char buffer[NK_MAX_NUMBER_BUFFER];
+    int length;
+    int cursor;
+    int select_start;
+    int select_end;
+    nk_hash name;
+    unsigned int seq;
+    unsigned int old;
+    int state;
+};
+
+struct nk_window {
+    unsigned int seq;
+    nk_hash name;
+    char name_string[NK_WINDOW_MAX_NAME];
+    nk_flags flags;
+
+    struct nk_rect bounds;
+    struct nk_scroll scrollbar;
+    struct nk_command_buffer buffer;
+    struct nk_panel *layout;
+    float scrollbar_hiding_timer;
+
+    /* persistent widget state */
+    struct nk_property_state property;
+    struct nk_popup_state popup;
+    struct nk_edit_state edit;
+    unsigned int scrolled;
+
+    struct nk_table *tables;
+    unsigned int table_count;
+
+    /* window list hooks */
+    struct nk_window *next;
+    struct nk_window *prev;
+    struct nk_window *parent;
+};
+
+/*==============================================================
+ *                          STACK
+ * =============================================================*/
+/* The style modifier stack can be used to temporarily change a
+ * property inside `nk_style`. For example if you want a special
+ * red button you can temporarily push the old button color onto a stack
+ * draw the button with a red color and then you just pop the old color
+ * back from the stack:
+ *
+ *      nk_style_push_style_item(ctx, &ctx->style.button.normal, nk_style_item_color(nk_rgb(255,0,0)));
+ *      nk_style_push_style_item(ctx, &ctx->style.button.hover, nk_style_item_color(nk_rgb(255,0,0)));
+ *      nk_style_push_style_item(ctx, &ctx->style.button.active, nk_style_item_color(nk_rgb(255,0,0)));
+ *      nk_style_push_vec2(ctx, &cx->style.button.padding, nk_vec2(2,2));
+ *
+ *      nk_button(...);
+ *
+ *      nk_style_pop_style_item(ctx);
+ *      nk_style_pop_style_item(ctx);
+ *      nk_style_pop_style_item(ctx);
+ *      nk_style_pop_vec2(ctx);
+ *
+ * Nuklear has a stack for style_items, float properties, vector properties,
+ * flags, colors, fonts and for button_behavior. Each has it's own fixed size stack
+ * which can be changed at compile time.
+ */
+#ifndef NK_BUTTON_BEHAVIOR_STACK_SIZE
+#define NK_BUTTON_BEHAVIOR_STACK_SIZE 8
+#endif
+
+#ifndef NK_FONT_STACK_SIZE
+#define NK_FONT_STACK_SIZE 8
+#endif
+
+#ifndef NK_STYLE_ITEM_STACK_SIZE
+#define NK_STYLE_ITEM_STACK_SIZE 16
+#endif
+
+#ifndef NK_FLOAT_STACK_SIZE
+#define NK_FLOAT_STACK_SIZE 32
+#endif
+
+#ifndef NK_VECTOR_STACK_SIZE
+#define NK_VECTOR_STACK_SIZE 16
+#endif
+
+#ifndef NK_FLAGS_STACK_SIZE
+#define NK_FLAGS_STACK_SIZE 32
+#endif
+
+#ifndef NK_COLOR_STACK_SIZE
+#define NK_COLOR_STACK_SIZE 32
+#endif
+
+#define NK_CONFIGURATION_STACK_TYPE(prefix, name, type)\
+    struct nk_config_stack_##name##_element {\
+        prefix##_##type *address;\
+        prefix##_##type old_value;\
+    }
+#define NK_CONFIG_STACK(type,size)\
+    struct nk_config_stack_##type {\
+        int head;\
+        struct nk_config_stack_##type##_element elements[size];\
+    }
+
+#define nk_float float
+NK_CONFIGURATION_STACK_TYPE(struct nk, style_item, style_item);
+NK_CONFIGURATION_STACK_TYPE(nk ,float, float);
+NK_CONFIGURATION_STACK_TYPE(struct nk, vec2, vec2);
+NK_CONFIGURATION_STACK_TYPE(nk ,flags, flags);
+NK_CONFIGURATION_STACK_TYPE(struct nk, color, color);
+NK_CONFIGURATION_STACK_TYPE(const struct nk, user_font, user_font*);
+NK_CONFIGURATION_STACK_TYPE(enum nk, button_behavior, button_behavior);
+
+NK_CONFIG_STACK(style_item, NK_STYLE_ITEM_STACK_SIZE);
+NK_CONFIG_STACK(float, NK_FLOAT_STACK_SIZE);
+NK_CONFIG_STACK(vec2, NK_VECTOR_STACK_SIZE);
+NK_CONFIG_STACK(flags, NK_FLAGS_STACK_SIZE);
+NK_CONFIG_STACK(color, NK_COLOR_STACK_SIZE);
+NK_CONFIG_STACK(user_font, NK_FONT_STACK_SIZE);
+NK_CONFIG_STACK(button_behavior, NK_BUTTON_BEHAVIOR_STACK_SIZE);
+
+struct nk_configuration_stacks {
+    struct nk_config_stack_style_item style_items;
+    struct nk_config_stack_float floats;
+    struct nk_config_stack_vec2 vectors;
+    struct nk_config_stack_flags flags;
+    struct nk_config_stack_color colors;
+    struct nk_config_stack_user_font fonts;
+    struct nk_config_stack_button_behavior button_behaviors;
+};
+
+/*==============================================================
+ *                          CONTEXT
+ * =============================================================*/
+#define NK_VALUE_PAGE_CAPACITY \
+    (((NK_MAX(sizeof(struct nk_window),sizeof(struct nk_panel)) / sizeof(nk_uint))) / 2)
+
+struct nk_table {
+    unsigned int seq;
+    unsigned int size;
+    nk_hash keys[NK_VALUE_PAGE_CAPACITY];
+    nk_uint values[NK_VALUE_PAGE_CAPACITY];
+    struct nk_table *next, *prev;
+};
+
+union nk_page_data {
+    struct nk_table tbl;
+    struct nk_panel pan;
+    struct nk_window win;
+};
+
+struct nk_page_element {
+    union nk_page_data data;
+    struct nk_page_element *next;
+    struct nk_page_element *prev;
+};
+
+struct nk_page {
+    unsigned int size;
+    struct nk_page *next;
+    struct nk_page_element win[1];
+};
+
+struct nk_pool {
+    struct nk_allocator alloc;
+    enum nk_allocation_type type;
+    unsigned int page_count;
+    struct nk_page *pages;
+    struct nk_page_element *freelist;
+    unsigned capacity;
+    nk_size size;
+    nk_size cap;
+};
+
+struct nk_context {
+/* public: can be accessed freely */
+    struct nk_input input;
+    struct nk_style style;
+    struct nk_buffer memory;
+    struct nk_clipboard clip;
+    nk_flags last_widget_state;
+    enum nk_button_behavior button_behavior;
+    struct nk_configuration_stacks stacks;
+    float delta_time_seconds;
+
+/* private:
+    should only be accessed if you
+    know what you are doing */
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+    struct nk_draw_list draw_list;
+#endif
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    nk_handle userdata;
+#endif
+    /* text editor objects are quite big because of an internal
+     * undo/redo stack. Therefore it does not make sense to have one for
+     * each window for temporary use cases, so I only provide *one* instance
+     * for all windows. This works because the content is cleared anyway */
+    struct nk_text_edit text_edit;
+    /* draw buffer used for overlay drawing operation like cursor */
+    struct nk_command_buffer overlay;
+
+    /* windows */
+    int build;
+    int use_pool;
+    struct nk_pool pool;
+    struct nk_window *begin;
+    struct nk_window *end;
+    struct nk_window *active;
+    struct nk_window *current;
+    struct nk_page_element *freelist;
+    unsigned int count;
+    unsigned int seq;
+};
+
+/* ==============================================================
+ *                          MATH
+ * =============================================================== */
+#define NK_PI 3.141592654f
+#define NK_UTF_INVALID 0xFFFD
+#define NK_MAX_FLOAT_PRECISION 2
+
+#define NK_UNUSED(x) ((void)(x))
+#define NK_SATURATE(x) (NK_MAX(0, NK_MIN(1.0f, x)))
+#define NK_LEN(a) (sizeof(a)/sizeof(a)[0])
+#define NK_ABS(a) (((a) < 0) ? -(a) : (a))
+#define NK_BETWEEN(x, a, b) ((a) <= (x) && (x) < (b))
+#define NK_INBOX(px, py, x, y, w, h)\
+    (NK_BETWEEN(px,x,x+w) && NK_BETWEEN(py,y,y+h))
+#define NK_INTERSECT(x0, y0, w0, h0, x1, y1, w1, h1) \
+    (!(((x1 > (x0 + w0)) || ((x1 + w1) < x0) || (y1 > (y0 + h0)) || (y1 + h1) < y0)))
+#define NK_CONTAINS(x, y, w, h, bx, by, bw, bh)\
+    (NK_INBOX(x,y, bx, by, bw, bh) && NK_INBOX(x+w,y+h, bx, by, bw, bh))
+
+#define nk_vec2_sub(a, b) nk_vec2((a).x - (b).x, (a).y - (b).y)
+#define nk_vec2_add(a, b) nk_vec2((a).x + (b).x, (a).y + (b).y)
+#define nk_vec2_len_sqr(a) ((a).x*(a).x+(a).y*(a).y)
+#define nk_vec2_muls(a, t) nk_vec2((a).x * (t), (a).y * (t))
+
+#define nk_ptr_add(t, p, i) ((t*)((void*)((nk_byte*)(p) + (i))))
+#define nk_ptr_add_const(t, p, i) ((const t*)((const void*)((const nk_byte*)(p) + (i))))
+#define nk_zero_struct(s) nk_zero(&s, sizeof(s))
+
+/* ==============================================================
+ *                          ALIGNMENT
+ * =============================================================== */
+/* Pointer to Integer type conversion for pointer alignment */
+#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC*/
+# define NK_UINT_TO_PTR(x) ((void*)(__PTRDIFF_TYPE__)(x))
+# define NK_PTR_TO_UINT(x) ((nk_size)(__PTRDIFF_TYPE__)(x))
+#elif !defined(__GNUC__) /* works for compilers other than LLVM */
+# define NK_UINT_TO_PTR(x) ((void*)&((char*)0)[x])
+# define NK_PTR_TO_UINT(x) ((nk_size)(((char*)x)-(char*)0))
+#elif defined(NK_USE_FIXED_TYPES) /* used if we have <stdint.h> */
+# define NK_UINT_TO_PTR(x) ((void*)(uintptr_t)(x))
+# define NK_PTR_TO_UINT(x) ((uintptr_t)(x))
+#else /* generates warning but works */
+# define NK_UINT_TO_PTR(x) ((void*)(x))
+# define NK_PTR_TO_UINT(x) ((nk_size)(x))
+#endif
+
+#define NK_ALIGN_PTR(x, mask)\
+    (NK_UINT_TO_PTR((NK_PTR_TO_UINT((nk_byte*)(x) + (mask-1)) & ~(mask-1))))
+#define NK_ALIGN_PTR_BACK(x, mask)\
+    (NK_UINT_TO_PTR((NK_PTR_TO_UINT((nk_byte*)(x)) & ~(mask-1))))
+
+#define NK_OFFSETOF(st,m) ((nk_ptr)&(((st*)0)->m))
+#define NK_CONTAINER_OF(ptr,type,member)\
+    (type*)((void*)((char*)(1 ? (ptr): &((type*)0)->member) - NK_OFFSETOF(type, member)))
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef __cplusplus
+template<typename T> struct nk_alignof;
+template<typename T, int size_diff> struct nk_helper{enum {value = size_diff};};
+template<typename T> struct nk_helper<T,0>{enum {value = nk_alignof<T>::value};};
+template<typename T> struct nk_alignof{struct Big {T x; char c;}; enum {
+    diff = sizeof(Big) - sizeof(T), value = nk_helper<Big, diff>::value};};
+#define NK_ALIGNOF(t) (nk_alignof<t>::value)
+#elif defined(_MSC_VER)
+#define NK_ALIGNOF(t) (__alignof(t))
+#else
+#define NK_ALIGNOF(t) ((char*)(&((struct {char c; t _h;}*)0)->_h) - (char*)0)
+#endif
+
+#endif /* NK_NUKLEAR_H_ */
+/*
+ * ==============================================================
+ *
+ *                          IMPLEMENTATION
+ *
+ * ===============================================================
+ */
+#ifdef NK_IMPLEMENTATION
+
+#ifndef NK_POOL_DEFAULT_CAPACITY
+#define NK_POOL_DEFAULT_CAPACITY 16
+#endif
+
+#ifndef NK_DEFAULT_COMMAND_BUFFER_SIZE
+#define NK_DEFAULT_COMMAND_BUFFER_SIZE (4*1024)
+#endif
+
+#ifndef NK_BUFFER_DEFAULT_INITIAL_SIZE
+#define NK_BUFFER_DEFAULT_INITIAL_SIZE (4*1024)
+#endif
+
+/* standard library headers */
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+#include <stdlib.h> /* malloc, free */
+#endif
+#ifdef NK_INCLUDE_STANDARD_IO
+#include <stdio.h> /* fopen, fclose,... */
+#endif
+#ifdef NK_INCLUDE_STANDARD_VARARGS
+#include <stdarg.h> /* valist, va_start, va_end, ... */
+#endif
+#ifndef NK_ASSERT
+#include <assert.h>
+#define NK_ASSERT(expr) assert(expr)
+#endif
+
+#ifndef NK_MEMSET
+#define NK_MEMSET nk_memset
+#endif
+#ifndef NK_MEMCPY
+#define NK_MEMCPY nk_memcopy
+#endif
+#ifndef NK_SQRT
+#define NK_SQRT nk_sqrt
+#endif
+#ifndef NK_SIN
+#define NK_SIN nk_sin
+#endif
+#ifndef NK_COS
+#define NK_COS nk_cos
+#endif
+#ifndef NK_STRTOD
+#define NK_STRTOD nk_strtod
+#endif
+#ifndef NK_DTOA
+#define NK_DTOA nk_dtoa
+#endif
+
+#define NK_DEFAULT (-1)
+
+#ifndef NK_VSNPRINTF
+/* If your compiler does support `vsnprintf` I would highly recommend
+ * defining this to vsnprintf instead since `vsprintf` is basically
+ * unbelievable unsafe and should *NEVER* be used. But I have to support
+ * it since C89 only provides this unsafe version. */
+  #if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) ||\
+      (defined(__cplusplus) && (__cplusplus >= 201103L)) || \
+      (defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) ||\
+      (defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) ||\
+       defined(_ISOC99_SOURCE) || defined(_BSD_SOURCE)
+      #define NK_VSNPRINTF(s,n,f,a) vsnprintf(s,n,f,a)
+  #else
+    #define NK_VSNPRINTF(s,n,f,a) vsprintf(s,f,a)
+  #endif
+#endif
+
+#define NK_SCHAR_MIN (-127)
+#define NK_SCHAR_MAX 127
+#define NK_UCHAR_MIN 0
+#define NK_UCHAR_MAX 256
+#define NK_SSHORT_MIN (-32767)
+#define NK_SSHORT_MAX 32767
+#define NK_USHORT_MIN 0
+#define NK_USHORT_MAX 65535
+#define NK_SINT_MIN (-2147483647)
+#define NK_SINT_MAX 2147483647
+#define NK_UINT_MIN 0
+#define NK_UINT_MAX 4294967295u
+
+/* Make sure correct type size:
+ * This will fire with a negative subscript error if the type sizes
+ * are set incorrectly by the compiler, and compile out if not */
+NK_STATIC_ASSERT(sizeof(nk_size) >= sizeof(void*));
+NK_STATIC_ASSERT(sizeof(nk_ptr) == sizeof(void*));
+NK_STATIC_ASSERT(sizeof(nk_flags) >= 4);
+NK_STATIC_ASSERT(sizeof(nk_rune) >= 4);
+NK_STATIC_ASSERT(sizeof(nk_ushort) == 2);
+NK_STATIC_ASSERT(sizeof(nk_short) == 2);
+NK_STATIC_ASSERT(sizeof(nk_uint) == 4);
+NK_STATIC_ASSERT(sizeof(nk_int) == 4);
+NK_STATIC_ASSERT(sizeof(nk_byte) == 1);
+
+NK_GLOBAL const struct nk_rect nk_null_rect = {-8192.0f, -8192.0f, 16384, 16384};
+#define NK_FLOAT_PRECISION 0.00000000000001
+
+NK_GLOBAL const struct nk_color nk_red = {255,0,0,255};
+NK_GLOBAL const struct nk_color nk_green = {0,255,0,255};
+NK_GLOBAL const struct nk_color nk_blue = {0,0,255,255};
+NK_GLOBAL const struct nk_color nk_white = {255,255,255,255};
+NK_GLOBAL const struct nk_color nk_black = {0,0,0,255};
+NK_GLOBAL const struct nk_color nk_yellow = {255,255,0,255};
+
+/*
+ * ==============================================================
+ *
+ *                          MATH
+ *
+ * ===============================================================
+ */
+/*  Since nuklear is supposed to work on all systems providing floating point
+    math without any dependencies I also had to implement my own math functions
+    for sqrt, sin and cos. Since the actual highly accurate implementations for
+    the standard library functions are quite complex and I do not need high
+    precision for my use cases I use approximations.
+
+    Sqrt
+    ----
+    For square root nuklear uses the famous fast inverse square root:
+    https://en.wikipedia.org/wiki/Fast_inverse_square_root with
+    slightly tweaked magic constant. While on today's hardware it is
+    probably not faster it is still fast and accurate enough for
+    nuklear's use cases. IMPORTANT: this requires float format IEEE 754
+
+    Sine/Cosine
+    -----------
+    All constants inside both function are generated Remez's minimax
+    approximations for value range 0...2*PI. The reason why I decided to
+    approximate exactly that range is that nuklear only needs sine and
+    cosine to generate circles which only requires that exact range.
+    In addition I used Remez instead of Taylor for additional precision:
+    www.lolengine.net/blog/2011/12/21/better-function-approximations.
+
+    The tool I used to generate constants for both sine and cosine
+    (it can actually approximate a lot more functions) can be
+    found here: www.lolengine.net/wiki/oss/lolremez
+*/
+NK_INTERN float
+nk_inv_sqrt(float number)
+{
+    float x2;
+    const float threehalfs = 1.5f;
+    union {nk_uint i; float f;} conv = {0};
+    conv.f = number;
+    x2 = number * 0.5f;
+    conv.i = 0x5f375A84 - (conv.i >> 1);
+    conv.f = conv.f * (threehalfs - (x2 * conv.f * conv.f));
+    return conv.f;
+}
+
+NK_INTERN float
+nk_sqrt(float x)
+{
+    return x * nk_inv_sqrt(x);
+}
+
+NK_INTERN float
+nk_sin(float x)
+{
+    NK_STORAGE const float a0 = +1.91059300966915117e-31f;
+    NK_STORAGE const float a1 = +1.00086760103908896f;
+    NK_STORAGE const float a2 = -1.21276126894734565e-2f;
+    NK_STORAGE const float a3 = -1.38078780785773762e-1f;
+    NK_STORAGE const float a4 = -2.67353392911981221e-2f;
+    NK_STORAGE const float a5 = +2.08026600266304389e-2f;
+    NK_STORAGE const float a6 = -3.03996055049204407e-3f;
+    NK_STORAGE const float a7 = +1.38235642404333740e-4f;
+    return a0 + x*(a1 + x*(a2 + x*(a3 + x*(a4 + x*(a5 + x*(a6 + x*a7))))));
+}
+
+NK_INTERN float
+nk_cos(float x)
+{
+    NK_STORAGE const float a0 = +1.00238601909309722f;
+    NK_STORAGE const float a1 = -3.81919947353040024e-2f;
+    NK_STORAGE const float a2 = -3.94382342128062756e-1f;
+    NK_STORAGE const float a3 = -1.18134036025221444e-1f;
+    NK_STORAGE const float a4 = +1.07123798512170878e-1f;
+    NK_STORAGE const float a5 = -1.86637164165180873e-2f;
+    NK_STORAGE const float a6 = +9.90140908664079833e-4f;
+    NK_STORAGE const float a7 = -5.23022132118824778e-14f;
+    return a0 + x*(a1 + x*(a2 + x*(a3 + x*(a4 + x*(a5 + x*(a6 + x*a7))))));
+}
+
+NK_INTERN nk_uint
+nk_round_up_pow2(nk_uint v)
+{
+    v--;
+    v |= v >> 1;
+    v |= v >> 2;
+    v |= v >> 4;
+    v |= v >> 8;
+    v |= v >> 16;
+    v++;
+    return v;
+}
+
+NK_API struct nk_rect
+nk_get_null_rect(void)
+{
+    return nk_null_rect;
+}
+
+NK_API struct nk_rect
+nk_rect(float x, float y, float w, float h)
+{
+    struct nk_rect r;
+    r.x = x; r.y = y;
+    r.w = w; r.h = h;
+    return r;
+}
+
+NK_API struct nk_rect
+nk_recti(int x, int y, int w, int h)
+{
+    struct nk_rect r;
+    r.x = (float)x;
+    r.y = (float)y;
+    r.w = (float)w;
+    r.h = (float)h;
+    return r;
+}
+
+NK_API struct nk_rect
+nk_recta(struct nk_vec2 pos, struct nk_vec2 size)
+{
+    return nk_rect(pos.x, pos.y, size.x, size.y);
+}
+
+NK_API struct nk_rect
+nk_rectv(const float *r)
+{
+    return nk_rect(r[0], r[1], r[2], r[3]);
+}
+
+NK_API struct nk_rect
+nk_rectiv(const int *r)
+{
+    return nk_recti(r[0], r[1], r[2], r[3]);
+}
+
+NK_API struct nk_vec2
+nk_rect_pos(struct nk_rect r)
+{
+    struct nk_vec2 ret;
+    ret.x = r.x; ret.y = r.y;
+    return ret;
+}
+
+NK_API struct nk_vec2
+nk_rect_size(struct nk_rect r)
+{
+    struct nk_vec2 ret;
+    ret.x = r.w; ret.y = r.h;
+    return ret;
+}
+
+NK_INTERN struct nk_rect
+nk_shrink_rect(struct nk_rect r, float amount)
+{
+    struct nk_rect res;
+    r.w = NK_MAX(r.w, 2 * amount);
+    r.h = NK_MAX(r.h, 2 * amount);
+    res.x = r.x + amount;
+    res.y = r.y + amount;
+    res.w = r.w - 2 * amount;
+    res.h = r.h - 2 * amount;
+    return res;
+}
+
+NK_INTERN struct nk_rect
+nk_pad_rect(struct nk_rect r, struct nk_vec2 pad)
+{
+    r.w = NK_MAX(r.w, 2 * pad.x);
+    r.h = NK_MAX(r.h, 2 * pad.y);
+    r.x += pad.x; r.y += pad.y;
+    r.w -= 2 * pad.x;
+    r.h -= 2 * pad.y;
+    return r;
+}
+
+NK_API struct nk_vec2
+nk_vec2(float x, float y)
+{
+    struct nk_vec2 ret;
+    ret.x = x; ret.y = y;
+    return ret;
+}
+
+NK_API struct nk_vec2
+nk_vec2i(int x, int y)
+{
+    struct nk_vec2 ret;
+    ret.x = (float)x;
+    ret.y = (float)y;
+    return ret;
+}
+
+NK_API struct nk_vec2
+nk_vec2v(const float *v)
+{
+    return nk_vec2(v[0], v[1]);
+}
+
+NK_API struct nk_vec2
+nk_vec2iv(const int *v)
+{
+    return nk_vec2i(v[0], v[1]);
+}
+
+/*
+ * ==============================================================
+ *
+ *                          UTIL
+ *
+ * ===============================================================
+ */
+NK_INTERN int nk_str_match_here(const char *regexp, const char *text);
+NK_INTERN int nk_str_match_star(int c, const char *regexp, const char *text);
+NK_INTERN int nk_is_lower(int c) {return (c >= 'a' && c <= 'z') || (c >= 0xE0 && c <= 0xFF);}
+NK_INTERN int nk_is_upper(int c){return (c >= 'A' && c <= 'Z') || (c >= 0xC0 && c <= 0xDF);}
+NK_INTERN int nk_to_upper(int c) {return (c >= 'a' && c <= 'z') ? (c - ('a' - 'A')) : c;}
+NK_INTERN int nk_to_lower(int c) {return (c >= 'A' && c <= 'Z') ? (c - ('a' + 'A')) : c;}
+
+NK_INTERN void*
+nk_memcopy(void *dst0, const void *src0, nk_size length)
+{
+    nk_ptr t;
+    char *dst = (char*)dst0;
+    const char *src = (const char*)src0;
+    if (length == 0 || dst == src)
+        goto done;
+
+    #define nk_word int
+    #define nk_wsize sizeof(nk_word)
+    #define nk_wmask (nk_wsize-1)
+    #define NK_TLOOP(s) if (t) NK_TLOOP1(s)
+    #define NK_TLOOP1(s) do { s; } while (--t)
+
+    if (dst < src) {
+        t = (nk_ptr)src; /* only need low bits */
+        if ((t | (nk_ptr)dst) & nk_wmask) {
+            if ((t ^ (nk_ptr)dst) & nk_wmask || length < nk_wsize)
+                t = length;
+            else
+                t = nk_wsize - (t & nk_wmask);
+            length -= t;
+            NK_TLOOP1(*dst++ = *src++);
+        }
+        t = length / nk_wsize;
+        NK_TLOOP(*(nk_word*)(void*)dst = *(const nk_word*)(const void*)src;
+            src += nk_wsize; dst += nk_wsize);
+        t = length & nk_wmask;
+        NK_TLOOP(*dst++ = *src++);
+    } else {
+        src += length;
+        dst += length;
+        t = (nk_ptr)src;
+        if ((t | (nk_ptr)dst) & nk_wmask) {
+            if ((t ^ (nk_ptr)dst) & nk_wmask || length <= nk_wsize)
+                t = length;
+            else
+                t &= nk_wmask;
+            length -= t;
+            NK_TLOOP1(*--dst = *--src);
+        }
+        t = length / nk_wsize;
+        NK_TLOOP(src -= nk_wsize; dst -= nk_wsize;
+            *(nk_word*)(void*)dst = *(const nk_word*)(const void*)src);
+        t = length & nk_wmask;
+        NK_TLOOP(*--dst = *--src);
+    }
+    #undef nk_word
+    #undef nk_wsize
+    #undef nk_wmask
+    #undef NK_TLOOP
+    #undef NK_TLOOP1
+done:
+    return (dst0);
+}
+
+NK_INTERN void
+nk_memset(void *ptr, int c0, nk_size size)
+{
+    #define nk_word unsigned
+    #define nk_wsize sizeof(nk_word)
+    #define nk_wmask (nk_wsize - 1)
+    nk_byte *dst = (nk_byte*)ptr;
+    unsigned c = 0;
+    nk_size t = 0;
+
+    if ((c = (nk_byte)c0) != 0) {
+        c = (c << 8) | c; /* at least 16-bits  */
+        if (sizeof(unsigned int) > 2)
+            c = (c << 16) | c; /* at least 32-bits*/
+    }
+
+    /* too small of a word count */
+    dst = (nk_byte*)ptr;
+    if (size < 3 * nk_wsize) {
+        while (size--) *dst++ = (nk_byte)c0;
+        return;
+    }
+
+    /* align destination */
+    if ((t = NK_PTR_TO_UINT(dst) & nk_wmask) != 0) {
+        t = nk_wsize -t;
+        size -= t;
+        do {
+            *dst++ = (nk_byte)c0;
+        } while (--t != 0);
+    }
+
+    /* fill word */
+    t = size / nk_wsize;
+    do {
+        *(nk_word*)((void*)dst) = c;
+        dst += nk_wsize;
+    } while (--t != 0);
+
+    /* fill trailing bytes */
+    t = (size & nk_wmask);
+    if (t != 0) {
+        do {
+            *dst++ = (nk_byte)c0;
+        } while (--t != 0);
+    }
+
+    #undef nk_word
+    #undef nk_wsize
+    #undef nk_wmask
+}
+
+NK_INTERN void
+nk_zero(void *ptr, nk_size size)
+{
+    NK_ASSERT(ptr);
+    NK_MEMSET(ptr, 0, size);
+}
+
+NK_API int
+nk_strlen(const char *str)
+{
+    int siz = 0;
+    NK_ASSERT(str);
+    while (str && *str++ != '\0') siz++;
+    return siz;
+}
+
+NK_API int
+nk_strtoi(const char *str, const char **endptr)
+{
+    int neg = 1;
+    const char *p = str;
+    int value = 0;
+
+    NK_ASSERT(str);
+    if (!str) return 0;
+
+    /* skip whitespace */
+    while (*p == ' ') p++;
+    if (*p == '-') {
+        neg = -1;
+        p++;
+    }
+    while (*p && *p >= '0' && *p <= '9') {
+        value = value * 10 + (int) (*p - '0');
+        p++;
+    }
+    if (endptr)
+        *endptr = p;
+    return neg*value;
+}
+
+NK_API double
+nk_strtod(const char *str, const char **endptr)
+{
+    double m;
+    double neg = 1.0;
+    const char *p = str;
+    double value = 0;
+    double number = 0;
+
+    NK_ASSERT(str);
+    if (!str) return 0;
+
+    /* skip whitespace */
+    while (*p == ' ') p++;
+    if (*p == '-') {
+        neg = -1.0;
+        p++;
+    }
+
+    while (*p && *p != '.' && *p != 'e') {
+        value = value * 10.0 + (double) (*p - '0');
+        p++;
+    }
+
+    if (*p == '.') {
+        p++;
+        for(m = 0.1; *p && *p != 'e'; p++ ) {
+            value = value + (double) (*p - '0') * m;
+            m *= 0.1;
+        }
+    }
+    if (*p == 'e') {
+        int i, pow, div;
+        p++;
+        if (*p == '-') {
+            div = nk_true;
+            p++;
+        } else if (*p == '+') {
+            div = nk_false;
+            p++;
+        } else div = nk_false;
+
+        for (pow = 0; *p; p++)
+            pow = pow * 10 + (int) (*p - '0');
+
+        for (m = 1.0, i = 0; i < pow; i++)
+            m *= 10.0;
+
+        if (div)
+            value /= m;
+        else value *= m;
+    }
+    number = value * neg;
+    if (endptr)
+        *endptr = p;
+    return number;
+}
+
+NK_API float
+nk_strtof(const char *str, const char **endptr)
+{
+    float float_value;
+    double double_value;
+    double_value = NK_STRTOD(str, endptr);
+    float_value = (float)double_value;
+    return float_value;
+}
+
+NK_API int
+nk_stricmp(const char *s1, const char *s2)
+{
+    nk_int c1,c2,d;
+    do {
+        c1 = *s1++;
+        c2 = *s2++;
+        d = c1 - c2;
+        while (d) {
+            if (c1 <= 'Z' && c1 >= 'A') {
+                d += ('a' - 'A');
+                if (!d) break;
+            }
+            if (c2 <= 'Z' && c2 >= 'A') {
+                d -= ('a' - 'A');
+                if (!d) break;
+            }
+            return ((d >= 0) << 1) - 1;
+        }
+    } while (c1);
+    return 0;
+}
+
+NK_API int
+nk_stricmpn(const char *s1, const char *s2, int n)
+{
+    int c1,c2,d;
+    NK_ASSERT(n >= 0);
+    do {
+        c1 = *s1++;
+        c2 = *s2++;
+        if (!n--) return 0;
+
+        d = c1 - c2;
+        while (d) {
+            if (c1 <= 'Z' && c1 >= 'A') {
+                d += ('a' - 'A');
+                if (!d) break;
+            }
+            if (c2 <= 'Z' && c2 >= 'A') {
+                d -= ('a' - 'A');
+                if (!d) break;
+            }
+            return ((d >= 0) << 1) - 1;
+        }
+    } while (c1);
+    return 0;
+}
+
+NK_INTERN int
+nk_str_match_here(const char *regexp, const char *text)
+{
+    if (regexp[0] == '\0')
+        return 1;
+    if (regexp[1] == '*')
+        return nk_str_match_star(regexp[0], regexp+2, text);
+    if (regexp[0] == '$' && regexp[1] == '\0')
+        return *text == '\0';
+    if (*text!='\0' && (regexp[0]=='.' || regexp[0]==*text))
+        return nk_str_match_here(regexp+1, text+1);
+    return 0;
+}
+
+NK_INTERN int
+nk_str_match_star(int c, const char *regexp, const char *text)
+{
+    do {/* a '* matches zero or more instances */
+        if (nk_str_match_here(regexp, text))
+            return 1;
+    } while (*text != '\0' && (*text++ == c || c == '.'));
+    return 0;
+}
+
+NK_API int
+nk_strfilter(const char *text, const char *regexp)
+{
+    /*
+    c    matches any literal character c
+    .    matches any single character
+    ^    matches the beginning of the input string
+    $    matches the end of the input string
+    *    matches zero or more occurrences of the previous character*/
+    if (regexp[0] == '^')
+        return nk_str_match_here(regexp+1, text);
+    do {    /* must look even if string is empty */
+        if (nk_str_match_here(regexp, text))
+            return 1;
+    } while (*text++ != '\0');
+    return 0;
+}
+
+NK_API int
+nk_strmatch_fuzzy_text(const char *str, int str_len,
+    const char *pattern, int *out_score)
+{
+    /* Returns true if each character in pattern is found sequentially within str
+     * if found then out_score is also set. Score value has no intrinsic meaning.
+     * Range varies with pattern. Can only compare scores with same search pattern. */
+
+    /* ------- scores --------- */
+    /* bonus for adjacent matches */
+    #define NK_ADJACENCY_BONUS 5
+    /* bonus if match occurs after a separator */
+    #define NK_SEPARATOR_BONUS 10
+    /* bonus if match is uppercase and prev is lower */
+    #define NK_CAMEL_BONUS 10
+    /* penalty applied for every letter in str before the first match */
+    #define NK_LEADING_LETTER_PENALTY (-3)
+    /* maximum penalty for leading letters */
+    #define NK_MAX_LEADING_LETTER_PENALTY (-9)
+    /* penalty for every letter that doesn't matter */
+    #define NK_UNMATCHED_LETTER_PENALTY (-1)
+
+    /* loop variables */
+    int score = 0;
+    char const * pattern_iter = pattern;
+    int str_iter = 0;
+    int prev_matched = nk_false;
+    int prev_lower = nk_false;
+    /* true so if first letter match gets separator bonus*/
+    int prev_separator = nk_true;
+
+    /* use "best" matched letter if multiple string letters match the pattern */
+    char const * best_letter = 0;
+    int best_letter_score = 0;
+
+    /* loop over strings */
+    NK_ASSERT(str);
+    NK_ASSERT(pattern);
+    if (!str || !str_len || !pattern) return 0;
+    while (str_iter < str_len)
+    {
+        const char pattern_letter = *pattern_iter;
+        const char str_letter = str[str_iter];
+
+        int next_match = *pattern_iter != '\0' &&
+            nk_to_lower(pattern_letter) == nk_to_lower(str_letter);
+        int rematch = best_letter && nk_to_upper(*best_letter) == nk_to_upper(str_letter);
+
+        int advanced = next_match && best_letter;
+        int pattern_repeat = best_letter && *pattern_iter != '\0';
+        pattern_repeat = pattern_repeat &&
+            nk_to_lower(*best_letter) == nk_to_lower(pattern_letter);
+
+        if (advanced || pattern_repeat) {
+            score += best_letter_score;
+            best_letter = 0;
+            best_letter_score = 0;
+        }
+
+        if (next_match || rematch)
+        {
+            int new_score = 0;
+            /* Apply penalty for each letter before the first pattern match */
+            if (pattern_iter == pattern) {
+                int count = (int)(&str[str_iter] - str);
+                int penalty = NK_LEADING_LETTER_PENALTY * count;
+                if (penalty < NK_MAX_LEADING_LETTER_PENALTY)
+                    penalty = NK_MAX_LEADING_LETTER_PENALTY;
+
+                score += penalty;
+            }
+
+            /* apply bonus for consecutive bonuses */
+            if (prev_matched)
+                new_score += NK_ADJACENCY_BONUS;
+
+            /* apply bonus for matches after a separator */
+            if (prev_separator)
+                new_score += NK_SEPARATOR_BONUS;
+
+            /* apply bonus across camel case boundaries */
+            if (prev_lower && nk_is_upper(str_letter))
+                new_score += NK_CAMEL_BONUS;
+
+            /* update pattern iter IFF the next pattern letter was matched */
+            if (next_match)
+                ++pattern_iter;
+
+            /* update best letter in str which may be for a "next" letter or a rematch */
+            if (new_score >= best_letter_score) {
+                /* apply penalty for now skipped letter */
+                if (best_letter != 0)
+                    score += NK_UNMATCHED_LETTER_PENALTY;
+
+                best_letter = &str[str_iter];
+                best_letter_score = new_score;
+            }
+            prev_matched = nk_true;
+        } else {
+            score += NK_UNMATCHED_LETTER_PENALTY;
+            prev_matched = nk_false;
+        }
+
+        /* separators should be more easily defined */
+        prev_lower = nk_is_lower(str_letter) != 0;
+        prev_separator = str_letter == '_' || str_letter == ' ';
+
+        ++str_iter;
+    }
+
+    /* apply score for last match */
+    if (best_letter)
+        score += best_letter_score;
+
+    /* did not match full pattern */
+    if (*pattern_iter != '\0')
+        return nk_false;
+
+    if (out_score)
+        *out_score = score;
+    return nk_true;
+}
+
+NK_API int
+nk_strmatch_fuzzy_string(char const *str, char const *pattern, int *out_score)
+{return nk_strmatch_fuzzy_text(str, nk_strlen(str), pattern, out_score);}
+
+NK_INTERN int
+nk_string_float_limit(char *string, int prec)
+{
+    int dot = 0;
+    char *c = string;
+    while (*c) {
+        if (*c == '.') {
+            dot = 1;
+            c++;
+            continue;
+        }
+        if (dot == (prec+1)) {
+            *c = 0;
+            break;
+        }
+        if (dot > 0) dot++;
+        c++;
+    }
+    return (int)(c - string);
+}
+
+NK_INTERN double
+nk_pow(double x, int n)
+{
+    /*  check the sign of n */
+    double r = 1;
+    int plus = n >= 0;
+    n = (plus) ? n : -n;
+    while (n > 0) {
+        if ((n & 1) == 1)
+            r *= x;
+        n /= 2;
+        x *= x;
+    }
+    return plus ? r : 1.0 / r;
+}
+
+NK_INTERN int
+nk_ifloord(double x)
+{
+    x = (double)((int)x - ((x < 0.0) ? 1 : 0));
+    return (int)x;
+}
+
+NK_INTERN int
+nk_ifloorf(float x)
+{
+    x = (float)((int)x - ((x < 0.0f) ? 1 : 0));
+    return (int)x;
+}
+
+NK_INTERN int
+nk_iceilf(float x)
+{
+    if (x >= 0) {
+        int i = (int)x;
+        return (x > i) ? i+1: i;
+    } else {
+        int t = (int)x;
+        float r = x - (float)t;
+        return (r > 0.0f) ? t+1: t;
+    }
+}
+
+NK_INTERN int
+nk_log10(double n)
+{
+    int neg;
+    int ret;
+    int exp = 0;
+
+    neg = (n < 0) ? 1 : 0;
+    ret = (neg) ? (int)-n : (int)n;
+    while ((ret / 10) > 0) {
+        ret /= 10;
+        exp++;
+    }
+    if (neg) exp = -exp;
+    return exp;
+}
+
+NK_INTERN void
+nk_strrev_ascii(char *s)
+{
+    int len = nk_strlen(s);
+    int end = len / 2;
+    int i = 0;
+    char t;
+    for (; i < end; ++i) {
+        t = s[i];
+        s[i] = s[len - 1 - i];
+        s[len -1 - i] = t;
+    }
+}
+
+NK_INTERN char*
+nk_itoa(char *s, long n)
+{
+    long i = 0;
+    if (n == 0) {
+        s[i++] = '0';
+        s[i] = 0;
+        return s;
+    }
+    if (n < 0) {
+        s[i++] = '-';
+        n = -n;
+    }
+    while (n > 0) {
+        s[i++] = (char)('0' + (n % 10));
+        n /= 10;
+    }
+    s[i] = 0;
+    if (s[0] == '-')
+        ++s;
+
+    nk_strrev_ascii(s);
+    return s;
+}
+
+NK_INTERN char*
+nk_dtoa(char *s, double n)
+{
+    int useExp = 0;
+    int digit = 0, m = 0, m1 = 0;
+    char *c = s;
+    int neg = 0;
+
+    NK_ASSERT(s);
+    if (!s) return 0;
+
+    if (n == 0.0) {
+        s[0] = '0'; s[1] = '\0';
+        return s;
+    }
+
+    neg = (n < 0);
+    if (neg) n = -n;
+
+    /* calculate magnitude */
+    m = nk_log10(n);
+    useExp = (m >= 14 || (neg && m >= 9) || m <= -9);
+    if (neg) *(c++) = '-';
+
+    /* set up for scientific notation */
+    if (useExp) {
+        if (m < 0)
+           m -= 1;
+        n = n / (double)nk_pow(10.0, m);
+        m1 = m;
+        m = 0;
+    }
+    if (m < 1.0) {
+        m = 0;
+    }
+
+    /* convert the number */
+    while (n > NK_FLOAT_PRECISION || m >= 0) {
+        double weight = nk_pow(10.0, m);
+        if (weight > 0) {
+            double t = (double)n / weight;
+            digit = nk_ifloord(t);
+            n -= ((double)digit * weight);
+            *(c++) = (char)('0' + (char)digit);
+        }
+        if (m == 0 && n > 0)
+            *(c++) = '.';
+        m--;
+    }
+
+    if (useExp) {
+        /* convert the exponent */
+        int i, j;
+        *(c++) = 'e';
+        if (m1 > 0) {
+            *(c++) = '+';
+        } else {
+            *(c++) = '-';
+            m1 = -m1;
+        }
+        m = 0;
+        while (m1 > 0) {
+            *(c++) = (char)('0' + (char)(m1 % 10));
+            m1 /= 10;
+            m++;
+        }
+        c -= m;
+        for (i = 0, j = m-1; i<j; i++, j--) {
+            /* swap without temporary */
+            c[i] ^= c[j];
+            c[j] ^= c[i];
+            c[i] ^= c[j];
+        }
+        c += m;
+    }
+    *(c) = '\0';
+    return s;
+}
+
+#ifdef NK_INCLUDE_STANDARD_VARARGS
+#ifndef NK_INCLUDE_STANDARD_IO
+static int
+nk_vsnprintf(char *buf, int buf_size, const char *fmt, va_list args)
+{
+    enum nk_arg_type {
+        NK_ARG_TYPE_CHAR,
+        NK_ARG_TYPE_SHORT,
+        NK_ARG_TYPE_DEFAULT,
+        NK_ARG_TYPE_LONG
+    };
+    enum nk_arg_flags {
+        NK_ARG_FLAG_LEFT = 0x01,
+        NK_ARG_FLAG_PLUS = 0x02,
+        NK_ARG_FLAG_SPACE = 0x04,
+        NK_ARG_FLAG_NUM = 0x10,
+        NK_ARG_FLAG_ZERO = 0x20
+    };
+
+    char number_buffer[NK_MAX_NUMBER_BUFFER];
+    enum nk_arg_type arg_type = NK_ARG_TYPE_DEFAULT;
+    int precision = NK_DEFAULT;
+    int width = NK_DEFAULT;
+    nk_flags flag = 0;
+
+    int len = 0;
+    int result = -1;
+    const char *iter = fmt;
+
+    NK_ASSERT(buf);
+    NK_ASSERT(buf_size);
+    if (!buf || !buf_size || !fmt) return 0;
+    for (iter = fmt; *iter && len < buf_size; iter++) {
+        /* copy all non-format characters */
+        while (*iter && (*iter != '%') && (len < buf_size))
+            buf[len++] = *iter++;
+        if (!(*iter) || len >= buf_size) break;
+        iter++;
+
+        /* flag arguments */
+        while (*iter) {
+            if (*iter == '-') flag |= NK_ARG_FLAG_LEFT;
+            else if (*iter == '+') flag |= NK_ARG_FLAG_PLUS;
+            else if (*iter == ' ') flag |= NK_ARG_FLAG_SPACE;
+            else if (*iter == '#') flag |= NK_ARG_FLAG_NUM;
+            else if (*iter == '0') flag |= NK_ARG_FLAG_ZERO;
+            else break;
+            iter++;
+        }
+
+        /* width argument */
+        width = NK_DEFAULT;
+        if (*iter >= '1' && *iter <= '9') {
+            const char *end;
+            width = nk_strtoi(iter, &end);
+            if (end == iter)
+                width = -1;
+            else iter = end;
+        } else if (*iter == '*') {
+            width = va_arg(args, int);
+            iter++;
+        }
+
+        /* precision argument */
+        precision = NK_DEFAULT;
+        if (*iter == '.') {
+            iter++;
+            if (*iter == '*') {
+                precision = va_arg(args, int);
+                iter++;
+            } else {
+                const char *end;
+                precision = nk_strtoi(iter, &end);
+                if (end == iter)
+                    precision = -1;
+                else iter = end;
+            }
+        }
+
+        /* length modifier */
+        if (*iter == 'h') {
+            if (*(iter+1) == 'h') {
+                arg_type = NK_ARG_TYPE_CHAR;
+                iter++;
+            } else arg_type = NK_ARG_TYPE_SHORT;
+            iter++;
+        } else if (*iter == 'l') {
+            arg_type = NK_ARG_TYPE_LONG;
+            iter++;
+        } else arg_type = NK_ARG_TYPE_DEFAULT;
+
+        /* specifier */
+        if (*iter == '%') {
+            NK_ASSERT(arg_type == NK_ARG_TYPE_DEFAULT);
+            NK_ASSERT(precision == NK_DEFAULT);
+            NK_ASSERT(width == NK_DEFAULT);
+            if (len < buf_size)
+                buf[len++] = '%';
+        } else if (*iter == 's') {
+            /* string  */
+            const char *str = va_arg(args, const char*);
+            NK_ASSERT(str != buf && "buffer and argument are not allowed to overlap!");
+            NK_ASSERT(arg_type == NK_ARG_TYPE_DEFAULT);
+            NK_ASSERT(precision == NK_DEFAULT);
+            NK_ASSERT(width == NK_DEFAULT);
+            if (str == buf) return -1;
+            while (str && *str && len < buf_size)
+                buf[len++] = *str++;
+        } else if (*iter == 'n') {
+            /* current length callback */
+            signed int *n = va_arg(args, int*);
+            NK_ASSERT(arg_type == NK_ARG_TYPE_DEFAULT);
+            NK_ASSERT(precision == NK_DEFAULT);
+            NK_ASSERT(width == NK_DEFAULT);
+            if (n) *n = len;
+        } else if (*iter == 'c' || *iter == 'i' || *iter == 'd') {
+            /* signed integer */
+            long value = 0;
+            const char *num_iter;
+            int num_len, num_print, padding;
+            int cur_precision = NK_MAX(precision, 1);
+            int cur_width = NK_MAX(width, 0);
+
+            /* retrieve correct value type */
+            if (arg_type == NK_ARG_TYPE_CHAR)
+                value = (signed char)va_arg(args, int);
+            else if (arg_type == NK_ARG_TYPE_SHORT)
+                value = (signed short)va_arg(args, int);
+            else if (arg_type == NK_ARG_TYPE_LONG)
+                value = va_arg(args, signed long);
+            else if (*iter == 'c')
+                value = (unsigned char)va_arg(args, int);
+            else value = va_arg(args, signed int);
+
+            /* convert number to string */
+            nk_itoa(number_buffer, value);
+            num_len = nk_strlen(number_buffer);
+            padding = NK_MAX(cur_width - NK_MAX(cur_precision, num_len), 0);
+            if ((flag & NK_ARG_FLAG_PLUS) || (flag & NK_ARG_FLAG_SPACE))
+                padding = NK_MAX(padding-1, 0);
+
+            /* fill left padding up to a total of `width` characters */
+            if (!(flag & NK_ARG_FLAG_LEFT)) {
+                while (padding-- > 0 && (len < buf_size)) {
+                    if ((flag & NK_ARG_FLAG_ZERO) && (precision == NK_DEFAULT))
+                        buf[len++] = '0';
+                    else buf[len++] = ' ';
+                }
+            }
+
+            /* copy string value representation into buffer */
+            if ((flag & NK_ARG_FLAG_PLUS) && value >= 0 && len < buf_size)
+                buf[len++] = '+';
+            else if ((flag & NK_ARG_FLAG_SPACE) && value >= 0 && len < buf_size)
+                buf[len++] = ' ';
+
+            /* fill up to precision number of digits with '0' */
+            num_print = NK_MAX(cur_precision, num_len);
+            while (precision && (num_print > num_len) && (len < buf_size)) {
+                buf[len++] = '0';
+                num_print--;
+            }
+
+            /* copy string value representation into buffer */
+            num_iter = number_buffer;
+            while (precision && *num_iter && len < buf_size)
+                buf[len++] = *num_iter++;
+
+            /* fill right padding up to width characters */
+            if (flag & NK_ARG_FLAG_LEFT) {
+                while ((padding-- > 0) && (len < buf_size))
+                    buf[len++] = ' ';
+            }
+        } else if (*iter == 'o' || *iter == 'x' || *iter == 'X' || *iter == 'u') {
+            /* unsigned integer */
+            unsigned long value = 0;
+            int num_len = 0, num_print, padding = 0;
+            int cur_precision = NK_MAX(precision, 1);
+            int cur_width = NK_MAX(width, 0);
+            unsigned int base = (*iter == 'o') ? 8: (*iter == 'u')? 10: 16;
+
+            /* print oct/hex/dec value */
+            const char *upper_output_format = "0123456789ABCDEF";
+            const char *lower_output_format = "0123456789abcdef";
+            const char *output_format = (*iter == 'x') ?
+                lower_output_format: upper_output_format;
+
+            /* retrieve correct value type */
+            if (arg_type == NK_ARG_TYPE_CHAR)
+                value = (unsigned char)va_arg(args, int);
+            else if (arg_type == NK_ARG_TYPE_SHORT)
+                value = (unsigned short)va_arg(args, int);
+            else if (arg_type == NK_ARG_TYPE_LONG)
+                value = va_arg(args, unsigned long);
+            else value = va_arg(args, unsigned int);
+
+            do {
+                /* convert decimal number into hex/oct number */
+                int digit = output_format[value % base];
+                if (num_len < NK_MAX_NUMBER_BUFFER)
+                    number_buffer[num_len++] = (char)digit;
+                value /= base;
+            } while (value > 0);
+
+            num_print = NK_MAX(cur_precision, num_len);
+            padding = NK_MAX(cur_width - NK_MAX(cur_precision, num_len), 0);
+            if (flag & NK_ARG_FLAG_NUM)
+                padding = NK_MAX(padding-1, 0);
+
+            /* fill left padding up to a total of `width` characters */
+            if (!(flag & NK_ARG_FLAG_LEFT)) {
+                while ((padding-- > 0) && (len < buf_size)) {
+                    if ((flag & NK_ARG_FLAG_ZERO) && (precision == NK_DEFAULT))
+                        buf[len++] = '0';
+                    else buf[len++] = ' ';
+                }
+            }
+
+            /* fill up to precision number of digits */
+            if (num_print && (flag & NK_ARG_FLAG_NUM)) {
+                if ((*iter == 'o') && (len < buf_size)) {
+                    buf[len++] = '0';
+                } else if ((*iter == 'x') && ((len+1) < buf_size)) {
+                    buf[len++] = '0';
+                    buf[len++] = 'x';
+                } else if ((*iter == 'X') && ((len+1) < buf_size)) {
+                    buf[len++] = '0';
+                    buf[len++] = 'X';
+                }
+            }
+            while (precision && (num_print > num_len) && (len < buf_size)) {
+                buf[len++] = '0';
+                num_print--;
+            }
+
+            /* reverse number direction */
+            while (num_len > 0) {
+                if (precision && (len < buf_size))
+                    buf[len++] = number_buffer[num_len-1];
+                num_len--;
+            }
+
+            /* fill right padding up to width characters */
+            if (flag & NK_ARG_FLAG_LEFT) {
+                while ((padding-- > 0) && (len < buf_size))
+                    buf[len++] = ' ';
+            }
+        } else if (*iter == 'f') {
+            /* floating point */
+            const char *num_iter;
+            int cur_precision = (precision < 0) ? 6: precision;
+            int prefix, cur_width = NK_MAX(width, 0);
+            double value = va_arg(args, double);
+            int num_len = 0, frac_len = 0, dot = 0;
+            int padding = 0;
+
+            NK_ASSERT(arg_type == NK_ARG_TYPE_DEFAULT);
+            NK_DTOA(number_buffer, value);
+            num_len = nk_strlen(number_buffer);
+
+            /* calculate padding */
+            num_iter = number_buffer;
+            while (*num_iter && *num_iter != '.')
+                num_iter++;
+
+            prefix = (*num_iter == '.')?(int)(num_iter - number_buffer)+1:0;
+            padding = NK_MAX(cur_width - (prefix + NK_MIN(cur_precision, num_len - prefix)) , 0);
+            if ((flag & NK_ARG_FLAG_PLUS) || (flag & NK_ARG_FLAG_SPACE))
+                padding = NK_MAX(padding-1, 0);
+
+            /* fill left padding up to a total of `width` characters */
+            if (!(flag & NK_ARG_FLAG_LEFT)) {
+                while (padding-- > 0 && (len < buf_size)) {
+                    if (flag & NK_ARG_FLAG_ZERO)
+                        buf[len++] = '0';
+                    else buf[len++] = ' ';
+                }
+            }
+
+            /* copy string value representation into buffer */
+            num_iter = number_buffer;
+            if ((flag & NK_ARG_FLAG_PLUS) && (value >= 0) && (len < buf_size))
+                buf[len++] = '+';
+            else if ((flag & NK_ARG_FLAG_SPACE) && (value >= 0) && (len < buf_size))
+                buf[len++] = ' ';
+            while (*num_iter) {
+                if (dot) frac_len++;
+                if (len < buf_size)
+                    buf[len++] = *num_iter;
+                if (*num_iter == '.') dot = 1;
+                if (frac_len >= cur_precision) break;
+                num_iter++;
+            }
+
+            /* fill number up to precision */
+            while (frac_len < cur_precision) {
+                if (!dot && len < buf_size) {
+                    buf[len++] = '.';
+                    dot = 1;
+                }
+                if (len < buf_size)
+                    buf[len++] = '0';
+                frac_len++;
+            }
+
+            /* fill right padding up to width characters */
+            if (flag & NK_ARG_FLAG_LEFT) {
+                while ((padding-- > 0) && (len < buf_size))
+                    buf[len++] = ' ';
+            }
+        } else {
+            /* Specifier not supported: g,G,e,E,p,z */
+            NK_ASSERT(0 && "specifier is not supported!");
+            return result;
+        }
+    }
+    buf[(len >= buf_size)?(buf_size-1):len] = 0;
+    result = (len >= buf_size)?-1:len;
+    return result;
+}
+#endif
+
+NK_INTERN int
+nk_strfmt(char *buf, int buf_size, const char *fmt, va_list args)
+{
+    int result = -1;
+    NK_ASSERT(buf);
+    NK_ASSERT(buf_size);
+    if (!buf || !buf_size || !fmt) return 0;
+#ifdef NK_INCLUDE_STANDARD_IO
+    result = NK_VSNPRINTF(buf, (nk_size)buf_size, fmt, args);
+    result = (result >= buf_size) ? -1: result;
+    buf[buf_size-1] = 0;
+#else
+    result = nk_vsnprintf(buf, buf_size, fmt, args);
+#endif
+    return result;
+}
+#endif
+
+NK_API nk_hash
+nk_murmur_hash(const void * key, int len, nk_hash seed)
+{
+    /* 32-Bit MurmurHash3: https://code.google.com/p/smhasher/wiki/MurmurHash3*/
+    #define NK_ROTL(x,r) ((x) << (r) | ((x) >> (32 - r)))
+    union {const nk_uint *i; const nk_byte *b;} conv = {0};
+    const nk_byte *data = (const nk_byte*)key;
+    const int nblocks = len/4;
+    nk_uint h1 = seed;
+    const nk_uint c1 = 0xcc9e2d51;
+    const nk_uint c2 = 0x1b873593;
+    const nk_byte *tail;
+    const nk_uint *blocks;
+    nk_uint k1;
+    int i;
+
+    /* body */
+    if (!key) return 0;
+    conv.b = (data + nblocks*4);
+    blocks = (const nk_uint*)conv.i;
+    for (i = -nblocks; i; ++i) {
+        k1 = blocks[i];
+        k1 *= c1;
+        k1 = NK_ROTL(k1,15);
+        k1 *= c2;
+
+        h1 ^= k1;
+        h1 = NK_ROTL(h1,13);
+        h1 = h1*5+0xe6546b64;
+    }
+
+    /* tail */
+    tail = (const nk_byte*)(data + nblocks*4);
+    k1 = 0;
+    switch (len & 3) {
+    case 3: k1 ^= (nk_uint)(tail[2] << 16);
+    case 2: k1 ^= (nk_uint)(tail[1] << 8u);
+    case 1: k1 ^= tail[0];
+            k1 *= c1;
+            k1 = NK_ROTL(k1,15);
+            k1 *= c2;
+            h1 ^= k1;
+    default: break;
+    }
+
+    /* finalization */
+    h1 ^= (nk_uint)len;
+    /* fmix32 */
+    h1 ^= h1 >> 16;
+    h1 *= 0x85ebca6b;
+    h1 ^= h1 >> 13;
+    h1 *= 0xc2b2ae35;
+    h1 ^= h1 >> 16;
+
+    #undef NK_ROTL
+    return h1;
+}
+
+#ifdef NK_INCLUDE_STANDARD_IO
+NK_INTERN char*
+nk_file_load(const char* path, nk_size* siz, struct nk_allocator *alloc)
+{
+    char *buf;
+    FILE *fd;
+    long ret;
+
+    NK_ASSERT(path);
+    NK_ASSERT(siz);
+    NK_ASSERT(alloc);
+    if (!path || !siz || !alloc)
+        return 0;
+
+    fd = fopen(path, "rb");
+    if (!fd) return 0;
+    fseek(fd, 0, SEEK_END);
+    ret = ftell(fd);
+    if (ret < 0) {
+        fclose(fd);
+        return 0;
+    }
+    *siz = (nk_size)ret;
+    fseek(fd, 0, SEEK_SET);
+    buf = (char*)alloc->alloc(alloc->userdata,0, *siz);
+    NK_ASSERT(buf);
+    if (!buf) {
+        fclose(fd);
+        return 0;
+    }
+    *siz = (nk_size)fread(buf, *siz, 1, fd);
+    fclose(fd);
+    return buf;
+}
+#endif
+
+/*
+ * ==============================================================
+ *
+ *                          COLOR
+ *
+ * ===============================================================
+ */
+NK_INTERN int
+nk_parse_hex(const char *p, int length)
+{
+    int i = 0;
+    int len = 0;
+    while (len < length) {
+        i <<= 4;
+        if (p[len] >= 'a' && p[len] <= 'f')
+            i += ((p[len] - 'a') + 10);
+        else if (p[len] >= 'A' && p[len] <= 'F')
+            i += ((p[len] - 'A') + 10);
+        else i += (p[len] - '0');
+        len++;
+    }
+    return i;
+}
+
+NK_API struct nk_color
+nk_rgba(int r, int g, int b, int a)
+{
+    struct nk_color ret;
+    ret.r = (nk_byte)NK_CLAMP(0, r, 255);
+    ret.g = (nk_byte)NK_CLAMP(0, g, 255);
+    ret.b = (nk_byte)NK_CLAMP(0, b, 255);
+    ret.a = (nk_byte)NK_CLAMP(0, a, 255);
+    return ret;
+}
+
+NK_API struct nk_color
+nk_rgb_hex(const char *rgb)
+{
+    struct nk_color col;
+    const char *c = rgb;
+    if (*c == '#') c++;
+    col.r = (nk_byte)nk_parse_hex(c, 2);
+    col.g = (nk_byte)nk_parse_hex(c+2, 2);
+    col.b = (nk_byte)nk_parse_hex(c+4, 2);
+    col.a = 255;
+    return col;
+}
+
+NK_API struct nk_color
+nk_rgba_hex(const char *rgb)
+{
+    struct nk_color col;
+    const char *c = rgb;
+    if (*c == '#') c++;
+    col.r = (nk_byte)nk_parse_hex(c, 2);
+    col.g = (nk_byte)nk_parse_hex(c+2, 2);
+    col.b = (nk_byte)nk_parse_hex(c+4, 2);
+    col.a = (nk_byte)nk_parse_hex(c+6, 2);
+    return col;
+}
+
+NK_API void
+nk_color_hex_rgba(char *output, struct nk_color col)
+{
+    #define NK_TO_HEX(i) ((i) <= 9 ? '0' + (i): 'A' - 10 + (i))
+    output[0] = (char)NK_TO_HEX((col.r & 0xF0) >> 4);
+    output[1] = (char)NK_TO_HEX((col.r & 0x0F));
+    output[2] = (char)NK_TO_HEX((col.g & 0xF0) >> 4);
+    output[3] = (char)NK_TO_HEX((col.g & 0x0F));
+    output[4] = (char)NK_TO_HEX((col.b & 0xF0) >> 4);
+    output[5] = (char)NK_TO_HEX((col.b & 0x0F));
+    output[6] = (char)NK_TO_HEX((col.a & 0xF0) >> 4);
+    output[7] = (char)NK_TO_HEX((col.a & 0x0F));
+    output[8] = '\0';
+    #undef NK_TO_HEX
+}
+
+NK_API void
+nk_color_hex_rgb(char *output, struct nk_color col)
+{
+    #define NK_TO_HEX(i) ((i) <= 9 ? '0' + (i): 'A' - 10 + (i))
+    output[0] = (char)NK_TO_HEX((col.r & 0xF0) >> 4);
+    output[1] = (char)NK_TO_HEX((col.r & 0x0F));
+    output[2] = (char)NK_TO_HEX((col.g & 0xF0) >> 4);
+    output[3] = (char)NK_TO_HEX((col.g & 0x0F));
+    output[4] = (char)NK_TO_HEX((col.b & 0xF0) >> 4);
+    output[5] = (char)NK_TO_HEX((col.b & 0x0F));
+    output[6] = '\0';
+    #undef NK_TO_HEX
+}
+
+NK_API struct nk_color
+nk_rgba_iv(const int *c)
+{
+    return nk_rgba(c[0], c[1], c[2], c[3]);
+}
+
+NK_API struct nk_color
+nk_rgba_bv(const nk_byte *c)
+{
+    return nk_rgba(c[0], c[1], c[2], c[3]);
+}
+
+NK_API struct nk_color
+nk_rgb(int r, int g, int b)
+{
+    struct nk_color ret;
+    ret.r = (nk_byte)NK_CLAMP(0, r, 255);
+    ret.g = (nk_byte)NK_CLAMP(0, g, 255);
+    ret.b = (nk_byte)NK_CLAMP(0, b, 255);
+    ret.a = (nk_byte)255;
+    return ret;
+}
+
+NK_API struct nk_color
+nk_rgb_iv(const int *c)
+{
+    return nk_rgb(c[0], c[1], c[2]);
+}
+
+NK_API struct nk_color
+nk_rgb_bv(const nk_byte* c)
+{
+    return nk_rgb(c[0], c[1], c[2]);
+}
+
+NK_API struct nk_color
+nk_rgba_u32(nk_uint in)
+{
+    struct nk_color ret;
+    ret.r = (in & 0xFF);
+    ret.g = ((in >> 8) & 0xFF);
+    ret.b = ((in >> 16) & 0xFF);
+    ret.a = (nk_byte)((in >> 24) & 0xFF);
+    return ret;
+}
+
+NK_API struct nk_color
+nk_rgba_f(float r, float g, float b, float a)
+{
+    struct nk_color ret;
+    ret.r = (nk_byte)(NK_SATURATE(r) * 255.0f);
+    ret.g = (nk_byte)(NK_SATURATE(g) * 255.0f);
+    ret.b = (nk_byte)(NK_SATURATE(b) * 255.0f);
+    ret.a = (nk_byte)(NK_SATURATE(a) * 255.0f);
+    return ret;
+}
+
+NK_API struct nk_color
+nk_rgba_fv(const float *c)
+{
+    return nk_rgba_f(c[0], c[1], c[2], c[3]);
+}
+
+NK_API struct nk_color
+nk_rgb_f(float r, float g, float b)
+{
+    struct nk_color ret;
+    ret.r = (nk_byte)(NK_SATURATE(r) * 255.0f);
+    ret.g = (nk_byte)(NK_SATURATE(g) * 255.0f);
+    ret.b = (nk_byte)(NK_SATURATE(b) * 255.0f);
+    ret.a = 255;
+    return ret;
+}
+
+NK_API struct nk_color
+nk_rgb_fv(const float *c)
+{
+    return nk_rgb_f(c[0], c[1], c[2]);
+}
+
+NK_API struct nk_color
+nk_hsv(int h, int s, int v)
+{
+    return nk_hsva(h, s, v, 255);
+}
+
+NK_API struct nk_color
+nk_hsv_iv(const int *c)
+{
+    return nk_hsv(c[0], c[1], c[2]);
+}
+
+NK_API struct nk_color
+nk_hsv_bv(const nk_byte *c)
+{
+    return nk_hsv(c[0], c[1], c[2]);
+}
+
+NK_API struct nk_color
+nk_hsv_f(float h, float s, float v)
+{
+    return nk_hsva_f(h, s, v, 1.0f);
+}
+
+NK_API struct nk_color
+nk_hsv_fv(const float *c)
+{
+    return nk_hsv_f(c[0], c[1], c[2]);
+}
+
+NK_API struct nk_color
+nk_hsva(int h, int s, int v, int a)
+{
+    float hf = ((float)NK_CLAMP(0, h, 255)) / 255.0f;
+    float sf = ((float)NK_CLAMP(0, s, 255)) / 255.0f;
+    float vf = ((float)NK_CLAMP(0, v, 255)) / 255.0f;
+    float af = ((float)NK_CLAMP(0, a, 255)) / 255.0f;
+    return nk_hsva_f(hf, sf, vf, af);
+}
+
+NK_API struct nk_color
+nk_hsva_iv(const int *c)
+{
+    return nk_hsva(c[0], c[1], c[2], c[3]);
+}
+
+NK_API struct nk_color
+nk_hsva_bv(const nk_byte *c)
+{
+    return nk_hsva(c[0], c[1], c[2], c[3]);
+}
+
+NK_API struct nk_color
+nk_hsva_f(float h, float s, float v, float a)
+{
+    struct nk_colorf out = {0,0,0,0};
+    float p, q, t, f;
+    int i;
+
+    if (s <= 0.0f) {
+        out.r = v; out.g = v; out.b = v;
+        return nk_rgb_f(out.r, out.g, out.b);
+    }
+
+    h = h / (60.0f/360.0f);
+    i = (int)h;
+    f = h - (float)i;
+    p = v * (1.0f - s);
+    q = v * (1.0f - (s * f));
+    t = v * (1.0f - s * (1.0f - f));
+
+    switch (i) {
+    case 0: default: out.r = v; out.g = t; out.b = p; break;
+    case 1: out.r = q; out.g = v; out.b = p; break;
+    case 2: out.r = p; out.g = v; out.b = t; break;
+    case 3: out.r = p; out.g = q; out.b = v; break;
+    case 4: out.r = t; out.g = p; out.b = v; break;
+    case 5: out.r = v; out.g = p; out.b = q; break;
+    }
+    return nk_rgba_f(out.r, out.g, out.b, a);
+}
+
+NK_API struct nk_color
+nk_hsva_fv(const float *c)
+{
+    return nk_hsva_f(c[0], c[1], c[2], c[3]);
+}
+
+NK_API nk_uint
+nk_color_u32(struct nk_color in)
+{
+    nk_uint out = (nk_uint)in.r;
+    out |= ((nk_uint)in.g << 8);
+    out |= ((nk_uint)in.b << 16);
+    out |= ((nk_uint)in.a << 24);
+    return out;
+}
+
+NK_API void
+nk_color_f(float *r, float *g, float *b, float *a, struct nk_color in)
+{
+    NK_STORAGE const float s = 1.0f/255.0f;
+    *r = (float)in.r * s;
+    *g = (float)in.g * s;
+    *b = (float)in.b * s;
+    *a = (float)in.a * s;
+}
+
+NK_API void
+nk_color_fv(float *c, struct nk_color in)
+{
+    nk_color_f(&c[0], &c[1], &c[2], &c[3], in);
+}
+
+NK_API void
+nk_color_d(double *r, double *g, double *b, double *a, struct nk_color in)
+{
+    NK_STORAGE const double s = 1.0/255.0;
+    *r = (double)in.r * s;
+    *g = (double)in.g * s;
+    *b = (double)in.b * s;
+    *a = (double)in.a * s;
+}
+
+NK_API void
+nk_color_dv(double *c, struct nk_color in)
+{
+    nk_color_d(&c[0], &c[1], &c[2], &c[3], in);
+}
+
+NK_API void
+nk_color_hsv_f(float *out_h, float *out_s, float *out_v, struct nk_color in)
+{
+    float a;
+    nk_color_hsva_f(out_h, out_s, out_v, &a, in);
+}
+
+NK_API void
+nk_color_hsv_fv(float *out, struct nk_color in)
+{
+    float a;
+    nk_color_hsva_f(&out[0], &out[1], &out[2], &a, in);
+}
+
+NK_API void
+nk_color_hsva_f(float *out_h, float *out_s,
+    float *out_v, float *out_a, struct nk_color in)
+{
+    float chroma;
+    float K = 0.0f;
+    float r,g,b,a;
+
+    nk_color_f(&r,&g,&b,&a, in);
+    if (g < b) {
+        const float t = g; g = b; b = t;
+        K = -1.f;
+    }
+    if (r < g) {
+        const float t = r; r = g; g = t;
+        K = -2.f/6.0f - K;
+    }
+    chroma = r - ((g < b) ? g: b);
+    *out_h = NK_ABS(K + (g - b)/(6.0f * chroma + 1e-20f));
+    *out_s = chroma / (r + 1e-20f);
+    *out_v = r;
+    *out_a = (float)in.a / 255.0f;
+}
+
+NK_API void
+nk_color_hsva_fv(float *out, struct nk_color in)
+{
+    nk_color_hsva_f(&out[0], &out[1], &out[2], &out[3], in);
+}
+
+NK_API void
+nk_color_hsva_i(int *out_h, int *out_s, int *out_v,
+                int *out_a, struct nk_color in)
+{
+    float h,s,v,a;
+    nk_color_hsva_f(&h, &s, &v, &a, in);
+    *out_h = (nk_byte)(h * 255.0f);
+    *out_s = (nk_byte)(s * 255.0f);
+    *out_v = (nk_byte)(v * 255.0f);
+    *out_a = (nk_byte)(a * 255.0f);
+}
+
+NK_API void
+nk_color_hsva_iv(int *out, struct nk_color in)
+{
+    nk_color_hsva_i(&out[0], &out[1], &out[2], &out[3], in);
+}
+
+NK_API void
+nk_color_hsva_bv(nk_byte *out, struct nk_color in)
+{
+    int tmp[4];
+    nk_color_hsva_i(&tmp[0], &tmp[1], &tmp[2], &tmp[3], in);
+    out[0] = (nk_byte)tmp[0];
+    out[1] = (nk_byte)tmp[1];
+    out[2] = (nk_byte)tmp[2];
+    out[3] = (nk_byte)tmp[3];
+}
+
+NK_API void
+nk_color_hsva_b(nk_byte *h, nk_byte *s, nk_byte *v, nk_byte *a, struct nk_color in)
+{
+    int tmp[4];
+    nk_color_hsva_i(&tmp[0], &tmp[1], &tmp[2], &tmp[3], in);
+    *h = (nk_byte)tmp[0];
+    *s = (nk_byte)tmp[1];
+    *v = (nk_byte)tmp[2];
+    *a = (nk_byte)tmp[3];
+}
+
+NK_API void
+nk_color_hsv_i(int *out_h, int *out_s, int *out_v, struct nk_color in)
+{
+    int a;
+    nk_color_hsva_i(out_h, out_s, out_v, &a, in);
+}
+
+NK_API void
+nk_color_hsv_b(nk_byte *out_h, nk_byte *out_s, nk_byte *out_v, struct nk_color in)
+{
+    int tmp[4];
+    nk_color_hsva_i(&tmp[0], &tmp[1], &tmp[2], &tmp[3], in);
+    *out_h = (nk_byte)tmp[0];
+    *out_s = (nk_byte)tmp[1];
+    *out_v = (nk_byte)tmp[2];
+}
+
+NK_API void
+nk_color_hsv_iv(int *out, struct nk_color in)
+{
+    nk_color_hsv_i(&out[0], &out[1], &out[2], in);
+}
+
+NK_API void
+nk_color_hsv_bv(nk_byte *out, struct nk_color in)
+{
+    int tmp[4];
+    nk_color_hsv_i(&tmp[0], &tmp[1], &tmp[2], in);
+    out[0] = (nk_byte)tmp[0];
+    out[1] = (nk_byte)tmp[1];
+    out[2] = (nk_byte)tmp[2];
+}
+/*
+ * ==============================================================
+ *
+ *                          IMAGE
+ *
+ * ===============================================================
+ */
+NK_API nk_handle
+nk_handle_ptr(void *ptr)
+{
+    nk_handle handle = {0};
+    handle.ptr = ptr;
+    return handle;
+}
+
+NK_API nk_handle
+nk_handle_id(int id)
+{
+    nk_handle handle;
+    nk_zero_struct(handle);
+    handle.id = id;
+    return handle;
+}
+
+NK_API struct nk_image
+nk_subimage_ptr(void *ptr, unsigned short w, unsigned short h, struct nk_rect r)
+{
+    struct nk_image s;
+    nk_zero(&s, sizeof(s));
+    s.handle.ptr = ptr;
+    s.w = w; s.h = h;
+    s.region[0] = (unsigned short)r.x;
+    s.region[1] = (unsigned short)r.y;
+    s.region[2] = (unsigned short)r.w;
+    s.region[3] = (unsigned short)r.h;
+    return s;
+}
+
+NK_API struct nk_image
+nk_subimage_id(int id, unsigned short w, unsigned short h, struct nk_rect r)
+{
+    struct nk_image s;
+    nk_zero(&s, sizeof(s));
+    s.handle.id = id;
+    s.w = w; s.h = h;
+    s.region[0] = (unsigned short)r.x;
+    s.region[1] = (unsigned short)r.y;
+    s.region[2] = (unsigned short)r.w;
+    s.region[3] = (unsigned short)r.h;
+    return s;
+}
+
+NK_API struct nk_image
+nk_subimage_handle(nk_handle handle, unsigned short w, unsigned short h,
+    struct nk_rect r)
+{
+    struct nk_image s;
+    nk_zero(&s, sizeof(s));
+    s.handle = handle;
+    s.w = w; s.h = h;
+    s.region[0] = (unsigned short)r.x;
+    s.region[1] = (unsigned short)r.y;
+    s.region[2] = (unsigned short)r.w;
+    s.region[3] = (unsigned short)r.h;
+    return s;
+}
+
+NK_API struct nk_image
+nk_image_handle(nk_handle handle)
+{
+    struct nk_image s;
+    nk_zero(&s, sizeof(s));
+    s.handle = handle;
+    s.w = 0; s.h = 0;
+    s.region[0] = 0;
+    s.region[1] = 0;
+    s.region[2] = 0;
+    s.region[3] = 0;
+    return s;
+}
+
+NK_API struct nk_image
+nk_image_ptr(void *ptr)
+{
+    struct nk_image s;
+    nk_zero(&s, sizeof(s));
+    NK_ASSERT(ptr);
+    s.handle.ptr = ptr;
+    s.w = 0; s.h = 0;
+    s.region[0] = 0;
+    s.region[1] = 0;
+    s.region[2] = 0;
+    s.region[3] = 0;
+    return s;
+}
+
+NK_API struct nk_image
+nk_image_id(int id)
+{
+    struct nk_image s;
+    nk_zero(&s, sizeof(s));
+    s.handle.id = id;
+    s.w = 0; s.h = 0;
+    s.region[0] = 0;
+    s.region[1] = 0;
+    s.region[2] = 0;
+    s.region[3] = 0;
+    return s;
+}
+
+NK_API int
+nk_image_is_subimage(const struct nk_image* img)
+{
+    NK_ASSERT(img);
+    return !(img->w == 0 && img->h == 0);
+}
+
+NK_INTERN void
+nk_unify(struct nk_rect *clip, const struct nk_rect *a, float x0, float y0,
+    float x1, float y1)
+{
+    NK_ASSERT(a);
+    NK_ASSERT(clip);
+    clip->x = NK_MAX(a->x, x0);
+    clip->y = NK_MAX(a->y, y0);
+    clip->w = NK_MIN(a->x + a->w, x1) - clip->x;
+    clip->h = NK_MIN(a->y + a->h, y1) - clip->y;
+    clip->w = NK_MAX(0, clip->w);
+    clip->h = NK_MAX(0, clip->h);
+}
+
+NK_API void
+nk_triangle_from_direction(struct nk_vec2 *result, struct nk_rect r,
+    float pad_x, float pad_y, enum nk_heading direction)
+{
+    float w_half, h_half;
+    NK_ASSERT(result);
+
+    r.w = NK_MAX(2 * pad_x, r.w);
+    r.h = NK_MAX(2 * pad_y, r.h);
+    r.w = r.w - 2 * pad_x;
+    r.h = r.h - 2 * pad_y;
+
+    r.x = r.x + pad_x;
+    r.y = r.y + pad_y;
+
+    w_half = r.w / 2.0f;
+    h_half = r.h / 2.0f;
+
+    if (direction == NK_UP) {
+        result[0] = nk_vec2(r.x + w_half, r.y);
+        result[1] = nk_vec2(r.x + r.w, r.y + r.h);
+        result[2] = nk_vec2(r.x, r.y + r.h);
+    } else if (direction == NK_RIGHT) {
+        result[0] = nk_vec2(r.x, r.y);
+        result[1] = nk_vec2(r.x + r.w, r.y + h_half);
+        result[2] = nk_vec2(r.x, r.y + r.h);
+    } else if (direction == NK_DOWN) {
+        result[0] = nk_vec2(r.x, r.y);
+        result[1] = nk_vec2(r.x + r.w, r.y);
+        result[2] = nk_vec2(r.x + w_half, r.y + r.h);
+    } else {
+        result[0] = nk_vec2(r.x, r.y + h_half);
+        result[1] = nk_vec2(r.x + r.w, r.y);
+        result[2] = nk_vec2(r.x + r.w, r.y + r.h);
+    }
+}
+
+NK_INTERN int
+nk_text_clamp(const struct nk_user_font *font, const char *text,
+    int text_len, float space, int *glyphs, float *text_width,
+    nk_rune *sep_list, int sep_count)
+{
+    int i = 0;
+    int glyph_len = 0;
+    float last_width = 0;
+    nk_rune unicode = 0;
+    float width = 0;
+    int len = 0;
+    int g = 0;
+    float s;
+
+    int sep_len = 0;
+    int sep_g = 0;
+    float sep_width = 0;
+    sep_count = NK_MAX(sep_count,0);
+
+    glyph_len = nk_utf_decode(text, &unicode, text_len);
+    while (glyph_len && (width < space) && (len < text_len)) {
+        len += glyph_len;
+        s = font->width(font->userdata, font->height, text, len);
+        for (i = 0; i < sep_count; ++i) {
+            if (unicode != sep_list[i]) continue;
+            sep_width = last_width = width;
+            sep_g = g+1;
+            sep_len = len;
+            break;
+        }
+        if (i == sep_count){
+            last_width = sep_width = width;
+            sep_g = g+1;
+        }
+        width = s;
+        glyph_len = nk_utf_decode(&text[len], &unicode, text_len - len);
+        g++;
+    }
+    if (len >= text_len) {
+        *glyphs = g;
+        *text_width = last_width;
+        return len;
+    } else {
+        *glyphs = sep_g;
+        *text_width = sep_width;
+        return (!sep_len) ? len: sep_len;
+    }
+}
+
+enum {NK_DO_NOT_STOP_ON_NEW_LINE, NK_STOP_ON_NEW_LINE};
+NK_INTERN struct nk_vec2
+nk_text_calculate_text_bounds(const struct nk_user_font *font,
+    const char *begin, int byte_len, float row_height, const char **remaining,
+    struct nk_vec2 *out_offset, int *glyphs, int op)
+{
+    float line_height = row_height;
+    struct nk_vec2 text_size = nk_vec2(0,0);
+    float line_width = 0.0f;
+
+    float glyph_width;
+    int glyph_len = 0;
+    nk_rune unicode = 0;
+    int text_len = 0;
+    if (!begin || byte_len <= 0 || !font)
+        return nk_vec2(0,row_height);
+
+    glyph_len = nk_utf_decode(begin, &unicode, byte_len);
+    if (!glyph_len) return text_size;
+    glyph_width = font->width(font->userdata, font->height, begin, glyph_len);
+
+    *glyphs = 0;
+    while ((text_len < byte_len) && glyph_len) {
+        if (unicode == '\n') {
+            text_size.x = NK_MAX(text_size.x, line_width);
+            text_size.y += line_height;
+            line_width = 0;
+            *glyphs+=1;
+            if (op == NK_STOP_ON_NEW_LINE)
+                break;
+
+            text_len++;
+            glyph_len = nk_utf_decode(begin + text_len, &unicode, byte_len-text_len);
+            continue;
+        }
+
+        if (unicode == '\r') {
+            text_len++;
+            *glyphs+=1;
+            glyph_len = nk_utf_decode(begin + text_len, &unicode, byte_len-text_len);
+            continue;
+        }
+
+        *glyphs = *glyphs + 1;
+        text_len += glyph_len;
+        line_width += (float)glyph_width;
+        glyph_len = nk_utf_decode(begin + text_len, &unicode, byte_len-text_len);
+        glyph_width = font->width(font->userdata, font->height, begin+text_len, glyph_len);
+        continue;
+    }
+
+    if (text_size.x < line_width)
+        text_size.x = line_width;
+    if (out_offset)
+        *out_offset = nk_vec2(line_width, text_size.y + line_height);
+    if (line_width > 0 || text_size.y == 0.0f)
+        text_size.y += line_height;
+    if (remaining)
+        *remaining = begin+text_len;
+    return text_size;
+}
+
+/* ==============================================================
+ *
+ *                          UTF-8
+ *
+ * ===============================================================*/
+NK_GLOBAL const nk_byte nk_utfbyte[NK_UTF_SIZE+1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+NK_GLOBAL const nk_byte nk_utfmask[NK_UTF_SIZE+1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+NK_GLOBAL const nk_uint nk_utfmin[NK_UTF_SIZE+1] = {0, 0, 0x80, 0x800, 0x10000};
+NK_GLOBAL const nk_uint nk_utfmax[NK_UTF_SIZE+1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+NK_INTERN int
+nk_utf_validate(nk_rune *u, int i)
+{
+    NK_ASSERT(u);
+    if (!u) return 0;
+    if (!NK_BETWEEN(*u, nk_utfmin[i], nk_utfmax[i]) ||
+         NK_BETWEEN(*u, 0xD800, 0xDFFF))
+            *u = NK_UTF_INVALID;
+    for (i = 1; *u > nk_utfmax[i]; ++i);
+    return i;
+}
+
+NK_INTERN nk_rune
+nk_utf_decode_byte(char c, int *i)
+{
+    NK_ASSERT(i);
+    if (!i) return 0;
+    for(*i = 0; *i < (int)NK_LEN(nk_utfmask); ++(*i)) {
+        if (((nk_byte)c & nk_utfmask[*i]) == nk_utfbyte[*i])
+            return (nk_byte)(c & ~nk_utfmask[*i]);
+    }
+    return 0;
+}
+
+NK_API int
+nk_utf_decode(const char *c, nk_rune *u, int clen)
+{
+    int i, j, len, type=0;
+    nk_rune udecoded;
+
+    NK_ASSERT(c);
+    NK_ASSERT(u);
+
+    if (!c || !u) return 0;
+    if (!clen) return 0;
+    *u = NK_UTF_INVALID;
+
+    udecoded = nk_utf_decode_byte(c[0], &len);
+    if (!NK_BETWEEN(len, 1, NK_UTF_SIZE))
+        return 1;
+
+    for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+        udecoded = (udecoded << 6) | nk_utf_decode_byte(c[i], &type);
+        if (type != 0)
+            return j;
+    }
+    if (j < len)
+        return 0;
+    *u = udecoded;
+    nk_utf_validate(u, len);
+    return len;
+}
+
+NK_INTERN char
+nk_utf_encode_byte(nk_rune u, int i)
+{
+    return (char)((nk_utfbyte[i]) | ((nk_byte)u & ~nk_utfmask[i]));
+}
+
+NK_API int
+nk_utf_encode(nk_rune u, char *c, int clen)
+{
+    int len, i;
+    len = nk_utf_validate(&u, 0);
+    if (clen < len || !len || len > NK_UTF_SIZE)
+        return 0;
+
+    for (i = len - 1; i != 0; --i) {
+        c[i] = nk_utf_encode_byte(u, 0);
+        u >>= 6;
+    }
+    c[0] = nk_utf_encode_byte(u, len);
+    return len;
+}
+
+NK_API int
+nk_utf_len(const char *str, int len)
+{
+    const char *text;
+    int glyphs = 0;
+    int text_len;
+    int glyph_len;
+    int src_len = 0;
+    nk_rune unicode;
+
+    NK_ASSERT(str);
+    if (!str || !len) return 0;
+
+    text = str;
+    text_len = len;
+    glyph_len = nk_utf_decode(text, &unicode, text_len);
+    while (glyph_len && src_len < len) {
+        glyphs++;
+        src_len = src_len + glyph_len;
+        glyph_len = nk_utf_decode(text + src_len, &unicode, text_len - src_len);
+    }
+    return glyphs;
+}
+
+NK_API const char*
+nk_utf_at(const char *buffer, int length, int index,
+    nk_rune *unicode, int *len)
+{
+    int i = 0;
+    int src_len = 0;
+    int glyph_len = 0;
+    const char *text;
+    int text_len;
+
+    NK_ASSERT(buffer);
+    NK_ASSERT(unicode);
+    NK_ASSERT(len);
+
+    if (!buffer || !unicode || !len) return 0;
+    if (index < 0) {
+        *unicode = NK_UTF_INVALID;
+        *len = 0;
+        return 0;
+    }
+
+    text = buffer;
+    text_len = length;
+    glyph_len = nk_utf_decode(text, unicode, text_len);
+    while (glyph_len) {
+        if (i == index) {
+            *len = glyph_len;
+            break;
+        }
+
+        i++;
+        src_len = src_len + glyph_len;
+        glyph_len = nk_utf_decode(text + src_len, unicode, text_len - src_len);
+    }
+    if (i != index) return 0;
+    return buffer + src_len;
+}
+
+/* ==============================================================
+ *
+ *                          BUFFER
+ *
+ * ===============================================================*/
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+NK_INTERN void* nk_malloc(nk_handle unused, void *old,nk_size size)
+{NK_UNUSED(unused); NK_UNUSED(old); return malloc(size);}
+NK_INTERN void nk_mfree(nk_handle unused, void *ptr)
+{NK_UNUSED(unused); free(ptr);}
+
+NK_API void
+nk_buffer_init_default(struct nk_buffer *buffer)
+{
+    struct nk_allocator alloc;
+    alloc.userdata.ptr = 0;
+    alloc.alloc = nk_malloc;
+    alloc.free = nk_mfree;
+    nk_buffer_init(buffer, &alloc, NK_BUFFER_DEFAULT_INITIAL_SIZE);
+}
+#endif
+
+NK_API void
+nk_buffer_init(struct nk_buffer *b, const struct nk_allocator *a,
+    nk_size initial_size)
+{
+    NK_ASSERT(b);
+    NK_ASSERT(a);
+    NK_ASSERT(initial_size);
+    if (!b || !a || !initial_size) return;
+
+    nk_zero(b, sizeof(*b));
+    b->type = NK_BUFFER_DYNAMIC;
+    b->memory.ptr = a->alloc(a->userdata,0, initial_size);
+    b->memory.size = initial_size;
+    b->size = initial_size;
+    b->grow_factor = 2.0f;
+    b->pool = *a;
+}
+
+NK_API void
+nk_buffer_init_fixed(struct nk_buffer *b, void *m, nk_size size)
+{
+    NK_ASSERT(b);
+    NK_ASSERT(m);
+    NK_ASSERT(size);
+    if (!b || !m || !size) return;
+
+    nk_zero(b, sizeof(*b));
+    b->type = NK_BUFFER_FIXED;
+    b->memory.ptr = m;
+    b->memory.size = size;
+    b->size = size;
+}
+
+NK_INTERN void*
+nk_buffer_align(void *unaligned, nk_size align, nk_size *alignment,
+    enum nk_buffer_allocation_type type)
+{
+    void *memory = 0;
+    switch (type) {
+    default:
+    case NK_BUFFER_MAX:
+    case NK_BUFFER_FRONT:
+        if (align) {
+            memory = NK_ALIGN_PTR(unaligned, align);
+            *alignment = (nk_size)((nk_byte*)memory - (nk_byte*)unaligned);
+        } else {
+            memory = unaligned;
+            *alignment = 0;
+        }
+        break;
+    case NK_BUFFER_BACK:
+        if (align) {
+            memory = NK_ALIGN_PTR_BACK(unaligned, align);
+            *alignment = (nk_size)((nk_byte*)unaligned - (nk_byte*)memory);
+        } else {
+            memory = unaligned;
+            *alignment = 0;
+        }
+        break;
+    }
+    return memory;
+}
+
+NK_INTERN void*
+nk_buffer_realloc(struct nk_buffer *b, nk_size capacity, nk_size *size)
+{
+    void *temp;
+    nk_size buffer_size;
+
+    NK_ASSERT(b);
+    NK_ASSERT(size);
+    if (!b || !size || !b->pool.alloc || !b->pool.free)
+        return 0;
+
+    buffer_size = b->memory.size;
+    temp = b->pool.alloc(b->pool.userdata, b->memory.ptr, capacity);
+    NK_ASSERT(temp);
+    if (!temp) return 0;
+
+    *size = capacity;
+    if (temp != b->memory.ptr) {
+        NK_MEMCPY(temp, b->memory.ptr, buffer_size);
+        b->pool.free(b->pool.userdata, b->memory.ptr);
+    }
+
+    if (b->size == buffer_size) {
+        /* no back buffer so just set correct size */
+        b->size = capacity;
+        return temp;
+    } else {
+        /* copy back buffer to the end of the new buffer */
+        void *dst, *src;
+        nk_size back_size;
+        back_size = buffer_size - b->size;
+        dst = nk_ptr_add(void, temp, capacity - back_size);
+        src = nk_ptr_add(void, temp, b->size);
+        NK_MEMCPY(dst, src, back_size);
+        b->size = capacity - back_size;
+    }
+    return temp;
+}
+
+NK_INTERN void*
+nk_buffer_alloc(struct nk_buffer *b, enum nk_buffer_allocation_type type,
+    nk_size size, nk_size align)
+{
+    int full;
+    nk_size alignment;
+    void *unaligned;
+    void *memory;
+
+    NK_ASSERT(b);
+    NK_ASSERT(size);
+    if (!b || !size) return 0;
+    b->needed += size;
+
+    /* calculate total size with needed alignment + size */
+    if (type == NK_BUFFER_FRONT)
+        unaligned = nk_ptr_add(void, b->memory.ptr, b->allocated);
+    else unaligned = nk_ptr_add(void, b->memory.ptr, b->size - size);
+    memory = nk_buffer_align(unaligned, align, &alignment, type);
+
+    /* check if buffer has enough memory*/
+    if (type == NK_BUFFER_FRONT)
+        full = ((b->allocated + size + alignment) > b->size);
+    else full = ((b->size - NK_MIN(b->size,(size + alignment))) <= b->allocated);
+
+    if (full) {
+        nk_size capacity;
+        if (b->type != NK_BUFFER_DYNAMIC)
+            return 0;
+        NK_ASSERT(b->pool.alloc && b->pool.free);
+        if (b->type != NK_BUFFER_DYNAMIC || !b->pool.alloc || !b->pool.free)
+            return 0;
+
+        /* buffer is full so allocate bigger buffer if dynamic */
+        capacity = (nk_size)((float)b->memory.size * b->grow_factor);
+        capacity = NK_MAX(capacity, nk_round_up_pow2((nk_uint)(b->allocated + size)));
+        b->memory.ptr = nk_buffer_realloc(b, capacity, &b->memory.size);
+        if (!b->memory.ptr) return 0;
+
+        /* align newly allocated pointer */
+        if (type == NK_BUFFER_FRONT)
+            unaligned = nk_ptr_add(void, b->memory.ptr, b->allocated);
+        else unaligned = nk_ptr_add(void, b->memory.ptr, b->size - size);
+        memory = nk_buffer_align(unaligned, align, &alignment, type);
+    }
+    if (type == NK_BUFFER_FRONT)
+        b->allocated += size + alignment;
+    else b->size -= (size + alignment);
+    b->needed += alignment;
+    b->calls++;
+    return memory;
+}
+
+NK_API void
+nk_buffer_push(struct nk_buffer *b, enum nk_buffer_allocation_type type,
+    const void *memory, nk_size size, nk_size align)
+{
+    void *mem = nk_buffer_alloc(b, type, size, align);
+    if (!mem) return;
+    NK_MEMCPY(mem, memory, size);
+}
+
+NK_API void
+nk_buffer_mark(struct nk_buffer *buffer, enum nk_buffer_allocation_type type)
+{
+    NK_ASSERT(buffer);
+    if (!buffer) return;
+    buffer->marker[type].active = nk_true;
+    if (type == NK_BUFFER_BACK)
+        buffer->marker[type].offset = buffer->size;
+    else buffer->marker[type].offset = buffer->allocated;
+}
+
+NK_API void
+nk_buffer_reset(struct nk_buffer *buffer, enum nk_buffer_allocation_type type)
+{
+    NK_ASSERT(buffer);
+    if (!buffer) return;
+    if (type == NK_BUFFER_BACK) {
+        /* reset back buffer either back to marker or empty */
+        buffer->needed -= (buffer->memory.size - buffer->marker[type].offset);
+        if (buffer->marker[type].active)
+            buffer->size = buffer->marker[type].offset;
+        else buffer->size = buffer->memory.size;
+        buffer->marker[type].active = nk_false;
+    } else {
+        /* reset front buffer either back to back marker or empty */
+        buffer->needed -= (buffer->allocated - buffer->marker[type].offset);
+        if (buffer->marker[type].active)
+            buffer->allocated = buffer->marker[type].offset;
+        else buffer->allocated = 0;
+        buffer->marker[type].active = nk_false;
+    }
+}
+
+NK_API void
+nk_buffer_clear(struct nk_buffer *b)
+{
+    NK_ASSERT(b);
+    if (!b) return;
+    b->allocated = 0;
+    b->size = b->memory.size;
+    b->calls = 0;
+    b->needed = 0;
+}
+
+NK_API void
+nk_buffer_free(struct nk_buffer *b)
+{
+    NK_ASSERT(b);
+    if (!b || !b->memory.ptr) return;
+    if (b->type == NK_BUFFER_FIXED) return;
+    if (!b->pool.free) return;
+    NK_ASSERT(b->pool.free);
+    b->pool.free(b->pool.userdata, b->memory.ptr);
+}
+
+NK_API void
+nk_buffer_info(struct nk_memory_status *s, struct nk_buffer *b)
+{
+    NK_ASSERT(b);
+    NK_ASSERT(s);
+    if (!s || !b) return;
+    s->allocated = b->allocated;
+    s->size =  b->memory.size;
+    s->needed = b->needed;
+    s->memory = b->memory.ptr;
+    s->calls = b->calls;
+}
+
+NK_API void*
+nk_buffer_memory(struct nk_buffer *buffer)
+{
+    NK_ASSERT(buffer);
+    if (!buffer) return 0;
+    return buffer->memory.ptr;
+}
+
+NK_API const void*
+nk_buffer_memory_const(const struct nk_buffer *buffer)
+{
+    NK_ASSERT(buffer);
+    if (!buffer) return 0;
+    return buffer->memory.ptr;
+}
+
+NK_API nk_size
+nk_buffer_total(struct nk_buffer *buffer)
+{
+    NK_ASSERT(buffer);
+    if (!buffer) return 0;
+    return buffer->memory.size;
+}
+
+/*
+ * ==============================================================
+ *
+ *                          STRING
+ *
+ * ===============================================================
+ */
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+NK_API void
+nk_str_init_default(struct nk_str *str)
+{
+    struct nk_allocator alloc;
+    alloc.userdata.ptr = 0;
+    alloc.alloc = nk_malloc;
+    alloc.free = nk_mfree;
+    nk_buffer_init(&str->buffer, &alloc, 32);
+    str->len = 0;
+}
+#endif
+
+NK_API void
+nk_str_init(struct nk_str *str, const struct nk_allocator *alloc, nk_size size)
+{
+    nk_buffer_init(&str->buffer, alloc, size);
+    str->len = 0;
+}
+
+NK_API void
+nk_str_init_fixed(struct nk_str *str, void *memory, nk_size size)
+{
+    nk_buffer_init_fixed(&str->buffer, memory, size);
+    str->len = 0;
+}
+
+NK_API int
+nk_str_append_text_char(struct nk_str *s, const char *str, int len)
+{
+    char *mem;
+    NK_ASSERT(s);
+    NK_ASSERT(str);
+    if (!s || !str || !len) return 0;
+    mem = (char*)nk_buffer_alloc(&s->buffer, NK_BUFFER_FRONT, (nk_size)len * sizeof(char), 0);
+    if (!mem) return 0;
+    NK_MEMCPY(mem, str, (nk_size)len * sizeof(char));
+    s->len += nk_utf_len(str, len);
+    return len;
+}
+
+NK_API int
+nk_str_append_str_char(struct nk_str *s, const char *str)
+{
+    return nk_str_append_text_char(s, str, nk_strlen(str));
+}
+
+NK_API int
+nk_str_append_text_utf8(struct nk_str *str, const char *text, int len)
+{
+    int i = 0;
+    int byte_len = 0;
+    nk_rune unicode;
+    if (!str || !text || !len) return 0;
+    for (i = 0; i < len; ++i)
+        byte_len += nk_utf_decode(text+byte_len, &unicode, 4);
+    nk_str_append_text_char(str, text, byte_len);
+    return len;
+}
+
+NK_API int
+nk_str_append_str_utf8(struct nk_str *str, const char *text)
+{
+    int runes = 0;
+    int byte_len = 0;
+    int num_runes = 0;
+    int glyph_len = 0;
+    nk_rune unicode;
+    if (!str || !text) return 0;
+
+    glyph_len = byte_len = nk_utf_decode(text+byte_len, &unicode, 4);
+    while (unicode != '\0' && glyph_len) {
+        glyph_len = nk_utf_decode(text+byte_len, &unicode, 4);
+        byte_len += glyph_len;
+        num_runes++;
+    }
+    nk_str_append_text_char(str, text, byte_len);
+    return runes;
+}
+
+NK_API int
+nk_str_append_text_runes(struct nk_str *str, const nk_rune *text, int len)
+{
+    int i = 0;
+    int byte_len = 0;
+    nk_glyph glyph;
+
+    NK_ASSERT(str);
+    if (!str || !text || !len) return 0;
+    for (i = 0; i < len; ++i) {
+        byte_len = nk_utf_encode(text[i], glyph, NK_UTF_SIZE);
+        if (!byte_len) break;
+        nk_str_append_text_char(str, glyph, byte_len);
+    }
+    return len;
+}
+
+NK_API int
+nk_str_append_str_runes(struct nk_str *str, const nk_rune *runes)
+{
+    int i = 0;
+    nk_glyph glyph;
+    int byte_len;
+    NK_ASSERT(str);
+    if (!str || !runes) return 0;
+    while (runes[i] != '\0') {
+        byte_len = nk_utf_encode(runes[i], glyph, NK_UTF_SIZE);
+        nk_str_append_text_char(str, glyph, byte_len);
+        i++;
+    }
+    return i;
+}
+
+NK_API int
+nk_str_insert_at_char(struct nk_str *s, int pos, const char *str, int len)
+{
+    int i;
+    void *mem;
+    char *src;
+    char *dst;
+
+    int copylen;
+    NK_ASSERT(s);
+    NK_ASSERT(str);
+    NK_ASSERT(len >= 0);
+    if (!s || !str || !len || (nk_size)pos > s->buffer.allocated) return 0;
+    if ((s->buffer.allocated + (nk_size)len >= s->buffer.memory.size) &&
+        (s->buffer.type == NK_BUFFER_FIXED)) return 0;
+
+    copylen = (int)s->buffer.allocated - pos;
+    if (!copylen) {
+        nk_str_append_text_char(s, str, len);
+        return 1;
+    }
+    mem = nk_buffer_alloc(&s->buffer, NK_BUFFER_FRONT, (nk_size)len * sizeof(char), 0);
+    if (!mem) return 0;
+
+    /* memmove */
+    NK_ASSERT(((int)pos + (int)len + ((int)copylen - 1)) >= 0);
+    NK_ASSERT(((int)pos + ((int)copylen - 1)) >= 0);
+    dst = nk_ptr_add(char, s->buffer.memory.ptr, pos + len + (copylen - 1));
+    src = nk_ptr_add(char, s->buffer.memory.ptr, pos + (copylen-1));
+    for (i = 0; i < copylen; ++i) *dst-- = *src--;
+    mem = nk_ptr_add(void, s->buffer.memory.ptr, pos);
+    NK_MEMCPY(mem, str, (nk_size)len * sizeof(char));
+    s->len = nk_utf_len((char *)s->buffer.memory.ptr, (int)s->buffer.allocated);
+    return 1;
+}
+
+NK_API int
+nk_str_insert_at_rune(struct nk_str *str, int pos, const char *cstr, int len)
+{
+    int glyph_len;
+    nk_rune unicode;
+    const char *begin;
+    const char *buffer;
+
+    NK_ASSERT(str);
+    NK_ASSERT(cstr);
+    NK_ASSERT(len);
+    if (!str || !cstr || !len) return 0;
+    begin = nk_str_at_rune(str, pos, &unicode, &glyph_len);
+    if (!str->len)
+        return nk_str_append_text_char(str, cstr, len);
+    buffer = nk_str_get_const(str);
+    if (!begin) return 0;
+    return nk_str_insert_at_char(str, (int)(begin - buffer), cstr, len);
+}
+
+NK_API int
+nk_str_insert_text_char(struct nk_str *str, int pos, const char *text, int len)
+{
+    return nk_str_insert_text_utf8(str, pos, text, len);
+}
+
+NK_API int
+nk_str_insert_str_char(struct nk_str *str, int pos, const char *text)
+{
+    return nk_str_insert_text_utf8(str, pos, text, nk_strlen(text));
+}
+
+NK_API int
+nk_str_insert_text_utf8(struct nk_str *str, int pos, const char *text, int len)
+{
+    int i = 0;
+    int byte_len = 0;
+    nk_rune unicode;
+
+    NK_ASSERT(str);
+    NK_ASSERT(text);
+    if (!str || !text || !len) return 0;
+    for (i = 0; i < len; ++i)
+        byte_len += nk_utf_decode(text+byte_len, &unicode, 4);
+    nk_str_insert_at_rune(str, pos, text, byte_len);
+    return len;
+}
+
+NK_API int
+nk_str_insert_str_utf8(struct nk_str *str, int pos, const char *text)
+{
+    int runes = 0;
+    int byte_len = 0;
+    int num_runes = 0;
+    int glyph_len = 0;
+    nk_rune unicode;
+    if (!str || !text) return 0;
+
+    glyph_len = byte_len = nk_utf_decode(text+byte_len, &unicode, 4);
+    while (unicode != '\0' && glyph_len) {
+        glyph_len = nk_utf_decode(text+byte_len, &unicode, 4);
+        byte_len += glyph_len;
+        num_runes++;
+    }
+    nk_str_insert_at_rune(str, pos, text, byte_len);
+    return runes;
+}
+
+NK_API int
+nk_str_insert_text_runes(struct nk_str *str, int pos, const nk_rune *runes, int len)
+{
+    int i = 0;
+    int byte_len = 0;
+    nk_glyph glyph;
+
+    NK_ASSERT(str);
+    if (!str || !runes || !len) return 0;
+    for (i = 0; i < len; ++i) {
+        byte_len = nk_utf_encode(runes[i], glyph, NK_UTF_SIZE);
+        if (!byte_len) break;
+        nk_str_insert_at_rune(str, pos+i, glyph, byte_len);
+    }
+    return len;
+}
+
+NK_API int
+nk_str_insert_str_runes(struct nk_str *str, int pos, const nk_rune *runes)
+{
+    int i = 0;
+    nk_glyph glyph;
+    int byte_len;
+    NK_ASSERT(str);
+    if (!str || !runes) return 0;
+    while (runes[i] != '\0') {
+        byte_len = nk_utf_encode(runes[i], glyph, NK_UTF_SIZE);
+        nk_str_insert_at_rune(str, pos+i, glyph, byte_len);
+        i++;
+    }
+    return i;
+}
+
+NK_API void
+nk_str_remove_chars(struct nk_str *s, int len)
+{
+    NK_ASSERT(s);
+    NK_ASSERT(len >= 0);
+    if (!s || len < 0 || (nk_size)len > s->buffer.allocated) return;
+    NK_ASSERT(((int)s->buffer.allocated - (int)len) >= 0);
+    s->buffer.allocated -= (nk_size)len;
+    s->len = nk_utf_len((char *)s->buffer.memory.ptr, (int)s->buffer.allocated);
+}
+
+NK_API void
+nk_str_remove_runes(struct nk_str *str, int len)
+{
+    int index;
+    const char *begin;
+    const char *end;
+    nk_rune unicode;
+
+    NK_ASSERT(str);
+    NK_ASSERT(len >= 0);
+    if (!str || len < 0) return;
+    if (len >= str->len) {
+        str->len = 0;
+        return;
+    }
+
+    index = str->len - len;
+    begin = nk_str_at_rune(str, index, &unicode, &len);
+    end = (const char*)str->buffer.memory.ptr + str->buffer.allocated;
+    nk_str_remove_chars(str, (int)(end-begin)+1);
+}
+
+NK_API void
+nk_str_delete_chars(struct nk_str *s, int pos, int len)
+{
+    NK_ASSERT(s);
+    if (!s || !len || (nk_size)pos > s->buffer.allocated ||
+        (nk_size)(pos + len) > s->buffer.allocated) return;
+
+    if ((nk_size)(pos + len) < s->buffer.allocated) {
+        /* memmove */
+        char *dst = nk_ptr_add(char, s->buffer.memory.ptr, pos);
+        char *src = nk_ptr_add(char, s->buffer.memory.ptr, pos + len);
+        NK_MEMCPY(dst, src, s->buffer.allocated - (nk_size)(pos + len));
+        NK_ASSERT(((int)s->buffer.allocated - (int)len) >= 0);
+        s->buffer.allocated -= (nk_size)len;
+    } else nk_str_remove_chars(s, len);
+    s->len = nk_utf_len((char *)s->buffer.memory.ptr, (int)s->buffer.allocated);
+}
+
+NK_API void
+nk_str_delete_runes(struct nk_str *s, int pos, int len)
+{
+    char *temp;
+    nk_rune unicode;
+    char *begin;
+    char *end;
+    int unused;
+
+    NK_ASSERT(s);
+    NK_ASSERT(s->len >= pos + len);
+    if (s->len < pos + len)
+        len = NK_CLAMP(0, (s->len - pos), s->len);
+    if (!len) return;
+
+    temp = (char *)s->buffer.memory.ptr;
+    begin = nk_str_at_rune(s, pos, &unicode, &unused);
+    if (!begin) return;
+    s->buffer.memory.ptr = begin;
+    end = nk_str_at_rune(s, len, &unicode, &unused);
+    s->buffer.memory.ptr = temp;
+    if (!end) return;
+    nk_str_delete_chars(s, (int)(begin - temp), (int)(end - begin));
+}
+
+NK_API char*
+nk_str_at_char(struct nk_str *s, int pos)
+{
+    NK_ASSERT(s);
+    if (!s || pos > (int)s->buffer.allocated) return 0;
+    return nk_ptr_add(char, s->buffer.memory.ptr, pos);
+}
+
+NK_API char*
+nk_str_at_rune(struct nk_str *str, int pos, nk_rune *unicode, int *len)
+{
+    int i = 0;
+    int src_len = 0;
+    int glyph_len = 0;
+    char *text;
+    int text_len;
+
+    NK_ASSERT(str);
+    NK_ASSERT(unicode);
+    NK_ASSERT(len);
+
+    if (!str || !unicode || !len) return 0;
+    if (pos < 0) {
+        *unicode = 0;
+        *len = 0;
+        return 0;
+    }
+
+    text = (char*)str->buffer.memory.ptr;
+    text_len = (int)str->buffer.allocated;
+    glyph_len = nk_utf_decode(text, unicode, text_len);
+    while (glyph_len) {
+        if (i == pos) {
+            *len = glyph_len;
+            break;
+        }
+
+        i++;
+        src_len = src_len + glyph_len;
+        glyph_len = nk_utf_decode(text + src_len, unicode, text_len - src_len);
+    }
+    if (i != pos) return 0;
+    return text + src_len;
+}
+
+NK_API const char*
+nk_str_at_char_const(const struct nk_str *s, int pos)
+{
+    NK_ASSERT(s);
+    if (!s || pos > (int)s->buffer.allocated) return 0;
+    return nk_ptr_add(char, s->buffer.memory.ptr, pos);
+}
+
+NK_API const char*
+nk_str_at_const(const struct nk_str *str, int pos, nk_rune *unicode, int *len)
+{
+    int i = 0;
+    int src_len = 0;
+    int glyph_len = 0;
+    char *text;
+    int text_len;
+
+    NK_ASSERT(str);
+    NK_ASSERT(unicode);
+    NK_ASSERT(len);
+
+    if (!str || !unicode || !len) return 0;
+    if (pos < 0) {
+        *unicode = 0;
+        *len = 0;
+        return 0;
+    }
+
+    text = (char*)str->buffer.memory.ptr;
+    text_len = (int)str->buffer.allocated;
+    glyph_len = nk_utf_decode(text, unicode, text_len);
+    while (glyph_len) {
+        if (i == pos) {
+            *len = glyph_len;
+            break;
+        }
+
+        i++;
+        src_len = src_len + glyph_len;
+        glyph_len = nk_utf_decode(text + src_len, unicode, text_len - src_len);
+    }
+    if (i != pos) return 0;
+    return text + src_len;
+}
+
+NK_API nk_rune
+nk_str_rune_at(const struct nk_str *str, int pos)
+{
+    int len;
+    nk_rune unicode = 0;
+    nk_str_at_const(str, pos, &unicode, &len);
+    return unicode;
+}
+
+NK_API char*
+nk_str_get(struct nk_str *s)
+{
+    NK_ASSERT(s);
+    if (!s || !s->len || !s->buffer.allocated) return 0;
+    return (char*)s->buffer.memory.ptr;
+}
+
+NK_API const char*
+nk_str_get_const(const struct nk_str *s)
+{
+    NK_ASSERT(s);
+    if (!s || !s->len || !s->buffer.allocated) return 0;
+    return (const char*)s->buffer.memory.ptr;
+}
+
+NK_API int
+nk_str_len(struct nk_str *s)
+{
+    NK_ASSERT(s);
+    if (!s || !s->len || !s->buffer.allocated) return 0;
+    return s->len;
+}
+
+NK_API int
+nk_str_len_char(struct nk_str *s)
+{
+    NK_ASSERT(s);
+    if (!s || !s->len || !s->buffer.allocated) return 0;
+    return (int)s->buffer.allocated;
+}
+
+NK_API void
+nk_str_clear(struct nk_str *str)
+{
+    NK_ASSERT(str);
+    nk_buffer_clear(&str->buffer);
+    str->len = 0;
+}
+
+NK_API void
+nk_str_free(struct nk_str *str)
+{
+    NK_ASSERT(str);
+    nk_buffer_free(&str->buffer);
+    str->len = 0;
+}
+
+/*
+ * ==============================================================
+ *
+ *                      Command buffer
+ *
+ * ===============================================================
+*/
+NK_INTERN void
+nk_command_buffer_init(struct nk_command_buffer *cmdbuf,
+    struct nk_buffer *buffer, enum nk_command_clipping clip)
+{
+    NK_ASSERT(cmdbuf);
+    NK_ASSERT(buffer);
+    if (!cmdbuf || !buffer) return;
+    cmdbuf->base = buffer;
+    cmdbuf->use_clipping = clip;
+    cmdbuf->begin = buffer->allocated;
+    cmdbuf->end = buffer->allocated;
+    cmdbuf->last = buffer->allocated;
+}
+
+NK_INTERN void
+nk_command_buffer_reset(struct nk_command_buffer *buffer)
+{
+    NK_ASSERT(buffer);
+    if (!buffer) return;
+    buffer->begin = 0;
+    buffer->end = 0;
+    buffer->last = 0;
+    buffer->clip = nk_null_rect;
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    buffer->userdata.ptr = 0;
+#endif
+}
+
+NK_INTERN void*
+nk_command_buffer_push(struct nk_command_buffer* b,
+    enum nk_command_type t, nk_size size)
+{
+    NK_STORAGE const nk_size align = NK_ALIGNOF(struct nk_command);
+    struct nk_command *cmd;
+    nk_size alignment;
+    void *unaligned;
+    void *memory;
+
+    NK_ASSERT(b);
+    NK_ASSERT(b->base);
+    if (!b) return 0;
+    cmd = (struct nk_command*)nk_buffer_alloc(b->base,NK_BUFFER_FRONT,size,align);
+    if (!cmd) return 0;
+
+    /* make sure the offset to the next command is aligned */
+    b->last = (nk_size)((nk_byte*)cmd - (nk_byte*)b->base->memory.ptr);
+    unaligned = (nk_byte*)cmd + size;
+    memory = NK_ALIGN_PTR(unaligned, align);
+    alignment = (nk_size)((nk_byte*)memory - (nk_byte*)unaligned);
+#ifdef NK_ZERO_COMMAND_MEMORY
+    NK_MEMSET(cmd, 0, size + alignment);
+#endif
+
+    cmd->type = t;
+    cmd->next = b->base->allocated + alignment;
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    cmd->userdata = b->userdata;
+#endif
+    b->end = cmd->next;
+    return cmd;
+}
+
+NK_API void
+nk_push_scissor(struct nk_command_buffer *b, struct nk_rect r)
+{
+    struct nk_command_scissor *cmd;
+    NK_ASSERT(b);
+    if (!b) return;
+
+    b->clip.x = r.x;
+    b->clip.y = r.y;
+    b->clip.w = r.w;
+    b->clip.h = r.h;
+    cmd = (struct nk_command_scissor*)
+        nk_command_buffer_push(b, NK_COMMAND_SCISSOR, sizeof(*cmd));
+
+    if (!cmd) return;
+    cmd->x = (short)r.x;
+    cmd->y = (short)r.y;
+    cmd->w = (unsigned short)NK_MAX(0, r.w);
+    cmd->h = (unsigned short)NK_MAX(0, r.h);
+}
+
+NK_API void
+nk_stroke_line(struct nk_command_buffer *b, float x0, float y0,
+    float x1, float y1, float line_thickness, struct nk_color c)
+{
+    struct nk_command_line *cmd;
+    NK_ASSERT(b);
+    if (!b || line_thickness <= 0) return;
+    cmd = (struct nk_command_line*)
+        nk_command_buffer_push(b, NK_COMMAND_LINE, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->line_thickness = (unsigned short)line_thickness;
+    cmd->begin.x = (short)x0;
+    cmd->begin.y = (short)y0;
+    cmd->end.x = (short)x1;
+    cmd->end.y = (short)y1;
+    cmd->color = c;
+}
+
+NK_API void
+nk_stroke_curve(struct nk_command_buffer *b, float ax, float ay,
+    float ctrl0x, float ctrl0y, float ctrl1x, float ctrl1y,
+    float bx, float by, float line_thickness, struct nk_color col)
+{
+    struct nk_command_curve *cmd;
+    NK_ASSERT(b);
+    if (!b || col.a == 0 || line_thickness <= 0) return;
+
+    cmd = (struct nk_command_curve*)
+        nk_command_buffer_push(b, NK_COMMAND_CURVE, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->line_thickness = (unsigned short)line_thickness;
+    cmd->begin.x = (short)ax;
+    cmd->begin.y = (short)ay;
+    cmd->ctrl[0].x = (short)ctrl0x;
+    cmd->ctrl[0].y = (short)ctrl0y;
+    cmd->ctrl[1].x = (short)ctrl1x;
+    cmd->ctrl[1].y = (short)ctrl1y;
+    cmd->end.x = (short)bx;
+    cmd->end.y = (short)by;
+    cmd->color = col;
+}
+
+NK_API void
+nk_stroke_rect(struct nk_command_buffer *b, struct nk_rect rect,
+    float rounding, float line_thickness, struct nk_color c)
+{
+    struct nk_command_rect *cmd;
+    NK_ASSERT(b);
+    if (!b || c.a == 0 || rect.w == 0 || rect.h == 0 || line_thickness <= 0) return;
+    if (b->use_clipping) {
+        const struct nk_rect *clip = &b->clip;
+        if (!NK_INTERSECT(rect.x, rect.y, rect.w, rect.h,
+            clip->x, clip->y, clip->w, clip->h)) return;
+    }
+    cmd = (struct nk_command_rect*)
+        nk_command_buffer_push(b, NK_COMMAND_RECT, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->rounding = (unsigned short)rounding;
+    cmd->line_thickness = (unsigned short)line_thickness;
+    cmd->x = (short)rect.x;
+    cmd->y = (short)rect.y;
+    cmd->w = (unsigned short)NK_MAX(0, rect.w);
+    cmd->h = (unsigned short)NK_MAX(0, rect.h);
+    cmd->color = c;
+}
+
+NK_API void
+nk_fill_rect(struct nk_command_buffer *b, struct nk_rect rect,
+    float rounding, struct nk_color c)
+{
+    struct nk_command_rect_filled *cmd;
+    NK_ASSERT(b);
+    if (!b || c.a == 0 || rect.w == 0 || rect.h == 0) return;
+    if (b->use_clipping) {
+        const struct nk_rect *clip = &b->clip;
+        if (!NK_INTERSECT(rect.x, rect.y, rect.w, rect.h,
+            clip->x, clip->y, clip->w, clip->h)) return;
+    }
+
+    cmd = (struct nk_command_rect_filled*)
+        nk_command_buffer_push(b, NK_COMMAND_RECT_FILLED, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->rounding = (unsigned short)rounding;
+    cmd->x = (short)rect.x;
+    cmd->y = (short)rect.y;
+    cmd->w = (unsigned short)NK_MAX(0, rect.w);
+    cmd->h = (unsigned short)NK_MAX(0, rect.h);
+    cmd->color = c;
+}
+
+NK_API void
+nk_fill_rect_multi_color(struct nk_command_buffer *b, struct nk_rect rect,
+    struct nk_color left, struct nk_color top, struct nk_color right,
+    struct nk_color bottom)
+{
+    struct nk_command_rect_multi_color *cmd;
+    NK_ASSERT(b);
+    if (!b || rect.w == 0 || rect.h == 0) return;
+    if (b->use_clipping) {
+        const struct nk_rect *clip = &b->clip;
+        if (!NK_INTERSECT(rect.x, rect.y, rect.w, rect.h,
+            clip->x, clip->y, clip->w, clip->h)) return;
+    }
+
+    cmd = (struct nk_command_rect_multi_color*)
+        nk_command_buffer_push(b, NK_COMMAND_RECT_MULTI_COLOR, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->x = (short)rect.x;
+    cmd->y = (short)rect.y;
+    cmd->w = (unsigned short)NK_MAX(0, rect.w);
+    cmd->h = (unsigned short)NK_MAX(0, rect.h);
+    cmd->left = left;
+    cmd->top = top;
+    cmd->right = right;
+    cmd->bottom = bottom;
+}
+
+NK_API void
+nk_stroke_circle(struct nk_command_buffer *b, struct nk_rect r,
+    float line_thickness, struct nk_color c)
+{
+    struct nk_command_circle *cmd;
+    if (!b || r.w == 0 || r.h == 0 || line_thickness <= 0) return;
+    if (b->use_clipping) {
+        const struct nk_rect *clip = &b->clip;
+        if (!NK_INTERSECT(r.x, r.y, r.w, r.h, clip->x, clip->y, clip->w, clip->h))
+            return;
+    }
+
+    cmd = (struct nk_command_circle*)
+        nk_command_buffer_push(b, NK_COMMAND_CIRCLE, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->line_thickness = (unsigned short)line_thickness;
+    cmd->x = (short)r.x;
+    cmd->y = (short)r.y;
+    cmd->w = (unsigned short)NK_MAX(r.w, 0);
+    cmd->h = (unsigned short)NK_MAX(r.h, 0);
+    cmd->color = c;
+}
+
+NK_API void
+nk_fill_circle(struct nk_command_buffer *b, struct nk_rect r, struct nk_color c)
+{
+    struct nk_command_circle_filled *cmd;
+    NK_ASSERT(b);
+    if (!b || c.a == 0 || r.w == 0 || r.h == 0) return;
+    if (b->use_clipping) {
+        const struct nk_rect *clip = &b->clip;
+        if (!NK_INTERSECT(r.x, r.y, r.w, r.h, clip->x, clip->y, clip->w, clip->h))
+            return;
+    }
+
+    cmd = (struct nk_command_circle_filled*)
+        nk_command_buffer_push(b, NK_COMMAND_CIRCLE_FILLED, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->x = (short)r.x;
+    cmd->y = (short)r.y;
+    cmd->w = (unsigned short)NK_MAX(r.w, 0);
+    cmd->h = (unsigned short)NK_MAX(r.h, 0);
+    cmd->color = c;
+}
+
+NK_API void
+nk_stroke_arc(struct nk_command_buffer *b, float cx, float cy, float radius,
+    float a_min, float a_max, float line_thickness, struct nk_color c)
+{
+    struct nk_command_arc *cmd;
+    if (!b || c.a == 0 || line_thickness <= 0) return;
+    cmd = (struct nk_command_arc*)
+        nk_command_buffer_push(b, NK_COMMAND_ARC, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->line_thickness = (unsigned short)line_thickness;
+    cmd->cx = (short)cx;
+    cmd->cy = (short)cy;
+    cmd->r = (unsigned short)radius;
+    cmd->a[0] = a_min;
+    cmd->a[1] = a_max;
+    cmd->color = c;
+}
+
+NK_API void
+nk_fill_arc(struct nk_command_buffer *b, float cx, float cy, float radius,
+    float a_min, float a_max, struct nk_color c)
+{
+    struct nk_command_arc_filled *cmd;
+    NK_ASSERT(b);
+    if (!b || c.a == 0) return;
+    cmd = (struct nk_command_arc_filled*)
+        nk_command_buffer_push(b, NK_COMMAND_ARC_FILLED, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->cx = (short)cx;
+    cmd->cy = (short)cy;
+    cmd->r = (unsigned short)radius;
+    cmd->a[0] = a_min;
+    cmd->a[1] = a_max;
+    cmd->color = c;
+}
+
+NK_API void
+nk_stroke_triangle(struct nk_command_buffer *b, float x0, float y0, float x1,
+    float y1, float x2, float y2, float line_thickness, struct nk_color c)
+{
+    struct nk_command_triangle *cmd;
+    NK_ASSERT(b);
+    if (!b || c.a == 0 || line_thickness <= 0) return;
+    if (b->use_clipping) {
+        const struct nk_rect *clip = &b->clip;
+        if (!NK_INBOX(x0, y0, clip->x, clip->y, clip->w, clip->h) &&
+            !NK_INBOX(x1, y1, clip->x, clip->y, clip->w, clip->h) &&
+            !NK_INBOX(x2, y2, clip->x, clip->y, clip->w, clip->h))
+            return;
+    }
+
+    cmd = (struct nk_command_triangle*)
+        nk_command_buffer_push(b, NK_COMMAND_TRIANGLE, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->line_thickness = (unsigned short)line_thickness;
+    cmd->a.x = (short)x0;
+    cmd->a.y = (short)y0;
+    cmd->b.x = (short)x1;
+    cmd->b.y = (short)y1;
+    cmd->c.x = (short)x2;
+    cmd->c.y = (short)y2;
+    cmd->color = c;
+}
+
+NK_API void
+nk_fill_triangle(struct nk_command_buffer *b, float x0, float y0, float x1,
+    float y1, float x2, float y2, struct nk_color c)
+{
+    struct nk_command_triangle_filled *cmd;
+    NK_ASSERT(b);
+    if (!b || c.a == 0) return;
+    if (!b) return;
+    if (b->use_clipping) {
+        const struct nk_rect *clip = &b->clip;
+        if (!NK_INBOX(x0, y0, clip->x, clip->y, clip->w, clip->h) &&
+            !NK_INBOX(x1, y1, clip->x, clip->y, clip->w, clip->h) &&
+            !NK_INBOX(x2, y2, clip->x, clip->y, clip->w, clip->h))
+            return;
+    }
+
+    cmd = (struct nk_command_triangle_filled*)
+        nk_command_buffer_push(b, NK_COMMAND_TRIANGLE_FILLED, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->a.x = (short)x0;
+    cmd->a.y = (short)y0;
+    cmd->b.x = (short)x1;
+    cmd->b.y = (short)y1;
+    cmd->c.x = (short)x2;
+    cmd->c.y = (short)y2;
+    cmd->color = c;
+}
+
+NK_API void
+nk_stroke_polygon(struct nk_command_buffer *b,  float *points, int point_count,
+    float line_thickness, struct nk_color col)
+{
+    int i;
+    nk_size size = 0;
+    struct nk_command_polygon *cmd;
+
+    NK_ASSERT(b);
+    if (!b || col.a == 0 || line_thickness <= 0) return;
+    size = sizeof(*cmd) + sizeof(short) * 2 * (nk_size)point_count;
+    cmd = (struct nk_command_polygon*) nk_command_buffer_push(b, NK_COMMAND_POLYGON, size);
+    if (!cmd) return;
+    cmd->color = col;
+    cmd->line_thickness = (unsigned short)line_thickness;
+    cmd->point_count = (unsigned short)point_count;
+    for (i = 0; i < point_count; ++i) {
+        cmd->points[i].x = (short)points[i*2];
+        cmd->points[i].y = (short)points[i*2+1];
+    }
+}
+
+NK_API void
+nk_fill_polygon(struct nk_command_buffer *b, float *points, int point_count,
+    struct nk_color col)
+{
+    int i;
+    nk_size size = 0;
+    struct nk_command_polygon_filled *cmd;
+
+    NK_ASSERT(b);
+    if (!b || col.a == 0) return;
+    size = sizeof(*cmd) + sizeof(short) * 2 * (nk_size)point_count;
+    cmd = (struct nk_command_polygon_filled*)
+        nk_command_buffer_push(b, NK_COMMAND_POLYGON_FILLED, size);
+    if (!cmd) return;
+    cmd->color = col;
+    cmd->point_count = (unsigned short)point_count;
+    for (i = 0; i < point_count; ++i) {
+        cmd->points[i].x = (short)points[i*2+0];
+        cmd->points[i].y = (short)points[i*2+1];
+    }
+}
+
+NK_API void
+nk_stroke_polyline(struct nk_command_buffer *b, float *points, int point_count,
+    float line_thickness, struct nk_color col)
+{
+    int i;
+    nk_size size = 0;
+    struct nk_command_polyline *cmd;
+
+    NK_ASSERT(b);
+    if (!b || col.a == 0 || line_thickness <= 0) return;
+    size = sizeof(*cmd) + sizeof(short) * 2 * (nk_size)point_count;
+    cmd = (struct nk_command_polyline*) nk_command_buffer_push(b, NK_COMMAND_POLYLINE, size);
+    if (!cmd) return;
+    cmd->color = col;
+    cmd->point_count = (unsigned short)point_count;
+    cmd->line_thickness = (unsigned short)line_thickness;
+    for (i = 0; i < point_count; ++i) {
+        cmd->points[i].x = (short)points[i*2];
+        cmd->points[i].y = (short)points[i*2+1];
+    }
+}
+
+NK_API void
+nk_draw_image(struct nk_command_buffer *b, struct nk_rect r,
+    const struct nk_image *img, struct nk_color col)
+{
+    struct nk_command_image *cmd;
+    NK_ASSERT(b);
+    if (!b) return;
+    if (b->use_clipping) {
+        const struct nk_rect *c = &b->clip;
+        if (c->w == 0 || c->h == 0 || !NK_INTERSECT(r.x, r.y, r.w, r.h, c->x, c->y, c->w, c->h))
+            return;
+    }
+
+    cmd = (struct nk_command_image*)
+        nk_command_buffer_push(b, NK_COMMAND_IMAGE, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->x = (short)r.x;
+    cmd->y = (short)r.y;
+    cmd->w = (unsigned short)NK_MAX(0, r.w);
+    cmd->h = (unsigned short)NK_MAX(0, r.h);
+    cmd->img = *img;
+    cmd->col = col;
+}
+
+NK_API void
+nk_push_custom(struct nk_command_buffer *b, struct nk_rect r,
+    nk_command_custom_callback cb, nk_handle usr)
+{
+    struct nk_command_custom *cmd;
+    NK_ASSERT(b);
+    if (!b) return;
+    if (b->use_clipping) {
+        const struct nk_rect *c = &b->clip;
+        if (c->w == 0 || c->h == 0 || !NK_INTERSECT(r.x, r.y, r.w, r.h, c->x, c->y, c->w, c->h))
+            return;
+    }
+
+    cmd = (struct nk_command_custom*)
+        nk_command_buffer_push(b, NK_COMMAND_CUSTOM, sizeof(*cmd));
+    if (!cmd) return;
+    cmd->x = (short)r.x;
+    cmd->y = (short)r.y;
+    cmd->w = (unsigned short)NK_MAX(0, r.w);
+    cmd->h = (unsigned short)NK_MAX(0, r.h);
+    cmd->callback_data = usr;
+    cmd->callback = cb;
+}
+
+NK_API void
+nk_draw_text(struct nk_command_buffer *b, struct nk_rect r,
+    const char *string, int length, const struct nk_user_font *font,
+    struct nk_color bg, struct nk_color fg)
+{
+    float text_width = 0;
+    struct nk_command_text *cmd;
+
+    NK_ASSERT(b);
+    NK_ASSERT(font);
+    if (!b || !string || !length || (bg.a == 0 && fg.a == 0)) return;
+    if (b->use_clipping) {
+        const struct nk_rect *c = &b->clip;
+        if (c->w == 0 || c->h == 0 || !NK_INTERSECT(r.x, r.y, r.w, r.h, c->x, c->y, c->w, c->h))
+            return;
+    }
+
+    /* make sure text fits inside bounds */
+    text_width = font->width(font->userdata, font->height, string, length);
+    if (text_width > r.w){
+        int glyphs = 0;
+        float txt_width = (float)text_width;
+        length = nk_text_clamp(font, string, length, r.w, &glyphs, &txt_width, 0,0);
+    }
+
+    if (!length) return;
+    cmd = (struct nk_command_text*)
+        nk_command_buffer_push(b, NK_COMMAND_TEXT, sizeof(*cmd) + (nk_size)(length + 1));
+    if (!cmd) return;
+    cmd->x = (short)r.x;
+    cmd->y = (short)r.y;
+    cmd->w = (unsigned short)r.w;
+    cmd->h = (unsigned short)r.h;
+    cmd->background = bg;
+    cmd->foreground = fg;
+    cmd->font = font;
+    cmd->length = length;
+    cmd->height = font->height;
+    NK_MEMCPY(cmd->string, string, (nk_size)length);
+    cmd->string[length] = '\0';
+}
+
+/* ==============================================================
+ *
+ *                          DRAW LIST
+ *
+ * ===============================================================*/
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+NK_API void
+nk_draw_list_init(struct nk_draw_list *list)
+{
+    nk_size i = 0;
+    NK_ASSERT(list);
+    if (!list) return;
+    nk_zero(list, sizeof(*list));
+    for (i = 0; i < NK_LEN(list->circle_vtx); ++i) {
+        const float a = ((float)i / (float)NK_LEN(list->circle_vtx)) * 2 * NK_PI;
+        list->circle_vtx[i].x = (float)NK_COS(a);
+        list->circle_vtx[i].y = (float)NK_SIN(a);
+    }
+}
+
+NK_API void
+nk_draw_list_setup(struct nk_draw_list *canvas, const struct nk_convert_config *config,
+    struct nk_buffer *cmds, struct nk_buffer *vertices, struct nk_buffer *elements,
+    enum nk_anti_aliasing line_aa, enum nk_anti_aliasing shape_aa)
+{
+    NK_ASSERT(canvas);
+    NK_ASSERT(config);
+    NK_ASSERT(cmds);
+    NK_ASSERT(vertices);
+    NK_ASSERT(elements);
+    if (!canvas || !config || !cmds || !vertices || !elements)
+        return;
+
+    canvas->buffer = cmds;
+    canvas->config = *config;
+    canvas->elements = elements;
+    canvas->vertices = vertices;
+    canvas->line_AA = line_aa;
+    canvas->shape_AA = shape_aa;
+    canvas->clip_rect = nk_null_rect;
+}
+
+NK_API const struct nk_draw_command*
+nk__draw_list_begin(const struct nk_draw_list *canvas, const struct nk_buffer *buffer)
+{
+    nk_byte *memory;
+    nk_size offset;
+    const struct nk_draw_command *cmd;
+
+    NK_ASSERT(buffer);
+    if (!buffer || !buffer->size || !canvas->cmd_count)
+        return 0;
+
+    memory = (nk_byte*)buffer->memory.ptr;
+    offset = buffer->memory.size - canvas->cmd_offset;
+    cmd = nk_ptr_add(const struct nk_draw_command, memory, offset);
+    return cmd;
+}
+
+NK_API const struct nk_draw_command*
+nk__draw_list_end(const struct nk_draw_list *canvas, const struct nk_buffer *buffer)
+{
+    nk_size size;
+    nk_size offset;
+    nk_byte *memory;
+    const struct nk_draw_command *end;
+
+    NK_ASSERT(buffer);
+    NK_ASSERT(canvas);
+    if (!buffer || !canvas)
+        return 0;
+
+    memory = (nk_byte*)buffer->memory.ptr;
+    size = buffer->memory.size;
+    offset = size - canvas->cmd_offset;
+    end = nk_ptr_add(const struct nk_draw_command, memory, offset);
+    end -= (canvas->cmd_count-1);
+    return end;
+}
+
+NK_API const struct nk_draw_command*
+nk__draw_list_next(const struct nk_draw_command *cmd,
+    const struct nk_buffer *buffer, const struct nk_draw_list *canvas)
+{
+    const struct nk_draw_command *end;
+    NK_ASSERT(buffer);
+    NK_ASSERT(canvas);
+    if (!cmd || !buffer || !canvas)
+        return 0;
+
+    end = nk__draw_list_end(canvas, buffer);
+    if (cmd <= end) return 0;
+    return (cmd-1);
+}
+
+NK_API void
+nk_draw_list_clear(struct nk_draw_list *list)
+{
+    NK_ASSERT(list);
+    if (!list) return;
+    if (list->buffer)
+        nk_buffer_clear(list->buffer);
+    if (list->vertices)
+        nk_buffer_clear(list->vertices);
+    if (list->elements)
+        nk_buffer_clear(list->elements);
+
+    list->element_count = 0;
+    list->vertex_count = 0;
+    list->cmd_offset = 0;
+    list->cmd_count = 0;
+    list->path_count = 0;
+    list->vertices = 0;
+    list->elements = 0;
+    list->clip_rect = nk_null_rect;
+}
+
+NK_INTERN struct nk_vec2*
+nk_draw_list_alloc_path(struct nk_draw_list *list, int count)
+{
+    struct nk_vec2 *points;
+    NK_STORAGE const nk_size point_align = NK_ALIGNOF(struct nk_vec2);
+    NK_STORAGE const nk_size point_size = sizeof(struct nk_vec2);
+    points = (struct nk_vec2*)
+        nk_buffer_alloc(list->buffer, NK_BUFFER_FRONT,
+                        point_size * (nk_size)count, point_align);
+
+    if (!points) return 0;
+    if (!list->path_offset) {
+        void *memory = nk_buffer_memory(list->buffer);
+        list->path_offset = (unsigned int)((nk_byte*)points - (nk_byte*)memory);
+    }
+    list->path_count += (unsigned int)count;
+    return points;
+}
+
+NK_INTERN struct nk_vec2
+nk_draw_list_path_last(struct nk_draw_list *list)
+{
+    void *memory;
+    struct nk_vec2 *point;
+    NK_ASSERT(list->path_count);
+    memory = nk_buffer_memory(list->buffer);
+    point = nk_ptr_add(struct nk_vec2, memory, list->path_offset);
+    point += (list->path_count-1);
+    return *point;
+}
+
+NK_INTERN struct nk_draw_command*
+nk_draw_list_push_command(struct nk_draw_list *list, struct nk_rect clip,
+    nk_handle texture)
+{
+    NK_STORAGE const nk_size cmd_align = NK_ALIGNOF(struct nk_draw_command);
+    NK_STORAGE const nk_size cmd_size = sizeof(struct nk_draw_command);
+    struct nk_draw_command *cmd;
+
+    NK_ASSERT(list);
+    cmd = (struct nk_draw_command*)
+        nk_buffer_alloc(list->buffer, NK_BUFFER_BACK, cmd_size, cmd_align);
+
+    if (!cmd) return 0;
+    if (!list->cmd_count) {
+        nk_byte *memory = (nk_byte*)nk_buffer_memory(list->buffer);
+        nk_size total = nk_buffer_total(list->buffer);
+        memory = nk_ptr_add(nk_byte, memory, total);
+        list->cmd_offset = (nk_size)(memory - (nk_byte*)cmd);
+    }
+
+    cmd->elem_count = 0;
+    cmd->clip_rect = clip;
+    cmd->texture = texture;
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    cmd->userdata = list->userdata;
+#endif
+
+    list->cmd_count++;
+    list->clip_rect = clip;
+    return cmd;
+}
+
+NK_INTERN struct nk_draw_command*
+nk_draw_list_command_last(struct nk_draw_list *list)
+{
+    void *memory;
+    nk_size size;
+    struct nk_draw_command *cmd;
+    NK_ASSERT(list->cmd_count);
+
+    memory = nk_buffer_memory(list->buffer);
+    size = nk_buffer_total(list->buffer);
+    cmd = nk_ptr_add(struct nk_draw_command, memory, size - list->cmd_offset);
+    return (cmd - (list->cmd_count-1));
+}
+
+NK_INTERN void
+nk_draw_list_add_clip(struct nk_draw_list *list, struct nk_rect rect)
+{
+    NK_ASSERT(list);
+    if (!list) return;
+    if (!list->cmd_count) {
+        nk_draw_list_push_command(list, rect, list->config.null.texture);
+    } else {
+        struct nk_draw_command *prev = nk_draw_list_command_last(list);
+        if (prev->elem_count == 0)
+            prev->clip_rect = rect;
+        nk_draw_list_push_command(list, rect, prev->texture);
+    }
+}
+
+NK_INTERN void
+nk_draw_list_push_image(struct nk_draw_list *list, nk_handle texture)
+{
+    NK_ASSERT(list);
+    if (!list) return;
+    if (!list->cmd_count) {
+        nk_draw_list_push_command(list, nk_null_rect, texture);
+    } else {
+        struct nk_draw_command *prev = nk_draw_list_command_last(list);
+        if (prev->elem_count == 0)
+            prev->texture = texture;
+        else if (prev->texture.id != texture.id)
+            nk_draw_list_push_command(list, prev->clip_rect, texture);
+    }
+}
+
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+NK_API void
+nk_draw_list_push_userdata(struct nk_draw_list *list, nk_handle userdata)
+{
+    list->userdata = userdata;
+}
+#endif
+
+NK_INTERN void*
+nk_draw_list_alloc_vertices(struct nk_draw_list *list, nk_size count)
+{
+    void *vtx;
+    NK_ASSERT(list);
+    if (!list) return 0;
+    vtx = nk_buffer_alloc(list->vertices, NK_BUFFER_FRONT,
+        list->config.vertex_size*count, list->config.vertex_alignment);
+    if (!vtx) return 0;
+    list->vertex_count += (unsigned int)count;
+    return vtx;
+}
+
+NK_INTERN nk_draw_index*
+nk_draw_list_alloc_elements(struct nk_draw_list *list, nk_size count)
+{
+    nk_draw_index *ids;
+    struct nk_draw_command *cmd;
+    NK_STORAGE const nk_size elem_align = NK_ALIGNOF(nk_draw_index);
+    NK_STORAGE const nk_size elem_size = sizeof(nk_draw_index);
+    NK_ASSERT(list);
+    if (!list) return 0;
+
+    ids = (nk_draw_index*)
+        nk_buffer_alloc(list->elements, NK_BUFFER_FRONT, elem_size*count, elem_align);
+    if (!ids) return 0;
+    cmd = nk_draw_list_command_last(list);
+    list->element_count += (unsigned int)count;
+    cmd->elem_count += (unsigned int)count;
+    return ids;
+}
+
+NK_INTERN int
+nk_draw_vertex_layout_element_is_end_of_layout(
+    const struct nk_draw_vertex_layout_element *element)
+{
+    return (element->attribute == NK_VERTEX_ATTRIBUTE_COUNT ||
+            element->format == NK_FORMAT_COUNT);
+}
+
+NK_INTERN void
+nk_draw_vertex_color(void *attribute, const float *values,
+    enum nk_draw_vertex_layout_format format)
+{
+    /* if this triggers you tried to provide a value format for a color */
+    NK_ASSERT(format >= NK_FORMAT_COLOR_BEGIN);
+    NK_ASSERT(format <= NK_FORMAT_COLOR_END);
+    if (format < NK_FORMAT_COLOR_BEGIN || format > NK_FORMAT_COLOR_END) return;
+
+    switch (format) {
+    default: NK_ASSERT(0 && "Invalid vertex layout color format"); break;
+    case NK_FORMAT_R8G8B8A8:
+    case NK_FORMAT_R8G8B8: {
+        struct nk_color col = nk_rgba_fv(values);
+        NK_MEMCPY(attribute, &col.r, sizeof(col));
+    } break;
+    case NK_FORMAT_B8G8R8A8: {
+        struct nk_color col = nk_rgba_fv(values);
+        struct nk_color bgra = nk_rgba(col.b, col.g, col.r, col.a);
+        NK_MEMCPY(attribute, &bgra, sizeof(bgra));
+    } break;
+    case NK_FORMAT_R16G15B16: {
+        nk_ushort col[3];
+        col[0] = (nk_ushort)NK_CLAMP(NK_USHORT_MIN, values[0] * NK_USHORT_MAX, NK_USHORT_MAX);
+        col[1] = (nk_ushort)NK_CLAMP(NK_USHORT_MIN, values[1] * NK_USHORT_MAX, NK_USHORT_MAX);
+        col[2] = (nk_ushort)NK_CLAMP(NK_USHORT_MIN, values[2] * NK_USHORT_MAX, NK_USHORT_MAX);
+        NK_MEMCPY(attribute, col, sizeof(col));
+    } break;
+    case NK_FORMAT_R16G15B16A16: {
+        nk_ushort col[4];
+        col[0] = (nk_ushort)NK_CLAMP(NK_USHORT_MIN, values[0] * NK_USHORT_MAX, NK_USHORT_MAX);
+        col[1] = (nk_ushort)NK_CLAMP(NK_USHORT_MIN, values[1] * NK_USHORT_MAX, NK_USHORT_MAX);
+        col[2] = (nk_ushort)NK_CLAMP(NK_USHORT_MIN, values[2] * NK_USHORT_MAX, NK_USHORT_MAX);
+        col[3] = (nk_ushort)NK_CLAMP(NK_USHORT_MIN, values[3] * NK_USHORT_MAX, NK_USHORT_MAX);
+        NK_MEMCPY(attribute, col, sizeof(col));
+    } break;
+    case NK_FORMAT_R32G32B32: {
+        nk_uint col[3];
+        col[0] = (nk_uint)NK_CLAMP(NK_UINT_MIN, values[0] * NK_UINT_MAX, NK_UINT_MAX);
+        col[1] = (nk_uint)NK_CLAMP(NK_UINT_MIN, values[1] * NK_UINT_MAX, NK_UINT_MAX);
+        col[2] = (nk_uint)NK_CLAMP(NK_UINT_MIN, values[2] * NK_UINT_MAX, NK_UINT_MAX);
+        NK_MEMCPY(attribute, col, sizeof(col));
+    } break;
+    case NK_FORMAT_R32G32B32A32: {
+        nk_uint col[4];
+        col[0] = (nk_uint)NK_CLAMP(NK_UINT_MIN, values[0] * NK_UINT_MAX, NK_UINT_MAX);
+        col[1] = (nk_uint)NK_CLAMP(NK_UINT_MIN, values[1] * NK_UINT_MAX, NK_UINT_MAX);
+        col[2] = (nk_uint)NK_CLAMP(NK_UINT_MIN, values[2] * NK_UINT_MAX, NK_UINT_MAX);
+        col[3] = (nk_uint)NK_CLAMP(NK_UINT_MIN, values[3] * NK_UINT_MAX, NK_UINT_MAX);
+        NK_MEMCPY(attribute, col, sizeof(col));
+    } break;
+    case NK_FORMAT_R32G32B32A32_FLOAT:
+        NK_MEMCPY(attribute, values, sizeof(float)*4);
+        break;
+    case NK_FORMAT_R32G32B32A32_DOUBLE: {
+        double col[4];
+        col[0] = (double)NK_SATURATE(values[0]);
+        col[1] = (double)NK_SATURATE(values[1]);
+        col[2] = (double)NK_SATURATE(values[2]);
+        col[3] = (double)NK_SATURATE(values[3]);
+        NK_MEMCPY(attribute, col, sizeof(col));
+    } break;
+    case NK_FORMAT_RGB32:
+    case NK_FORMAT_RGBA32: {
+        struct nk_color col = nk_rgba_fv(values);
+        nk_uint color = nk_color_u32(col);
+        NK_MEMCPY(attribute, &color, sizeof(color));
+    } break;
+    }
+}
+
+NK_INTERN void
+nk_draw_vertex_element(void *dst, const float *values, int value_count,
+    enum nk_draw_vertex_layout_format format)
+{
+    int value_index;
+    void *attribute = dst;
+    /* if this triggers you tried to provide a color format for a value */
+    NK_ASSERT(format < NK_FORMAT_COLOR_BEGIN);
+    if (format >= NK_FORMAT_COLOR_BEGIN && format <= NK_FORMAT_COLOR_END) return;
+    for (value_index = 0; value_index < value_count; ++value_index) {
+        switch (format) {
+        default: NK_ASSERT(0 && "invalid vertex layout format"); break;
+        case NK_FORMAT_SCHAR: {
+            char value = (char)NK_CLAMP(NK_SCHAR_MIN, values[value_index], NK_SCHAR_MAX);
+            NK_MEMCPY(attribute, &value, sizeof(value));
+            attribute = (void*)((char*)attribute + sizeof(char));
+        } break;
+        case NK_FORMAT_SSHORT: {
+            nk_short value = (nk_short)NK_CLAMP(NK_SSHORT_MIN, values[value_index], NK_SSHORT_MAX);
+            NK_MEMCPY(attribute, &value, sizeof(value));
+            attribute = (void*)((char*)attribute + sizeof(value));
+        } break;
+        case NK_FORMAT_SINT: {
+            nk_int value = (nk_int)NK_CLAMP(NK_SINT_MIN, values[value_index], NK_SINT_MAX);
+            NK_MEMCPY(attribute, &value, sizeof(value));
+            attribute = (void*)((char*)attribute + sizeof(nk_int));
+        } break;
+        case NK_FORMAT_UCHAR: {
+            unsigned char value = (unsigned char)NK_CLAMP(NK_UCHAR_MIN, values[value_index], NK_UCHAR_MAX);
+            NK_MEMCPY(attribute, &value, sizeof(value));
+            attribute = (void*)((char*)attribute + sizeof(unsigned char));
+        } break;
+        case NK_FORMAT_USHORT: {
+            nk_ushort value = (nk_ushort)NK_CLAMP(NK_USHORT_MIN, values[value_index], NK_USHORT_MAX);
+            NK_MEMCPY(attribute, &value, sizeof(value));
+            attribute = (void*)((char*)attribute + sizeof(value));
+            } break;
+        case NK_FORMAT_UINT: {
+            nk_uint value = (nk_uint)NK_CLAMP(NK_UINT_MIN, values[value_index], NK_UINT_MAX);
+            NK_MEMCPY(attribute, &value, sizeof(value));
+            attribute = (void*)((char*)attribute + sizeof(nk_uint));
+        } break;
+        case NK_FORMAT_FLOAT:
+            NK_MEMCPY(attribute, &values[value_index], sizeof(values[value_index]));
+            attribute = (void*)((char*)attribute + sizeof(float));
+            break;
+        case NK_FORMAT_DOUBLE: {
+            double value = (double)values[value_index];
+            NK_MEMCPY(attribute, &value, sizeof(value));
+            attribute = (void*)((char*)attribute + sizeof(double));
+            } break;
+        }
+    }
+}
+
+NK_INTERN void*
+nk_draw_vertex(void *dst, const struct nk_convert_config *config,
+    struct nk_vec2 pos, struct nk_vec2 uv, struct nk_colorf color)
+{
+    void *result = (void*)((char*)dst + config->vertex_size);
+    const struct nk_draw_vertex_layout_element *elem_iter = config->vertex_layout;
+    while (!nk_draw_vertex_layout_element_is_end_of_layout(elem_iter)) {
+        void *address = (void*)((char*)dst + elem_iter->offset);
+        switch (elem_iter->attribute) {
+        case NK_VERTEX_ATTRIBUTE_COUNT:
+        default: NK_ASSERT(0 && "wrong element attribute");
+        case NK_VERTEX_POSITION: nk_draw_vertex_element(address, &pos.x, 2, elem_iter->format); break;
+        case NK_VERTEX_TEXCOORD: nk_draw_vertex_element(address, &uv.x, 2, elem_iter->format); break;
+        case NK_VERTEX_COLOR: nk_draw_vertex_color(address, &color.r, elem_iter->format); break;
+        }
+        elem_iter++;
+    }
+    return result;
+}
+
+NK_API void
+nk_draw_list_stroke_poly_line(struct nk_draw_list *list, const struct nk_vec2 *points,
+    const unsigned int points_count, struct nk_color color, enum nk_draw_list_stroke closed,
+    float thickness, enum nk_anti_aliasing aliasing)
+{
+    nk_size count;
+    int thick_line;
+    struct nk_colorf col;
+    struct nk_colorf col_trans;
+    NK_ASSERT(list);
+    if (!list || points_count < 2) return;
+
+    color.a = (nk_byte)((float)color.a * list->config.global_alpha);
+    count = points_count;
+    if (!closed) count = points_count-1;
+    thick_line = thickness > 1.0f;
+
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    nk_draw_list_push_userdata(list, list->userdata);
+#endif
+
+    color.a = (nk_byte)((float)color.a * list->config.global_alpha);
+    nk_color_fv(&col.r, color);
+    col_trans = col;
+    col_trans.a = 0;
+
+    if (aliasing == NK_ANTI_ALIASING_ON) {
+        /* ANTI-ALIASED STROKE */
+        const float AA_SIZE = 1.0f;
+        NK_STORAGE const nk_size pnt_align = NK_ALIGNOF(struct nk_vec2);
+        NK_STORAGE const nk_size pnt_size = sizeof(struct nk_vec2);
+
+        /* allocate vertices and elements  */
+        nk_size i1 = 0;
+        nk_size vertex_offset;
+        nk_size index = list->vertex_count;
+
+        const nk_size idx_count = (thick_line) ?  (count * 18) : (count * 12);
+        const nk_size vtx_count = (thick_line) ? (points_count * 4): (points_count *3);
+
+        void *vtx = nk_draw_list_alloc_vertices(list, vtx_count);
+        nk_draw_index *ids = nk_draw_list_alloc_elements(list, idx_count);
+
+        nk_size size;
+        struct nk_vec2 *normals, *temp;
+        if (!vtx || !ids) return;
+
+        /* temporary allocate normals + points */
+        vertex_offset = (nk_size)((nk_byte*)vtx - (nk_byte*)list->vertices->memory.ptr);
+        nk_buffer_mark(list->vertices, NK_BUFFER_FRONT);
+        size = pnt_size * ((thick_line) ? 5 : 3) * points_count;
+        normals = (struct nk_vec2*) nk_buffer_alloc(list->vertices, NK_BUFFER_FRONT, size, pnt_align);
+        NK_ASSERT(normals);
+        if (!normals) return;
+        temp = normals + points_count;
+
+        /* make sure vertex pointer is still correct */
+        vtx = (void*)((nk_byte*)list->vertices->memory.ptr + vertex_offset);
+
+        /* calculate normals */
+        for (i1 = 0; i1 < count; ++i1) {
+            const nk_size i2 = ((i1 + 1) == points_count) ? 0 : (i1 + 1);
+            struct nk_vec2 diff = nk_vec2_sub(points[i2], points[i1]);
+            float len;
+
+            /* vec2 inverted length  */
+            len = nk_vec2_len_sqr(diff);
+            if (len != 0.0f)
+                len = nk_inv_sqrt(len);
+            else len = 1.0f;
+
+            diff = nk_vec2_muls(diff, len);
+            normals[i1].x = diff.y;
+            normals[i1].y = -diff.x;
+        }
+
+        if (!closed)
+            normals[points_count-1] = normals[points_count-2];
+
+        if (!thick_line) {
+            nk_size idx1, i;
+            if (!closed) {
+                struct nk_vec2 d;
+                temp[0] = nk_vec2_add(points[0], nk_vec2_muls(normals[0], AA_SIZE));
+                temp[1] = nk_vec2_sub(points[0], nk_vec2_muls(normals[0], AA_SIZE));
+                d = nk_vec2_muls(normals[points_count-1], AA_SIZE);
+                temp[(points_count-1) * 2 + 0] = nk_vec2_add(points[points_count-1], d);
+                temp[(points_count-1) * 2 + 1] = nk_vec2_sub(points[points_count-1], d);
+            }
+
+            /* fill elements */
+            idx1 = index;
+            for (i1 = 0; i1 < count; i1++) {
+                struct nk_vec2 dm;
+                float dmr2;
+                nk_size i2 = ((i1 + 1) == points_count) ? 0 : (i1 + 1);
+                nk_size idx2 = ((i1+1) == points_count) ? index: (idx1 + 3);
+
+                /* average normals */
+                dm = nk_vec2_muls(nk_vec2_add(normals[i1], normals[i2]), 0.5f);
+                dmr2 = dm.x * dm.x + dm.y* dm.y;
+                if (dmr2 > 0.000001f) {
+                    float scale = 1.0f/dmr2;
+                    scale = NK_MIN(100.0f, scale);
+                    dm = nk_vec2_muls(dm, scale);
+                }
+
+                dm = nk_vec2_muls(dm, AA_SIZE);
+                temp[i2*2+0] = nk_vec2_add(points[i2], dm);
+                temp[i2*2+1] = nk_vec2_sub(points[i2], dm);
+
+                ids[0] = (nk_draw_index)(idx2 + 0); ids[1] = (nk_draw_index)(idx1+0);
+                ids[2] = (nk_draw_index)(idx1 + 2); ids[3] = (nk_draw_index)(idx1+2);
+                ids[4] = (nk_draw_index)(idx2 + 2); ids[5] = (nk_draw_index)(idx2+0);
+                ids[6] = (nk_draw_index)(idx2 + 1); ids[7] = (nk_draw_index)(idx1+1);
+                ids[8] = (nk_draw_index)(idx1 + 0); ids[9] = (nk_draw_index)(idx1+0);
+                ids[10]= (nk_draw_index)(idx2 + 0); ids[11]= (nk_draw_index)(idx2+1);
+                ids += 12;
+                idx1 = idx2;
+            }
+
+            /* fill vertices */
+            for (i = 0; i < points_count; ++i) {
+                const struct nk_vec2 uv = list->config.null.uv;
+                vtx = nk_draw_vertex(vtx, &list->config, points[i], uv, col);
+                vtx = nk_draw_vertex(vtx, &list->config, temp[i*2+0], uv, col_trans);
+                vtx = nk_draw_vertex(vtx, &list->config, temp[i*2+1], uv, col_trans);
+            }
+        } else {
+            nk_size idx1, i;
+            const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f;
+            if (!closed) {
+                struct nk_vec2 d1 = nk_vec2_muls(normals[0], half_inner_thickness + AA_SIZE);
+                struct nk_vec2 d2 = nk_vec2_muls(normals[0], half_inner_thickness);
+
+                temp[0] = nk_vec2_add(points[0], d1);
+                temp[1] = nk_vec2_add(points[0], d2);
+                temp[2] = nk_vec2_sub(points[0], d2);
+                temp[3] = nk_vec2_sub(points[0], d1);
+
+                d1 = nk_vec2_muls(normals[points_count-1], half_inner_thickness + AA_SIZE);
+                d2 = nk_vec2_muls(normals[points_count-1], half_inner_thickness);
+
+                temp[(points_count-1)*4+0] = nk_vec2_add(points[points_count-1], d1);
+                temp[(points_count-1)*4+1] = nk_vec2_add(points[points_count-1], d2);
+                temp[(points_count-1)*4+2] = nk_vec2_sub(points[points_count-1], d2);
+                temp[(points_count-1)*4+3] = nk_vec2_sub(points[points_count-1], d1);
+            }
+
+            /* add all elements */
+            idx1 = index;
+            for (i1 = 0; i1 < count; ++i1) {
+                struct nk_vec2 dm_out, dm_in;
+                const nk_size i2 = ((i1+1) == points_count) ? 0: (i1 + 1);
+                nk_size idx2 = ((i1+1) == points_count) ? index: (idx1 + 4);
+
+                /* average normals */
+                struct nk_vec2 dm = nk_vec2_muls(nk_vec2_add(normals[i1], normals[i2]), 0.5f);
+                float dmr2 = dm.x * dm.x + dm.y* dm.y;
+                if (dmr2 > 0.000001f) {
+                    float scale = 1.0f/dmr2;
+                    scale = NK_MIN(100.0f, scale);
+                    dm = nk_vec2_muls(dm, scale);
+                }
+
+                dm_out = nk_vec2_muls(dm, ((half_inner_thickness) + AA_SIZE));
+                dm_in = nk_vec2_muls(dm, half_inner_thickness);
+                temp[i2*4+0] = nk_vec2_add(points[i2], dm_out);
+                temp[i2*4+1] = nk_vec2_add(points[i2], dm_in);
+                temp[i2*4+2] = nk_vec2_sub(points[i2], dm_in);
+                temp[i2*4+3] = nk_vec2_sub(points[i2], dm_out);
+
+                /* add indexes */
+                ids[0] = (nk_draw_index)(idx2 + 1); ids[1] = (nk_draw_index)(idx1+1);
+                ids[2] = (nk_draw_index)(idx1 + 2); ids[3] = (nk_draw_index)(idx1+2);
+                ids[4] = (nk_draw_index)(idx2 + 2); ids[5] = (nk_draw_index)(idx2+1);
+                ids[6] = (nk_draw_index)(idx2 + 1); ids[7] = (nk_draw_index)(idx1+1);
+                ids[8] = (nk_draw_index)(idx1 + 0); ids[9] = (nk_draw_index)(idx1+0);
+                ids[10]= (nk_draw_index)(idx2 + 0); ids[11] = (nk_draw_index)(idx2+1);
+                ids[12]= (nk_draw_index)(idx2 + 2); ids[13] = (nk_draw_index)(idx1+2);
+                ids[14]= (nk_draw_index)(idx1 + 3); ids[15] = (nk_draw_index)(idx1+3);
+                ids[16]= (nk_draw_index)(idx2 + 3); ids[17] = (nk_draw_index)(idx2+2);
+                ids += 18;
+                idx1 = idx2;
+            }
+
+            /* add vertices */
+            for (i = 0; i < points_count; ++i) {
+                const struct nk_vec2 uv = list->config.null.uv;
+                vtx = nk_draw_vertex(vtx, &list->config, temp[i*4+0], uv, col_trans);
+                vtx = nk_draw_vertex(vtx, &list->config, temp[i*4+1], uv, col);
+                vtx = nk_draw_vertex(vtx, &list->config, temp[i*4+2], uv, col);
+                vtx = nk_draw_vertex(vtx, &list->config, temp[i*4+3], uv, col_trans);
+            }
+        }
+        /* free temporary normals + points */
+        nk_buffer_reset(list->vertices, NK_BUFFER_FRONT);
+    } else {
+        /* NON ANTI-ALIASED STROKE */
+        nk_size i1 = 0;
+        nk_size idx = list->vertex_count;
+        const nk_size idx_count = count * 6;
+        const nk_size vtx_count = count * 4;
+        void *vtx = nk_draw_list_alloc_vertices(list, vtx_count);
+        nk_draw_index *ids = nk_draw_list_alloc_elements(list, idx_count);
+        if (!vtx || !ids) return;
+
+        for (i1 = 0; i1 < count; ++i1) {
+            float dx, dy;
+            const struct nk_vec2 uv = list->config.null.uv;
+            const nk_size i2 = ((i1+1) == points_count) ? 0 : i1 + 1;
+            const struct nk_vec2 p1 = points[i1];
+            const struct nk_vec2 p2 = points[i2];
+            struct nk_vec2 diff = nk_vec2_sub(p2, p1);
+            float len;
+
+            /* vec2 inverted length  */
+            len = nk_vec2_len_sqr(diff);
+            if (len != 0.0f)
+                len = nk_inv_sqrt(len);
+            else len = 1.0f;
+            diff = nk_vec2_muls(diff, len);
+
+            /* add vertices */
+            dx = diff.x * (thickness * 0.5f);
+            dy = diff.y * (thickness * 0.5f);
+
+            vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(p1.x + dy, p1.y - dx), uv, col);
+            vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(p2.x + dy, p2.y - dx), uv, col);
+            vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(p2.x - dy, p2.y + dx), uv, col);
+            vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(p1.x - dy, p1.y + dx), uv, col);
+
+            ids[0] = (nk_draw_index)(idx+0); ids[1] = (nk_draw_index)(idx+1);
+            ids[2] = (nk_draw_index)(idx+2); ids[3] = (nk_draw_index)(idx+0);
+            ids[4] = (nk_draw_index)(idx+2); ids[5] = (nk_draw_index)(idx+3);
+
+            ids += 6;
+            idx += 4;
+        }
+    }
+}
+
+NK_API void
+nk_draw_list_fill_poly_convex(struct nk_draw_list *list,
+    const struct nk_vec2 *points, const unsigned int points_count,
+    struct nk_color color, enum nk_anti_aliasing aliasing)
+{
+    struct nk_colorf col;
+    struct nk_colorf col_trans;
+
+    NK_STORAGE const nk_size pnt_align = NK_ALIGNOF(struct nk_vec2);
+    NK_STORAGE const nk_size pnt_size = sizeof(struct nk_vec2);
+    NK_ASSERT(list);
+    if (!list || points_count < 3) return;
+
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    nk_draw_list_push_userdata(list, list->userdata);
+#endif
+
+    color.a = (nk_byte)((float)color.a * list->config.global_alpha);
+    nk_color_fv(&col.r, color);
+    col_trans = col;
+    col_trans.a = 0;
+
+    if (aliasing == NK_ANTI_ALIASING_ON) {
+        nk_size i = 0;
+        nk_size i0 = 0;
+        nk_size i1 = 0;
+
+        const float AA_SIZE = 1.0f;
+        nk_size vertex_offset = 0;
+        nk_size index = list->vertex_count;
+
+        const nk_size idx_count = (points_count-2)*3 + points_count*6;
+        const nk_size vtx_count = (points_count*2);
+
+        void *vtx = nk_draw_list_alloc_vertices(list, vtx_count);
+        nk_draw_index *ids = nk_draw_list_alloc_elements(list, idx_count);
+
+        nk_size size = 0;
+        struct nk_vec2 *normals = 0;
+        unsigned int vtx_inner_idx = (unsigned int)(index + 0);
+        unsigned int vtx_outer_idx = (unsigned int)(index + 1);
+        if (!vtx || !ids) return;
+
+        /* temporary allocate normals */
+        vertex_offset = (nk_size)((nk_byte*)vtx - (nk_byte*)list->vertices->memory.ptr);
+        nk_buffer_mark(list->vertices, NK_BUFFER_FRONT);
+        size = pnt_size * points_count;
+        normals = (struct nk_vec2*) nk_buffer_alloc(list->vertices, NK_BUFFER_FRONT, size, pnt_align);
+        NK_ASSERT(normals);
+        if (!normals) return;
+        vtx = (void*)((nk_byte*)list->vertices->memory.ptr + vertex_offset);
+
+        /* add elements */
+        for (i = 2; i < points_count; i++) {
+            ids[0] = (nk_draw_index)(vtx_inner_idx);
+            ids[1] = (nk_draw_index)(vtx_inner_idx + ((i-1) << 1));
+            ids[2] = (nk_draw_index)(vtx_inner_idx + (i << 1));
+            ids += 3;
+        }
+
+        /* compute normals */
+        for (i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) {
+            struct nk_vec2 p0 = points[i0];
+            struct nk_vec2 p1 = points[i1];
+            struct nk_vec2 diff = nk_vec2_sub(p1, p0);
+
+            /* vec2 inverted length  */
+            float len = nk_vec2_len_sqr(diff);
+            if (len != 0.0f)
+                len = nk_inv_sqrt(len);
+            else len = 1.0f;
+            diff = nk_vec2_muls(diff, len);
+
+            normals[i0].x = diff.y;
+            normals[i0].y = -diff.x;
+        }
+
+        /* add vertices + indexes */
+        for (i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) {
+            const struct nk_vec2 uv = list->config.null.uv;
+            struct nk_vec2 n0 = normals[i0];
+            struct nk_vec2 n1 = normals[i1];
+            struct nk_vec2 dm = nk_vec2_muls(nk_vec2_add(n0, n1), 0.5f);
+            float dmr2 = dm.x*dm.x + dm.y*dm.y;
+            if (dmr2 > 0.000001f) {
+                float scale = 1.0f / dmr2;
+                scale = NK_MIN(scale, 100.0f);
+                dm = nk_vec2_muls(dm, scale);
+            }
+            dm = nk_vec2_muls(dm, AA_SIZE * 0.5f);
+
+            /* add vertices */
+            vtx = nk_draw_vertex(vtx, &list->config, nk_vec2_sub(points[i1], dm), uv, col);
+            vtx = nk_draw_vertex(vtx, &list->config, nk_vec2_add(points[i1], dm), uv, col_trans);
+
+            /* add indexes */
+            ids[0] = (nk_draw_index)(vtx_inner_idx+(i1<<1));
+            ids[1] = (nk_draw_index)(vtx_inner_idx+(i0<<1));
+            ids[2] = (nk_draw_index)(vtx_outer_idx+(i0<<1));
+            ids[3] = (nk_draw_index)(vtx_outer_idx+(i0<<1));
+            ids[4] = (nk_draw_index)(vtx_outer_idx+(i1<<1));
+            ids[5] = (nk_draw_index)(vtx_inner_idx+(i1<<1));
+            ids += 6;
+        }
+        /* free temporary normals + points */
+        nk_buffer_reset(list->vertices, NK_BUFFER_FRONT);
+    } else {
+        nk_size i = 0;
+        nk_size index = list->vertex_count;
+        const nk_size idx_count = (points_count-2)*3;
+        const nk_size vtx_count = points_count;
+        void *vtx = nk_draw_list_alloc_vertices(list, vtx_count);
+        nk_draw_index *ids = nk_draw_list_alloc_elements(list, idx_count);
+
+        if (!vtx || !ids) return;
+        for (i = 0; i < vtx_count; ++i)
+            vtx = nk_draw_vertex(vtx, &list->config, points[i], list->config.null.uv, col);
+        for (i = 2; i < points_count; ++i) {
+            ids[0] = (nk_draw_index)index;
+            ids[1] = (nk_draw_index)(index+ i - 1);
+            ids[2] = (nk_draw_index)(index+i);
+            ids += 3;
+        }
+    }
+}
+
+NK_API void
+nk_draw_list_path_clear(struct nk_draw_list *list)
+{
+    NK_ASSERT(list);
+    if (!list) return;
+    nk_buffer_reset(list->buffer, NK_BUFFER_FRONT);
+    list->path_count = 0;
+    list->path_offset = 0;
+}
+
+NK_API void
+nk_draw_list_path_line_to(struct nk_draw_list *list, struct nk_vec2 pos)
+{
+    struct nk_vec2 *points = 0;
+    struct nk_draw_command *cmd = 0;
+    NK_ASSERT(list);
+    if (!list) return;
+    if (!list->cmd_count)
+        nk_draw_list_add_clip(list, nk_null_rect);
+
+    cmd = nk_draw_list_command_last(list);
+    if (cmd && cmd->texture.ptr != list->config.null.texture.ptr)
+        nk_draw_list_push_image(list, list->config.null.texture);
+
+    points = nk_draw_list_alloc_path(list, 1);
+    if (!points) return;
+    points[0] = pos;
+}
+
+NK_API void
+nk_draw_list_path_arc_to_fast(struct nk_draw_list *list, struct nk_vec2 center,
+    float radius, int a_min, int a_max)
+{
+    int a = 0;
+    NK_ASSERT(list);
+    if (!list) return;
+    if (a_min <= a_max) {
+        for (a = a_min; a <= a_max; a++) {
+            const struct nk_vec2 c = list->circle_vtx[(nk_size)a % NK_LEN(list->circle_vtx)];
+            const float x = center.x + c.x * radius;
+            const float y = center.y + c.y * radius;
+            nk_draw_list_path_line_to(list, nk_vec2(x, y));
+        }
+    }
+}
+
+NK_API void
+nk_draw_list_path_arc_to(struct nk_draw_list *list, struct nk_vec2 center,
+    float radius, float a_min, float a_max, unsigned int segments)
+{
+    unsigned int i = 0;
+    NK_ASSERT(list);
+    if (!list) return;
+    if (radius == 0.0f) return;
+
+    /*  This algorithm for arc drawing relies on these two trigonometric identities[1]:
+            sin(a + b) = sin(a) * cos(b) + cos(a) * sin(b)
+            cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b)
+
+        Two coordinates (x, y) of a point on a circle centered on
+        the origin can be written in polar form as:
+            x = r * cos(a)
+            y = r * sin(a)
+        where r is the radius of the circle,
+            a is the angle between (x, y) and the origin.
+
+        This allows us to rotate the coordinates around the
+        origin by an angle b using the following transformation:
+            x' = r * cos(a + b) = x * cos(b) - y * sin(b)
+            y' = r * sin(a + b) = y * cos(b) + x * sin(b)
+
+        [1] https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities
+    */
+    const float d_angle = (a_max - a_min) / (float)segments;
+    const float sin_d = (float)NK_SIN(d_angle);
+    const float cos_d = (float)NK_COS(d_angle);
+
+    float cx = (float)NK_COS(a_min) * radius;
+    float cy = (float)NK_SIN(a_min) * radius;
+    for(i = 0; i <= segments; ++i) {
+        const float x = center.x + cx;
+        const float y = center.y + cy;
+        nk_draw_list_path_line_to(list, nk_vec2(x, y));
+
+        const float new_cx = cx * cos_d - cy * sin_d;
+        const float new_cy = cy * cos_d + cx * sin_d;
+        cx = new_cx;
+        cy = new_cy;
+    }
+}
+
+NK_API void
+nk_draw_list_path_rect_to(struct nk_draw_list *list, struct nk_vec2 a,
+    struct nk_vec2 b, float rounding)
+{
+    float r;
+    NK_ASSERT(list);
+    if (!list) return;
+    r = rounding;
+    r = NK_MIN(r, ((b.x-a.x) < 0) ? -(b.x-a.x): (b.x-a.x));
+    r = NK_MIN(r, ((b.y-a.y) < 0) ? -(b.y-a.y): (b.y-a.y));
+
+    if (r == 0.0f) {
+        nk_draw_list_path_line_to(list, a);
+        nk_draw_list_path_line_to(list, nk_vec2(b.x,a.y));
+        nk_draw_list_path_line_to(list, b);
+        nk_draw_list_path_line_to(list, nk_vec2(a.x,b.y));
+    } else {
+        nk_draw_list_path_arc_to_fast(list, nk_vec2(a.x + r, a.y + r), r, 6, 9);
+        nk_draw_list_path_arc_to_fast(list, nk_vec2(b.x - r, a.y + r), r, 9, 12);
+        nk_draw_list_path_arc_to_fast(list, nk_vec2(b.x - r, b.y - r), r, 0, 3);
+        nk_draw_list_path_arc_to_fast(list, nk_vec2(a.x + r, b.y - r), r, 3, 6);
+    }
+}
+
+NK_API void
+nk_draw_list_path_curve_to(struct nk_draw_list *list, struct nk_vec2 p2,
+    struct nk_vec2 p3, struct nk_vec2 p4, unsigned int num_segments)
+{
+    float t_step;
+    unsigned int i_step;
+    struct nk_vec2 p1;
+
+    NK_ASSERT(list);
+    NK_ASSERT(list->path_count);
+    if (!list || !list->path_count) return;
+    num_segments = NK_MAX(num_segments, 1);
+
+    p1 = nk_draw_list_path_last(list);
+    t_step = 1.0f/(float)num_segments;
+    for (i_step = 1; i_step <= num_segments; ++i_step) {
+        float t = t_step * (float)i_step;
+        float u = 1.0f - t;
+        float w1 = u*u*u;
+        float w2 = 3*u*u*t;
+        float w3 = 3*u*t*t;
+        float w4 = t * t *t;
+        float x = w1 * p1.x + w2 * p2.x + w3 * p3.x + w4 * p4.x;
+        float y = w1 * p1.y + w2 * p2.y + w3 * p3.y + w4 * p4.y;
+        nk_draw_list_path_line_to(list, nk_vec2(x,y));
+    }
+}
+
+NK_API void
+nk_draw_list_path_fill(struct nk_draw_list *list, struct nk_color color)
+{
+    struct nk_vec2 *points;
+    NK_ASSERT(list);
+    if (!list) return;
+    points = (struct nk_vec2*)nk_buffer_memory(list->buffer);
+    nk_draw_list_fill_poly_convex(list, points, list->path_count, color, list->config.shape_AA);
+    nk_draw_list_path_clear(list);
+}
+
+NK_API void
+nk_draw_list_path_stroke(struct nk_draw_list *list, struct nk_color color,
+    enum nk_draw_list_stroke closed, float thickness)
+{
+    struct nk_vec2 *points;
+    NK_ASSERT(list);
+    if (!list) return;
+    points = (struct nk_vec2*)nk_buffer_memory(list->buffer);
+    nk_draw_list_stroke_poly_line(list, points, list->path_count, color,
+        closed, thickness, list->config.line_AA);
+    nk_draw_list_path_clear(list);
+}
+
+NK_API void
+nk_draw_list_stroke_line(struct nk_draw_list *list, struct nk_vec2 a,
+    struct nk_vec2 b, struct nk_color col, float thickness)
+{
+    NK_ASSERT(list);
+    if (!list || !col.a) return;
+    if (list->line_AA == NK_ANTI_ALIASING_ON) {
+        nk_draw_list_path_line_to(list, a);
+        nk_draw_list_path_line_to(list, b);
+    } else {
+        nk_draw_list_path_line_to(list, nk_vec2_sub(a,nk_vec2(0.5f,0.5f)));
+        nk_draw_list_path_line_to(list, nk_vec2_sub(b,nk_vec2(0.5f,0.5f)));
+    }
+    nk_draw_list_path_stroke(list,  col, NK_STROKE_OPEN, thickness);
+}
+
+NK_API void
+nk_draw_list_fill_rect(struct nk_draw_list *list, struct nk_rect rect,
+    struct nk_color col, float rounding)
+{
+    NK_ASSERT(list);
+    if (!list || !col.a) return;
+
+    if (list->line_AA == NK_ANTI_ALIASING_ON) {
+        nk_draw_list_path_rect_to(list, nk_vec2(rect.x, rect.y),
+            nk_vec2(rect.x + rect.w, rect.y + rect.h), rounding);
+    } else {
+        nk_draw_list_path_rect_to(list, nk_vec2(rect.x-0.5f, rect.y-0.5f),
+            nk_vec2(rect.x + rect.w, rect.y + rect.h), rounding);
+    } nk_draw_list_path_fill(list,  col);
+}
+
+NK_API void
+nk_draw_list_stroke_rect(struct nk_draw_list *list, struct nk_rect rect,
+    struct nk_color col, float rounding, float thickness)
+{
+    NK_ASSERT(list);
+    if (!list || !col.a) return;
+    if (list->line_AA == NK_ANTI_ALIASING_ON) {
+        nk_draw_list_path_rect_to(list, nk_vec2(rect.x, rect.y),
+            nk_vec2(rect.x + rect.w, rect.y + rect.h), rounding);
+    } else {
+        nk_draw_list_path_rect_to(list, nk_vec2(rect.x-0.5f, rect.y-0.5f),
+            nk_vec2(rect.x + rect.w, rect.y + rect.h), rounding);
+    } nk_draw_list_path_stroke(list,  col, NK_STROKE_CLOSED, thickness);
+}
+
+NK_API void
+nk_draw_list_fill_rect_multi_color(struct nk_draw_list *list, struct nk_rect rect,
+    struct nk_color left, struct nk_color top, struct nk_color right,
+    struct nk_color bottom)
+{
+    void *vtx;
+    struct nk_colorf col_left, col_top;
+    struct nk_colorf col_right, col_bottom;
+    nk_draw_index *idx;
+    nk_draw_index index;
+
+    nk_color_fv(&col_left.r, left);
+    nk_color_fv(&col_right.r, right);
+    nk_color_fv(&col_top.r, top);
+    nk_color_fv(&col_bottom.r, bottom);
+
+    NK_ASSERT(list);
+    if (!list) return;
+
+    nk_draw_list_push_image(list, list->config.null.texture);
+    index = (nk_draw_index)list->vertex_count;
+    vtx = nk_draw_list_alloc_vertices(list, 4);
+    idx = nk_draw_list_alloc_elements(list, 6);
+    if (!vtx || !idx) return;
+
+    idx[0] = (nk_draw_index)(index+0); idx[1] = (nk_draw_index)(index+1);
+    idx[2] = (nk_draw_index)(index+2); idx[3] = (nk_draw_index)(index+0);
+    idx[4] = (nk_draw_index)(index+2); idx[5] = (nk_draw_index)(index+3);
+
+    vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(rect.x, rect.y), list->config.null.uv, col_left);
+    vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(rect.x + rect.w, rect.y), list->config.null.uv, col_top);
+    vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(rect.x + rect.w, rect.y + rect.h), list->config.null.uv, col_right);
+    vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(rect.x, rect.y + rect.h), list->config.null.uv, col_bottom);
+}
+
+NK_API void
+nk_draw_list_fill_triangle(struct nk_draw_list *list, struct nk_vec2 a,
+    struct nk_vec2 b, struct nk_vec2 c, struct nk_color col)
+{
+    NK_ASSERT(list);
+    if (!list || !col.a) return;
+    nk_draw_list_path_line_to(list, a);
+    nk_draw_list_path_line_to(list, b);
+    nk_draw_list_path_line_to(list, c);
+    nk_draw_list_path_fill(list, col);
+}
+
+NK_API void
+nk_draw_list_stroke_triangle(struct nk_draw_list *list, struct nk_vec2 a,
+    struct nk_vec2 b, struct nk_vec2 c, struct nk_color col, float thickness)
+{
+    NK_ASSERT(list);
+    if (!list || !col.a) return;
+    nk_draw_list_path_line_to(list, a);
+    nk_draw_list_path_line_to(list, b);
+    nk_draw_list_path_line_to(list, c);
+    nk_draw_list_path_stroke(list, col, NK_STROKE_CLOSED, thickness);
+}
+
+NK_API void
+nk_draw_list_fill_circle(struct nk_draw_list *list, struct nk_vec2 center,
+    float radius, struct nk_color col, unsigned int segs)
+{
+    float a_max;
+    NK_ASSERT(list);
+    if (!list || !col.a) return;
+    a_max = NK_PI * 2.0f * ((float)segs - 1.0f) / (float)segs;
+    nk_draw_list_path_arc_to(list, center, radius, 0.0f, a_max, segs);
+    nk_draw_list_path_fill(list, col);
+}
+
+NK_API void
+nk_draw_list_stroke_circle(struct nk_draw_list *list, struct nk_vec2 center,
+    float radius, struct nk_color col, unsigned int segs, float thickness)
+{
+    float a_max;
+    NK_ASSERT(list);
+    if (!list || !col.a) return;
+    a_max = NK_PI * 2.0f * ((float)segs - 1.0f) / (float)segs;
+    nk_draw_list_path_arc_to(list, center, radius, 0.0f, a_max, segs);
+    nk_draw_list_path_stroke(list, col, NK_STROKE_CLOSED, thickness);
+}
+
+NK_API void
+nk_draw_list_stroke_curve(struct nk_draw_list *list, struct nk_vec2 p0,
+    struct nk_vec2 cp0, struct nk_vec2 cp1, struct nk_vec2 p1,
+    struct nk_color col, unsigned int segments, float thickness)
+{
+    NK_ASSERT(list);
+    if (!list || !col.a) return;
+    nk_draw_list_path_line_to(list, p0);
+    nk_draw_list_path_curve_to(list, cp0, cp1, p1, segments);
+    nk_draw_list_path_stroke(list, col, NK_STROKE_OPEN, thickness);
+}
+
+NK_INTERN void
+nk_draw_list_push_rect_uv(struct nk_draw_list *list, struct nk_vec2 a,
+    struct nk_vec2 c, struct nk_vec2 uva, struct nk_vec2 uvc,
+    struct nk_color color)
+{
+    void *vtx;
+    struct nk_vec2 uvb;
+    struct nk_vec2 uvd;
+    struct nk_vec2 b;
+    struct nk_vec2 d;
+
+    struct nk_colorf col;
+    nk_draw_index *idx;
+    nk_draw_index index;
+    NK_ASSERT(list);
+    if (!list) return;
+
+    nk_color_fv(&col.r, color);
+    uvb = nk_vec2(uvc.x, uva.y);
+    uvd = nk_vec2(uva.x, uvc.y);
+    b = nk_vec2(c.x, a.y);
+    d = nk_vec2(a.x, c.y);
+
+    index = (nk_draw_index)list->vertex_count;
+    vtx = nk_draw_list_alloc_vertices(list, 4);
+    idx = nk_draw_list_alloc_elements(list, 6);
+    if (!vtx || !idx) return;
+
+    idx[0] = (nk_draw_index)(index+0); idx[1] = (nk_draw_index)(index+1);
+    idx[2] = (nk_draw_index)(index+2); idx[3] = (nk_draw_index)(index+0);
+    idx[4] = (nk_draw_index)(index+2); idx[5] = (nk_draw_index)(index+3);
+
+    vtx = nk_draw_vertex(vtx, &list->config, a, uva, col);
+    vtx = nk_draw_vertex(vtx, &list->config, b, uvb, col);
+    vtx = nk_draw_vertex(vtx, &list->config, c, uvc, col);
+    vtx = nk_draw_vertex(vtx, &list->config, d, uvd, col);
+}
+
+NK_API void
+nk_draw_list_add_image(struct nk_draw_list *list, struct nk_image texture,
+    struct nk_rect rect, struct nk_color color)
+{
+    NK_ASSERT(list);
+    if (!list) return;
+    /* push new command with given texture */
+    nk_draw_list_push_image(list, texture.handle);
+    if (nk_image_is_subimage(&texture)) {
+        /* add region inside of the texture  */
+        struct nk_vec2 uv[2];
+        uv[0].x = (float)texture.region[0]/(float)texture.w;
+        uv[0].y = (float)texture.region[1]/(float)texture.h;
+        uv[1].x = (float)(texture.region[0] + texture.region[2])/(float)texture.w;
+        uv[1].y = (float)(texture.region[1] + texture.region[3])/(float)texture.h;
+        nk_draw_list_push_rect_uv(list, nk_vec2(rect.x, rect.y),
+            nk_vec2(rect.x + rect.w, rect.y + rect.h),  uv[0], uv[1], color);
+    } else nk_draw_list_push_rect_uv(list, nk_vec2(rect.x, rect.y),
+            nk_vec2(rect.x + rect.w, rect.y + rect.h),
+            nk_vec2(0.0f, 0.0f), nk_vec2(1.0f, 1.0f),color);
+}
+
+NK_API void
+nk_draw_list_add_text(struct nk_draw_list *list, const struct nk_user_font *font,
+    struct nk_rect rect, const char *text, int len, float font_height,
+    struct nk_color fg)
+{
+    float x = 0;
+    int text_len = 0;
+    nk_rune unicode = 0;
+    nk_rune next = 0;
+    int glyph_len = 0;
+    int next_glyph_len = 0;
+    struct nk_user_font_glyph g;
+
+    NK_ASSERT(list);
+    if (!list || !len || !text) return;
+    if (!NK_INTERSECT(rect.x, rect.y, rect.w, rect.h,
+        list->clip_rect.x, list->clip_rect.y, list->clip_rect.w, list->clip_rect.h)) return;
+
+    nk_draw_list_push_image(list, font->texture);
+    x = rect.x;
+    glyph_len = nk_utf_decode(text, &unicode, len);
+    if (!glyph_len) return;
+
+    /* draw every glyph image */
+    fg.a = (nk_byte)((float)fg.a * list->config.global_alpha);
+    while (text_len < len && glyph_len) {
+        float gx, gy, gh, gw;
+        float char_width = 0;
+        if (unicode == NK_UTF_INVALID) break;
+
+        /* query currently drawn glyph information */
+        next_glyph_len = nk_utf_decode(text + text_len + glyph_len, &next, (int)len - text_len);
+        font->query(font->userdata, font_height, &g, unicode,
+                    (next == NK_UTF_INVALID) ? '\0' : next);
+
+        /* calculate and draw glyph drawing rectangle and image */
+        gx = x + g.offset.x;
+        gy = rect.y + g.offset.y;
+        gw = g.width; gh = g.height;
+        char_width = g.xadvance;
+        nk_draw_list_push_rect_uv(list, nk_vec2(gx,gy), nk_vec2(gx + gw, gy+ gh),
+            g.uv[0], g.uv[1], fg);
+
+        /* offset next glyph */
+        text_len += glyph_len;
+        x += char_width;
+        glyph_len = next_glyph_len;
+        unicode = next;
+    }
+}
+
+NK_API nk_flags
+nk_convert(struct nk_context *ctx, struct nk_buffer *cmds,
+    struct nk_buffer *vertices, struct nk_buffer *elements,
+    const struct nk_convert_config *config)
+{
+    nk_flags res = NK_CONVERT_SUCCESS;
+    const struct nk_command *cmd;
+    NK_ASSERT(ctx);
+    NK_ASSERT(cmds);
+    NK_ASSERT(vertices);
+    NK_ASSERT(elements);
+    NK_ASSERT(config);
+    NK_ASSERT(config->vertex_layout);
+    NK_ASSERT(config->vertex_size);
+    if (!ctx || !cmds || !vertices || !elements || !config || !config->vertex_layout)
+        return NK_CONVERT_INVALID_PARAM;
+
+    nk_draw_list_setup(&ctx->draw_list, config, cmds, vertices, elements,
+        config->line_AA, config->shape_AA);
+    nk_foreach(cmd, ctx)
+    {
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+        ctx->draw_list.userdata = cmd->userdata;
+#endif
+        switch (cmd->type) {
+        case NK_COMMAND_NOP: break;
+        case NK_COMMAND_SCISSOR: {
+            const struct nk_command_scissor *s = (const struct nk_command_scissor*)cmd;
+            nk_draw_list_add_clip(&ctx->draw_list, nk_rect(s->x, s->y, s->w, s->h));
+        } break;
+        case NK_COMMAND_LINE: {
+            const struct nk_command_line *l = (const struct nk_command_line*)cmd;
+            nk_draw_list_stroke_line(&ctx->draw_list, nk_vec2(l->begin.x, l->begin.y),
+                nk_vec2(l->end.x, l->end.y), l->color, l->line_thickness);
+        } break;
+        case NK_COMMAND_CURVE: {
+            const struct nk_command_curve *q = (const struct nk_command_curve*)cmd;
+            nk_draw_list_stroke_curve(&ctx->draw_list, nk_vec2(q->begin.x, q->begin.y),
+                nk_vec2(q->ctrl[0].x, q->ctrl[0].y), nk_vec2(q->ctrl[1].x,
+                q->ctrl[1].y), nk_vec2(q->end.x, q->end.y), q->color,
+                config->curve_segment_count, q->line_thickness);
+        } break;
+        case NK_COMMAND_RECT: {
+            const struct nk_command_rect *r = (const struct nk_command_rect*)cmd;
+            nk_draw_list_stroke_rect(&ctx->draw_list, nk_rect(r->x, r->y, r->w, r->h),
+                r->color, (float)r->rounding, r->line_thickness);
+        } break;
+        case NK_COMMAND_RECT_FILLED: {
+            const struct nk_command_rect_filled *r = (const struct nk_command_rect_filled*)cmd;
+            nk_draw_list_fill_rect(&ctx->draw_list, nk_rect(r->x, r->y, r->w, r->h),
+                r->color, (float)r->rounding);
+        } break;
+        case NK_COMMAND_RECT_MULTI_COLOR: {
+            const struct nk_command_rect_multi_color *r = (const struct nk_command_rect_multi_color*)cmd;
+            nk_draw_list_fill_rect_multi_color(&ctx->draw_list, nk_rect(r->x, r->y, r->w, r->h),
+                r->left, r->top, r->right, r->bottom);
+        } break;
+        case NK_COMMAND_CIRCLE: {
+            const struct nk_command_circle *c = (const struct nk_command_circle*)cmd;
+            nk_draw_list_stroke_circle(&ctx->draw_list, nk_vec2((float)c->x + (float)c->w/2,
+                (float)c->y + (float)c->h/2), (float)c->w/2, c->color,
+                config->circle_segment_count, c->line_thickness);
+        } break;
+        case NK_COMMAND_CIRCLE_FILLED: {
+            const struct nk_command_circle_filled *c = (const struct nk_command_circle_filled *)cmd;
+            nk_draw_list_fill_circle(&ctx->draw_list, nk_vec2((float)c->x + (float)c->w/2,
+                (float)c->y + (float)c->h/2), (float)c->w/2, c->color,
+                config->circle_segment_count);
+        } break;
+        case NK_COMMAND_ARC: {
+            const struct nk_command_arc *c = (const struct nk_command_arc*)cmd;
+            nk_draw_list_path_line_to(&ctx->draw_list, nk_vec2(c->cx, c->cy));
+            nk_draw_list_path_arc_to(&ctx->draw_list, nk_vec2(c->cx, c->cy), c->r,
+                c->a[0], c->a[1], config->arc_segment_count);
+            nk_draw_list_path_stroke(&ctx->draw_list, c->color, NK_STROKE_CLOSED, c->line_thickness);
+        } break;
+        case NK_COMMAND_ARC_FILLED: {
+            const struct nk_command_arc_filled *c = (const struct nk_command_arc_filled*)cmd;
+            nk_draw_list_path_line_to(&ctx->draw_list, nk_vec2(c->cx, c->cy));
+            nk_draw_list_path_arc_to(&ctx->draw_list, nk_vec2(c->cx, c->cy), c->r,
+                c->a[0], c->a[1], config->arc_segment_count);
+            nk_draw_list_path_fill(&ctx->draw_list, c->color);
+        } break;
+        case NK_COMMAND_TRIANGLE: {
+            const struct nk_command_triangle *t = (const struct nk_command_triangle*)cmd;
+            nk_draw_list_stroke_triangle(&ctx->draw_list, nk_vec2(t->a.x, t->a.y),
+                nk_vec2(t->b.x, t->b.y), nk_vec2(t->c.x, t->c.y), t->color,
+                t->line_thickness);
+        } break;
+        case NK_COMMAND_TRIANGLE_FILLED: {
+            const struct nk_command_triangle_filled *t = (const struct nk_command_triangle_filled*)cmd;
+            nk_draw_list_fill_triangle(&ctx->draw_list, nk_vec2(t->a.x, t->a.y),
+                nk_vec2(t->b.x, t->b.y), nk_vec2(t->c.x, t->c.y), t->color);
+        } break;
+        case NK_COMMAND_POLYGON: {
+            int i;
+            const struct nk_command_polygon*p = (const struct nk_command_polygon*)cmd;
+            for (i = 0; i < p->point_count; ++i) {
+                struct nk_vec2 pnt = nk_vec2((float)p->points[i].x, (float)p->points[i].y);
+                nk_draw_list_path_line_to(&ctx->draw_list, pnt);
+            }
+            nk_draw_list_path_stroke(&ctx->draw_list, p->color, NK_STROKE_CLOSED, p->line_thickness);
+        } break;
+        case NK_COMMAND_POLYGON_FILLED: {
+            int i;
+            const struct nk_command_polygon_filled *p = (const struct nk_command_polygon_filled*)cmd;
+            for (i = 0; i < p->point_count; ++i) {
+                struct nk_vec2 pnt = nk_vec2((float)p->points[i].x, (float)p->points[i].y);
+                nk_draw_list_path_line_to(&ctx->draw_list, pnt);
+            }
+            nk_draw_list_path_fill(&ctx->draw_list, p->color);
+        } break;
+        case NK_COMMAND_POLYLINE: {
+            int i;
+            const struct nk_command_polyline *p = (const struct nk_command_polyline*)cmd;
+            for (i = 0; i < p->point_count; ++i) {
+                struct nk_vec2 pnt = nk_vec2((float)p->points[i].x, (float)p->points[i].y);
+                nk_draw_list_path_line_to(&ctx->draw_list, pnt);
+            }
+            nk_draw_list_path_stroke(&ctx->draw_list, p->color, NK_STROKE_OPEN, p->line_thickness);
+        } break;
+        case NK_COMMAND_TEXT: {
+            const struct nk_command_text *t = (const struct nk_command_text*)cmd;
+            nk_draw_list_add_text(&ctx->draw_list, t->font, nk_rect(t->x, t->y, t->w, t->h),
+                t->string, t->length, t->height, t->foreground);
+        } break;
+        case NK_COMMAND_IMAGE: {
+            const struct nk_command_image *i = (const struct nk_command_image*)cmd;
+            nk_draw_list_add_image(&ctx->draw_list, i->img, nk_rect(i->x, i->y, i->w, i->h), i->col);
+        } break;
+        case NK_COMMAND_CUSTOM: {
+            const struct nk_command_custom *c = (const struct nk_command_custom*)cmd;
+            c->callback(&ctx->draw_list, c->x, c->y, c->w, c->h, c->callback_data);
+        } break;
+        default: break;
+        }
+    }
+    res |= (cmds->needed > cmds->allocated + (cmds->memory.size - cmds->size)) ? NK_CONVERT_COMMAND_BUFFER_FULL: 0;
+    res |= (vertices->needed > vertices->allocated) ? NK_CONVERT_VERTEX_BUFFER_FULL: 0;
+    res |= (elements->needed > elements->allocated) ? NK_CONVERT_ELEMENT_BUFFER_FULL: 0;
+    return res;
+}
+NK_API const struct nk_draw_command*
+nk__draw_begin(const struct nk_context *ctx,
+    const struct nk_buffer *buffer)
+{return nk__draw_list_begin(&ctx->draw_list, buffer);}
+
+NK_API const struct nk_draw_command*
+nk__draw_end(const struct nk_context *ctx, const struct nk_buffer *buffer)
+{return nk__draw_list_end(&ctx->draw_list, buffer);}
+
+NK_API const struct nk_draw_command*
+nk__draw_next(const struct nk_draw_command *cmd,
+    const struct nk_buffer *buffer, const struct nk_context *ctx)
+{return nk__draw_list_next(cmd, buffer, &ctx->draw_list);}
+
+#endif
+
+/*
+ * ==============================================================
+ *
+ *                          FONT HANDLING
+ *
+ * ===============================================================
+ */
+#ifdef NK_INCLUDE_FONT_BAKING
+/* -------------------------------------------------------------
+ *
+ *                          RECT PACK
+ *
+ * --------------------------------------------------------------*/
+/* stb_rect_pack.h - v0.05 - public domain - rectangle packing */
+/* Sean Barrett 2014 */
+#define NK_RP__MAXVAL  0xffff
+typedef unsigned short nk_rp_coord;
+
+struct nk_rp_rect {
+    /* reserved for your use: */
+    int id;
+    /* input: */
+    nk_rp_coord w, h;
+    /* output: */
+    nk_rp_coord x, y;
+    int was_packed;
+    /* non-zero if valid packing */
+}; /* 16 bytes, nominally */
+
+struct nk_rp_node {
+    nk_rp_coord  x,y;
+    struct nk_rp_node  *next;
+};
+
+struct nk_rp_context {
+    int width;
+    int height;
+    int align;
+    int init_mode;
+    int heuristic;
+    int num_nodes;
+    struct nk_rp_node *active_head;
+    struct nk_rp_node *free_head;
+    struct nk_rp_node extra[2];
+    /* we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' */
+};
+
+struct nk_rp__findresult {
+    int x,y;
+    struct nk_rp_node **prev_link;
+};
+
+enum NK_RP_HEURISTIC {
+    NK_RP_HEURISTIC_Skyline_default=0,
+    NK_RP_HEURISTIC_Skyline_BL_sortHeight = NK_RP_HEURISTIC_Skyline_default,
+    NK_RP_HEURISTIC_Skyline_BF_sortHeight
+};
+enum NK_RP_INIT_STATE{NK_RP__INIT_skyline = 1};
+
+NK_INTERN void
+nk_rp_setup_allow_out_of_mem(struct nk_rp_context *context, int allow_out_of_mem)
+{
+    if (allow_out_of_mem)
+        /* if it's ok to run out of memory, then don't bother aligning them; */
+        /* this gives better packing, but may fail due to OOM (even though */
+        /* the rectangles easily fit). @TODO a smarter approach would be to only */
+        /* quantize once we've hit OOM, then we could get rid of this parameter. */
+        context->align = 1;
+    else {
+        /* if it's not ok to run out of memory, then quantize the widths */
+        /* so that num_nodes is always enough nodes. */
+        /* */
+        /* I.e. num_nodes * align >= width */
+        /*                  align >= width / num_nodes */
+        /*                  align = ceil(width/num_nodes) */
+        context->align = (context->width + context->num_nodes-1) / context->num_nodes;
+    }
+}
+
+NK_INTERN void
+nk_rp_init_target(struct nk_rp_context *context, int width, int height,
+    struct nk_rp_node *nodes, int num_nodes)
+{
+    int i;
+#ifndef STBRP_LARGE_RECTS
+    NK_ASSERT(width <= 0xffff && height <= 0xffff);
+#endif
+
+    for (i=0; i < num_nodes-1; ++i)
+        nodes[i].next = &nodes[i+1];
+    nodes[i].next = 0;
+    context->init_mode = NK_RP__INIT_skyline;
+    context->heuristic = NK_RP_HEURISTIC_Skyline_default;
+    context->free_head = &nodes[0];
+    context->active_head = &context->extra[0];
+    context->width = width;
+    context->height = height;
+    context->num_nodes = num_nodes;
+    nk_rp_setup_allow_out_of_mem(context, 0);
+
+    /* node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) */
+    context->extra[0].x = 0;
+    context->extra[0].y = 0;
+    context->extra[0].next = &context->extra[1];
+    context->extra[1].x = (nk_rp_coord) width;
+    context->extra[1].y = 65535;
+    context->extra[1].next = 0;
+}
+
+/* find minimum y position if it starts at x1 */
+NK_INTERN int
+nk_rp__skyline_find_min_y(struct nk_rp_context *c, struct nk_rp_node *first,
+    int x0, int width, int *pwaste)
+{
+    struct nk_rp_node *node = first;
+    int x1 = x0 + width;
+    int min_y, visited_width, waste_area;
+    NK_ASSERT(first->x <= x0);
+    NK_UNUSED(c);
+
+    NK_ASSERT(node->next->x > x0);
+    /* we ended up handling this in the caller for efficiency */
+    NK_ASSERT(node->x <= x0);
+
+    min_y = 0;
+    waste_area = 0;
+    visited_width = 0;
+    while (node->x < x1)
+    {
+        if (node->y > min_y) {
+            /* raise min_y higher. */
+            /* we've accounted for all waste up to min_y, */
+            /* but we'll now add more waste for everything we've visited */
+            waste_area += visited_width * (node->y - min_y);
+            min_y = node->y;
+            /* the first time through, visited_width might be reduced */
+            if (node->x < x0)
+            visited_width += node->next->x - x0;
+            else
+            visited_width += node->next->x - node->x;
+        } else {
+            /* add waste area */
+            int under_width = node->next->x - node->x;
+            if (under_width + visited_width > width)
+            under_width = width - visited_width;
+            waste_area += under_width * (min_y - node->y);
+            visited_width += under_width;
+        }
+        node = node->next;
+    }
+    *pwaste = waste_area;
+    return min_y;
+}
+
+NK_INTERN struct nk_rp__findresult
+nk_rp__skyline_find_best_pos(struct nk_rp_context *c, int width, int height)
+{
+    int best_waste = (1<<30), best_x, best_y = (1 << 30);
+    struct nk_rp__findresult fr;
+    struct nk_rp_node **prev, *node, *tail, **best = 0;
+
+    /* align to multiple of c->align */
+    width = (width + c->align - 1);
+    width -= width % c->align;
+    NK_ASSERT(width % c->align == 0);
+
+    node = c->active_head;
+    prev = &c->active_head;
+    while (node->x + width <= c->width) {
+        int y,waste;
+        y = nk_rp__skyline_find_min_y(c, node, node->x, width, &waste);
+        /* actually just want to test BL */
+        if (c->heuristic == NK_RP_HEURISTIC_Skyline_BL_sortHeight) {
+            /* bottom left */
+            if (y < best_y) {
+            best_y = y;
+            best = prev;
+            }
+        } else {
+            /* best-fit */
+            if (y + height <= c->height) {
+                /* can only use it if it first vertically */
+                if (y < best_y || (y == best_y && waste < best_waste)) {
+                    best_y = y;
+                    best_waste = waste;
+                    best = prev;
+                }
+            }
+        }
+        prev = &node->next;
+        node = node->next;
+    }
+    best_x = (best == 0) ? 0 : (*best)->x;
+
+    /* if doing best-fit (BF), we also have to try aligning right edge to each node position */
+    /* */
+    /* e.g, if fitting */
+    /* */
+    /*     ____________________ */
+    /*    |____________________| */
+    /* */
+    /*            into */
+    /* */
+    /*   |                         | */
+    /*   |             ____________| */
+    /*   |____________| */
+    /* */
+    /* then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned */
+    /* */
+    /* This makes BF take about 2x the time */
+    if (c->heuristic == NK_RP_HEURISTIC_Skyline_BF_sortHeight)
+    {
+        tail = c->active_head;
+        node = c->active_head;
+        prev = &c->active_head;
+        /* find first node that's admissible */
+        while (tail->x < width)
+            tail = tail->next;
+        while (tail)
+        {
+            int xpos = tail->x - width;
+            int y,waste;
+            NK_ASSERT(xpos >= 0);
+            /* find the left position that matches this */
+            while (node->next->x <= xpos) {
+                prev = &node->next;
+                node = node->next;
+            }
+            NK_ASSERT(node->next->x > xpos && node->x <= xpos);
+            y = nk_rp__skyline_find_min_y(c, node, xpos, width, &waste);
+            if (y + height < c->height) {
+                if (y <= best_y) {
+                    if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
+                        best_x = xpos;
+                        NK_ASSERT(y <= best_y);
+                        best_y = y;
+                        best_waste = waste;
+                        best = prev;
+                    }
+                }
+            }
+            tail = tail->next;
+        }
+    }
+    fr.prev_link = best;
+    fr.x = best_x;
+    fr.y = best_y;
+    return fr;
+}
+
+NK_INTERN struct nk_rp__findresult
+nk_rp__skyline_pack_rectangle(struct nk_rp_context *context, int width, int height)
+{
+    /* find best position according to heuristic */
+    struct nk_rp__findresult res = nk_rp__skyline_find_best_pos(context, width, height);
+    struct nk_rp_node *node, *cur;
+
+    /* bail if: */
+    /*    1. it failed */
+    /*    2. the best node doesn't fit (we don't always check this) */
+    /*    3. we're out of memory */
+    if (res.prev_link == 0 || res.y + height > context->height || context->free_head == 0) {
+        res.prev_link = 0;
+        return res;
+    }
+
+    /* on success, create new node */
+    node = context->free_head;
+    node->x = (nk_rp_coord) res.x;
+    node->y = (nk_rp_coord) (res.y + height);
+
+    context->free_head = node->next;
+
+    /* insert the new node into the right starting point, and */
+    /* let 'cur' point to the remaining nodes needing to be */
+    /* stitched back in */
+    cur = *res.prev_link;
+    if (cur->x < res.x) {
+        /* preserve the existing one, so start testing with the next one */
+        struct nk_rp_node *next = cur->next;
+        cur->next = node;
+        cur = next;
+    } else {
+        *res.prev_link = node;
+    }
+
+    /* from here, traverse cur and free the nodes, until we get to one */
+    /* that shouldn't be freed */
+    while (cur->next && cur->next->x <= res.x + width) {
+        struct nk_rp_node *next = cur->next;
+        /* move the current node to the free list */
+        cur->next = context->free_head;
+        context->free_head = cur;
+        cur = next;
+    }
+    /* stitch the list back in */
+    node->next = cur;
+
+    if (cur->x < res.x + width)
+        cur->x = (nk_rp_coord) (res.x + width);
+    return res;
+}
+
+NK_INTERN int
+nk_rect_height_compare(const void *a, const void *b)
+{
+    const struct nk_rp_rect *p = (const struct nk_rp_rect *) a;
+    const struct nk_rp_rect *q = (const struct nk_rp_rect *) b;
+    if (p->h > q->h)
+        return -1;
+    if (p->h < q->h)
+        return  1;
+    return (p->w > q->w) ? -1 : (p->w < q->w);
+}
+
+NK_INTERN int
+nk_rect_original_order(const void *a, const void *b)
+{
+    const struct nk_rp_rect *p = (const struct nk_rp_rect *) a;
+    const struct nk_rp_rect *q = (const struct nk_rp_rect *) b;
+    return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
+}
+
+NK_INTERN void
+nk_rp_qsort(struct nk_rp_rect *array, unsigned int len, int(*cmp)(const void*,const void*))
+{
+    /* iterative quick sort */
+    #define NK_MAX_SORT_STACK 64
+    unsigned right, left = 0, stack[NK_MAX_SORT_STACK], pos = 0;
+    unsigned seed = len/2 * 69069+1;
+    for (;;) {
+        for (; left+1 < len; len++) {
+            struct nk_rp_rect pivot, tmp;
+            if (pos == NK_MAX_SORT_STACK) len = stack[pos = 0];
+            pivot = array[left+seed%(len-left)];
+            seed = seed * 69069 + 1;
+            stack[pos++] = len;
+            for (right = left-1;;) {
+                while (cmp(&array[++right], &pivot) < 0);
+                while (cmp(&pivot, &array[--len]) < 0);
+                if (right >= len) break;
+                tmp = array[right];
+                array[right] = array[len];
+                array[len] = tmp;
+            }
+        }
+        if (pos == 0) break;
+        left = len;
+        len = stack[--pos];
+    }
+    #undef NK_MAX_SORT_STACK
+}
+
+NK_INTERN void
+nk_rp_pack_rects(struct nk_rp_context *context, struct nk_rp_rect *rects, int num_rects)
+{
+    int i;
+    /* we use the 'was_packed' field internally to allow sorting/unsorting */
+    for (i=0; i < num_rects; ++i) {
+        rects[i].was_packed = i;
+    }
+
+    /* sort according to heuristic */
+    nk_rp_qsort(rects, (unsigned)num_rects, nk_rect_height_compare);
+
+    for (i=0; i < num_rects; ++i) {
+        struct nk_rp__findresult fr = nk_rp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
+        if (fr.prev_link) {
+            rects[i].x = (nk_rp_coord) fr.x;
+            rects[i].y = (nk_rp_coord) fr.y;
+        } else {
+            rects[i].x = rects[i].y = NK_RP__MAXVAL;
+        }
+    }
+
+    /* unsort */
+    nk_rp_qsort(rects, (unsigned)num_rects, nk_rect_original_order);
+
+    /* set was_packed flags */
+    for (i=0; i < num_rects; ++i)
+        rects[i].was_packed = !(rects[i].x == NK_RP__MAXVAL && rects[i].y == NK_RP__MAXVAL);
+}
+
+/*
+ * ==============================================================
+ *
+ *                          TRUETYPE
+ *
+ * ===============================================================
+ */
+/* stb_truetype.h - v1.07 - public domain */
+#define NK_TT_MAX_OVERSAMPLE   8
+#define NK_TT__OVER_MASK  (NK_TT_MAX_OVERSAMPLE-1)
+
+struct nk_tt_bakedchar {
+    unsigned short x0,y0,x1,y1;
+    /* coordinates of bbox in bitmap */
+    float xoff,yoff,xadvance;
+};
+
+struct nk_tt_aligned_quad{
+    float x0,y0,s0,t0; /* top-left */
+    float x1,y1,s1,t1; /* bottom-right */
+};
+
+struct nk_tt_packedchar {
+    unsigned short x0,y0,x1,y1;
+    /* coordinates of bbox in bitmap */
+    float xoff,yoff,xadvance;
+    float xoff2,yoff2;
+};
+
+struct nk_tt_pack_range {
+    float font_size;
+    int first_unicode_codepoint_in_range;
+    /* if non-zero, then the chars are continuous, and this is the first codepoint */
+    int *array_of_unicode_codepoints;
+    /* if non-zero, then this is an array of unicode codepoints */
+    int num_chars;
+    struct nk_tt_packedchar *chardata_for_range; /* output */
+    unsigned char h_oversample, v_oversample;
+    /* don't set these, they're used internally */
+};
+
+struct nk_tt_pack_context {
+    void *pack_info;
+    int   width;
+    int   height;
+    int   stride_in_bytes;
+    int   padding;
+    unsigned int   h_oversample, v_oversample;
+    unsigned char *pixels;
+    void  *nodes;
+};
+
+struct nk_tt_fontinfo {
+    const unsigned char* data; /* pointer to .ttf file */
+    int fontstart;/* offset of start of font */
+    int numGlyphs;/* number of glyphs, needed for range checking */
+    int loca,head,glyf,hhea,hmtx,kern; /* table locations as offset from start of .ttf */
+    int index_map; /* a cmap mapping for our chosen character encoding */
+    int indexToLocFormat; /* format needed to map from glyph index to glyph */
+};
+
+enum {
+  NK_TT_vmove=1,
+  NK_TT_vline,
+  NK_TT_vcurve
+};
+
+struct nk_tt_vertex {
+    short x,y,cx,cy;
+    unsigned char type,padding;
+};
+
+struct nk_tt__bitmap{
+   int w,h,stride;
+   unsigned char *pixels;
+};
+
+struct nk_tt__hheap_chunk {
+    struct nk_tt__hheap_chunk *next;
+};
+struct nk_tt__hheap {
+    struct nk_allocator alloc;
+    struct nk_tt__hheap_chunk *head;
+    void   *first_free;
+    int    num_remaining_in_head_chunk;
+};
+
+struct nk_tt__edge {
+    float x0,y0, x1,y1;
+    int invert;
+};
+
+struct nk_tt__active_edge {
+    struct nk_tt__active_edge *next;
+    float fx,fdx,fdy;
+    float direction;
+    float sy;
+    float ey;
+};
+struct nk_tt__point {float x,y;};
+
+#define NK_TT_MACSTYLE_DONTCARE     0
+#define NK_TT_MACSTYLE_BOLD         1
+#define NK_TT_MACSTYLE_ITALIC       2
+#define NK_TT_MACSTYLE_UNDERSCORE   4
+#define NK_TT_MACSTYLE_NONE         8
+/* <= not same as 0, this makes us check the bitfield is 0 */
+
+enum { /* platformID */
+   NK_TT_PLATFORM_ID_UNICODE   =0,
+   NK_TT_PLATFORM_ID_MAC       =1,
+   NK_TT_PLATFORM_ID_ISO       =2,
+   NK_TT_PLATFORM_ID_MICROSOFT =3
+};
+
+enum { /* encodingID for NK_TT_PLATFORM_ID_UNICODE */
+   NK_TT_UNICODE_EID_UNICODE_1_0    =0,
+   NK_TT_UNICODE_EID_UNICODE_1_1    =1,
+   NK_TT_UNICODE_EID_ISO_10646      =2,
+   NK_TT_UNICODE_EID_UNICODE_2_0_BMP=3,
+   NK_TT_UNICODE_EID_UNICODE_2_0_FULL=4
+};
+
+enum { /* encodingID for NK_TT_PLATFORM_ID_MICROSOFT */
+   NK_TT_MS_EID_SYMBOL        =0,
+   NK_TT_MS_EID_UNICODE_BMP   =1,
+   NK_TT_MS_EID_SHIFTJIS      =2,
+   NK_TT_MS_EID_UNICODE_FULL  =10
+};
+
+enum { /* encodingID for NK_TT_PLATFORM_ID_MAC; same as Script Manager codes */
+   NK_TT_MAC_EID_ROMAN        =0,   NK_TT_MAC_EID_ARABIC       =4,
+   NK_TT_MAC_EID_JAPANESE     =1,   NK_TT_MAC_EID_HEBREW       =5,
+   NK_TT_MAC_EID_CHINESE_TRAD =2,   NK_TT_MAC_EID_GREEK        =6,
+   NK_TT_MAC_EID_KOREAN       =3,   NK_TT_MAC_EID_RUSSIAN      =7
+};
+
+enum { /* languageID for NK_TT_PLATFORM_ID_MICROSOFT; same as LCID... */
+       /* problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs */
+   NK_TT_MS_LANG_ENGLISH     =0x0409,   NK_TT_MS_LANG_ITALIAN     =0x0410,
+   NK_TT_MS_LANG_CHINESE     =0x0804,   NK_TT_MS_LANG_JAPANESE    =0x0411,
+   NK_TT_MS_LANG_DUTCH       =0x0413,   NK_TT_MS_LANG_KOREAN      =0x0412,
+   NK_TT_MS_LANG_FRENCH      =0x040c,   NK_TT_MS_LANG_RUSSIAN     =0x0419,
+   NK_TT_MS_LANG_GERMAN      =0x0407,   NK_TT_MS_LANG_SPANISH     =0x0409,
+   NK_TT_MS_LANG_HEBREW      =0x040d,   NK_TT_MS_LANG_SWEDISH     =0x041D
+};
+
+enum { /* languageID for NK_TT_PLATFORM_ID_MAC */
+   NK_TT_MAC_LANG_ENGLISH      =0 ,   NK_TT_MAC_LANG_JAPANESE     =11,
+   NK_TT_MAC_LANG_ARABIC       =12,   NK_TT_MAC_LANG_KOREAN       =23,
+   NK_TT_MAC_LANG_DUTCH        =4 ,   NK_TT_MAC_LANG_RUSSIAN      =32,
+   NK_TT_MAC_LANG_FRENCH       =1 ,   NK_TT_MAC_LANG_SPANISH      =6 ,
+   NK_TT_MAC_LANG_GERMAN       =2 ,   NK_TT_MAC_LANG_SWEDISH      =5 ,
+   NK_TT_MAC_LANG_HEBREW       =10,   NK_TT_MAC_LANG_CHINESE_SIMPLIFIED =33,
+   NK_TT_MAC_LANG_ITALIAN      =3 ,   NK_TT_MAC_LANG_CHINESE_TRAD =19
+};
+
+#define nk_ttBYTE(p)     (* (const nk_byte *) (p))
+#define nk_ttCHAR(p)     (* (const char *) (p))
+
+#if defined(NK_BIGENDIAN) && !defined(NK_ALLOW_UNALIGNED_TRUETYPE)
+   #define nk_ttUSHORT(p)   (* (nk_ushort *) (p))
+   #define nk_ttSHORT(p)    (* (nk_short *) (p))
+   #define nk_ttULONG(p)    (* (nk_uint *) (p))
+   #define nk_ttLONG(p)     (* (nk_int *) (p))
+#else
+    static nk_ushort nk_ttUSHORT(const nk_byte *p) { return (nk_ushort)(p[0]*256 + p[1]); }
+    static nk_short nk_ttSHORT(const nk_byte *p)   { return (nk_short)(p[0]*256 + p[1]); }
+    static nk_uint nk_ttULONG(const nk_byte *p)  { return (nk_uint)((p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]); }
+#endif
+
+#define nk_tt_tag4(p,c0,c1,c2,c3)\
+    ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3))
+#define nk_tt_tag(p,str) nk_tt_tag4(p,str[0],str[1],str[2],str[3])
+
+NK_INTERN int nk_tt_GetGlyphShape(const struct nk_tt_fontinfo *info, struct nk_allocator *alloc,
+                                int glyph_index, struct nk_tt_vertex **pvertices);
+
+NK_INTERN nk_uint
+nk_tt__find_table(const nk_byte *data, nk_uint fontstart, const char *tag)
+{
+    /* @OPTIMIZE: binary search */
+    nk_int num_tables = nk_ttUSHORT(data+fontstart+4);
+    nk_uint tabledir = fontstart + 12;
+    nk_int i;
+    for (i = 0; i < num_tables; ++i) {
+        nk_uint loc = tabledir + (nk_uint)(16*i);
+        if (nk_tt_tag(data+loc+0, tag))
+            return nk_ttULONG(data+loc+8);
+    }
+    return 0;
+}
+
+NK_INTERN int
+nk_tt_InitFont(struct nk_tt_fontinfo *info, const unsigned char *data2, int fontstart)
+{
+    nk_uint cmap, t;
+    nk_int i,numTables;
+    const nk_byte *data = (const nk_byte *) data2;
+
+    info->data = data;
+    info->fontstart = fontstart;
+
+    cmap = nk_tt__find_table(data, (nk_uint)fontstart, "cmap");       /* required */
+    info->loca = (int)nk_tt__find_table(data, (nk_uint)fontstart, "loca"); /* required */
+    info->head = (int)nk_tt__find_table(data, (nk_uint)fontstart, "head"); /* required */
+    info->glyf = (int)nk_tt__find_table(data, (nk_uint)fontstart, "glyf"); /* required */
+    info->hhea = (int)nk_tt__find_table(data, (nk_uint)fontstart, "hhea"); /* required */
+    info->hmtx = (int)nk_tt__find_table(data, (nk_uint)fontstart, "hmtx"); /* required */
+    info->kern = (int)nk_tt__find_table(data, (nk_uint)fontstart, "kern"); /* not required */
+    if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx)
+        return 0;
+
+    t = nk_tt__find_table(data, (nk_uint)fontstart, "maxp");
+    if (t) info->numGlyphs = nk_ttUSHORT(data+t+4);
+    else info->numGlyphs = 0xffff;
+
+    /* find a cmap encoding table we understand *now* to avoid searching */
+    /* later. (todo: could make this installable) */
+    /* the same regardless of glyph. */
+    numTables = nk_ttUSHORT(data + cmap + 2);
+    info->index_map = 0;
+    for (i=0; i < numTables; ++i)
+    {
+        nk_uint encoding_record = cmap + 4 + 8 * (nk_uint)i;
+        /* find an encoding we understand: */
+        switch(nk_ttUSHORT(data+encoding_record)) {
+        case NK_TT_PLATFORM_ID_MICROSOFT:
+            switch (nk_ttUSHORT(data+encoding_record+2)) {
+            case NK_TT_MS_EID_UNICODE_BMP:
+            case NK_TT_MS_EID_UNICODE_FULL:
+                /* MS/Unicode */
+                info->index_map = (int)(cmap + nk_ttULONG(data+encoding_record+4));
+                break;
+            default: break;
+            } break;
+        case NK_TT_PLATFORM_ID_UNICODE:
+            /* Mac/iOS has these */
+            /* all the encodingIDs are unicode, so we don't bother to check it */
+            info->index_map = (int)(cmap + nk_ttULONG(data+encoding_record+4));
+            break;
+        default: break;
+        }
+    }
+    if (info->index_map == 0)
+        return 0;
+    info->indexToLocFormat = nk_ttUSHORT(data+info->head + 50);
+    return 1;
+}
+
+NK_INTERN int
+nk_tt_FindGlyphIndex(const struct nk_tt_fontinfo *info, int unicode_codepoint)
+{
+    const nk_byte *data = info->data;
+    nk_uint index_map = (nk_uint)info->index_map;
+
+    nk_ushort format = nk_ttUSHORT(data + index_map + 0);
+    if (format == 0) { /* apple byte encoding */
+        nk_int bytes = nk_ttUSHORT(data + index_map + 2);
+        if (unicode_codepoint < bytes-6)
+            return nk_ttBYTE(data + index_map + 6 + unicode_codepoint);
+        return 0;
+    } else if (format == 6) {
+        nk_uint first = nk_ttUSHORT(data + index_map + 6);
+        nk_uint count = nk_ttUSHORT(data + index_map + 8);
+        if ((nk_uint) unicode_codepoint >= first && (nk_uint) unicode_codepoint < first+count)
+            return nk_ttUSHORT(data + index_map + 10 + (unicode_codepoint - (int)first)*2);
+        return 0;
+    } else if (format == 2) {
+        NK_ASSERT(0); /* @TODO: high-byte mapping for japanese/chinese/korean */
+        return 0;
+    } else if (format == 4) { /* standard mapping for windows fonts: binary search collection of ranges */
+        nk_ushort segcount = nk_ttUSHORT(data+index_map+6) >> 1;
+        nk_ushort searchRange = nk_ttUSHORT(data+index_map+8) >> 1;
+        nk_ushort entrySelector = nk_ttUSHORT(data+index_map+10);
+        nk_ushort rangeShift = nk_ttUSHORT(data+index_map+12) >> 1;
+
+        /* do a binary search of the segments */
+        nk_uint endCount = index_map + 14;
+        nk_uint search = endCount;
+
+        if (unicode_codepoint > 0xffff)
+            return 0;
+
+        /* they lie from endCount .. endCount + segCount */
+        /* but searchRange is the nearest power of two, so... */
+        if (unicode_codepoint >= nk_ttUSHORT(data + search + rangeShift*2))
+            search += (nk_uint)(rangeShift*2);
+
+        /* now decrement to bias correctly to find smallest */
+        search -= 2;
+        while (entrySelector) {
+            nk_ushort end;
+            searchRange >>= 1;
+            end = nk_ttUSHORT(data + search + searchRange*2);
+            if (unicode_codepoint > end)
+                search += (nk_uint)(searchRange*2);
+            --entrySelector;
+        }
+        search += 2;
+
+      {
+         nk_ushort offset, start;
+         nk_ushort item = (nk_ushort) ((search - endCount) >> 1);
+
+         NK_ASSERT(unicode_codepoint <= nk_ttUSHORT(data + endCount + 2*item));
+         start = nk_ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item);
+         if (unicode_codepoint < start)
+            return 0;
+
+         offset = nk_ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item);
+         if (offset == 0)
+            return (nk_ushort) (unicode_codepoint + nk_ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item));
+
+         return nk_ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item);
+      }
+   } else if (format == 12 || format == 13) {
+        nk_uint ngroups = nk_ttULONG(data+index_map+12);
+        nk_int low,high;
+        low = 0; high = (nk_int)ngroups;
+        /* Binary search the right group. */
+        while (low < high) {
+            nk_int mid = low + ((high-low) >> 1); /* rounds down, so low <= mid < high */
+            nk_uint start_char = nk_ttULONG(data+index_map+16+mid*12);
+            nk_uint end_char = nk_ttULONG(data+index_map+16+mid*12+4);
+            if ((nk_uint) unicode_codepoint < start_char)
+                high = mid;
+            else if ((nk_uint) unicode_codepoint > end_char)
+                low = mid+1;
+            else {
+                nk_uint start_glyph = nk_ttULONG(data+index_map+16+mid*12+8);
+                if (format == 12)
+                    return (int)start_glyph + (int)unicode_codepoint - (int)start_char;
+                else /* format == 13 */
+                    return (int)start_glyph;
+            }
+        }
+        return 0; /* not found */
+    }
+    /* @TODO */
+    NK_ASSERT(0);
+    return 0;
+}
+
+NK_INTERN void
+nk_tt_setvertex(struct nk_tt_vertex *v, nk_byte type, nk_int x, nk_int y, nk_int cx, nk_int cy)
+{
+    v->type = type;
+    v->x = (nk_short) x;
+    v->y = (nk_short) y;
+    v->cx = (nk_short) cx;
+    v->cy = (nk_short) cy;
+}
+
+NK_INTERN int
+nk_tt__GetGlyfOffset(const struct nk_tt_fontinfo *info, int glyph_index)
+{
+    int g1,g2;
+    if (glyph_index >= info->numGlyphs) return -1; /* glyph index out of range */
+    if (info->indexToLocFormat >= 2)    return -1; /* unknown index->glyph map format */
+
+    if (info->indexToLocFormat == 0) {
+        g1 = info->glyf + nk_ttUSHORT(info->data + info->loca + glyph_index * 2) * 2;
+        g2 = info->glyf + nk_ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2;
+    } else {
+        g1 = info->glyf + (int)nk_ttULONG (info->data + info->loca + glyph_index * 4);
+        g2 = info->glyf + (int)nk_ttULONG (info->data + info->loca + glyph_index * 4 + 4);
+    }
+    return g1==g2 ? -1 : g1; /* if length is 0, return -1 */
+}
+
+NK_INTERN int
+nk_tt_GetGlyphBox(const struct nk_tt_fontinfo *info, int glyph_index,
+    int *x0, int *y0, int *x1, int *y1)
+{
+    int g = nk_tt__GetGlyfOffset(info, glyph_index);
+    if (g < 0) return 0;
+
+    if (x0) *x0 = nk_ttSHORT(info->data + g + 2);
+    if (y0) *y0 = nk_ttSHORT(info->data + g + 4);
+    if (x1) *x1 = nk_ttSHORT(info->data + g + 6);
+    if (y1) *y1 = nk_ttSHORT(info->data + g + 8);
+    return 1;
+}
+
+NK_INTERN int
+stbtt__close_shape(struct nk_tt_vertex *vertices, int num_vertices, int was_off,
+    int start_off, nk_int sx, nk_int sy, nk_int scx, nk_int scy, nk_int cx, nk_int cy)
+{
+   if (start_off) {
+      if (was_off)
+         nk_tt_setvertex(&vertices[num_vertices++], NK_TT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy);
+      nk_tt_setvertex(&vertices[num_vertices++], NK_TT_vcurve, sx,sy,scx,scy);
+   } else {
+      if (was_off)
+         nk_tt_setvertex(&vertices[num_vertices++], NK_TT_vcurve,sx,sy,cx,cy);
+      else
+         nk_tt_setvertex(&vertices[num_vertices++], NK_TT_vline,sx,sy,0,0);
+   }
+   return num_vertices;
+}
+
+NK_INTERN int
+nk_tt_GetGlyphShape(const struct nk_tt_fontinfo *info, struct nk_allocator *alloc,
+    int glyph_index, struct nk_tt_vertex **pvertices)
+{
+    nk_short numberOfContours;
+    const nk_byte *endPtsOfContours;
+    const nk_byte *data = info->data;
+    struct nk_tt_vertex *vertices=0;
+    int num_vertices=0;
+    int g = nk_tt__GetGlyfOffset(info, glyph_index);
+    *pvertices = 0;
+
+    if (g < 0) return 0;
+    numberOfContours = nk_ttSHORT(data + g);
+    if (numberOfContours > 0) {
+        nk_byte flags=0,flagcount;
+        nk_int ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0;
+        nk_int x,y,cx,cy,sx,sy, scx,scy;
+        const nk_byte *points;
+        endPtsOfContours = (data + g + 10);
+        ins = nk_ttUSHORT(data + g + 10 + numberOfContours * 2);
+        points = data + g + 10 + numberOfContours * 2 + 2 + ins;
+
+        n = 1+nk_ttUSHORT(endPtsOfContours + numberOfContours*2-2);
+        m = n + 2*numberOfContours;  /* a loose bound on how many vertices we might need */
+        vertices = (struct nk_tt_vertex *)alloc->alloc(alloc->userdata, 0, (nk_size)m * sizeof(vertices[0]));
+        if (vertices == 0)
+            return 0;
+
+        next_move = 0;
+        flagcount=0;
+
+        /* in first pass, we load uninterpreted data into the allocated array */
+        /* above, shifted to the end of the array so we won't overwrite it when */
+        /* we create our final data starting from the front */
+        off = m - n; /* starting offset for uninterpreted data, regardless of how m ends up being calculated */
+
+        /* first load flags */
+        for (i=0; i < n; ++i) {
+            if (flagcount == 0) {
+                flags = *points++;
+                if (flags & 8)
+                    flagcount = *points++;
+            } else --flagcount;
+            vertices[off+i].type = flags;
+        }
+
+        /* now load x coordinates */
+        x=0;
+        for (i=0; i < n; ++i) {
+            flags = vertices[off+i].type;
+            if (flags & 2) {
+                nk_short dx = *points++;
+                x += (flags & 16) ? dx : -dx; /* ??? */
+            } else {
+                if (!(flags & 16)) {
+                    x = x + (nk_short) (points[0]*256 + points[1]);
+                    points += 2;
+                }
+            }
+            vertices[off+i].x = (nk_short) x;
+        }
+
+        /* now load y coordinates */
+        y=0;
+        for (i=0; i < n; ++i) {
+            flags = vertices[off+i].type;
+            if (flags & 4) {
+                nk_short dy = *points++;
+                y += (flags & 32) ? dy : -dy; /* ??? */
+            } else {
+                if (!(flags & 32)) {
+                    y = y + (nk_short) (points[0]*256 + points[1]);
+                    points += 2;
+                }
+            }
+            vertices[off+i].y = (nk_short) y;
+        }
+
+        /* now convert them to our format */
+        num_vertices=0;
+        sx = sy = cx = cy = scx = scy = 0;
+        for (i=0; i < n; ++i)
+        {
+            flags = vertices[off+i].type;
+            x     = (nk_short) vertices[off+i].x;
+            y     = (nk_short) vertices[off+i].y;
+
+            if (next_move == i) {
+                if (i != 0)
+                    num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
+
+                /* now start the new one                */
+                start_off = !(flags & 1);
+                if (start_off) {
+                    /* if we start off with an off-curve point, then when we need to find a point on the curve */
+                    /* where we can start, and we need to save some state for when we wraparound. */
+                    scx = x;
+                    scy = y;
+                    if (!(vertices[off+i+1].type & 1)) {
+                        /* next point is also a curve point, so interpolate an on-point curve */
+                        sx = (x + (nk_int) vertices[off+i+1].x) >> 1;
+                        sy = (y + (nk_int) vertices[off+i+1].y) >> 1;
+                    } else {
+                        /* otherwise just use the next point as our start point */
+                        sx = (nk_int) vertices[off+i+1].x;
+                        sy = (nk_int) vertices[off+i+1].y;
+                        ++i; /* we're using point i+1 as the starting point, so skip it */
+                    }
+                } else {
+                    sx = x;
+                    sy = y;
+                }
+                nk_tt_setvertex(&vertices[num_vertices++], NK_TT_vmove,sx,sy,0,0);
+                was_off = 0;
+                next_move = 1 + nk_ttUSHORT(endPtsOfContours+j*2);
+                ++j;
+            } else {
+                if (!(flags & 1))
+                { /* if it's a curve */
+                    if (was_off) /* two off-curve control points in a row means interpolate an on-curve midpoint */
+                        nk_tt_setvertex(&vertices[num_vertices++], NK_TT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy);
+                    cx = x;
+                    cy = y;
+                    was_off = 1;
+                } else {
+                    if (was_off)
+                        nk_tt_setvertex(&vertices[num_vertices++], NK_TT_vcurve, x,y, cx, cy);
+                    else nk_tt_setvertex(&vertices[num_vertices++], NK_TT_vline, x,y,0,0);
+                    was_off = 0;
+                }
+            }
+        }
+        num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
+    } else if (numberOfContours == -1) {
+        /* Compound shapes. */
+        int more = 1;
+        const nk_byte *comp = data + g + 10;
+        num_vertices = 0;
+        vertices = 0;
+
+        while (more)
+        {
+            nk_ushort flags, gidx;
+            int comp_num_verts = 0, i;
+            struct nk_tt_vertex *comp_verts = 0, *tmp = 0;
+            float mtx[6] = {1,0,0,1,0,0}, m, n;
+
+            flags = (nk_ushort)nk_ttSHORT(comp); comp+=2;
+            gidx = (nk_ushort)nk_ttSHORT(comp); comp+=2;
+
+            if (flags & 2) { /* XY values */
+                if (flags & 1) { /* shorts */
+                    mtx[4] = nk_ttSHORT(comp); comp+=2;
+                    mtx[5] = nk_ttSHORT(comp); comp+=2;
+                } else {
+                    mtx[4] = nk_ttCHAR(comp); comp+=1;
+                    mtx[5] = nk_ttCHAR(comp); comp+=1;
+                }
+            } else {
+                /* @TODO handle matching point */
+                NK_ASSERT(0);
+            }
+            if (flags & (1<<3)) { /* WE_HAVE_A_SCALE */
+                mtx[0] = mtx[3] = nk_ttSHORT(comp)/16384.0f; comp+=2;
+                mtx[1] = mtx[2] = 0;
+            } else if (flags & (1<<6)) { /* WE_HAVE_AN_X_AND_YSCALE */
+                mtx[0] = nk_ttSHORT(comp)/16384.0f; comp+=2;
+                mtx[1] = mtx[2] = 0;
+                mtx[3] = nk_ttSHORT(comp)/16384.0f; comp+=2;
+            } else if (flags & (1<<7)) { /* WE_HAVE_A_TWO_BY_TWO */
+                mtx[0] = nk_ttSHORT(comp)/16384.0f; comp+=2;
+                mtx[1] = nk_ttSHORT(comp)/16384.0f; comp+=2;
+                mtx[2] = nk_ttSHORT(comp)/16384.0f; comp+=2;
+                mtx[3] = nk_ttSHORT(comp)/16384.0f; comp+=2;
+            }
+
+             /* Find transformation scales. */
+            m = (float) NK_SQRT(mtx[0]*mtx[0] + mtx[1]*mtx[1]);
+            n = (float) NK_SQRT(mtx[2]*mtx[2] + mtx[3]*mtx[3]);
+
+             /* Get indexed glyph. */
+            comp_num_verts = nk_tt_GetGlyphShape(info, alloc, gidx, &comp_verts);
+            if (comp_num_verts > 0)
+            {
+                /* Transform vertices. */
+                for (i = 0; i < comp_num_verts; ++i) {
+                    struct nk_tt_vertex* v = &comp_verts[i];
+                    short x,y;
+                    x=v->x; y=v->y;
+                    v->x = (short)(m * (mtx[0]*x + mtx[2]*y + mtx[4]));
+                    v->y = (short)(n * (mtx[1]*x + mtx[3]*y + mtx[5]));
+                    x=v->cx; y=v->cy;
+                    v->cx = (short)(m * (mtx[0]*x + mtx[2]*y + mtx[4]));
+                    v->cy = (short)(n * (mtx[1]*x + mtx[3]*y + mtx[5]));
+                }
+                /* Append vertices. */
+                tmp = (struct nk_tt_vertex*)alloc->alloc(alloc->userdata, 0,
+                    (nk_size)(num_vertices+comp_num_verts)*sizeof(struct nk_tt_vertex));
+                if (!tmp) {
+                    if (vertices) alloc->free(alloc->userdata, vertices);
+                    if (comp_verts) alloc->free(alloc->userdata, comp_verts);
+                    return 0;
+                }
+                if (num_vertices > 0) NK_MEMCPY(tmp, vertices, (nk_size)num_vertices*sizeof(struct nk_tt_vertex));
+                NK_MEMCPY(tmp+num_vertices, comp_verts, (nk_size)comp_num_verts*sizeof(struct nk_tt_vertex));
+                if (vertices) alloc->free(alloc->userdata,vertices);
+                vertices = tmp;
+                alloc->free(alloc->userdata,comp_verts);
+                num_vertices += comp_num_verts;
+            }
+            /* More components ? */
+            more = flags & (1<<5);
+        }
+    } else if (numberOfContours < 0) {
+        /* @TODO other compound variations? */
+        NK_ASSERT(0);
+    } else {
+        /* numberOfCounters == 0, do nothing */
+    }
+    *pvertices = vertices;
+    return num_vertices;
+}
+
+NK_INTERN void
+nk_tt_GetGlyphHMetrics(const struct nk_tt_fontinfo *info, int glyph_index,
+    int *advanceWidth, int *leftSideBearing)
+{
+    nk_ushort numOfLongHorMetrics = nk_ttUSHORT(info->data+info->hhea + 34);
+    if (glyph_index < numOfLongHorMetrics) {
+        if (advanceWidth)
+            *advanceWidth    = nk_ttSHORT(info->data + info->hmtx + 4*glyph_index);
+        if (leftSideBearing)
+            *leftSideBearing = nk_ttSHORT(info->data + info->hmtx + 4*glyph_index + 2);
+    } else {
+        if (advanceWidth)
+            *advanceWidth    = nk_ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1));
+        if (leftSideBearing)
+            *leftSideBearing = nk_ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics));
+    }
+}
+
+NK_INTERN void
+nk_tt_GetFontVMetrics(const struct nk_tt_fontinfo *info,
+    int *ascent, int *descent, int *lineGap)
+{
+   if (ascent ) *ascent  = nk_ttSHORT(info->data+info->hhea + 4);
+   if (descent) *descent = nk_ttSHORT(info->data+info->hhea + 6);
+   if (lineGap) *lineGap = nk_ttSHORT(info->data+info->hhea + 8);
+}
+
+NK_INTERN float
+nk_tt_ScaleForPixelHeight(const struct nk_tt_fontinfo *info, float height)
+{
+   int fheight = nk_ttSHORT(info->data + info->hhea + 4) - nk_ttSHORT(info->data + info->hhea + 6);
+   return (float) height / (float)fheight;
+}
+
+NK_INTERN float
+nk_tt_ScaleForMappingEmToPixels(const struct nk_tt_fontinfo *info, float pixels)
+{
+   int unitsPerEm = nk_ttUSHORT(info->data + info->head + 18);
+   return pixels / (float)unitsPerEm;
+}
+
+/*-------------------------------------------------------------
+ *            antialiasing software rasterizer
+ * --------------------------------------------------------------*/
+NK_INTERN void
+nk_tt_GetGlyphBitmapBoxSubpixel(const struct nk_tt_fontinfo *font,
+    int glyph, float scale_x, float scale_y,float shift_x, float shift_y,
+    int *ix0, int *iy0, int *ix1, int *iy1)
+{
+    int x0,y0,x1,y1;
+    if (!nk_tt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) {
+        /* e.g. space character */
+        if (ix0) *ix0 = 0;
+        if (iy0) *iy0 = 0;
+        if (ix1) *ix1 = 0;
+        if (iy1) *iy1 = 0;
+    } else {
+        /* move to integral bboxes (treating pixels as little squares, what pixels get touched)? */
+        if (ix0) *ix0 = nk_ifloorf((float)x0 * scale_x + shift_x);
+        if (iy0) *iy0 = nk_ifloorf((float)-y1 * scale_y + shift_y);
+        if (ix1) *ix1 = nk_iceilf ((float)x1 * scale_x + shift_x);
+        if (iy1) *iy1 = nk_iceilf ((float)-y0 * scale_y + shift_y);
+    }
+}
+
+NK_INTERN void
+nk_tt_GetGlyphBitmapBox(const struct nk_tt_fontinfo *font, int glyph,
+    float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1)
+{
+   nk_tt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1);
+}
+
+/*-------------------------------------------------------------
+ *                          Rasterizer
+ * --------------------------------------------------------------*/
+NK_INTERN void*
+nk_tt__hheap_alloc(struct nk_tt__hheap *hh, nk_size size)
+{
+    if (hh->first_free) {
+        void *p = hh->first_free;
+        hh->first_free = * (void **) p;
+        return p;
+    } else {
+        if (hh->num_remaining_in_head_chunk == 0) {
+            int count = (size < 32 ? 2000 : size < 128 ? 800 : 100);
+            struct nk_tt__hheap_chunk *c = (struct nk_tt__hheap_chunk *)
+                hh->alloc.alloc(hh->alloc.userdata, 0,
+                sizeof(struct nk_tt__hheap_chunk) + size * (nk_size)count);
+            if (c == 0) return 0;
+            c->next = hh->head;
+            hh->head = c;
+            hh->num_remaining_in_head_chunk = count;
+        }
+        --hh->num_remaining_in_head_chunk;
+        return (char *) (hh->head) + size * (nk_size)hh->num_remaining_in_head_chunk;
+    }
+}
+
+NK_INTERN void
+nk_tt__hheap_free(struct nk_tt__hheap *hh, void *p)
+{
+    *(void **) p = hh->first_free;
+    hh->first_free = p;
+}
+
+NK_INTERN void
+nk_tt__hheap_cleanup(struct nk_tt__hheap *hh)
+{
+    struct nk_tt__hheap_chunk *c = hh->head;
+    while (c) {
+        struct nk_tt__hheap_chunk *n = c->next;
+        hh->alloc.free(hh->alloc.userdata, c);
+        c = n;
+    }
+}
+
+NK_INTERN struct nk_tt__active_edge*
+nk_tt__new_active(struct nk_tt__hheap *hh, struct nk_tt__edge *e,
+    int off_x, float start_point)
+{
+    struct nk_tt__active_edge *z = (struct nk_tt__active_edge *)
+        nk_tt__hheap_alloc(hh, sizeof(*z));
+    float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
+    /*STBTT_assert(e->y0 <= start_point); */
+    if (!z) return z;
+    z->fdx = dxdy;
+    z->fdy = (dxdy != 0) ? (1/dxdy): 0;
+    z->fx = e->x0 + dxdy * (start_point - e->y0);
+    z->fx -= (float)off_x;
+    z->direction = e->invert ? 1.0f : -1.0f;
+    z->sy = e->y0;
+    z->ey = e->y1;
+    z->next = 0;
+    return z;
+}
+
+NK_INTERN void
+nk_tt__handle_clipped_edge(float *scanline, int x, struct nk_tt__active_edge *e,
+    float x0, float y0, float x1, float y1)
+{
+    if (y0 == y1) return;
+    NK_ASSERT(y0 < y1);
+    NK_ASSERT(e->sy <= e->ey);
+    if (y0 > e->ey) return;
+    if (y1 < e->sy) return;
+    if (y0 < e->sy) {
+        x0 += (x1-x0) * (e->sy - y0) / (y1-y0);
+        y0 = e->sy;
+    }
+    if (y1 > e->ey) {
+        x1 += (x1-x0) * (e->ey - y1) / (y1-y0);
+        y1 = e->ey;
+    }
+
+    if (x0 == x) NK_ASSERT(x1 <= x+1);
+    else if (x0 == x+1) NK_ASSERT(x1 >= x);
+    else if (x0 <= x) NK_ASSERT(x1 <= x);
+    else if (x0 >= x+1) NK_ASSERT(x1 >= x+1);
+    else NK_ASSERT(x1 >= x && x1 <= x+1);
+
+    if (x0 <= x && x1 <= x)
+        scanline[x] += e->direction * (y1-y0);
+    else if (x0 >= x+1 && x1 >= x+1);
+    else {
+        NK_ASSERT(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1);
+        /* coverage = 1 - average x position */
+        scanline[x] += (float)e->direction * (float)(y1-y0) * (1.0f-((x0-(float)x)+(x1-(float)x))/2.0f);
+    }
+}
+
+NK_INTERN void
+nk_tt__fill_active_edges_new(float *scanline, float *scanline_fill, int len,
+    struct nk_tt__active_edge *e, float y_top)
+{
+    float y_bottom = y_top+1;
+    while (e)
+    {
+        /* brute force every pixel */
+        /* compute intersection points with top & bottom */
+        NK_ASSERT(e->ey >= y_top);
+        if (e->fdx == 0) {
+            float x0 = e->fx;
+            if (x0 < len) {
+                if (x0 >= 0) {
+                    nk_tt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom);
+                    nk_tt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom);
+                } else {
+                    nk_tt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom);
+                }
+            }
+        } else {
+            float x0 = e->fx;
+            float dx = e->fdx;
+            float xb = x0 + dx;
+            float x_top, x_bottom;
+            float y0,y1;
+            float dy = e->fdy;
+            NK_ASSERT(e->sy <= y_bottom && e->ey >= y_top);
+
+            /* compute endpoints of line segment clipped to this scanline (if the */
+            /* line segment starts on this scanline. x0 is the intersection of the */
+            /* line with y_top, but that may be off the line segment. */
+            if (e->sy > y_top) {
+                x_top = x0 + dx * (e->sy - y_top);
+                y0 = e->sy;
+            } else {
+                x_top = x0;
+                y0 = y_top;
+            }
+
+            if (e->ey < y_bottom) {
+                x_bottom = x0 + dx * (e->ey - y_top);
+                y1 = e->ey;
+            } else {
+                x_bottom = xb;
+                y1 = y_bottom;
+            }
+
+            if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len)
+            {
+                /* from here on, we don't have to range check x values */
+                if ((int) x_top == (int) x_bottom) {
+                    float height;
+                    /* simple case, only spans one pixel */
+                    int x = (int) x_top;
+                    height = y1 - y0;
+                    NK_ASSERT(x >= 0 && x < len);
+                    scanline[x] += e->direction * (1.0f-(((float)x_top - (float)x) + ((float)x_bottom-(float)x))/2.0f)  * (float)height;
+                    scanline_fill[x] += e->direction * (float)height; /* everything right of this pixel is filled */
+                } else {
+                    int x,x1,x2;
+                    float y_crossing, step, sign, area;
+                    /* covers 2+ pixels */
+                    if (x_top > x_bottom)
+                    {
+                        /* flip scanline vertically; signed area is the same */
+                        float t;
+                        y0 = y_bottom - (y0 - y_top);
+                        y1 = y_bottom - (y1 - y_top);
+                        t = y0; y0 = y1; y1 = t;
+                        t = x_bottom; x_bottom = x_top; x_top = t;
+                        dx = -dx;
+                        dy = -dy;
+                        t = x0; x0 = xb; xb = t;
+                    }
+
+                    x1 = (int) x_top;
+                    x2 = (int) x_bottom;
+                    /* compute intersection with y axis at x1+1 */
+                    y_crossing = ((float)x1+1 - (float)x0) * (float)dy + (float)y_top;
+
+                    sign = e->direction;
+                    /* area of the rectangle covered from y0..y_crossing */
+                    area = sign * (y_crossing-y0);
+                    /* area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) */
+                    scanline[x1] += area * (1.0f-((float)((float)x_top - (float)x1)+(float)(x1+1-x1))/2.0f);
+
+                    step = sign * dy;
+                    for (x = x1+1; x < x2; ++x) {
+                        scanline[x] += area + step/2;
+                        area += step;
+                    }
+                    y_crossing += (float)dy * (float)(x2 - (x1+1));
+
+                    scanline[x2] += area + sign * (1.0f-((float)(x2-x2)+((float)x_bottom-(float)x2))/2.0f) * (y1-y_crossing);
+                    scanline_fill[x2] += sign * (y1-y0);
+                }
+            }
+            else
+            {
+                /* if edge goes outside of box we're drawing, we require */
+                /* clipping logic. since this does not match the intended use */
+                /* of this library, we use a different, very slow brute */
+                /* force implementation */
+                int x;
+                for (x=0; x < len; ++x)
+                {
+                    /* cases: */
+                    /* */
+                    /* there can be up to two intersections with the pixel. any intersection */
+                    /* with left or right edges can be handled by splitting into two (or three) */
+                    /* regions. intersections with top & bottom do not necessitate case-wise logic. */
+                    /* */
+                    /* the old way of doing this found the intersections with the left & right edges, */
+                    /* then used some simple logic to produce up to three segments in sorted order */
+                    /* from top-to-bottom. however, this had a problem: if an x edge was epsilon */
+                    /* across the x border, then the corresponding y position might not be distinct */
+                    /* from the other y segment, and it might ignored as an empty segment. to avoid */
+                    /* that, we need to explicitly produce segments based on x positions. */
+
+                    /* rename variables to clear pairs */
+                    float ya = y_top;
+                    float x1 = (float) (x);
+                    float x2 = (float) (x+1);
+                    float x3 = xb;
+                    float y3 = y_bottom;
+                    float yb,y2;
+
+                    yb = ((float)x - x0) / dx + y_top;
+                    y2 = ((float)x+1 - x0) / dx + y_top;
+
+                    if (x0 < x1 && x3 > x2) {         /* three segments descending down-right */
+                        nk_tt__handle_clipped_edge(scanline,x,e, x0,ya, x1,yb);
+                        nk_tt__handle_clipped_edge(scanline,x,e, x1,yb, x2,y2);
+                        nk_tt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
+                    } else if (x3 < x1 && x0 > x2) {  /* three segments descending down-left */
+                        nk_tt__handle_clipped_edge(scanline,x,e, x0,ya, x2,y2);
+                        nk_tt__handle_clipped_edge(scanline,x,e, x2,y2, x1,yb);
+                        nk_tt__handle_clipped_edge(scanline,x,e, x1,yb, x3,y3);
+                    } else if (x0 < x1 && x3 > x1) {  /* two segments across x, down-right */
+                        nk_tt__handle_clipped_edge(scanline,x,e, x0,ya, x1,yb);
+                        nk_tt__handle_clipped_edge(scanline,x,e, x1,yb, x3,y3);
+                    } else if (x3 < x1 && x0 > x1) {  /* two segments across x, down-left */
+                        nk_tt__handle_clipped_edge(scanline,x,e, x0,ya, x1,yb);
+                        nk_tt__handle_clipped_edge(scanline,x,e, x1,yb, x3,y3);
+                    } else if (x0 < x2 && x3 > x2) {  /* two segments across x+1, down-right */
+                        nk_tt__handle_clipped_edge(scanline,x,e, x0,ya, x2,y2);
+                        nk_tt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
+                    } else if (x3 < x2 && x0 > x2) {  /* two segments across x+1, down-left */
+                        nk_tt__handle_clipped_edge(scanline,x,e, x0,ya, x2,y2);
+                        nk_tt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
+                    } else {  /* one segment */
+                        nk_tt__handle_clipped_edge(scanline,x,e, x0,ya, x3,y3);
+                    }
+                }
+            }
+        }
+        e = e->next;
+    }
+}
+
+/* directly AA rasterize edges w/o supersampling */
+NK_INTERN void
+nk_tt__rasterize_sorted_edges(struct nk_tt__bitmap *result, struct nk_tt__edge *e,
+    int n, int vsubsample, int off_x, int off_y, struct nk_allocator *alloc)
+{
+    struct nk_tt__hheap hh;
+    struct nk_tt__active_edge *active = 0;
+    int y,j=0, i;
+    float scanline_data[129], *scanline, *scanline2;
+
+    NK_UNUSED(vsubsample);
+    nk_zero_struct(hh);
+    hh.alloc = *alloc;
+
+    if (result->w > 64)
+        scanline = (float *) alloc->alloc(alloc->userdata,0, (nk_size)(result->w*2+1) * sizeof(float));
+    else scanline = scanline_data;
+
+    scanline2 = scanline + result->w;
+    y = off_y;
+    e[n].y0 = (float) (off_y + result->h) + 1;
+
+    while (j < result->h)
+    {
+        /* find center of pixel for this scanline */
+        float scan_y_top    = (float)y + 0.0f;
+        float scan_y_bottom = (float)y + 1.0f;
+        struct nk_tt__active_edge **step = &active;
+
+        NK_MEMSET(scanline , 0, (nk_size)result->w*sizeof(scanline[0]));
+        NK_MEMSET(scanline2, 0, (nk_size)(result->w+1)*sizeof(scanline[0]));
+
+        /* update all active edges; */
+        /* remove all active edges that terminate before the top of this scanline */
+        while (*step) {
+            struct nk_tt__active_edge * z = *step;
+            if (z->ey <= scan_y_top) {
+                *step = z->next; /* delete from list */
+                NK_ASSERT(z->direction);
+                z->direction = 0;
+                nk_tt__hheap_free(&hh, z);
+            } else {
+                step = &((*step)->next); /* advance through list */
+            }
+        }
+
+        /* insert all edges that start before the bottom of this scanline */
+        while (e->y0 <= scan_y_bottom) {
+            if (e->y0 != e->y1) {
+                struct nk_tt__active_edge *z = nk_tt__new_active(&hh, e, off_x, scan_y_top);
+                if (z != 0) {
+                    NK_ASSERT(z->ey >= scan_y_top);
+                    /* insert at front */
+                    z->next = active;
+                    active = z;
+                }
+            }
+            ++e;
+        }
+
+        /* now process all active edges */
+        if (active)
+            nk_tt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top);
+
+        {
+            float sum = 0;
+            for (i=0; i < result->w; ++i) {
+                float k;
+                int m;
+                sum += scanline2[i];
+                k = scanline[i] + sum;
+                k = (float) NK_ABS(k) * 255.0f + 0.5f;
+                m = (int) k;
+                if (m > 255) m = 255;
+                result->pixels[j*result->stride + i] = (unsigned char) m;
+            }
+        }
+        /* advance all the edges */
+        step = &active;
+        while (*step) {
+            struct nk_tt__active_edge *z = *step;
+            z->fx += z->fdx; /* advance to position for current scanline */
+            step = &((*step)->next); /* advance through list */
+        }
+        ++y;
+        ++j;
+    }
+    nk_tt__hheap_cleanup(&hh);
+    if (scanline != scanline_data)
+        alloc->free(alloc->userdata, scanline);
+}
+
+#define NK_TT__COMPARE(a,b)  ((a)->y0 < (b)->y0)
+NK_INTERN void
+nk_tt__sort_edges_ins_sort(struct nk_tt__edge *p, int n)
+{
+    int i,j;
+    for (i=1; i < n; ++i) {
+        struct nk_tt__edge t = p[i], *a = &t;
+        j = i;
+        while (j > 0) {
+            struct nk_tt__edge *b = &p[j-1];
+            int c = NK_TT__COMPARE(a,b);
+            if (!c) break;
+            p[j] = p[j-1];
+            --j;
+        }
+        if (i != j)
+            p[j] = t;
+    }
+}
+
+NK_INTERN void
+nk_tt__sort_edges_quicksort(struct nk_tt__edge *p, int n)
+{
+    /* threshold for transitioning to insertion sort */
+    while (n > 12) {
+        struct nk_tt__edge t;
+        int c01,c12,c,m,i,j;
+
+        /* compute median of three */
+        m = n >> 1;
+        c01 = NK_TT__COMPARE(&p[0],&p[m]);
+        c12 = NK_TT__COMPARE(&p[m],&p[n-1]);
+
+        /* if 0 >= mid >= end, or 0 < mid < end, then use mid */
+        if (c01 != c12) {
+            /* otherwise, we'll need to swap something else to middle */
+            int z;
+            c = NK_TT__COMPARE(&p[0],&p[n-1]);
+            /* 0>mid && mid<n:  0>n => n; 0<n => 0 */
+            /* 0<mid && mid>n:  0>n => 0; 0<n => n */
+            z = (c == c12) ? 0 : n-1;
+            t = p[z];
+            p[z] = p[m];
+            p[m] = t;
+        }
+
+        /* now p[m] is the median-of-three */
+        /* swap it to the beginning so it won't move around */
+        t = p[0];
+        p[0] = p[m];
+        p[m] = t;
+
+        /* partition loop */
+        i=1;
+        j=n-1;
+        for(;;) {
+            /* handling of equality is crucial here */
+            /* for sentinels & efficiency with duplicates */
+            for (;;++i) {
+                if (!NK_TT__COMPARE(&p[i], &p[0])) break;
+            }
+            for (;;--j) {
+                if (!NK_TT__COMPARE(&p[0], &p[j])) break;
+            }
+
+            /* make sure we haven't crossed */
+             if (i >= j) break;
+             t = p[i];
+             p[i] = p[j];
+             p[j] = t;
+
+            ++i;
+            --j;
+
+        }
+
+        /* recurse on smaller side, iterate on larger */
+        if (j < (n-i)) {
+            nk_tt__sort_edges_quicksort(p,j);
+            p = p+i;
+            n = n-i;
+        } else {
+            nk_tt__sort_edges_quicksort(p+i, n-i);
+            n = j;
+        }
+    }
+}
+
+NK_INTERN void
+nk_tt__sort_edges(struct nk_tt__edge *p, int n)
+{
+   nk_tt__sort_edges_quicksort(p, n);
+   nk_tt__sort_edges_ins_sort(p, n);
+}
+
+NK_INTERN void
+nk_tt__rasterize(struct nk_tt__bitmap *result, struct nk_tt__point *pts,
+    int *wcount, int windings, float scale_x, float scale_y,
+    float shift_x, float shift_y, int off_x, int off_y, int invert,
+    struct nk_allocator *alloc)
+{
+    float y_scale_inv = invert ? -scale_y : scale_y;
+    struct nk_tt__edge *e;
+    int n,i,j,k,m;
+    int vsubsample = 1;
+    /* vsubsample should divide 255 evenly; otherwise we won't reach full opacity */
+
+    /* now we have to blow out the windings into explicit edge lists */
+    n = 0;
+    for (i=0; i < windings; ++i)
+        n += wcount[i];
+
+    e = (struct nk_tt__edge*)
+       alloc->alloc(alloc->userdata, 0,(sizeof(*e) * (nk_size)(n+1)));
+    if (e == 0) return;
+    n = 0;
+
+    m=0;
+    for (i=0; i < windings; ++i)
+    {
+        struct nk_tt__point *p = pts + m;
+        m += wcount[i];
+        j = wcount[i]-1;
+        for (k=0; k < wcount[i]; j=k++) {
+            int a=k,b=j;
+            /* skip the edge if horizontal */
+            if (p[j].y == p[k].y)
+                continue;
+
+            /* add edge from j to k to the list */
+            e[n].invert = 0;
+            if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) {
+                e[n].invert = 1;
+                a=j,b=k;
+            }
+            e[n].x0 = p[a].x * scale_x + shift_x;
+            e[n].y0 = (p[a].y * y_scale_inv + shift_y) * (float)vsubsample;
+            e[n].x1 = p[b].x * scale_x + shift_x;
+            e[n].y1 = (p[b].y * y_scale_inv + shift_y) * (float)vsubsample;
+            ++n;
+        }
+    }
+
+    /* now sort the edges by their highest point (should snap to integer, and then by x) */
+    /*STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); */
+    nk_tt__sort_edges(e, n);
+    /* now, traverse the scanlines and find the intersections on each scanline, use xor winding rule */
+    nk_tt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, alloc);
+    alloc->free(alloc->userdata, e);
+}
+
+NK_INTERN void
+nk_tt__add_point(struct nk_tt__point *points, int n, float x, float y)
+{
+    if (!points) return; /* during first pass, it's unallocated */
+    points[n].x = x;
+    points[n].y = y;
+}
+
+NK_INTERN int
+nk_tt__tesselate_curve(struct nk_tt__point *points, int *num_points,
+    float x0, float y0, float x1, float y1, float x2, float y2,
+    float objspace_flatness_squared, int n)
+{
+    /* tesselate until threshold p is happy...
+     * @TODO warped to compensate for non-linear stretching */
+    /* midpoint */
+    float mx = (x0 + 2*x1 + x2)/4;
+    float my = (y0 + 2*y1 + y2)/4;
+    /* versus directly drawn line */
+    float dx = (x0+x2)/2 - mx;
+    float dy = (y0+y2)/2 - my;
+    if (n > 16) /* 65536 segments on one curve better be enough! */
+        return 1;
+
+    /* half-pixel error allowed... need to be smaller if AA */
+    if (dx*dx+dy*dy > objspace_flatness_squared) {
+        nk_tt__tesselate_curve(points, num_points, x0,y0,
+            (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1);
+        nk_tt__tesselate_curve(points, num_points, mx,my,
+            (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1);
+    } else {
+        nk_tt__add_point(points, *num_points,x2,y2);
+        *num_points = *num_points+1;
+    }
+    return 1;
+}
+
+/* returns number of contours */
+NK_INTERN struct nk_tt__point*
+nk_tt_FlattenCurves(struct nk_tt_vertex *vertices, int num_verts,
+    float objspace_flatness, int **contour_lengths, int *num_contours,
+    struct nk_allocator *alloc)
+{
+    struct nk_tt__point *points=0;
+    int num_points=0;
+    float objspace_flatness_squared = objspace_flatness * objspace_flatness;
+    int i;
+    int n=0;
+    int start=0;
+    int pass;
+
+    /* count how many "moves" there are to get the contour count */
+    for (i=0; i < num_verts; ++i)
+        if (vertices[i].type == NK_TT_vmove) ++n;
+
+    *num_contours = n;
+    if (n == 0) return 0;
+
+    *contour_lengths = (int *)
+        alloc->alloc(alloc->userdata,0, (sizeof(**contour_lengths) * (nk_size)n));
+    if (*contour_lengths == 0) {
+        *num_contours = 0;
+        return 0;
+    }
+
+    /* make two passes through the points so we don't need to realloc */
+    for (pass=0; pass < 2; ++pass)
+    {
+        float x=0,y=0;
+        if (pass == 1) {
+            points = (struct nk_tt__point *)
+                alloc->alloc(alloc->userdata,0, (nk_size)num_points * sizeof(points[0]));
+            if (points == 0) goto error;
+        }
+        num_points = 0;
+        n= -1;
+
+        for (i=0; i < num_verts; ++i)
+        {
+            switch (vertices[i].type) {
+            case NK_TT_vmove:
+                /* start the next contour */
+                if (n >= 0)
+                (*contour_lengths)[n] = num_points - start;
+                ++n;
+                start = num_points;
+
+                x = vertices[i].x, y = vertices[i].y;
+                nk_tt__add_point(points, num_points++, x,y);
+                break;
+            case NK_TT_vline:
+               x = vertices[i].x, y = vertices[i].y;
+               nk_tt__add_point(points, num_points++, x, y);
+               break;
+            case NK_TT_vcurve:
+               nk_tt__tesselate_curve(points, &num_points, x,y,
+                                        vertices[i].cx, vertices[i].cy,
+                                        vertices[i].x,  vertices[i].y,
+                                        objspace_flatness_squared, 0);
+               x = vertices[i].x, y = vertices[i].y;
+               break;
+            default: break;
+         }
+      }
+      (*contour_lengths)[n] = num_points - start;
+   }
+   return points;
+
+error:
+   alloc->free(alloc->userdata, points);
+   alloc->free(alloc->userdata, *contour_lengths);
+   *contour_lengths = 0;
+   *num_contours = 0;
+   return 0;
+}
+
+NK_INTERN void
+nk_tt_Rasterize(struct nk_tt__bitmap *result, float flatness_in_pixels,
+    struct nk_tt_vertex *vertices, int num_verts,
+    float scale_x, float scale_y, float shift_x, float shift_y,
+    int x_off, int y_off, int invert, struct nk_allocator *alloc)
+{
+    float scale = scale_x > scale_y ? scale_y : scale_x;
+    int winding_count, *winding_lengths;
+    struct nk_tt__point *windings = nk_tt_FlattenCurves(vertices, num_verts,
+        flatness_in_pixels / scale, &winding_lengths, &winding_count, alloc);
+
+    NK_ASSERT(alloc);
+    if (windings) {
+        nk_tt__rasterize(result, windings, winding_lengths, winding_count,
+            scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, alloc);
+        alloc->free(alloc->userdata, winding_lengths);
+        alloc->free(alloc->userdata, windings);
+    }
+}
+
+NK_INTERN void
+nk_tt_MakeGlyphBitmapSubpixel(const struct nk_tt_fontinfo *info, unsigned char *output,
+    int out_w, int out_h, int out_stride, float scale_x, float scale_y,
+    float shift_x, float shift_y, int glyph, struct nk_allocator *alloc)
+{
+    int ix0,iy0;
+    struct nk_tt_vertex *vertices;
+    int num_verts = nk_tt_GetGlyphShape(info, alloc, glyph, &vertices);
+    struct nk_tt__bitmap gbm;
+
+    nk_tt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x,
+        shift_y, &ix0,&iy0,0,0);
+    gbm.pixels = output;
+    gbm.w = out_w;
+    gbm.h = out_h;
+    gbm.stride = out_stride;
+
+    if (gbm.w && gbm.h)
+        nk_tt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y,
+            shift_x, shift_y, ix0,iy0, 1, alloc);
+    alloc->free(alloc->userdata, vertices);
+}
+
+/*-------------------------------------------------------------
+ *                          Bitmap baking
+ * --------------------------------------------------------------*/
+NK_INTERN int
+nk_tt_PackBegin(struct nk_tt_pack_context *spc, unsigned char *pixels,
+    int pw, int ph, int stride_in_bytes, int padding, struct nk_allocator *alloc)
+{
+    int num_nodes = pw - padding;
+    struct nk_rp_context *context = (struct nk_rp_context *)
+        alloc->alloc(alloc->userdata,0, sizeof(*context));
+    struct nk_rp_node *nodes = (struct nk_rp_node*)
+        alloc->alloc(alloc->userdata,0, (sizeof(*nodes  ) * (nk_size)num_nodes));
+
+    if (context == 0 || nodes == 0) {
+        if (context != 0) alloc->free(alloc->userdata, context);
+        if (nodes   != 0) alloc->free(alloc->userdata, nodes);
+        return 0;
+    }
+
+    spc->width = pw;
+    spc->height = ph;
+    spc->pixels = pixels;
+    spc->pack_info = context;
+    spc->nodes = nodes;
+    spc->padding = padding;
+    spc->stride_in_bytes = (stride_in_bytes != 0) ? stride_in_bytes : pw;
+    spc->h_oversample = 1;
+    spc->v_oversample = 1;
+
+    nk_rp_init_target(context, pw-padding, ph-padding, nodes, num_nodes);
+    if (pixels)
+        NK_MEMSET(pixels, 0, (nk_size)(pw*ph)); /* background of 0 around pixels */
+    return 1;
+}
+
+NK_INTERN void
+nk_tt_PackEnd(struct nk_tt_pack_context *spc, struct nk_allocator *alloc)
+{
+    alloc->free(alloc->userdata, spc->nodes);
+    alloc->free(alloc->userdata, spc->pack_info);
+}
+
+NK_INTERN void
+nk_tt_PackSetOversampling(struct nk_tt_pack_context *spc,
+    unsigned int h_oversample, unsigned int v_oversample)
+{
+   NK_ASSERT(h_oversample <= NK_TT_MAX_OVERSAMPLE);
+   NK_ASSERT(v_oversample <= NK_TT_MAX_OVERSAMPLE);
+   if (h_oversample <= NK_TT_MAX_OVERSAMPLE)
+      spc->h_oversample = h_oversample;
+   if (v_oversample <= NK_TT_MAX_OVERSAMPLE)
+      spc->v_oversample = v_oversample;
+}
+
+NK_INTERN void
+nk_tt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes,
+    int kernel_width)
+{
+    unsigned char buffer[NK_TT_MAX_OVERSAMPLE];
+    int safe_w = w - kernel_width;
+    int j;
+
+    for (j=0; j < h; ++j)
+    {
+        int i;
+        unsigned int total;
+        NK_MEMSET(buffer, 0, (nk_size)kernel_width);
+
+        total = 0;
+
+        /* make kernel_width a constant in common cases so compiler can optimize out the divide */
+        switch (kernel_width) {
+        case 2:
+            for (i=0; i <= safe_w; ++i) {
+                total += (unsigned int)(pixels[i] - buffer[i & NK_TT__OVER_MASK]);
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i];
+                pixels[i] = (unsigned char) (total / 2);
+            }
+            break;
+        case 3:
+            for (i=0; i <= safe_w; ++i) {
+                total += (unsigned int)(pixels[i] - buffer[i & NK_TT__OVER_MASK]);
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i];
+                pixels[i] = (unsigned char) (total / 3);
+            }
+            break;
+        case 4:
+            for (i=0; i <= safe_w; ++i) {
+                total += (unsigned int)pixels[i] - buffer[i & NK_TT__OVER_MASK];
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i];
+                pixels[i] = (unsigned char) (total / 4);
+            }
+            break;
+        case 5:
+            for (i=0; i <= safe_w; ++i) {
+                total += (unsigned int)(pixels[i] - buffer[i & NK_TT__OVER_MASK]);
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i];
+                pixels[i] = (unsigned char) (total / 5);
+            }
+            break;
+        default:
+            for (i=0; i <= safe_w; ++i) {
+                total += (unsigned int)(pixels[i] - buffer[i & NK_TT__OVER_MASK]);
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i];
+                pixels[i] = (unsigned char) (total / (unsigned int)kernel_width);
+            }
+            break;
+        }
+
+        for (; i < w; ++i) {
+            NK_ASSERT(pixels[i] == 0);
+            total -= (unsigned int)(buffer[i & NK_TT__OVER_MASK]);
+            pixels[i] = (unsigned char) (total / (unsigned int)kernel_width);
+        }
+        pixels += stride_in_bytes;
+    }
+}
+
+NK_INTERN void
+nk_tt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes,
+    int kernel_width)
+{
+    unsigned char buffer[NK_TT_MAX_OVERSAMPLE];
+    int safe_h = h - kernel_width;
+    int j;
+
+    for (j=0; j < w; ++j)
+    {
+        int i;
+        unsigned int total;
+        NK_MEMSET(buffer, 0, (nk_size)kernel_width);
+
+        total = 0;
+
+        /* make kernel_width a constant in common cases so compiler can optimize out the divide */
+        switch (kernel_width) {
+        case 2:
+            for (i=0; i <= safe_h; ++i) {
+                total += (unsigned int)(pixels[i*stride_in_bytes] - buffer[i & NK_TT__OVER_MASK]);
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i*stride_in_bytes];
+                pixels[i*stride_in_bytes] = (unsigned char) (total / 2);
+            }
+            break;
+         case 3:
+            for (i=0; i <= safe_h; ++i) {
+                total += (unsigned int)(pixels[i*stride_in_bytes] - buffer[i & NK_TT__OVER_MASK]);
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i*stride_in_bytes];
+                pixels[i*stride_in_bytes] = (unsigned char) (total / 3);
+            }
+            break;
+         case 4:
+            for (i=0; i <= safe_h; ++i) {
+                total += (unsigned int)(pixels[i*stride_in_bytes] - buffer[i & NK_TT__OVER_MASK]);
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i*stride_in_bytes];
+                pixels[i*stride_in_bytes] = (unsigned char) (total / 4);
+            }
+            break;
+         case 5:
+            for (i=0; i <= safe_h; ++i) {
+                total += (unsigned int)(pixels[i*stride_in_bytes] - buffer[i & NK_TT__OVER_MASK]);
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i*stride_in_bytes];
+                pixels[i*stride_in_bytes] = (unsigned char) (total / 5);
+            }
+            break;
+         default:
+            for (i=0; i <= safe_h; ++i) {
+                total += (unsigned int)(pixels[i*stride_in_bytes] - buffer[i & NK_TT__OVER_MASK]);
+                buffer[(i+kernel_width) & NK_TT__OVER_MASK] = pixels[i*stride_in_bytes];
+                pixels[i*stride_in_bytes] = (unsigned char) (total / (unsigned int)kernel_width);
+            }
+            break;
+        }
+
+        for (; i < h; ++i) {
+            NK_ASSERT(pixels[i*stride_in_bytes] == 0);
+            total -= (unsigned int)(buffer[i & NK_TT__OVER_MASK]);
+            pixels[i*stride_in_bytes] = (unsigned char) (total / (unsigned int)kernel_width);
+        }
+        pixels += 1;
+    }
+}
+
+NK_INTERN float
+nk_tt__oversample_shift(int oversample)
+{
+    if (!oversample)
+        return 0.0f;
+
+    /* The prefilter is a box filter of width "oversample", */
+    /* which shifts phase by (oversample - 1)/2 pixels in */
+    /* oversampled space. We want to shift in the opposite */
+    /* direction to counter this. */
+    return (float)-(oversample - 1) / (2.0f * (float)oversample);
+}
+
+/* rects array must be big enough to accommodate all characters in the given ranges */
+NK_INTERN int
+nk_tt_PackFontRangesGatherRects(struct nk_tt_pack_context *spc,
+    struct nk_tt_fontinfo *info, struct nk_tt_pack_range *ranges,
+    int num_ranges, struct nk_rp_rect *rects)
+{
+    int i,j,k;
+    k = 0;
+
+    for (i=0; i < num_ranges; ++i) {
+        float fh = ranges[i].font_size;
+        float scale = (fh > 0) ? nk_tt_ScaleForPixelHeight(info, fh):
+            nk_tt_ScaleForMappingEmToPixels(info, -fh);
+        ranges[i].h_oversample = (unsigned char) spc->h_oversample;
+        ranges[i].v_oversample = (unsigned char) spc->v_oversample;
+        for (j=0; j < ranges[i].num_chars; ++j) {
+            int x0,y0,x1,y1;
+            int codepoint = ranges[i].first_unicode_codepoint_in_range ?
+                ranges[i].first_unicode_codepoint_in_range + j :
+                ranges[i].array_of_unicode_codepoints[j];
+
+            int glyph = nk_tt_FindGlyphIndex(info, codepoint);
+            nk_tt_GetGlyphBitmapBoxSubpixel(info,glyph, scale * (float)spc->h_oversample,
+                scale * (float)spc->v_oversample, 0,0, &x0,&y0,&x1,&y1);
+            rects[k].w = (nk_rp_coord) (x1-x0 + spc->padding + (int)spc->h_oversample-1);
+            rects[k].h = (nk_rp_coord) (y1-y0 + spc->padding + (int)spc->v_oversample-1);
+            ++k;
+        }
+    }
+    return k;
+}
+
+NK_INTERN int
+nk_tt_PackFontRangesRenderIntoRects(struct nk_tt_pack_context *spc,
+    struct nk_tt_fontinfo *info, struct nk_tt_pack_range *ranges,
+    int num_ranges, struct nk_rp_rect *rects, struct nk_allocator *alloc)
+{
+    int i,j,k, return_value = 1;
+    /* save current values */
+    int old_h_over = (int)spc->h_oversample;
+    int old_v_over = (int)spc->v_oversample;
+    /* rects array must be big enough to accommodate all characters in the given ranges */
+
+    k = 0;
+    for (i=0; i < num_ranges; ++i)
+    {
+        float fh = ranges[i].font_size;
+        float recip_h,recip_v,sub_x,sub_y;
+        float scale = fh > 0 ? nk_tt_ScaleForPixelHeight(info, fh):
+            nk_tt_ScaleForMappingEmToPixels(info, -fh);
+
+        spc->h_oversample = ranges[i].h_oversample;
+        spc->v_oversample = ranges[i].v_oversample;
+
+        recip_h = 1.0f / (float)spc->h_oversample;
+        recip_v = 1.0f / (float)spc->v_oversample;
+
+        sub_x = nk_tt__oversample_shift((int)spc->h_oversample);
+        sub_y = nk_tt__oversample_shift((int)spc->v_oversample);
+
+        for (j=0; j < ranges[i].num_chars; ++j)
+        {
+            struct nk_rp_rect *r = &rects[k];
+            if (r->was_packed)
+            {
+                struct nk_tt_packedchar *bc = &ranges[i].chardata_for_range[j];
+                int advance, lsb, x0,y0,x1,y1;
+                int codepoint = ranges[i].first_unicode_codepoint_in_range ?
+                    ranges[i].first_unicode_codepoint_in_range + j :
+                    ranges[i].array_of_unicode_codepoints[j];
+                int glyph = nk_tt_FindGlyphIndex(info, codepoint);
+                nk_rp_coord pad = (nk_rp_coord) spc->padding;
+
+                /* pad on left and top */
+                r->x = (nk_rp_coord)((int)r->x + (int)pad);
+                r->y = (nk_rp_coord)((int)r->y + (int)pad);
+                r->w = (nk_rp_coord)((int)r->w - (int)pad);
+                r->h = (nk_rp_coord)((int)r->h - (int)pad);
+
+                nk_tt_GetGlyphHMetrics(info, glyph, &advance, &lsb);
+                nk_tt_GetGlyphBitmapBox(info, glyph, scale * (float)spc->h_oversample,
+                        (scale * (float)spc->v_oversample), &x0,&y0,&x1,&y1);
+                nk_tt_MakeGlyphBitmapSubpixel(info, spc->pixels + r->x + r->y*spc->stride_in_bytes,
+                    (int)(r->w - spc->h_oversample+1), (int)(r->h - spc->v_oversample+1),
+                    spc->stride_in_bytes, scale * (float)spc->h_oversample,
+                    scale * (float)spc->v_oversample, 0,0, glyph, alloc);
+
+                if (spc->h_oversample > 1)
+                   nk_tt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,
+                        r->w, r->h, spc->stride_in_bytes, (int)spc->h_oversample);
+
+                if (spc->v_oversample > 1)
+                   nk_tt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,
+                        r->w, r->h, spc->stride_in_bytes, (int)spc->v_oversample);
+
+                bc->x0       = (nk_ushort)  r->x;
+                bc->y0       = (nk_ushort)  r->y;
+                bc->x1       = (nk_ushort) (r->x + r->w);
+                bc->y1       = (nk_ushort) (r->y + r->h);
+                bc->xadvance = scale * (float)advance;
+                bc->xoff     = (float)  x0 * recip_h + sub_x;
+                bc->yoff     = (float)  y0 * recip_v + sub_y;
+                bc->xoff2    = ((float)x0 + r->w) * recip_h + sub_x;
+                bc->yoff2    = ((float)y0 + r->h) * recip_v + sub_y;
+            } else {
+                return_value = 0; /* if any fail, report failure */
+            }
+            ++k;
+        }
+    }
+    /* restore original values */
+    spc->h_oversample = (unsigned int)old_h_over;
+    spc->v_oversample = (unsigned int)old_v_over;
+    return return_value;
+}
+
+NK_INTERN void
+nk_tt_GetPackedQuad(struct nk_tt_packedchar *chardata, int pw, int ph,
+    int char_index, float *xpos, float *ypos, struct nk_tt_aligned_quad *q,
+    int align_to_integer)
+{
+    float ipw = 1.0f / (float)pw, iph = 1.0f / (float)ph;
+    struct nk_tt_packedchar *b = (struct nk_tt_packedchar*)(chardata + char_index);
+    if (align_to_integer) {
+        int tx = nk_ifloorf((*xpos + b->xoff) + 0.5f);
+        int ty = nk_ifloorf((*ypos + b->yoff) + 0.5f);
+
+        float x = (float)tx;
+        float y = (float)ty;
+
+        q->x0 = x;
+        q->y0 = y;
+        q->x1 = x + b->xoff2 - b->xoff;
+        q->y1 = y + b->yoff2 - b->yoff;
+    } else {
+        q->x0 = *xpos + b->xoff;
+        q->y0 = *ypos + b->yoff;
+        q->x1 = *xpos + b->xoff2;
+        q->y1 = *ypos + b->yoff2;
+    }
+    q->s0 = b->x0 * ipw;
+    q->t0 = b->y0 * iph;
+    q->s1 = b->x1 * ipw;
+    q->t1 = b->y1 * iph;
+    *xpos += b->xadvance;
+}
+
+/* -------------------------------------------------------------
+ *
+ *                          FONT BAKING
+ *
+ * --------------------------------------------------------------*/
+struct nk_font_bake_data {
+    struct nk_tt_fontinfo info;
+    struct nk_rp_rect *rects;
+    struct nk_tt_pack_range *ranges;
+    nk_rune range_count;
+};
+
+struct nk_font_baker {
+    struct nk_allocator alloc;
+    struct nk_tt_pack_context spc;
+    struct nk_font_bake_data *build;
+    struct nk_tt_packedchar *packed_chars;
+    struct nk_rp_rect *rects;
+    struct nk_tt_pack_range *ranges;
+};
+
+NK_GLOBAL const nk_size nk_rect_align = NK_ALIGNOF(struct nk_rp_rect);
+NK_GLOBAL const nk_size nk_range_align = NK_ALIGNOF(struct nk_tt_pack_range);
+NK_GLOBAL const nk_size nk_char_align = NK_ALIGNOF(struct nk_tt_packedchar);
+NK_GLOBAL const nk_size nk_build_align = NK_ALIGNOF(struct nk_font_bake_data);
+NK_GLOBAL const nk_size nk_baker_align = NK_ALIGNOF(struct nk_font_baker);
+
+NK_INTERN int
+nk_range_count(const nk_rune *range)
+{
+    const nk_rune *iter = range;
+    NK_ASSERT(range);
+    if (!range) return 0;
+    while (*(iter++) != 0);
+    return (iter == range) ? 0 : (int)((iter - range)/2);
+}
+
+NK_INTERN int
+nk_range_glyph_count(const nk_rune *range, int count)
+{
+    int i = 0;
+    int total_glyphs = 0;
+    for (i = 0; i < count; ++i) {
+        int diff;
+        nk_rune f = range[(i*2)+0];
+        nk_rune t = range[(i*2)+1];
+        NK_ASSERT(t >= f);
+        diff = (int)((t - f) + 1);
+        total_glyphs += diff;
+    }
+    return total_glyphs;
+}
+
+NK_API const nk_rune*
+nk_font_default_glyph_ranges(void)
+{
+    NK_STORAGE const nk_rune ranges[] = {0x0020, 0x00FF, 0};
+    return ranges;
+}
+
+NK_API const nk_rune*
+nk_font_chinese_glyph_ranges(void)
+{
+    NK_STORAGE const nk_rune ranges[] = {
+        0x0020, 0x00FF,
+        0x3000, 0x30FF,
+        0x31F0, 0x31FF,
+        0xFF00, 0xFFEF,
+        0x4e00, 0x9FAF,
+        0
+    };
+    return ranges;
+}
+
+NK_API const nk_rune*
+nk_font_cyrillic_glyph_ranges(void)
+{
+    NK_STORAGE const nk_rune ranges[] = {
+        0x0020, 0x00FF,
+        0x0400, 0x052F,
+        0x2DE0, 0x2DFF,
+        0xA640, 0xA69F,
+        0
+    };
+    return ranges;
+}
+
+NK_API const nk_rune*
+nk_font_korean_glyph_ranges(void)
+{
+    NK_STORAGE const nk_rune ranges[] = {
+        0x0020, 0x00FF,
+        0x3131, 0x3163,
+        0xAC00, 0xD79D,
+        0
+    };
+    return ranges;
+}
+
+NK_INTERN void
+nk_font_baker_memory(nk_size *temp, int *glyph_count,
+    struct nk_font_config *config_list, int count)
+{
+    int range_count = 0;
+    int total_range_count = 0;
+    struct nk_font_config *iter;
+
+    NK_ASSERT(config_list);
+    NK_ASSERT(glyph_count);
+    if (!config_list) {
+        *temp = 0;
+        *glyph_count = 0;
+        return;
+    }
+
+    *glyph_count = 0;
+    if (!config_list->range)
+        config_list->range = nk_font_default_glyph_ranges();
+    for (iter = config_list; iter; iter = iter->next) {
+        range_count = nk_range_count(iter->range);
+        total_range_count += range_count;
+        *glyph_count += nk_range_glyph_count(iter->range, range_count);
+    }
+
+    *temp = (nk_size)*glyph_count * sizeof(struct nk_rp_rect);
+    *temp += (nk_size)total_range_count * sizeof(struct nk_tt_pack_range);
+    *temp += (nk_size)*glyph_count * sizeof(struct nk_tt_packedchar);
+    *temp += (nk_size)count * sizeof(struct nk_font_bake_data);
+    *temp += sizeof(struct nk_font_baker);
+    *temp += nk_rect_align + nk_range_align + nk_char_align;
+    *temp += nk_build_align + nk_baker_align;
+}
+
+NK_INTERN struct nk_font_baker*
+nk_font_baker(void *memory, int glyph_count, int count, struct nk_allocator *alloc)
+{
+    struct nk_font_baker *baker;
+    if (!memory) return 0;
+    /* setup baker inside a memory block  */
+    baker = (struct nk_font_baker*)NK_ALIGN_PTR(memory, nk_baker_align);
+    baker->build = (struct nk_font_bake_data*)NK_ALIGN_PTR((baker + 1), nk_build_align);
+    baker->packed_chars = (struct nk_tt_packedchar*)NK_ALIGN_PTR((baker->build + count), nk_char_align);
+    baker->rects = (struct nk_rp_rect*)NK_ALIGN_PTR((baker->packed_chars + glyph_count), nk_rect_align);
+    baker->ranges = (struct nk_tt_pack_range*)NK_ALIGN_PTR((baker->rects + glyph_count), nk_range_align);
+    baker->alloc = *alloc;
+    return baker;
+}
+
+NK_INTERN int
+nk_font_bake_pack(struct nk_font_baker *baker,
+    nk_size *image_memory, int *width, int *height, struct nk_recti *custom,
+    const struct nk_font_config *config_list, int count,
+    struct nk_allocator *alloc)
+{
+    NK_STORAGE const nk_size max_height = 1024 * 32;
+    const struct nk_font_config *config_iter;
+    int total_glyph_count = 0;
+    int total_range_count = 0;
+    int range_count = 0;
+    int i = 0;
+
+    NK_ASSERT(image_memory);
+    NK_ASSERT(width);
+    NK_ASSERT(height);
+    NK_ASSERT(config_list);
+    NK_ASSERT(count);
+    NK_ASSERT(alloc);
+
+    if (!image_memory || !width || !height || !config_list || !count) return nk_false;
+    for (config_iter = config_list; config_iter; config_iter = config_iter->next) {
+        range_count = nk_range_count(config_iter->range);
+        total_range_count += range_count;
+        total_glyph_count += nk_range_glyph_count(config_iter->range, range_count);
+    }
+
+    /* setup font baker from temporary memory */
+    for (config_iter = config_list; config_iter; config_iter = config_iter->next) {
+        const struct nk_font_config *cfg = config_iter;
+        if (!nk_tt_InitFont(&baker->build[i++].info, (const unsigned char*)cfg->ttf_blob, 0))
+            return nk_false;
+    }
+
+    *height = 0;
+    *width = (total_glyph_count > 1000) ? 1024 : 512;
+    nk_tt_PackBegin(&baker->spc, 0, (int)*width, (int)max_height, 0, 1, alloc);
+    {
+        int input_i = 0;
+        int range_n = 0;
+        int rect_n = 0;
+        int char_n = 0;
+
+        if (custom) {
+            /* pack custom user data first so it will be in the upper left corner*/
+            struct nk_rp_rect custom_space;
+            nk_zero(&custom_space, sizeof(custom_space));
+            custom_space.w = (nk_rp_coord)((custom->w * 2) + 1);
+            custom_space.h = (nk_rp_coord)(custom->h + 1);
+
+            nk_tt_PackSetOversampling(&baker->spc, 1, 1);
+            nk_rp_pack_rects((struct nk_rp_context*)baker->spc.pack_info, &custom_space, 1);
+            *height = NK_MAX(*height, (int)(custom_space.y + custom_space.h));
+
+            custom->x = (short)custom_space.x;
+            custom->y = (short)custom_space.y;
+            custom->w = (short)custom_space.w;
+            custom->h = (short)custom_space.h;
+        }
+
+        /* first font pass: pack all glyphs */
+        for (input_i = 0, config_iter = config_list; input_i < count && config_iter;
+            input_i++, config_iter = config_iter->next)
+        {
+            int n = 0;
+            int glyph_count;
+            const nk_rune *in_range;
+            const struct nk_font_config *cfg = config_iter;
+            struct nk_font_bake_data *tmp = &baker->build[input_i];
+
+            /* count glyphs + ranges in current font */
+            glyph_count = 0; range_count = 0;
+            for (in_range = cfg->range; in_range[0] && in_range[1]; in_range += 2) {
+                glyph_count += (int)(in_range[1] - in_range[0]) + 1;
+                range_count++;
+            }
+
+            /* setup ranges  */
+            tmp->ranges = baker->ranges + range_n;
+            tmp->range_count = (nk_rune)range_count;
+            range_n += range_count;
+            for (i = 0; i < range_count; ++i) {
+                in_range = &cfg->range[i * 2];
+                tmp->ranges[i].font_size = cfg->size;
+                tmp->ranges[i].first_unicode_codepoint_in_range = (int)in_range[0];
+                tmp->ranges[i].num_chars = (int)(in_range[1]- in_range[0]) + 1;
+                tmp->ranges[i].chardata_for_range = baker->packed_chars + char_n;
+                char_n += tmp->ranges[i].num_chars;
+            }
+
+            /* pack */
+            tmp->rects = baker->rects + rect_n;
+            rect_n += glyph_count;
+            nk_tt_PackSetOversampling(&baker->spc, cfg->oversample_h, cfg->oversample_v);
+            n = nk_tt_PackFontRangesGatherRects(&baker->spc, &tmp->info,
+                tmp->ranges, (int)tmp->range_count, tmp->rects);
+            nk_rp_pack_rects((struct nk_rp_context*)baker->spc.pack_info, tmp->rects, (int)n);
+
+            /* texture height */
+            for (i = 0; i < n; ++i) {
+                if (tmp->rects[i].was_packed)
+                    *height = NK_MAX(*height, tmp->rects[i].y + tmp->rects[i].h);
+            }
+        }
+        NK_ASSERT(rect_n == total_glyph_count);
+        NK_ASSERT(char_n == total_glyph_count);
+        NK_ASSERT(range_n == total_range_count);
+    }
+    *height = (int)nk_round_up_pow2((nk_uint)*height);
+    *image_memory = (nk_size)(*width) * (nk_size)(*height);
+    return nk_true;
+}
+
+NK_INTERN void
+nk_font_bake(struct nk_font_baker *baker, void *image_memory, int width, int height,
+    struct nk_font_glyph *glyphs, int glyphs_count,
+    const struct nk_font_config *config_list, int font_count)
+{
+    int input_i = 0;
+    nk_rune glyph_n = 0;
+    const struct nk_font_config *config_iter;
+
+    NK_ASSERT(image_memory);
+    NK_ASSERT(width);
+    NK_ASSERT(height);
+    NK_ASSERT(config_list);
+    NK_ASSERT(baker);
+    NK_ASSERT(font_count);
+    NK_ASSERT(glyphs_count);
+    if (!image_memory || !width || !height || !config_list ||
+        !font_count || !glyphs || !glyphs_count)
+        return;
+
+    /* second font pass: render glyphs */
+    nk_zero(image_memory, (nk_size)((nk_size)width * (nk_size)height));
+    baker->spc.pixels = (unsigned char*)image_memory;
+    baker->spc.height = (int)height;
+    for (input_i = 0, config_iter = config_list; input_i < font_count && config_iter;
+        ++input_i, config_iter = config_iter->next)
+    {
+        const struct nk_font_config *cfg = config_iter;
+        struct nk_font_bake_data *tmp = &baker->build[input_i];
+        nk_tt_PackSetOversampling(&baker->spc, cfg->oversample_h, cfg->oversample_v);
+        nk_tt_PackFontRangesRenderIntoRects(&baker->spc, &tmp->info, tmp->ranges,
+            (int)tmp->range_count, tmp->rects, &baker->alloc);
+    }
+    nk_tt_PackEnd(&baker->spc, &baker->alloc);
+
+    /* third pass: setup font and glyphs */
+    for (input_i = 0, config_iter = config_list; input_i < font_count && config_iter;
+        ++input_i, config_iter = config_iter->next)
+    {
+        nk_size i = 0;
+        int char_idx = 0;
+        nk_rune glyph_count = 0;
+        const struct nk_font_config *cfg = config_iter;
+        struct nk_font_bake_data *tmp = &baker->build[input_i];
+        struct nk_baked_font *dst_font = cfg->font;
+
+        float font_scale = nk_tt_ScaleForPixelHeight(&tmp->info, cfg->size);
+        int unscaled_ascent, unscaled_descent, unscaled_line_gap;
+        nk_tt_GetFontVMetrics(&tmp->info, &unscaled_ascent, &unscaled_descent,
+                                &unscaled_line_gap);
+
+        /* fill baked font */
+        if (!cfg->merge_mode) {
+            dst_font->ranges = cfg->range;
+            dst_font->height = cfg->size;
+            dst_font->ascent = ((float)unscaled_ascent * font_scale);
+            dst_font->descent = ((float)unscaled_descent * font_scale);
+            dst_font->glyph_offset = glyph_n;
+        }
+
+        /* fill own baked font glyph array */
+        for (i = 0; i < tmp->range_count; ++i)
+        {
+            struct nk_tt_pack_range *range = &tmp->ranges[i];
+            for (char_idx = 0; char_idx < range->num_chars; char_idx++)
+            {
+                nk_rune codepoint = 0;
+                float dummy_x = 0, dummy_y = 0;
+                struct nk_tt_aligned_quad q;
+                struct nk_font_glyph *glyph;
+
+                /* query glyph bounds from stb_truetype */
+                const struct nk_tt_packedchar *pc = &range->chardata_for_range[char_idx];
+                if (!pc->x0 && !pc->x1 && !pc->y0 && !pc->y1) continue;
+                codepoint = (nk_rune)(range->first_unicode_codepoint_in_range + char_idx);
+                nk_tt_GetPackedQuad(range->chardata_for_range, (int)width,
+                    (int)height, char_idx, &dummy_x, &dummy_y, &q, 0);
+
+                /* fill own glyph type with data */
+                glyph = &glyphs[dst_font->glyph_offset + (unsigned int)glyph_count];
+                glyph->codepoint = codepoint;
+                glyph->x0 = q.x0; glyph->y0 = q.y0;
+                glyph->x1 = q.x1; glyph->y1 = q.y1;
+                glyph->y0 += (dst_font->ascent + 0.5f);
+                glyph->y1 += (dst_font->ascent + 0.5f);
+                glyph->w = glyph->x1 - glyph->x0 + 0.5f;
+                glyph->h = glyph->y1 - glyph->y0;
+
+                if (cfg->coord_type == NK_COORD_PIXEL) {
+                    glyph->u0 = q.s0 * (float)width;
+                    glyph->v0 = q.t0 * (float)height;
+                    glyph->u1 = q.s1 * (float)width;
+                    glyph->v1 = q.t1 * (float)height;
+                } else {
+                    glyph->u0 = q.s0;
+                    glyph->v0 = q.t0;
+                    glyph->u1 = q.s1;
+                    glyph->v1 = q.t1;
+                }
+                glyph->xadvance = (pc->xadvance + cfg->spacing.x);
+                if (cfg->pixel_snap)
+                    glyph->xadvance = (float)(int)(glyph->xadvance + 0.5f);
+                glyph_count++;
+            }
+        }
+        dst_font->glyph_count = glyph_count;
+        glyph_n += dst_font->glyph_count;
+    }
+}
+
+NK_INTERN void
+nk_font_bake_custom_data(void *img_memory, int img_width, int img_height,
+    struct nk_recti img_dst, const char *texture_data_mask, int tex_width,
+    int tex_height, char white, char black)
+{
+    nk_byte *pixels;
+    int y = 0;
+    int x = 0;
+    int n = 0;
+
+    NK_ASSERT(img_memory);
+    NK_ASSERT(img_width);
+    NK_ASSERT(img_height);
+    NK_ASSERT(texture_data_mask);
+    NK_UNUSED(tex_height);
+    if (!img_memory || !img_width || !img_height || !texture_data_mask)
+        return;
+
+    pixels = (nk_byte*)img_memory;
+    for (y = 0, n = 0; y < tex_height; ++y) {
+        for (x = 0; x < tex_width; ++x, ++n) {
+            const int off0 = ((img_dst.x + x) + (img_dst.y + y) * img_width);
+            const int off1 = off0 + 1 + tex_width;
+            pixels[off0] = (texture_data_mask[n] == white) ? 0xFF : 0x00;
+            pixels[off1] = (texture_data_mask[n] == black) ? 0xFF : 0x00;
+        }
+    }
+}
+
+NK_INTERN void
+nk_font_bake_convert(void *out_memory, int img_width, int img_height,
+    const void *in_memory)
+{
+    int n = 0;
+    nk_rune *dst;
+    const nk_byte *src;
+
+    NK_ASSERT(out_memory);
+    NK_ASSERT(in_memory);
+    NK_ASSERT(img_width);
+    NK_ASSERT(img_height);
+    if (!out_memory || !in_memory || !img_height || !img_width) return;
+
+    dst = (nk_rune*)out_memory;
+    src = (const nk_byte*)in_memory;
+    for (n = (int)(img_width * img_height); n > 0; n--)
+        *dst++ = ((nk_rune)(*src++) << 24) | 0x00FFFFFF;
+}
+
+/* -------------------------------------------------------------
+ *
+ *                          FONT
+ *
+ * --------------------------------------------------------------*/
+NK_INTERN float
+nk_font_text_width(nk_handle handle, float height, const char *text, int len)
+{
+    nk_rune unicode;
+    int text_len  = 0;
+    float text_width = 0;
+    int glyph_len = 0;
+    float scale = 0;
+
+    struct nk_font *font = (struct nk_font*)handle.ptr;
+    NK_ASSERT(font);
+    NK_ASSERT(font->glyphs);
+    if (!font || !text || !len)
+        return 0;
+
+    scale = height/font->info.height;
+    glyph_len = text_len = nk_utf_decode(text, &unicode, (int)len);
+    if (!glyph_len) return 0;
+    while (text_len <= (int)len && glyph_len) {
+        const struct nk_font_glyph *g;
+        if (unicode == NK_UTF_INVALID) break;
+
+        /* query currently drawn glyph information */
+        g = nk_font_find_glyph(font, unicode);
+        text_width += g->xadvance * scale;
+
+        /* offset next glyph */
+        glyph_len = nk_utf_decode(text + text_len, &unicode, (int)len - text_len);
+        text_len += glyph_len;
+    }
+    return text_width;
+}
+
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+NK_INTERN void
+nk_font_query_font_glyph(nk_handle handle, float height,
+    struct nk_user_font_glyph *glyph, nk_rune codepoint, nk_rune next_codepoint)
+{
+    float scale;
+    const struct nk_font_glyph *g;
+    struct nk_font *font;
+
+    NK_ASSERT(glyph);
+    NK_UNUSED(next_codepoint);
+
+    font = (struct nk_font*)handle.ptr;
+    NK_ASSERT(font);
+    NK_ASSERT(font->glyphs);
+    if (!font || !glyph)
+        return;
+
+    scale = height/font->info.height;
+    g = nk_font_find_glyph(font, codepoint);
+    glyph->width = (g->x1 - g->x0) * scale;
+    glyph->height = (g->y1 - g->y0) * scale;
+    glyph->offset = nk_vec2(g->x0 * scale, g->y0 * scale);
+    glyph->xadvance = (g->xadvance * scale);
+    glyph->uv[0] = nk_vec2(g->u0, g->v0);
+    glyph->uv[1] = nk_vec2(g->u1, g->v1);
+}
+#endif
+
+NK_API const struct nk_font_glyph*
+nk_font_find_glyph(struct nk_font *font, nk_rune unicode)
+{
+    int i = 0;
+    int count;
+    int total_glyphs = 0;
+    const struct nk_font_glyph *glyph = 0;
+
+    NK_ASSERT(font);
+    NK_ASSERT(font->glyphs);
+    NK_ASSERT(font->info.ranges);
+    if (!font || !font->glyphs) return 0;
+
+    glyph = font->fallback;
+    count = nk_range_count(font->info.ranges);
+    for (i = 0; i < count; ++i) {
+        nk_rune f = font->info.ranges[(i*2)+0];
+        nk_rune t = font->info.ranges[(i*2)+1];
+        int diff = (int)((t - f) + 1);
+        if (unicode >= f && unicode <= t)
+            return &font->glyphs[((nk_rune)total_glyphs + (unicode - f))];
+        total_glyphs += diff;
+    }
+    return glyph;
+}
+
+NK_INTERN void
+nk_font_init(struct nk_font *font, float pixel_height,
+    nk_rune fallback_codepoint, struct nk_font_glyph *glyphs,
+    const struct nk_baked_font *baked_font, nk_handle atlas)
+{
+    struct nk_baked_font baked;
+    NK_ASSERT(font);
+    NK_ASSERT(glyphs);
+    NK_ASSERT(baked_font);
+    if (!font || !glyphs || !baked_font)
+        return;
+
+    baked = *baked_font;
+    font->fallback = 0;
+    font->info = baked;
+    font->scale = (float)pixel_height / (float)font->info.height;
+    font->glyphs = &glyphs[baked_font->glyph_offset];
+    font->texture = atlas;
+    font->fallback_codepoint = fallback_codepoint;
+    font->fallback = nk_font_find_glyph(font, fallback_codepoint);
+
+    font->handle.height = font->info.height * font->scale;
+    font->handle.width = nk_font_text_width;
+    font->handle.userdata.ptr = font;
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+    font->handle.query = nk_font_query_font_glyph;
+    font->handle.texture = font->texture;
+#endif
+}
+
+/* ---------------------------------------------------------------------------
+ *
+ *                          DEFAULT FONT
+ *
+ * ProggyClean.ttf
+ * Copyright (c) 2004, 2005 Tristan Grimmer
+ * MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip)
+ * Download and more information at http://upperbounds.net
+ *-----------------------------------------------------------------------------*/
+#ifdef NK_INCLUDE_DEFAULT_FONT
+
+ #ifdef __clang__
+#pragma clang diagnostic push
+
+#pragma clang diagnostic ignored "-Woverlength-strings"
+#elif defined(__GNUC__) || defined(__GNUG__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Woverlength-strings"
+#endif
+
+NK_GLOBAL const char nk_proggy_clean_ttf_compressed_data_base85[11980+1] =
+    "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/"
+    "2*>]b(MC;$jPfY.;h^`IWM9<Lh2TlS+f-s$o6Q<BWH`YiU.xfLq$N;$0iR/GX:U(jcW2p/W*q?-qmnUCI;jHSAiFWM.R*kU@C=GH?a9wp8f$e.-4^Qg1)Q-GL(lf(r/7GrRgwV%MS=C#"
+    "`8ND>Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1<q-UE31#^-V'8IRUo7Qf./L>=Ke$$'5F%)]0^#0X@U.a<r:QLtFsLcL6##lOj)#.Y5<-R&KgLwqJfLgN&;Q?gI^#DY2uL"
+    "i@^rMl9t=cWq6##weg>$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;-<nLENhvx>-VsM.M0rJfLH2eTM`*oJMHRC`N"
+    "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`&#0j@'DbG&#^$PG.Ll+DNa<XCMKEV*N)LN/N"
+    "*b=%Q6pia-Xg8I$<MR&,VdJe$<(7G;Ckl'&hF;;$<_=X(b.RS%%)###MPBuuE1V:v&cX&#2m#(&cV]`k9OhLMbn%s$G2,B$BfD3X*sp5#l,$R#]x_X1xKX%b5U*[r5iMfUo9U`N99hG)"
+    "tm+/Us9pG)XPu`<0s-)WTt(gCRxIg(%6sfh=ktMKn3j)<6<b5Sk_/0(^]AaN#(p/L>&VZ>1i%h1S9u5o@YaaW$e+b<TWFn/Z:Oh(Cx2$lNEoN^e)#CFY@@I;BOQ*sRwZtZxRcU7uW6CX"
+    "ow0i(?$Q[cjOd[P4d)]>ROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc."
+    "x]Ip.PH^'/aqUO/$1WxLoW0[iLA<QT;5HKD+@qQ'NQ(3_PLhE48R.qAPSwQ0/WK?Z,[x?-J;jQTWA0X@KJ(_Y8N-:/M74:/-ZpKrUss?d#dZq]DAbkU*JqkL+nwX@@47`5>w=4h(9.`G"
+    "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?G<Nald$qs]@]L<J7bR*>gv:[7MI2k).'2($5FNP&EQ(,)"
+    "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#"
+    "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM"
+    "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0<q-]L_?^)1vw'.,MRsqVr.L;aN&#/EgJ)PBc[-f>+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu"
+    "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/"
+    "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[K<L"
+    "%a2E-grWVM3@2=-k22tL]4$##6We'8UJCKE[d_=%wI;'6X-GsLX4j^SgJ$##R*w,vP3wK#iiW&#*h^D&R?jp7+/u&#(AP##XU8c$fSYW-J95_-Dp[g9wcO&#M-h1OcJlc-*vpw0xUX&#"
+    "OQFKNX@QI'IoPp7nb,QU//MQ&ZDkKP)X<WSVL(68uVl&#c'[0#(s1X&xm$Y%B7*K:eDA323j998GXbA#pwMs-jgD$9QISB-A_(aN4xoFM^@C58D0+Q+q3n0#3U1InDjF682-SjMXJK)("
+    "h$hxua_K]ul92%'BOU&#BRRh-slg8KDlr:%L71Ka:.A;%YULjDPmL<LYs8i#XwJOYaKPKc1h:'9Ke,g)b),78=I39B;xiY$bgGw-&.Zi9InXDuYa%G*f2Bq7mn9^#p1vv%#(Wi-;/Z5h"
+    "o;#2:;%d&#x9v68C5g?ntX0X)pT`;%pB3q7mgGN)3%(P8nTd5L7GeA-GL@+%J3u2:(Yf>et`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO"
+    "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J<j$UpK<Q4a1]MupW^-"
+    "sj_$%[HK%'F####QRZJ::Y3EGl4'@%FkiAOg#p[##O`gukTfBHagL<LHw%q&OV0##F=6/:chIm0@eCP8X]:kFI%hl8hgO@RcBhS-@Qb$%+m=hPDLg*%K8ln(wcf3/'DW-$.lR?n[nCH-"
+    "eXOONTJlh:.RYF%3'p6sq:UIMA945&^HFS87@$EP2iG<-lCO$%c`uKGD3rC$x0BL8aFn--`ke%#HMP'vh1/R&O_J9'um,.<tx[@%wsJk&bUT2`0uMv7gg#qp/ij.L56'hl;.s5CUrxjO"
+    "M7-##.l+Au'A&O:-T72L]P`&=;ctp'XScX*rU.>-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%"
+    "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$M<Jnq79VsJW/mWS*PUiq76;]/NM_>hLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]"
+    "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et"
+    "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$<M-SGZ':+Q_k+uvOSLiEo(<aD/K<CCc`'Lx>'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:"
+    "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VB<HFF*qL("
+    "$/V,;(kXZejWO`<[5?\?ewY(*9=%wDc;,u<'9t3W-(H1th3+G]ucQ]kLs7df($/*JL]@*t7Bu_G3_7mp7<iaQjO@.kLg;x3B0lqp7Hf,^Ze7-##@/c58Mo(3;knp0%)A7?-W+eI'o8)b<"
+    "nKnw'Ho8C=Y>pqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<<aG/1N$#FX$0V5Y6x'aErI3I$7x%E`v<-BY,)%-?Psf*l?%C3.mM(=/M0:JxG'?"
+    "7WhH%o'a<-80g0NBxoO(GH<dM]n.+%q@jH?f.UsJ2Ggs&4<-e47&Kl+f//9@`b+?.TeN_&B8Ss?v;^Trk;f#YvJkl&w$]>-+k?'(<S:68tq*WoDfZu';mM?8X[ma8W%*`-=;D.(nc7/;"
+    ")g:T1=^J$&BRV(-lTmNB6xqB[@0*o.erM*<SWF]u2=st-*(6v>^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M"
+    "D?@f&1'BW-)Ju<L25gl8uhVm1hL$##*8###'A3/LkKW+(^rWX?5W_8g)a(m&K8P>#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX("
+    "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs"
+    "bIu)'Z,*[>br5fX^:FPAWr-m2KgL<LUN098kTF&#lvo58=/vjDo;.;)Ka*hLR#/k=rKbxuV`>Q_nN6'8uTG&#1T5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q"
+    "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aeg<Z'<$#4H)6,>e0jT6'N#(q%.O=?2S]u*(m<-"
+    "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i"
+    "sZ88+dKQ)W6>J%CL<KE>`.d*(B`-n8D9oK<Up]c$X$(,)M8Zt7/[rdkqTgl-0cuGMv'?>-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P&#9r+$%CE=68>K8r0=dSC%%(@p7"
+    ".m7jilQ02'0-VWAg<a/''3u.=4L$Y)6k/K:_[3=&jvL<L0C/2'v:^;-DIBW,B4E68:kZ;%?8(Q8BH=kO65BW?xSG&#@uU,DS*,?.+(o(#1vCS8#CHF>TlGW'b)Tq7VT9q^*^$$.:&N@@"
+    "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*"
+    "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u"
+    "@-W$U%VEQ/,,>>#)D<h#`)h0:<Q6909ua+&VU%n2:cG3FJ-%@Bj-DgLr`Hw&HAKjKjseK</xKT*)B,N9X3]krc12t'pgTV(Lv-tL[xg_%=M_q7a^x?7Ubd>#%8cY#YZ?=,`Wdxu/ae&#"
+    "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$s<Eh#c&)q.MXI%#v9ROa5FZO%sF7q7Nwb&#ptUJ:aqJe$Sl68%.D###EC><?-aF&#RNQv>o8lKN%5/$(vdfq7+ebA#"
+    "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(<c`Q8N)jEIF*+?P2a8g%)$q]o2aH8C&<SibC/q,(e:v;-b#6[$NtDZ84Je2KNvB#$P5?tQ3nt(0"
+    "d=j.LQf./Ll33+(;q3L-w=8dX$#WF&uIJ@-bfI>%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoF&#4DoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8"
+    "6e%B/:=>)N4xeW.*wft-;$'58-ESqr<b?UI(_%@[P46>#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#"
+    "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjL<Lni;''X.`$#8+1GD"
+    ":k$YUWsbn8ogh6rxZ2Z9]%nd+>V#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#<NEdtg(n'=S1A(Q1/I&4([%dM`,Iu'1:_hL>SfD07&6D<fp8dHM7/g+"
+    "tlPN9J*rKaPct&?'uBCem^jn%9_K)<,C5K3s=5g&GmJb*[SYq7K;TRLGCsM-$$;S%:Y@r7AK0pprpL<Lrh,q7e/%KWK:50I^+m'vi`3?%Zp+<-d+$L-Sv:@.o19n$s0&39;kn;S%BSq*"
+    "$3WoJSCLweV[aZ'MQIjO<7;X-X;&+dMLvu#^UsGEC9WEc[X(wI7#2.(F0jV*eZf<-Qv3J-c+J5AlrB#$p(H68LvEA'q3n0#m,[`*8Ft)FcYgEud]CWfm68,(aLA$@EFTgLXoBq/UPlp7"
+    ":d[/;r_ix=:TF`S5H-b<LI&HY(K=h#)]Lk$K14lVfm:x$H<3^Ql<M`$OhapBnkup'D#L$Pb_`N*g]2e;X/Dtg,bsj&K#2[-:iYr'_wgH)NUIR8a1n#S?Yej'h8^58UbZd+^FKD*T@;6A"
+    "7aQC[K8d-(v6GI$x:T<&'Gp5Uf>@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-<aN((^7('#Z0wK#5GX@7"
+    "u][`*S^43933A4rl][`*O4CgLEl]v$1Q3AeF37dbXk,.)vj#x'd`;qgbQR%FW,2(?LO=s%Sc68%NP'##Aotl8x=BE#j1UD([3$M(]UI2LX3RpKN@;/#f'f/&_mt&F)XdF<9t4)Qa.*kT"
+    "LwQ'(TTB9.xH'>#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5<N?)NBS)QN*_I,?&)2'IM%L3I)X((e/dl2&8'<M"
+    ":^#M*Q+[T.Xri.LYS3v%fF`68h;b-X[/En'CR.q7E)p'/kle2HM,u;^%OKC-N+Ll%F9CF<Nf'^#t2L,;27W:0O@6##U6W7:$rJfLWHj$#)woqBefIZ.PK<b*t7ed;p*_m;4ExK#h@&]>"
+    "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%"
+    "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;"
+    "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmL<LD)F^%[tC'8;+9E#C$g%#5Y>q9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:"
+    "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3<n-&%H%b<FDj2M<hH=&Eh<2Len$b*aTX=-8QxN)k11IM1c^j%"
+    "9s<L<NFSo)B?+<-(GxsF,^-Eh@$4dXhN$+#rxK8'je'D7k`e;)2pYwPA'_p9&@^18ml1^[@g4t*[JOa*[=Qp7(qJ_oOL^('7fB&Hq-:sf,sNj8xq^>$U4O]GKx'm9)b@p7YsvK3w^YR-"
+    "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*"
+    "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdF<TddF<9Ah-6&9tWoDlh]&1SpGMq>Ti1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IX<N+T+0MlMBPQ*Vj>SsD<U4JHY"
+    "8kD2)2fU/M#$e.)T4,_=8hLim[&);?UkK'-x?'(:siIfL<$pFM`i<?%W(mGDHM%>iWP,##P`%/L<eXi:@Z9C.7o=@(pXdAO/NLQ8lPl+HPOQa8wD8=^GlPa8TKI1CjhsCTSLJM'/Wl>-"
+    "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n<bhPmUkMw>%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL<LoNs'6,'85`"
+    "0?t/'_U59@]ddF<#LdF<eWdF<OuN/45rY<-L@&#+fm>69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdF<gR@2L=FNU-<b[(9c/ML3m;Z[$oF3g)GAWqpARc=<ROu7cL5l;-[A]%/"
+    "+fsd;l#SafT/f*W]0=O'$(Tb<[)*@e775R-:Yob%g*>l*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj"
+    "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#<IGe;__.thjZl<%w(Wk2xmp4Q@I#I9,DF]u7-P=.-_:YJ]aS@V"
+    "?6*C()dOp7:WL,b&3Rg/.cmM9&r^>$(>.Z-I&J(Q0Hd5Q%7Co-b`-c<N(6r@ip+AurK<m86QIth*#v;-OBqi+L7wDE-Ir8K['m+DDSLwK&/.?-V%U_%3:qKNu$_b*B-kp7NaD'QdWQPK"
+    "Yq[@>P)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8<FfNkgg^oIbah*#8/Qt$F&:K*-(N/'+1vMB,u()-a.VUU*#[e%gAAO(S>WlA2);Sa"
+    ">gXm8YB`1d@K#n]76-a$U,mF<fX]idqd)<3,]J7JmW4`6]uks=4-72L(jEk+:bJ0M^q-8Dm_Z?0olP1C9Sa&H[d&c$ooQUj]Exd*3ZM@-WGW2%s',B-_M%>%Ul:#/'xoFM9QX-$.QN'>"
+    "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B</R90;eZ]%Ncq;-Tl]#F>2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I"
+    "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1<Vc52=u`3^o-n1'g4v58Hj&6_t7$##?M)c<$bgQ_'SY((-xkA#"
+    "Y(,p'H9rIVY-b,'%bCPF7.J<Up^,(dU1VY*5#WkTU>h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-u<Hp,3@e^9UB1J+ak9-TN/mhKPg+AJYd$"
+    "MlvAF_jCK*.O-^(63adMT->W%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)"
+    "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo"
+    "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P"
+    "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*<h`e-GI7)?OK2A.d7_c)?wQ5AS@DL3r#7fSkgl6-++D:'A,uq7SvlB$pcpH'q3n0#_%dY#xCpr-l<F0NR@-##FEV6NTF6##$l84N1w?AO>'IAO"
+    "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#"
+    ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T<XoIB&hx=T1PcDaB&;HH+-AFr?(m9HZV)FKS8JCw;SD=6[^/DZUL`EUDf]GGlG&>"
+    "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#<xU?#@.i?#D:%@#HF7@#LRI@#P_[@#Tkn@#Xw*A#]-=A#a9OA#"
+    "d<F&#*;G##.GY##2Sl##6`($#:l:$#>xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4&#3^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4"
+    "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#"
+    "/QHC#3^ZC#7jmC#;v)D#?,<D#C8ND#GDaD#KPsD#O]/E#g1A5#KA*1#gC17#MGd;#8(02#L-d3#rWM4#Hga1#,<w0#T.j<#O#'2#CYN1#qa^:#_4m3#o@/=#eG8=#t8J5#`+78#4uI-#"
+    "m3B2#SB[8#Q0@8#i[*9#iOn8#1Nm;#^sN9#qh<9#:=x-#P;K2#$%X9#bC+.#Rg;<#mN=.#MTF.#RZO.#2?)4#Y#(/#[)1/#b;L/#dAU/#0Sv;#lY$0#n`-0#sf60#(F24#wrH0#%/e0#"
+    "TmD<#%JSMFove:CTBEXI:<eh2g)B,3h2^G3i;#d3jD>)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP"
+    "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp"
+    "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#";
+#endif /* NK_INCLUDE_DEFAULT_FONT */
+
+#define NK_CURSOR_DATA_W 90
+#define NK_CURSOR_DATA_H 27
+NK_GLOBAL const char nk_custom_cursor_data[NK_CURSOR_DATA_W * NK_CURSOR_DATA_H + 1] =
+{
+    "..-         -XXXXXXX-    X    -           X           -XXXXXXX          -          XXXXXXX"
+    "..-         -X.....X-   X.X   -          X.X          -X.....X          -          X.....X"
+    "---         -XXX.XXX-  X...X  -         X...X         -X....X           -           X....X"
+    "X           -  X.X  - X.....X -        X.....X        -X...X            -            X...X"
+    "XX          -  X.X  -X.......X-       X.......X       -X..X.X           -           X.X..X"
+    "X.X         -  X.X  -XXXX.XXXX-       XXXX.XXXX       -X.X X.X          -          X.X X.X"
+    "X..X        -  X.X  -   X.X   -          X.X          -XX   X.X         -         X.X   XX"
+    "X...X       -  X.X  -   X.X   -    XX    X.X    XX    -      X.X        -        X.X      "
+    "X....X      -  X.X  -   X.X   -   X.X    X.X    X.X   -       X.X       -       X.X       "
+    "X.....X     -  X.X  -   X.X   -  X..X    X.X    X..X  -        X.X      -      X.X        "
+    "X......X    -  X.X  -   X.X   - X...XXXXXX.XXXXXX...X -         X.X   XX-XX   X.X         "
+    "X.......X   -  X.X  -   X.X   -X.....................X-          X.X X.X-X.X X.X          "
+    "X........X  -  X.X  -   X.X   - X...XXXXXX.XXXXXX...X -           X.X..X-X..X.X           "
+    "X.........X -XXX.XXX-   X.X   -  X..X    X.X    X..X  -            X...X-X...X            "
+    "X..........X-X.....X-   X.X   -   X.X    X.X    X.X   -           X....X-X....X           "
+    "X......XXXXX-XXXXXXX-   X.X   -    XX    X.X    XX    -          X.....X-X.....X          "
+    "X...X..X    ---------   X.X   -          X.X          -          XXXXXXX-XXXXXXX          "
+    "X..X X..X   -       -XXXX.XXXX-       XXXX.XXXX       ------------------------------------"
+    "X.X  X..X   -       -X.......X-       X.......X       -    XX           XX    -           "
+    "XX    X..X  -       - X.....X -        X.....X        -   X.X           X.X   -           "
+    "      X..X          -  X...X  -         X...X         -  X..X           X..X  -           "
+    "       XX           -   X.X   -          X.X          - X...XXXXXXXXXXXXX...X -           "
+    "------------        -    X    -           X           -X.....................X-           "
+    "                    ----------------------------------- X...XXXXXXXXXXXXX...X -           "
+    "                                                      -  X..X           X..X  -           "
+    "                                                      -   X.X           X.X   -           "
+    "                                                      -    XX           XX    -           "
+};
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#elif defined(__GNUC__) || defined(__GNUG__)
+#pragma GCC diagnostic pop
+#endif
+
+NK_INTERN unsigned int
+nk_decompress_length(unsigned char *input)
+{
+    return (unsigned int)((input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]);
+}
+
+NK_GLOBAL unsigned char *nk__barrier;
+NK_GLOBAL unsigned char *nk__barrier2;
+NK_GLOBAL unsigned char *nk__barrier3;
+NK_GLOBAL unsigned char *nk__barrier4;
+NK_GLOBAL unsigned char *nk__dout;
+
+NK_INTERN void
+nk__match(unsigned char *data, unsigned int length)
+{
+    /* INVERSE of memmove... write each byte before copying the next...*/
+    NK_ASSERT (nk__dout + length <= nk__barrier);
+    if (nk__dout + length > nk__barrier) { nk__dout += length; return; }
+    if (data < nk__barrier4) { nk__dout = nk__barrier+1; return; }
+    while (length--) *nk__dout++ = *data++;
+}
+
+NK_INTERN void
+nk__lit(unsigned char *data, unsigned int length)
+{
+    NK_ASSERT (nk__dout + length <= nk__barrier);
+    if (nk__dout + length > nk__barrier) { nk__dout += length; return; }
+    if (data < nk__barrier2) { nk__dout = nk__barrier+1; return; }
+    NK_MEMCPY(nk__dout, data, length);
+    nk__dout += length;
+}
+
+#define nk__in2(x)   ((i[x] << 8) + i[(x)+1])
+#define nk__in3(x)   ((i[x] << 16) + nk__in2((x)+1))
+#define nk__in4(x)   ((i[x] << 24) + nk__in3((x)+1))
+
+NK_INTERN unsigned char*
+nk_decompress_token(unsigned char *i)
+{
+    if (*i >= 0x20) { /* use fewer if's for cases that expand small */
+        if (*i >= 0x80)       nk__match(nk__dout-i[1]-1, (unsigned int)i[0] - 0x80 + 1), i += 2;
+        else if (*i >= 0x40)  nk__match(nk__dout-(nk__in2(0) - 0x4000 + 1), (unsigned int)i[2]+1), i += 3;
+        else /* *i >= 0x20 */ nk__lit(i+1, (unsigned int)i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1);
+    } else { /* more ifs for cases that expand large, since overhead is amortized */
+        if (*i >= 0x18)       nk__match(nk__dout-(unsigned int)(nk__in3(0) - 0x180000 + 1), (unsigned int)i[3]+1), i += 4;
+        else if (*i >= 0x10)  nk__match(nk__dout-(unsigned int)(nk__in3(0) - 0x100000 + 1), (unsigned int)nk__in2(3)+1), i += 5;
+        else if (*i >= 0x08)  nk__lit(i+2, (unsigned int)nk__in2(0) - 0x0800 + 1), i += 2 + (nk__in2(0) - 0x0800 + 1);
+        else if (*i == 0x07)  nk__lit(i+3, (unsigned int)nk__in2(1) + 1), i += 3 + (nk__in2(1) + 1);
+        else if (*i == 0x06)  nk__match(nk__dout-(unsigned int)(nk__in3(1)+1), i[4]+1u), i += 5;
+        else if (*i == 0x04)  nk__match(nk__dout-(unsigned int)(nk__in3(1)+1), (unsigned int)nk__in2(4)+1u), i += 6;
+    }
+    return i;
+}
+
+NK_INTERN unsigned int
+nk_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen)
+{
+    const unsigned long ADLER_MOD = 65521;
+    unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16;
+    unsigned long blocklen, i;
+
+    blocklen = buflen % 5552;
+    while (buflen) {
+        for (i=0; i + 7 < blocklen; i += 8) {
+            s1 += buffer[0]; s2 += s1;
+            s1 += buffer[1]; s2 += s1;
+            s1 += buffer[2]; s2 += s1;
+            s1 += buffer[3]; s2 += s1;
+            s1 += buffer[4]; s2 += s1;
+            s1 += buffer[5]; s2 += s1;
+            s1 += buffer[6]; s2 += s1;
+            s1 += buffer[7]; s2 += s1;
+            buffer += 8;
+        }
+        for (; i < blocklen; ++i) {
+            s1 += *buffer++; s2 += s1;
+        }
+
+        s1 %= ADLER_MOD; s2 %= ADLER_MOD;
+        buflen -= (unsigned int)blocklen;
+        blocklen = 5552;
+    }
+    return (unsigned int)(s2 << 16) + (unsigned int)s1;
+}
+
+NK_INTERN unsigned int
+nk_decompress(unsigned char *output, unsigned char *i, unsigned int length)
+{
+    unsigned int olen;
+    if (nk__in4(0) != 0x57bC0000) return 0;
+    if (nk__in4(4) != 0)          return 0; /* error! stream is > 4GB */
+    olen = nk_decompress_length(i);
+    nk__barrier2 = i;
+    nk__barrier3 = i+length;
+    nk__barrier = output + olen;
+    nk__barrier4 = output;
+    i += 16;
+
+    nk__dout = output;
+    for (;;) {
+        unsigned char *old_i = i;
+        i = nk_decompress_token(i);
+        if (i == old_i) {
+            if (*i == 0x05 && i[1] == 0xfa) {
+                NK_ASSERT(nk__dout == output + olen);
+                if (nk__dout != output + olen) return 0;
+                if (nk_adler32(1, output, olen) != (unsigned int) nk__in4(2))
+                    return 0;
+                return olen;
+            } else {
+                NK_ASSERT(0); /* NOTREACHED */
+                return 0;
+            }
+        }
+        NK_ASSERT(nk__dout <= output + olen);
+        if (nk__dout > output + olen)
+            return 0;
+    }
+}
+
+NK_INTERN unsigned int
+nk_decode_85_byte(char c)
+{ return (unsigned int)((c >= '\\') ? c-36 : c-35); }
+
+NK_INTERN void
+nk_decode_85(unsigned char* dst, const unsigned char* src)
+{
+    while (*src)
+    {
+        unsigned int tmp =
+            nk_decode_85_byte((char)src[0]) +
+            85 * (nk_decode_85_byte((char)src[1]) +
+            85 * (nk_decode_85_byte((char)src[2]) +
+            85 * (nk_decode_85_byte((char)src[3]) +
+            85 * nk_decode_85_byte((char)src[4]))));
+
+        /* we can't assume little-endianess. */
+        dst[0] = (unsigned char)((tmp >> 0) & 0xFF);
+        dst[1] = (unsigned char)((tmp >> 8) & 0xFF);
+        dst[2] = (unsigned char)((tmp >> 16) & 0xFF);
+        dst[3] = (unsigned char)((tmp >> 24) & 0xFF);
+
+        src += 5;
+        dst += 4;
+    }
+}
+
+/* -------------------------------------------------------------
+ *
+ *                          FONT ATLAS
+ *
+ * --------------------------------------------------------------*/
+NK_API struct nk_font_config
+nk_font_config(float pixel_height)
+{
+    struct nk_font_config cfg;
+    nk_zero_struct(cfg);
+    cfg.ttf_blob = 0;
+    cfg.ttf_size = 0;
+    cfg.ttf_data_owned_by_atlas = 0;
+    cfg.size = pixel_height;
+    cfg.oversample_h = 3;
+    cfg.oversample_v = 1;
+    cfg.pixel_snap = 0;
+    cfg.coord_type = NK_COORD_UV;
+    cfg.spacing = nk_vec2(0,0);
+    cfg.range = nk_font_default_glyph_ranges();
+    cfg.merge_mode = 0;
+    cfg.fallback_glyph = '?';
+    cfg.font = 0;
+    return cfg;
+}
+
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+NK_API void
+nk_font_atlas_init_default(struct nk_font_atlas *atlas)
+{
+    NK_ASSERT(atlas);
+    if (!atlas) return;
+    nk_zero_struct(*atlas);
+    atlas->temporary.userdata.ptr = 0;
+    atlas->temporary.alloc = nk_malloc;
+    atlas->temporary.free = nk_mfree;
+    atlas->permanent.userdata.ptr = 0;
+    atlas->permanent.alloc = nk_malloc;
+    atlas->permanent.free = nk_mfree;
+}
+#endif
+
+NK_API void
+nk_font_atlas_init(struct nk_font_atlas *atlas, struct nk_allocator *alloc)
+{
+    NK_ASSERT(atlas);
+    NK_ASSERT(alloc);
+    if (!atlas || !alloc) return;
+    nk_zero_struct(*atlas);
+    atlas->permanent = *alloc;
+    atlas->temporary = *alloc;
+}
+
+NK_API void
+nk_font_atlas_init_custom(struct nk_font_atlas *atlas,
+    struct nk_allocator *permanent, struct nk_allocator *temporary)
+{
+    NK_ASSERT(atlas);
+    NK_ASSERT(permanent);
+    NK_ASSERT(temporary);
+    if (!atlas || !permanent || !temporary) return;
+    nk_zero_struct(*atlas);
+    atlas->permanent = *permanent;
+    atlas->temporary = *temporary;
+}
+
+NK_API void
+nk_font_atlas_begin(struct nk_font_atlas *atlas)
+{
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->temporary.alloc && atlas->temporary.free);
+    NK_ASSERT(atlas->permanent.alloc && atlas->permanent.free);
+    if (!atlas || !atlas->permanent.alloc || !atlas->permanent.free ||
+        !atlas->temporary.alloc || !atlas->temporary.free) return;
+    if (atlas->glyphs) {
+        atlas->permanent.free(atlas->permanent.userdata, atlas->glyphs);
+        atlas->glyphs = 0;
+    }
+    if (atlas->pixel) {
+        atlas->permanent.free(atlas->permanent.userdata, atlas->pixel);
+        atlas->pixel = 0;
+    }
+}
+
+NK_API struct nk_font*
+nk_font_atlas_add(struct nk_font_atlas *atlas, const struct nk_font_config *config)
+{
+    struct nk_font *font = 0;
+    struct nk_font_config *cfg;
+
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->permanent.alloc);
+    NK_ASSERT(atlas->permanent.free);
+    NK_ASSERT(atlas->temporary.alloc);
+    NK_ASSERT(atlas->temporary.free);
+
+    NK_ASSERT(config);
+    NK_ASSERT(config->ttf_blob);
+    NK_ASSERT(config->ttf_size);
+    NK_ASSERT(config->size > 0.0f);
+
+    if (!atlas || !config || !config->ttf_blob || !config->ttf_size || config->size <= 0.0f||
+        !atlas->permanent.alloc || !atlas->permanent.free ||
+        !atlas->temporary.alloc || !atlas->temporary.free)
+        return 0;
+
+    /* allocate and insert font config into list */
+    cfg = (struct nk_font_config*)
+        atlas->permanent.alloc(atlas->permanent.userdata,0, sizeof(struct nk_font_config));
+    NK_MEMCPY(cfg, config, sizeof(*config));
+    if (!atlas->config) {
+        atlas->config = cfg;
+        cfg->next = 0;
+    } else {
+        cfg->next = atlas->config;
+        atlas->config = cfg;
+    }
+
+    /* allocate new font */
+    if (!config->merge_mode) {
+        font = (struct nk_font*)
+            atlas->permanent.alloc(atlas->permanent.userdata,0, sizeof(struct nk_font));
+        NK_ASSERT(font);
+        if (!font) return 0;
+        font->config = cfg;
+    } else {
+        NK_ASSERT(atlas->font_num);
+        font = atlas->fonts;
+        font->config = cfg;
+    }
+
+    /* insert font into list */
+    if (!config->merge_mode) {
+        if (!atlas->fonts) {
+            atlas->fonts = font;
+            font->next = 0;
+        } else {
+            font->next = atlas->fonts;
+            atlas->fonts = font;
+        }
+        cfg->font = &font->info;
+    }
+
+    /* create own copy of .TTF font blob */
+    if (!config->ttf_data_owned_by_atlas) {
+        cfg->ttf_blob = atlas->permanent.alloc(atlas->permanent.userdata,0, cfg->ttf_size);
+        NK_ASSERT(cfg->ttf_blob);
+        if (!cfg->ttf_blob) {
+            atlas->font_num++;
+            return 0;
+        }
+        NK_MEMCPY(cfg->ttf_blob, config->ttf_blob, cfg->ttf_size);
+        cfg->ttf_data_owned_by_atlas = 1;
+    }
+    atlas->font_num++;
+    return font;
+}
+
+NK_API struct nk_font*
+nk_font_atlas_add_from_memory(struct nk_font_atlas *atlas, void *memory,
+    nk_size size, float height, const struct nk_font_config *config)
+{
+    struct nk_font_config cfg;
+    NK_ASSERT(memory);
+    NK_ASSERT(size);
+
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->temporary.alloc);
+    NK_ASSERT(atlas->temporary.free);
+    NK_ASSERT(atlas->permanent.alloc);
+    NK_ASSERT(atlas->permanent.free);
+    if (!atlas || !atlas->temporary.alloc || !atlas->temporary.free || !memory || !size ||
+        !atlas->permanent.alloc || !atlas->permanent.free)
+        return 0;
+
+    cfg = (config) ? *config: nk_font_config(height);
+    cfg.ttf_blob = memory;
+    cfg.ttf_size = size;
+    cfg.size = height;
+    cfg.ttf_data_owned_by_atlas = 0;
+    return nk_font_atlas_add(atlas, &cfg);
+}
+
+#ifdef NK_INCLUDE_STANDARD_IO
+NK_API struct nk_font*
+nk_font_atlas_add_from_file(struct nk_font_atlas *atlas, const char *file_path,
+    float height, const struct nk_font_config *config)
+{
+    nk_size size;
+    char *memory;
+    struct nk_font_config cfg;
+
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->temporary.alloc);
+    NK_ASSERT(atlas->temporary.free);
+    NK_ASSERT(atlas->permanent.alloc);
+    NK_ASSERT(atlas->permanent.free);
+
+    if (!atlas || !file_path) return 0;
+    memory = nk_file_load(file_path, &size, &atlas->permanent);
+    if (!memory) return 0;
+
+    cfg = (config) ? *config: nk_font_config(height);
+    cfg.ttf_blob = memory;
+    cfg.ttf_size = size;
+    cfg.size = height;
+    cfg.ttf_data_owned_by_atlas = 1;
+    return nk_font_atlas_add(atlas, &cfg);
+}
+#endif
+
+NK_API struct nk_font*
+nk_font_atlas_add_compressed(struct nk_font_atlas *atlas,
+    void *compressed_data, nk_size compressed_size, float height,
+    const struct nk_font_config *config)
+{
+    unsigned int decompressed_size;
+    void *decompressed_data;
+    struct nk_font_config cfg;
+
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->temporary.alloc);
+    NK_ASSERT(atlas->temporary.free);
+    NK_ASSERT(atlas->permanent.alloc);
+    NK_ASSERT(atlas->permanent.free);
+
+    NK_ASSERT(compressed_data);
+    NK_ASSERT(compressed_size);
+    if (!atlas || !compressed_data || !atlas->temporary.alloc || !atlas->temporary.free ||
+        !atlas->permanent.alloc || !atlas->permanent.free)
+        return 0;
+
+    decompressed_size = nk_decompress_length((unsigned char*)compressed_data);
+    decompressed_data = atlas->permanent.alloc(atlas->permanent.userdata,0,decompressed_size);
+    NK_ASSERT(decompressed_data);
+    if (!decompressed_data) return 0;
+    nk_decompress((unsigned char*)decompressed_data, (unsigned char*)compressed_data,
+        (unsigned int)compressed_size);
+
+    cfg = (config) ? *config: nk_font_config(height);
+    cfg.ttf_blob = decompressed_data;
+    cfg.ttf_size = decompressed_size;
+    cfg.size = height;
+    cfg.ttf_data_owned_by_atlas = 1;
+    return nk_font_atlas_add(atlas, &cfg);
+}
+
+NK_API struct nk_font*
+nk_font_atlas_add_compressed_base85(struct nk_font_atlas *atlas,
+    const char *data_base85, float height, const struct nk_font_config *config)
+{
+    int compressed_size;
+    void *compressed_data;
+    struct nk_font *font;
+
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->temporary.alloc);
+    NK_ASSERT(atlas->temporary.free);
+    NK_ASSERT(atlas->permanent.alloc);
+    NK_ASSERT(atlas->permanent.free);
+
+    NK_ASSERT(data_base85);
+    if (!atlas || !data_base85 || !atlas->temporary.alloc || !atlas->temporary.free ||
+        !atlas->permanent.alloc || !atlas->permanent.free)
+        return 0;
+
+    compressed_size = (((int)nk_strlen(data_base85) + 4) / 5) * 4;
+    compressed_data = atlas->temporary.alloc(atlas->temporary.userdata,0, (nk_size)compressed_size);
+    NK_ASSERT(compressed_data);
+    if (!compressed_data) return 0;
+    nk_decode_85((unsigned char*)compressed_data, (const unsigned char*)data_base85);
+    font = nk_font_atlas_add_compressed(atlas, compressed_data,
+                    (nk_size)compressed_size, height, config);
+    atlas->temporary.free(atlas->temporary.userdata, compressed_data);
+    return font;
+}
+
+#ifdef NK_INCLUDE_DEFAULT_FONT
+NK_API struct nk_font*
+nk_font_atlas_add_default(struct nk_font_atlas *atlas,
+    float pixel_height, const struct nk_font_config *config)
+{
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->temporary.alloc);
+    NK_ASSERT(atlas->temporary.free);
+    NK_ASSERT(atlas->permanent.alloc);
+    NK_ASSERT(atlas->permanent.free);
+    return nk_font_atlas_add_compressed_base85(atlas,
+        nk_proggy_clean_ttf_compressed_data_base85, pixel_height, config);
+}
+#endif
+
+NK_API const void*
+nk_font_atlas_bake(struct nk_font_atlas *atlas, int *width, int *height,
+    enum nk_font_atlas_format fmt)
+{
+    int i = 0;
+    void *tmp = 0;
+    nk_size tmp_size, img_size;
+    struct nk_font *font_iter;
+    struct nk_font_baker *baker;
+
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->temporary.alloc);
+    NK_ASSERT(atlas->temporary.free);
+    NK_ASSERT(atlas->permanent.alloc);
+    NK_ASSERT(atlas->permanent.free);
+
+    NK_ASSERT(width);
+    NK_ASSERT(height);
+    if (!atlas || !width || !height ||
+        !atlas->temporary.alloc || !atlas->temporary.free ||
+        !atlas->permanent.alloc || !atlas->permanent.free)
+        return 0;
+
+#ifdef NK_INCLUDE_DEFAULT_FONT
+    /* no font added so just use default font */
+    if (!atlas->font_num)
+        atlas->default_font = nk_font_atlas_add_default(atlas, 13.0f, 0);
+#endif
+    NK_ASSERT(atlas->font_num);
+    if (!atlas->font_num) return 0;
+
+    /* allocate temporary baker memory required for the baking process */
+    nk_font_baker_memory(&tmp_size, &atlas->glyph_count, atlas->config, atlas->font_num);
+    tmp = atlas->temporary.alloc(atlas->temporary.userdata,0, tmp_size);
+    NK_ASSERT(tmp);
+    if (!tmp) goto failed;
+
+    /* allocate glyph memory for all fonts */
+    baker = nk_font_baker(tmp, atlas->glyph_count, atlas->font_num, &atlas->temporary);
+    atlas->glyphs = (struct nk_font_glyph*)atlas->permanent.alloc(
+        atlas->permanent.userdata,0, sizeof(struct nk_font_glyph)*(nk_size)atlas->glyph_count);
+    NK_ASSERT(atlas->glyphs);
+    if (!atlas->glyphs)
+        goto failed;
+
+    /* pack all glyphs into a tight fit space */
+    atlas->custom.w = (NK_CURSOR_DATA_W*2)+1;
+    atlas->custom.h = NK_CURSOR_DATA_H + 1;
+    if (!nk_font_bake_pack(baker, &img_size, width, height, &atlas->custom,
+        atlas->config, atlas->font_num, &atlas->temporary))
+        goto failed;
+
+    /* allocate memory for the baked image font atlas */
+    atlas->pixel = atlas->temporary.alloc(atlas->temporary.userdata,0, img_size);
+    NK_ASSERT(atlas->pixel);
+    if (!atlas->pixel)
+        goto failed;
+
+    /* bake glyphs and custom white pixel into image */
+    nk_font_bake(baker, atlas->pixel, *width, *height,
+        atlas->glyphs, atlas->glyph_count, atlas->config, atlas->font_num);
+    nk_font_bake_custom_data(atlas->pixel, *width, *height, atlas->custom,
+            nk_custom_cursor_data, NK_CURSOR_DATA_W, NK_CURSOR_DATA_H, '.', 'X');
+
+    if (fmt == NK_FONT_ATLAS_RGBA32) {
+        /* convert alpha8 image into rgba32 image */
+        void *img_rgba = atlas->temporary.alloc(atlas->temporary.userdata,0,
+                            (nk_size)(*width * *height * 4));
+        NK_ASSERT(img_rgba);
+        if (!img_rgba) goto failed;
+        nk_font_bake_convert(img_rgba, *width, *height, atlas->pixel);
+        atlas->temporary.free(atlas->temporary.userdata, atlas->pixel);
+        atlas->pixel = img_rgba;
+    }
+    atlas->tex_width = *width;
+    atlas->tex_height = *height;
+
+    /* initialize each font */
+    for (font_iter = atlas->fonts; font_iter; font_iter = font_iter->next) {
+        struct nk_font *font = font_iter;
+        struct nk_font_config *config = font->config;
+        nk_font_init(font, config->size, config->fallback_glyph, atlas->glyphs,
+            config->font, nk_handle_ptr(0));
+    }
+
+    /* initialize each cursor */
+    {NK_STORAGE const struct nk_vec2 nk_cursor_data[NK_CURSOR_COUNT][3] = {
+        /* Pos ----- Size ------- Offset --*/
+        {{ 0, 3},   {12,19},    { 0, 0}},
+        {{13, 0},   { 7,16},    { 4, 8}},
+        {{31, 0},   {23,23},    {11,11}},
+        {{21, 0},   { 9, 23},   { 5,11}},
+        {{55,18},   {23, 9},    {11, 5}},
+        {{73, 0},   {17,17},    { 9, 9}},
+        {{55, 0},   {17,17},    { 9, 9}}
+    };
+    for (i = 0; i < NK_CURSOR_COUNT; ++i) {
+        struct nk_cursor *cursor = &atlas->cursors[i];
+        cursor->img.w = (unsigned short)*width;
+        cursor->img.h = (unsigned short)*height;
+        cursor->img.region[0] = (unsigned short)(atlas->custom.x + nk_cursor_data[i][0].x);
+        cursor->img.region[1] = (unsigned short)(atlas->custom.y + nk_cursor_data[i][0].y);
+        cursor->img.region[2] = (unsigned short)nk_cursor_data[i][1].x;
+        cursor->img.region[3] = (unsigned short)nk_cursor_data[i][1].y;
+        cursor->size = nk_cursor_data[i][1];
+        cursor->offset = nk_cursor_data[i][2];
+    }}
+    /* free temporary memory */
+    atlas->temporary.free(atlas->temporary.userdata, tmp);
+    return atlas->pixel;
+
+failed:
+    /* error so cleanup all memory */
+    if (tmp) atlas->temporary.free(atlas->temporary.userdata, tmp);
+    if (atlas->glyphs) {
+        atlas->permanent.free(atlas->permanent.userdata, atlas->glyphs);
+        atlas->glyphs = 0;
+    }
+    if (atlas->pixel) {
+        atlas->temporary.free(atlas->temporary.userdata, atlas->pixel);
+        atlas->pixel = 0;
+    }
+    return 0;
+}
+
+NK_API void
+nk_font_atlas_end(struct nk_font_atlas *atlas, nk_handle texture,
+    struct nk_draw_null_texture *null)
+{
+    int i = 0;
+    struct nk_font *font_iter;
+    NK_ASSERT(atlas);
+    if (!atlas) {
+        if (!null) return;
+        null->texture = texture;
+        null->uv = nk_vec2(0.5f,0.5f);
+    }
+    if (null) {
+        null->texture = texture;
+        null->uv.x = (atlas->custom.x + 0.5f)/(float)atlas->tex_width;
+        null->uv.y = (atlas->custom.y + 0.5f)/(float)atlas->tex_height;
+    }
+    for (font_iter = atlas->fonts; font_iter; font_iter = font_iter->next) {
+        font_iter->texture = texture;
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+        font_iter->handle.texture = texture;
+#endif
+    }
+    for (i = 0; i < NK_CURSOR_COUNT; ++i)
+        atlas->cursors[i].img.handle = texture;
+
+    atlas->temporary.free(atlas->temporary.userdata, atlas->pixel);
+    atlas->pixel = 0;
+    atlas->tex_width = 0;
+    atlas->tex_height = 0;
+    atlas->custom.x = 0;
+    atlas->custom.y = 0;
+    atlas->custom.w = 0;
+    atlas->custom.h = 0;
+}
+
+NK_API void
+nk_font_atlas_cleanup(struct nk_font_atlas *atlas)
+{
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->temporary.alloc);
+    NK_ASSERT(atlas->temporary.free);
+    NK_ASSERT(atlas->permanent.alloc);
+    NK_ASSERT(atlas->permanent.free);
+
+    if (!atlas || !atlas->permanent.alloc || !atlas->permanent.free) return;
+    if (atlas->config) {
+        struct nk_font_config *iter, *next;
+        for (iter = atlas->config; iter; iter = next) {
+            next = iter->next;
+            atlas->permanent.free(atlas->permanent.userdata, iter->ttf_blob);
+            atlas->permanent.free(atlas->permanent.userdata, iter);
+        }
+        atlas->config = 0;
+    }
+}
+
+NK_API void
+nk_font_atlas_clear(struct nk_font_atlas *atlas)
+{
+    NK_ASSERT(atlas);
+    NK_ASSERT(atlas->temporary.alloc);
+    NK_ASSERT(atlas->temporary.free);
+    NK_ASSERT(atlas->permanent.alloc);
+    NK_ASSERT(atlas->permanent.free);
+    if (!atlas || !atlas->permanent.alloc || !atlas->permanent.free) return;
+
+    nk_font_atlas_cleanup(atlas);
+    if (atlas->fonts) {
+        struct nk_font *iter, *next;
+        for (iter = atlas->fonts; iter; iter = next) {
+            next = iter->next;
+            atlas->permanent.free(atlas->permanent.userdata, iter);
+        }
+        atlas->fonts = 0;
+    }
+    if (atlas->glyphs)
+        atlas->permanent.free(atlas->permanent.userdata, atlas->glyphs);
+    nk_zero_struct(*atlas);
+}
+#endif
+/* ==============================================================
+ *
+ *                          INPUT
+ *
+ * ===============================================================*/
+NK_API void
+nk_input_begin(struct nk_context *ctx)
+{
+    int i;
+    struct nk_input *in;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    in = &ctx->input;
+    for (i = 0; i < NK_BUTTON_MAX; ++i)
+        in->mouse.buttons[i].clicked = 0;
+
+    in->keyboard.text_len = 0;
+    in->mouse.scroll_delta = nk_vec2(0,0);
+    in->mouse.prev.x = in->mouse.pos.x;
+    in->mouse.prev.y = in->mouse.pos.y;
+    in->mouse.delta.x = 0;
+    in->mouse.delta.y = 0;
+    for (i = 0; i < NK_KEY_MAX; i++)
+        in->keyboard.keys[i].clicked = 0;
+}
+
+NK_API void
+nk_input_end(struct nk_context *ctx)
+{
+    struct nk_input *in;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    in = &ctx->input;
+    if (in->mouse.grab)
+        in->mouse.grab = 0;
+    if (in->mouse.ungrab) {
+        in->mouse.grabbed = 0;
+        in->mouse.ungrab = 0;
+        in->mouse.grab = 0;
+    }
+}
+
+NK_API void
+nk_input_motion(struct nk_context *ctx, int x, int y)
+{
+    struct nk_input *in;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    in = &ctx->input;
+    in->mouse.pos.x = (float)x;
+    in->mouse.pos.y = (float)y;
+    in->mouse.delta.x = in->mouse.pos.x - in->mouse.prev.x;
+    in->mouse.delta.y = in->mouse.pos.y - in->mouse.prev.y;
+}
+
+NK_API void
+nk_input_key(struct nk_context *ctx, enum nk_keys key, int down)
+{
+    struct nk_input *in;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    in = &ctx->input;
+    if (in->keyboard.keys[key].down != down)
+        in->keyboard.keys[key].clicked++;
+    in->keyboard.keys[key].down = down;
+}
+
+NK_API void
+nk_input_button(struct nk_context *ctx, enum nk_buttons id, int x, int y, int down)
+{
+    struct nk_mouse_button *btn;
+    struct nk_input *in;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    in = &ctx->input;
+    if (in->mouse.buttons[id].down == down) return;
+
+    btn = &in->mouse.buttons[id];
+    btn->clicked_pos.x = (float)x;
+    btn->clicked_pos.y = (float)y;
+    btn->down = down;
+    btn->clicked++;
+}
+
+NK_API void
+nk_input_scroll(struct nk_context *ctx, struct nk_vec2 val)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    ctx->input.mouse.scroll_delta.x += val.x;
+    ctx->input.mouse.scroll_delta.y += val.y;
+}
+
+NK_API void
+nk_input_glyph(struct nk_context *ctx, const nk_glyph glyph)
+{
+    int len = 0;
+    nk_rune unicode;
+    struct nk_input *in;
+
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    in = &ctx->input;
+
+    len = nk_utf_decode(glyph, &unicode, NK_UTF_SIZE);
+    if (len && ((in->keyboard.text_len + len) < NK_INPUT_MAX)) {
+        nk_utf_encode(unicode, &in->keyboard.text[in->keyboard.text_len],
+            NK_INPUT_MAX - in->keyboard.text_len);
+        in->keyboard.text_len += len;
+    }
+}
+
+NK_API void
+nk_input_char(struct nk_context *ctx, char c)
+{
+    nk_glyph glyph;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    glyph[0] = c;
+    nk_input_glyph(ctx, glyph);
+}
+
+NK_API void
+nk_input_unicode(struct nk_context *ctx, nk_rune unicode)
+{
+    nk_glyph rune;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    nk_utf_encode(unicode, rune, NK_UTF_SIZE);
+    nk_input_glyph(ctx, rune);
+}
+
+NK_API int
+nk_input_has_mouse_click(const struct nk_input *i, enum nk_buttons id)
+{
+    const struct nk_mouse_button *btn;
+    if (!i) return nk_false;
+    btn = &i->mouse.buttons[id];
+    return (btn->clicked && btn->down == nk_false) ? nk_true : nk_false;
+}
+
+NK_API int
+nk_input_has_mouse_click_in_rect(const struct nk_input *i, enum nk_buttons id,
+    struct nk_rect b)
+{
+    const struct nk_mouse_button *btn;
+    if (!i) return nk_false;
+    btn = &i->mouse.buttons[id];
+    if (!NK_INBOX(btn->clicked_pos.x,btn->clicked_pos.y,b.x,b.y,b.w,b.h))
+        return nk_false;
+    return nk_true;
+}
+
+NK_API int
+nk_input_has_mouse_click_down_in_rect(const struct nk_input *i, enum nk_buttons id,
+    struct nk_rect b, int down)
+{
+    const struct nk_mouse_button *btn;
+    if (!i) return nk_false;
+    btn = &i->mouse.buttons[id];
+    return nk_input_has_mouse_click_in_rect(i, id, b) && (btn->down == down);
+}
+
+NK_API int
+nk_input_is_mouse_click_in_rect(const struct nk_input *i, enum nk_buttons id,
+    struct nk_rect b)
+{
+    const struct nk_mouse_button *btn;
+    if (!i) return nk_false;
+    btn = &i->mouse.buttons[id];
+    return (nk_input_has_mouse_click_down_in_rect(i, id, b, nk_false) &&
+            btn->clicked) ? nk_true : nk_false;
+}
+
+NK_API int
+nk_input_is_mouse_click_down_in_rect(const struct nk_input *i, enum nk_buttons id,
+    struct nk_rect b, int down)
+{
+    const struct nk_mouse_button *btn;
+    if (!i) return nk_false;
+    btn = &i->mouse.buttons[id];
+    return (nk_input_has_mouse_click_down_in_rect(i, id, b, down) &&
+            btn->clicked) ? nk_true : nk_false;
+}
+
+NK_API int
+nk_input_any_mouse_click_in_rect(const struct nk_input *in, struct nk_rect b)
+{
+    int i, down = 0;
+    for (i = 0; i < NK_BUTTON_MAX; ++i)
+        down = down || nk_input_is_mouse_click_in_rect(in, (enum nk_buttons)i, b);
+    return down;
+}
+
+NK_API int
+nk_input_is_mouse_hovering_rect(const struct nk_input *i, struct nk_rect rect)
+{
+    if (!i) return nk_false;
+    return NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h);
+}
+
+NK_API int
+nk_input_is_mouse_prev_hovering_rect(const struct nk_input *i, struct nk_rect rect)
+{
+    if (!i) return nk_false;
+    return NK_INBOX(i->mouse.prev.x, i->mouse.prev.y, rect.x, rect.y, rect.w, rect.h);
+}
+
+NK_API int
+nk_input_mouse_clicked(const struct nk_input *i, enum nk_buttons id, struct nk_rect rect)
+{
+    if (!i) return nk_false;
+    if (!nk_input_is_mouse_hovering_rect(i, rect)) return nk_false;
+    return nk_input_is_mouse_click_in_rect(i, id, rect);
+}
+
+NK_API int
+nk_input_is_mouse_down(const struct nk_input *i, enum nk_buttons id)
+{
+    if (!i) return nk_false;
+    return i->mouse.buttons[id].down;
+}
+
+NK_API int
+nk_input_is_mouse_pressed(const struct nk_input *i, enum nk_buttons id)
+{
+    const struct nk_mouse_button *b;
+    if (!i) return nk_false;
+    b = &i->mouse.buttons[id];
+    if (b->down && b->clicked)
+        return nk_true;
+    return nk_false;
+}
+
+NK_API int
+nk_input_is_mouse_released(const struct nk_input *i, enum nk_buttons id)
+{
+    if (!i) return nk_false;
+    return (!i->mouse.buttons[id].down && i->mouse.buttons[id].clicked);
+}
+
+NK_API int
+nk_input_is_key_pressed(const struct nk_input *i, enum nk_keys key)
+{
+    const struct nk_key *k;
+    if (!i) return nk_false;
+    k = &i->keyboard.keys[key];
+    if ((k->down && k->clicked) || (!k->down && k->clicked >= 2))
+        return nk_true;
+    return nk_false;
+}
+
+NK_API int
+nk_input_is_key_released(const struct nk_input *i, enum nk_keys key)
+{
+    const struct nk_key *k;
+    if (!i) return nk_false;
+    k = &i->keyboard.keys[key];
+    if ((!k->down && k->clicked) || (k->down && k->clicked >= 2))
+        return nk_true;
+    return nk_false;
+}
+
+NK_API int
+nk_input_is_key_down(const struct nk_input *i, enum nk_keys key)
+{
+    const struct nk_key *k;
+    if (!i) return nk_false;
+    k = &i->keyboard.keys[key];
+    if (k->down) return nk_true;
+    return nk_false;
+}
+
+/*
+ * ==============================================================
+ *
+ *                          TEXT EDITOR
+ *
+ * ===============================================================
+ */
+/* stb_textedit.h - v1.8  - public domain - Sean Barrett */
+struct nk_text_find {
+   float x,y;    /* position of n'th character */
+   float height; /* height of line */
+   int first_char, length; /* first char of row, and length */
+   int prev_first;  /*_ first char of previous row */
+};
+
+struct nk_text_edit_row {
+   float x0,x1;
+   /* starting x location, end x location (allows for align=right, etc) */
+   float baseline_y_delta;
+   /* position of baseline relative to previous row's baseline*/
+   float ymin,ymax;
+   /* height of row above and below baseline */
+   int num_chars;
+};
+
+/* forward declarations */
+NK_INTERN void nk_textedit_makeundo_delete(struct nk_text_edit*, int, int);
+NK_INTERN void nk_textedit_makeundo_insert(struct nk_text_edit*, int, int);
+NK_INTERN void nk_textedit_makeundo_replace(struct nk_text_edit*, int, int, int);
+#define NK_TEXT_HAS_SELECTION(s)   ((s)->select_start != (s)->select_end)
+
+NK_INTERN float
+nk_textedit_get_width(const struct nk_text_edit *edit, int line_start, int char_id,
+    const struct nk_user_font *font)
+{
+    int len = 0;
+    nk_rune unicode = 0;
+    const char *str = nk_str_at_const(&edit->string, line_start + char_id, &unicode, &len);
+    return font->width(font->userdata, font->height, str, len);
+}
+
+NK_INTERN void
+nk_textedit_layout_row(struct nk_text_edit_row *r, struct nk_text_edit *edit,
+    int line_start_id, float row_height, const struct nk_user_font *font)
+{
+    int l;
+    int glyphs = 0;
+    nk_rune unicode;
+    const char *remaining;
+    int len = nk_str_len_char(&edit->string);
+    const char *end = nk_str_get_const(&edit->string) + len;
+    const char *text = nk_str_at_const(&edit->string, line_start_id, &unicode, &l);
+    const struct nk_vec2 size = nk_text_calculate_text_bounds(font,
+        text, (int)(end - text), row_height, &remaining, 0, &glyphs, NK_STOP_ON_NEW_LINE);
+
+    r->x0 = 0.0f;
+    r->x1 = size.x;
+    r->baseline_y_delta = size.y;
+    r->ymin = 0.0f;
+    r->ymax = size.y;
+    r->num_chars = glyphs;
+}
+
+NK_INTERN int
+nk_textedit_locate_coord(struct nk_text_edit *edit, float x, float y,
+    const struct nk_user_font *font, float row_height)
+{
+    struct nk_text_edit_row r;
+    int n = edit->string.len;
+    float base_y = 0, prev_x;
+    int i=0, k;
+
+    r.x0 = r.x1 = 0;
+    r.ymin = r.ymax = 0;
+    r.num_chars = 0;
+
+    /* search rows to find one that straddles 'y' */
+    while (i < n) {
+        nk_textedit_layout_row(&r, edit, i, row_height, font);
+        if (r.num_chars <= 0)
+            return n;
+
+        if (i==0 && y < base_y + r.ymin)
+            return 0;
+
+        if (y < base_y + r.ymax)
+            break;
+
+        i += r.num_chars;
+        base_y += r.baseline_y_delta;
+    }
+
+    /* below all text, return 'after' last character */
+    if (i >= n)
+        return n;
+
+    /* check if it's before the beginning of the line */
+    if (x < r.x0)
+        return i;
+
+    /* check if it's before the end of the line */
+    if (x < r.x1) {
+        /* search characters in row for one that straddles 'x' */
+        k = i;
+        prev_x = r.x0;
+        for (i=0; i < r.num_chars; ++i) {
+            float w = nk_textedit_get_width(edit, k, i, font);
+            if (x < prev_x+w) {
+                if (x < prev_x+w/2)
+                    return k+i;
+                else return k+i+1;
+            }
+            prev_x += w;
+        }
+        /* shouldn't happen, but if it does, fall through to end-of-line case */
+    }
+
+    /* if the last character is a newline, return that.
+     * otherwise return 'after' the last character */
+    if (nk_str_rune_at(&edit->string, i+r.num_chars-1) == '\n')
+        return i+r.num_chars-1;
+    else return i+r.num_chars;
+}
+
+NK_INTERN void
+nk_textedit_click(struct nk_text_edit *state, float x, float y,
+    const struct nk_user_font *font, float row_height)
+{
+    /* API click: on mouse down, move the cursor to the clicked location,
+     * and reset the selection */
+    state->cursor = nk_textedit_locate_coord(state, x, y, font, row_height);
+    state->select_start = state->cursor;
+    state->select_end = state->cursor;
+    state->has_preferred_x = 0;
+}
+
+NK_INTERN void
+nk_textedit_drag(struct nk_text_edit *state, float x, float y,
+    const struct nk_user_font *font, float row_height)
+{
+    /* API drag: on mouse drag, move the cursor and selection endpoint
+     * to the clicked location */
+    int p = nk_textedit_locate_coord(state, x, y, font, row_height);
+    if (state->select_start == state->select_end)
+        state->select_start = state->cursor;
+    state->cursor = state->select_end = p;
+}
+
+NK_INTERN void
+nk_textedit_find_charpos(struct nk_text_find *find, struct nk_text_edit *state,
+    int n, int single_line, const struct nk_user_font *font, float row_height)
+{
+    /* find the x/y location of a character, and remember info about the previous
+     * row in case we get a move-up event (for page up, we'll have to rescan) */
+    struct nk_text_edit_row r;
+    int prev_start = 0;
+    int z = state->string.len;
+    int i=0, first;
+
+    nk_zero_struct(r);
+    if (n == z) {
+        /* if it's at the end, then find the last line -- simpler than trying to
+        explicitly handle this case in the regular code */
+        nk_textedit_layout_row(&r, state, 0, row_height, font);
+        if (single_line) {
+            find->first_char = 0;
+            find->length = z;
+        } else {
+            while (i < z) {
+                prev_start = i;
+                i += r.num_chars;
+                nk_textedit_layout_row(&r, state, i, row_height, font);
+            }
+
+            find->first_char = i;
+            find->length = r.num_chars;
+        }
+        find->x = r.x1;
+        find->y = r.ymin;
+        find->height = r.ymax - r.ymin;
+        find->prev_first = prev_start;
+        return;
+    }
+
+    /* search rows to find the one that straddles character n */
+    find->y = 0;
+
+    for(;;) {
+        nk_textedit_layout_row(&r, state, i, row_height, font);
+        if (n < i + r.num_chars) break;
+        prev_start = i;
+        i += r.num_chars;
+        find->y += r.baseline_y_delta;
+    }
+
+    find->first_char = first = i;
+    find->length = r.num_chars;
+    find->height = r.ymax - r.ymin;
+    find->prev_first = prev_start;
+
+    /* now scan to find xpos */
+    find->x = r.x0;
+    for (i=0; first+i < n; ++i)
+        find->x += nk_textedit_get_width(state, first, i, font);
+}
+
+NK_INTERN void
+nk_textedit_clamp(struct nk_text_edit *state)
+{
+    /* make the selection/cursor state valid if client altered the string */
+    int n = state->string.len;
+    if (NK_TEXT_HAS_SELECTION(state)) {
+        if (state->select_start > n) state->select_start = n;
+        if (state->select_end   > n) state->select_end = n;
+        /* if clamping forced them to be equal, move the cursor to match */
+        if (state->select_start == state->select_end)
+            state->cursor = state->select_start;
+    }
+    if (state->cursor > n) state->cursor = n;
+}
+
+NK_API void
+nk_textedit_delete(struct nk_text_edit *state, int where, int len)
+{
+    /* delete characters while updating undo */
+    nk_textedit_makeundo_delete(state, where, len);
+    nk_str_delete_runes(&state->string, where, len);
+    state->has_preferred_x = 0;
+}
+
+NK_API void
+nk_textedit_delete_selection(struct nk_text_edit *state)
+{
+    /* delete the section */
+    nk_textedit_clamp(state);
+    if (NK_TEXT_HAS_SELECTION(state)) {
+        if (state->select_start < state->select_end) {
+            nk_textedit_delete(state, state->select_start,
+                state->select_end - state->select_start);
+            state->select_end = state->cursor = state->select_start;
+        } else {
+            nk_textedit_delete(state, state->select_end,
+                state->select_start - state->select_end);
+            state->select_start = state->cursor = state->select_end;
+        }
+        state->has_preferred_x = 0;
+    }
+}
+
+NK_INTERN void
+nk_textedit_sortselection(struct nk_text_edit *state)
+{
+    /* canonicalize the selection so start <= end */
+    if (state->select_end < state->select_start) {
+        int temp = state->select_end;
+        state->select_end = state->select_start;
+        state->select_start = temp;
+    }
+}
+
+NK_INTERN void
+nk_textedit_move_to_first(struct nk_text_edit *state)
+{
+    /* move cursor to first character of selection */
+    if (NK_TEXT_HAS_SELECTION(state)) {
+        nk_textedit_sortselection(state);
+        state->cursor = state->select_start;
+        state->select_end = state->select_start;
+        state->has_preferred_x = 0;
+    }
+}
+
+NK_INTERN void
+nk_textedit_move_to_last(struct nk_text_edit *state)
+{
+    /* move cursor to last character of selection */
+    if (NK_TEXT_HAS_SELECTION(state)) {
+        nk_textedit_sortselection(state);
+        nk_textedit_clamp(state);
+        state->cursor = state->select_end;
+        state->select_start = state->select_end;
+        state->has_preferred_x = 0;
+    }
+}
+
+NK_INTERN int
+nk_is_word_boundary( struct nk_text_edit *state, int idx)
+{
+    int len;
+    nk_rune c;
+    if (idx <= 0) return 1;
+    if (!nk_str_at_rune(&state->string, idx, &c, &len)) return 1;
+    return (c == ' ' || c == '\t' ||c == 0x3000 || c == ',' || c == ';' ||
+            c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' ||
+            c == '|');
+}
+
+NK_INTERN int
+nk_textedit_move_to_word_previous(struct nk_text_edit *state)
+{
+   int c = state->cursor - 1;
+   while( c >= 0 && !nk_is_word_boundary(state, c))
+      --c;
+
+   if( c < 0 )
+      c = 0;
+
+   return c;
+}
+
+NK_INTERN int
+nk_textedit_move_to_word_next(struct nk_text_edit *state)
+{
+   const int len = state->string.len;
+   int c = state->cursor+1;
+   while( c < len && !nk_is_word_boundary(state, c))
+      ++c;
+
+   if( c > len )
+      c = len;
+
+   return c;
+}
+
+NK_INTERN void
+nk_textedit_prep_selection_at_cursor(struct nk_text_edit *state)
+{
+    /* update selection and cursor to match each other */
+    if (!NK_TEXT_HAS_SELECTION(state))
+        state->select_start = state->select_end = state->cursor;
+    else state->cursor = state->select_end;
+}
+
+NK_API int
+nk_textedit_cut(struct nk_text_edit *state)
+{
+    /* API cut: delete selection */
+    if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
+        return 0;
+    if (NK_TEXT_HAS_SELECTION(state)) {
+        nk_textedit_delete_selection(state); /* implicitly clamps */
+        state->has_preferred_x = 0;
+        return 1;
+    }
+   return 0;
+}
+
+NK_API int
+nk_textedit_paste(struct nk_text_edit *state, char const *ctext, int len)
+{
+    /* API paste: replace existing selection with passed-in text */
+    int glyphs;
+    const char *text = (const char *) ctext;
+    if (state->mode == NK_TEXT_EDIT_MODE_VIEW) return 0;
+
+    /* if there's a selection, the paste should delete it */
+    nk_textedit_clamp(state);
+    nk_textedit_delete_selection(state);
+
+    /* try to insert the characters */
+    glyphs = nk_utf_len(ctext, len);
+    if (nk_str_insert_text_char(&state->string, state->cursor, text, len)) {
+        nk_textedit_makeundo_insert(state, state->cursor, glyphs);
+        state->cursor += len;
+        state->has_preferred_x = 0;
+        return 1;
+    }
+    /* remove the undo since we didn't actually insert the characters */
+    if (state->undo.undo_point)
+        --state->undo.undo_point;
+    return 0;
+}
+
+NK_API void
+nk_textedit_text(struct nk_text_edit *state, const char *text, int total_len)
+{
+    nk_rune unicode;
+    int glyph_len;
+    int text_len = 0;
+
+    NK_ASSERT(state);
+    NK_ASSERT(text);
+    if (!text || !total_len || state->mode == NK_TEXT_EDIT_MODE_VIEW) return;
+
+    glyph_len = nk_utf_decode(text, &unicode, total_len);
+    while ((text_len < total_len) && glyph_len)
+    {
+        /* don't insert a backward delete, just process the event */
+        if (unicode == 127) goto next;
+        /* can't add newline in single-line mode */
+        if (unicode == '\n' && state->single_line) goto next;
+        /* filter incoming text */
+        if (state->filter && !state->filter(state, unicode)) goto next;
+
+        if (!NK_TEXT_HAS_SELECTION(state) &&
+            state->cursor < state->string.len)
+        {
+            if (state->mode == NK_TEXT_EDIT_MODE_REPLACE) {
+                nk_textedit_makeundo_replace(state, state->cursor, 1, 1);
+                nk_str_delete_runes(&state->string, state->cursor, 1);
+            }
+            if (nk_str_insert_text_utf8(&state->string, state->cursor,
+                                        text+text_len, 1))
+            {
+                ++state->cursor;
+                state->has_preferred_x = 0;
+            }
+        } else {
+            nk_textedit_delete_selection(state); /* implicitly clamps */
+            if (nk_str_insert_text_utf8(&state->string, state->cursor,
+                                        text+text_len, 1))
+            {
+                nk_textedit_makeundo_insert(state, state->cursor, 1);
+                ++state->cursor;
+                state->has_preferred_x = 0;
+            }
+        }
+        next:
+        text_len += glyph_len;
+        glyph_len = nk_utf_decode(text + text_len, &unicode, total_len-text_len);
+    }
+}
+
+NK_INTERN void
+nk_textedit_key(struct nk_text_edit *state, enum nk_keys key, int shift_mod,
+    const struct nk_user_font *font, float row_height)
+{
+retry:
+    switch (key)
+    {
+    case NK_KEY_NONE:
+    case NK_KEY_CTRL:
+    case NK_KEY_ENTER:
+    case NK_KEY_SHIFT:
+    case NK_KEY_TAB:
+    case NK_KEY_COPY:
+    case NK_KEY_CUT:
+    case NK_KEY_PASTE:
+    case NK_KEY_MAX:
+    default: break;
+    case NK_KEY_TEXT_UNDO:
+         nk_textedit_undo(state);
+         state->has_preferred_x = 0;
+         break;
+
+    case NK_KEY_TEXT_REDO:
+        nk_textedit_redo(state);
+        state->has_preferred_x = 0;
+        break;
+
+    case NK_KEY_TEXT_SELECT_ALL:
+        nk_textedit_select_all(state);
+        state->has_preferred_x = 0;
+        break;
+
+    case NK_KEY_TEXT_INSERT_MODE:
+        if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
+            state->mode = NK_TEXT_EDIT_MODE_INSERT;
+        break;
+    case NK_KEY_TEXT_REPLACE_MODE:
+        if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
+            state->mode = NK_TEXT_EDIT_MODE_REPLACE;
+        break;
+    case NK_KEY_TEXT_RESET_MODE:
+        if (state->mode == NK_TEXT_EDIT_MODE_INSERT ||
+            state->mode == NK_TEXT_EDIT_MODE_REPLACE)
+            state->mode = NK_TEXT_EDIT_MODE_VIEW;
+        break;
+
+    case NK_KEY_LEFT:
+        if (shift_mod) {
+            nk_textedit_clamp(state);
+            nk_textedit_prep_selection_at_cursor(state);
+            /* move selection left */
+            if (state->select_end > 0)
+                --state->select_end;
+            state->cursor = state->select_end;
+            state->has_preferred_x = 0;
+        } else {
+            /* if currently there's a selection,
+             * move cursor to start of selection */
+            if (NK_TEXT_HAS_SELECTION(state))
+                nk_textedit_move_to_first(state);
+            else if (state->cursor > 0)
+               --state->cursor;
+            state->has_preferred_x = 0;
+        } break;
+
+    case NK_KEY_RIGHT:
+        if (shift_mod) {
+            nk_textedit_prep_selection_at_cursor(state);
+            /* move selection right */
+            ++state->select_end;
+            nk_textedit_clamp(state);
+            state->cursor = state->select_end;
+            state->has_preferred_x = 0;
+        } else {
+            /* if currently there's a selection,
+             * move cursor to end of selection */
+            if (NK_TEXT_HAS_SELECTION(state))
+                nk_textedit_move_to_last(state);
+            else ++state->cursor;
+            nk_textedit_clamp(state);
+            state->has_preferred_x = 0;
+        } break;
+
+    case NK_KEY_TEXT_WORD_LEFT:
+        if (shift_mod) {
+            if( !NK_TEXT_HAS_SELECTION( state ) )
+            nk_textedit_prep_selection_at_cursor(state);
+            state->cursor = nk_textedit_move_to_word_previous(state);
+            state->select_end = state->cursor;
+            nk_textedit_clamp(state );
+        } else {
+            if (NK_TEXT_HAS_SELECTION(state))
+                nk_textedit_move_to_first(state);
+            else {
+                state->cursor = nk_textedit_move_to_word_previous(state);
+                nk_textedit_clamp(state );
+            }
+        } break;
+
+    case NK_KEY_TEXT_WORD_RIGHT:
+        if (shift_mod) {
+            if( !NK_TEXT_HAS_SELECTION( state ) )
+                nk_textedit_prep_selection_at_cursor(state);
+            state->cursor = nk_textedit_move_to_word_next(state);
+            state->select_end = state->cursor;
+            nk_textedit_clamp(state);
+        } else {
+            if (NK_TEXT_HAS_SELECTION(state))
+                nk_textedit_move_to_last(state);
+            else {
+                state->cursor = nk_textedit_move_to_word_next(state);
+                nk_textedit_clamp(state );
+            }
+        } break;
+
+    case NK_KEY_DOWN: {
+        struct nk_text_find find;
+        struct nk_text_edit_row row;
+        int i, sel = shift_mod;
+
+        if (state->single_line) {
+            /* on windows, up&down in single-line behave like left&right */
+            key = NK_KEY_RIGHT;
+            goto retry;
+        }
+
+        if (sel)
+            nk_textedit_prep_selection_at_cursor(state);
+        else if (NK_TEXT_HAS_SELECTION(state))
+            nk_textedit_move_to_last(state);
+
+        /* compute current position of cursor point */
+        nk_textedit_clamp(state);
+        nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
+            font, row_height);
+
+        /* now find character position down a row */
+        if (find.length)
+        {
+            float x;
+            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
+            int start = find.first_char + find.length;
+
+            state->cursor = start;
+            nk_textedit_layout_row(&row, state, state->cursor, row_height, font);
+            x = row.x0;
+
+            for (i=0; i < row.num_chars && x < row.x1; ++i) {
+                float dx = nk_textedit_get_width(state, start, i, font);
+                x += dx;
+                if (x > goal_x)
+                    break;
+                ++state->cursor;
+            }
+            nk_textedit_clamp(state);
+
+            state->has_preferred_x = 1;
+            state->preferred_x = goal_x;
+            if (sel)
+                state->select_end = state->cursor;
+        }
+    } break;
+
+    case NK_KEY_UP: {
+        struct nk_text_find find;
+        struct nk_text_edit_row row;
+        int i, sel = shift_mod;
+
+        if (state->single_line) {
+            /* on windows, up&down become left&right */
+            key = NK_KEY_LEFT;
+            goto retry;
+        }
+
+        if (sel)
+            nk_textedit_prep_selection_at_cursor(state);
+        else if (NK_TEXT_HAS_SELECTION(state))
+            nk_textedit_move_to_first(state);
+
+         /* compute current position of cursor point */
+         nk_textedit_clamp(state);
+         nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
+                font, row_height);
+
+         /* can only go up if there's a previous row */
+         if (find.prev_first != find.first_char) {
+            /* now find character position up a row */
+            float x;
+            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
+
+            state->cursor = find.prev_first;
+            nk_textedit_layout_row(&row, state, state->cursor, row_height, font);
+            x = row.x0;
+
+            for (i=0; i < row.num_chars && x < row.x1; ++i) {
+                float dx = nk_textedit_get_width(state, find.prev_first, i, font);
+                x += dx;
+                if (x > goal_x)
+                    break;
+                ++state->cursor;
+            }
+            nk_textedit_clamp(state);
+
+            state->has_preferred_x = 1;
+            state->preferred_x = goal_x;
+            if (sel) state->select_end = state->cursor;
+         }
+      } break;
+
+    case NK_KEY_DEL:
+        if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
+            break;
+        if (NK_TEXT_HAS_SELECTION(state))
+            nk_textedit_delete_selection(state);
+        else {
+            int n = state->string.len;
+            if (state->cursor < n)
+                nk_textedit_delete(state, state->cursor, 1);
+         }
+         state->has_preferred_x = 0;
+         break;
+
+    case NK_KEY_BACKSPACE:
+        if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
+            break;
+        if (NK_TEXT_HAS_SELECTION(state))
+            nk_textedit_delete_selection(state);
+        else {
+            nk_textedit_clamp(state);
+            if (state->cursor > 0) {
+                nk_textedit_delete(state, state->cursor-1, 1);
+                --state->cursor;
+            }
+         }
+         state->has_preferred_x = 0;
+         break;
+
+    case NK_KEY_TEXT_START:
+         if (shift_mod) {
+            nk_textedit_prep_selection_at_cursor(state);
+            state->cursor = state->select_end = 0;
+            state->has_preferred_x = 0;
+         } else {
+            state->cursor = state->select_start = state->select_end = 0;
+            state->has_preferred_x = 0;
+         }
+         break;
+
+    case NK_KEY_TEXT_END:
+         if (shift_mod) {
+            nk_textedit_prep_selection_at_cursor(state);
+            state->cursor = state->select_end = state->string.len;
+            state->has_preferred_x = 0;
+         } else {
+            state->cursor = state->string.len;
+            state->select_start = state->select_end = 0;
+            state->has_preferred_x = 0;
+         }
+         break;
+
+    case NK_KEY_TEXT_LINE_START: {
+        if (shift_mod) {
+            struct nk_text_find find;
+           nk_textedit_clamp(state);
+            nk_textedit_prep_selection_at_cursor(state);
+            if (state->string.len && state->cursor == state->string.len)
+                --state->cursor;
+            nk_textedit_find_charpos(&find, state,state->cursor, state->single_line,
+                font, row_height);
+            state->cursor = state->select_end = find.first_char;
+            state->has_preferred_x = 0;
+        } else {
+            struct nk_text_find find;
+            if (state->string.len && state->cursor == state->string.len)
+                --state->cursor;
+            nk_textedit_clamp(state);
+            nk_textedit_move_to_first(state);
+            nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
+                font, row_height);
+            state->cursor = find.first_char;
+            state->has_preferred_x = 0;
+        }
+      } break;
+
+    case NK_KEY_TEXT_LINE_END: {
+        if (shift_mod) {
+            struct nk_text_find find;
+            nk_textedit_clamp(state);
+            nk_textedit_prep_selection_at_cursor(state);
+            nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
+                font, row_height);
+            state->has_preferred_x = 0;
+            state->cursor = find.first_char + find.length;
+            if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n')
+                --state->cursor;
+            state->select_end = state->cursor;
+        } else {
+            struct nk_text_find find;
+            nk_textedit_clamp(state);
+            nk_textedit_move_to_first(state);
+            nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
+                font, row_height);
+
+            state->has_preferred_x = 0;
+            state->cursor = find.first_char + find.length;
+            if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n')
+                --state->cursor;
+        }} break;
+    }
+}
+
+NK_INTERN void
+nk_textedit_flush_redo(struct nk_text_undo_state *state)
+{
+    state->redo_point = NK_TEXTEDIT_UNDOSTATECOUNT;
+    state->redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT;
+}
+
+NK_INTERN void
+nk_textedit_discard_undo(struct nk_text_undo_state *state)
+{
+    /* discard the oldest entry in the undo list */
+    if (state->undo_point > 0) {
+        /* if the 0th undo state has characters, clean those up */
+        if (state->undo_rec[0].char_storage >= 0) {
+            int n = state->undo_rec[0].insert_length, i;
+            /* delete n characters from all other records */
+            state->undo_char_point = (short)(state->undo_char_point - n);
+            NK_MEMCPY(state->undo_char, state->undo_char + n,
+                (nk_size)state->undo_char_point*sizeof(nk_rune));
+            for (i=0; i < state->undo_point; ++i) {
+                if (state->undo_rec[i].char_storage >= 0)
+                state->undo_rec[i].char_storage = (short)
+                    (state->undo_rec[i].char_storage - n);
+            }
+        }
+        --state->undo_point;
+        NK_MEMCPY(state->undo_rec, state->undo_rec+1,
+            (nk_size)((nk_size)state->undo_point * sizeof(state->undo_rec[0])));
+    }
+}
+
+NK_INTERN void
+nk_textedit_discard_redo(struct nk_text_undo_state *state)
+{
+/*  discard the oldest entry in the redo list--it's bad if this
+    ever happens, but because undo & redo have to store the actual
+    characters in different cases, the redo character buffer can
+    fill up even though the undo buffer didn't */
+    nk_size num;
+    int k = NK_TEXTEDIT_UNDOSTATECOUNT-1;
+    if (state->redo_point <= k) {
+        /* if the k'th undo state has characters, clean those up */
+        if (state->undo_rec[k].char_storage >= 0) {
+            int n = state->undo_rec[k].insert_length, i;
+            /* delete n characters from all other records */
+            state->redo_char_point = (short)(state->redo_char_point + n);
+            num = (nk_size)(NK_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point);
+            NK_MEMCPY(state->undo_char + state->redo_char_point,
+                state->undo_char + state->redo_char_point-n, num * sizeof(char));
+            for (i = state->redo_point; i < k; ++i) {
+                if (state->undo_rec[i].char_storage >= 0) {
+                    state->undo_rec[i].char_storage = (short)
+                        (state->undo_rec[i].char_storage + n);
+                }
+            }
+        }
+        ++state->redo_point;
+        num = (nk_size)(NK_TEXTEDIT_UNDOSTATECOUNT - state->redo_point);
+        if (num) NK_MEMCPY(state->undo_rec + state->redo_point-1,
+            state->undo_rec + state->redo_point, num * sizeof(state->undo_rec[0]));
+    }
+}
+
+NK_INTERN struct nk_text_undo_record*
+nk_textedit_create_undo_record(struct nk_text_undo_state *state, int numchars)
+{
+    /* any time we create a new undo record, we discard redo*/
+    nk_textedit_flush_redo(state);
+
+    /* if we have no free records, we have to make room,
+     * by sliding the existing records down */
+    if (state->undo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
+        nk_textedit_discard_undo(state);
+
+    /* if the characters to store won't possibly fit in the buffer,
+     * we can't undo */
+    if (numchars > NK_TEXTEDIT_UNDOCHARCOUNT) {
+        state->undo_point = 0;
+        state->undo_char_point = 0;
+        return 0;
+    }
+
+    /* if we don't have enough free characters in the buffer,
+     * we have to make room */
+    while (state->undo_char_point + numchars > NK_TEXTEDIT_UNDOCHARCOUNT)
+        nk_textedit_discard_undo(state);
+    return &state->undo_rec[state->undo_point++];
+}
+
+NK_INTERN nk_rune*
+nk_textedit_createundo(struct nk_text_undo_state *state, int pos,
+    int insert_len, int delete_len)
+{
+    struct nk_text_undo_record *r = nk_textedit_create_undo_record(state, insert_len);
+    if (r == 0)
+        return 0;
+
+    r->where = pos;
+    r->insert_length = (short) insert_len;
+    r->delete_length = (short) delete_len;
+
+    if (insert_len == 0) {
+        r->char_storage = -1;
+        return 0;
+    } else {
+        r->char_storage = state->undo_char_point;
+        state->undo_char_point = (short)(state->undo_char_point +  insert_len);
+        return &state->undo_char[r->char_storage];
+    }
+}
+
+NK_API void
+nk_textedit_undo(struct nk_text_edit *state)
+{
+    struct nk_text_undo_state *s = &state->undo;
+    struct nk_text_undo_record u, *r;
+    if (s->undo_point == 0)
+        return;
+
+    /* we need to do two things: apply the undo record, and create a redo record */
+    u = s->undo_rec[s->undo_point-1];
+    r = &s->undo_rec[s->redo_point-1];
+    r->char_storage = -1;
+
+    r->insert_length = u.delete_length;
+    r->delete_length = u.insert_length;
+    r->where = u.where;
+
+    if (u.delete_length)
+    {
+       /*   if the undo record says to delete characters, then the redo record will
+            need to re-insert the characters that get deleted, so we need to store
+            them.
+            there are three cases:
+                - there's enough room to store the characters
+                - characters stored for *redoing* don't leave room for redo
+                - characters stored for *undoing* don't leave room for redo
+            if the last is true, we have to bail */
+        if (s->undo_char_point + u.delete_length >= NK_TEXTEDIT_UNDOCHARCOUNT) {
+            /* the undo records take up too much character space; there's no space
+            * to store the redo characters */
+            r->insert_length = 0;
+        } else {
+            int i;
+            /* there's definitely room to store the characters eventually */
+            while (s->undo_char_point + u.delete_length > s->redo_char_point) {
+                /* there's currently not enough room, so discard a redo record */
+                nk_textedit_discard_redo(s);
+                /* should never happen: */
+                if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
+                    return;
+            }
+
+            r = &s->undo_rec[s->redo_point-1];
+            r->char_storage = (short)(s->redo_char_point - u.delete_length);
+            s->redo_char_point = (short)(s->redo_char_point -  u.delete_length);
+
+            /* now save the characters */
+            for (i=0; i < u.delete_length; ++i)
+                s->undo_char[r->char_storage + i] =
+                    nk_str_rune_at(&state->string, u.where + i);
+        }
+        /* now we can carry out the deletion */
+        nk_str_delete_runes(&state->string, u.where, u.delete_length);
+    }
+
+    /* check type of recorded action: */
+    if (u.insert_length) {
+        /* easy case: was a deletion, so we need to insert n characters */
+        nk_str_insert_text_runes(&state->string, u.where,
+            &s->undo_char[u.char_storage], u.insert_length);
+        s->undo_char_point = (short)(s->undo_char_point - u.insert_length);
+    }
+    state->cursor = (short)(u.where + u.insert_length);
+
+    s->undo_point--;
+    s->redo_point--;
+}
+
+NK_API void
+nk_textedit_redo(struct nk_text_edit *state)
+{
+    struct nk_text_undo_state *s = &state->undo;
+    struct nk_text_undo_record *u, r;
+    if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
+        return;
+
+    /* we need to do two things: apply the redo record, and create an undo record */
+    u = &s->undo_rec[s->undo_point];
+    r = s->undo_rec[s->redo_point];
+
+    /* we KNOW there must be room for the undo record, because the redo record
+    was derived from an undo record */
+    u->delete_length = r.insert_length;
+    u->insert_length = r.delete_length;
+    u->where = r.where;
+    u->char_storage = -1;
+
+    if (r.delete_length) {
+        /* the redo record requires us to delete characters, so the undo record
+        needs to store the characters */
+        if (s->undo_char_point + u->insert_length > s->redo_char_point) {
+            u->insert_length = 0;
+            u->delete_length = 0;
+        } else {
+            int i;
+            u->char_storage = s->undo_char_point;
+            s->undo_char_point = (short)(s->undo_char_point + u->insert_length);
+
+            /* now save the characters */
+            for (i=0; i < u->insert_length; ++i) {
+                s->undo_char[u->char_storage + i] =
+                    nk_str_rune_at(&state->string, u->where + i);
+            }
+        }
+        nk_str_delete_runes(&state->string, r.where, r.delete_length);
+    }
+
+    if (r.insert_length) {
+        /* easy case: need to insert n characters */
+        nk_str_insert_text_runes(&state->string, r.where,
+            &s->undo_char[r.char_storage], r.insert_length);
+    }
+    state->cursor = r.where + r.insert_length;
+
+    s->undo_point++;
+    s->redo_point++;
+}
+
+NK_INTERN void
+nk_textedit_makeundo_insert(struct nk_text_edit *state, int where, int length)
+{
+    nk_textedit_createundo(&state->undo, where, 0, length);
+}
+
+NK_INTERN void
+nk_textedit_makeundo_delete(struct nk_text_edit *state, int where, int length)
+{
+    int i;
+    nk_rune *p = nk_textedit_createundo(&state->undo, where, length, 0);
+    if (p) {
+        for (i=0; i < length; ++i)
+            p[i] = nk_str_rune_at(&state->string, where+i);
+    }
+}
+
+NK_INTERN void
+nk_textedit_makeundo_replace(struct nk_text_edit *state, int where,
+    int old_length, int new_length)
+{
+    int i;
+    nk_rune *p = nk_textedit_createundo(&state->undo, where, old_length, new_length);
+    if (p) {
+        for (i=0; i < old_length; ++i)
+            p[i] = nk_str_rune_at(&state->string, where+i);
+    }
+}
+
+NK_INTERN void
+nk_textedit_clear_state(struct nk_text_edit *state, enum nk_text_edit_type type,
+    nk_plugin_filter filter)
+{
+    /* reset the state to default */
+   state->undo.undo_point = 0;
+   state->undo.undo_char_point = 0;
+   state->undo.redo_point = NK_TEXTEDIT_UNDOSTATECOUNT;
+   state->undo.redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT;
+   state->select_end = state->select_start = 0;
+   state->cursor = 0;
+   state->has_preferred_x = 0;
+   state->preferred_x = 0;
+   state->cursor_at_end_of_line = 0;
+   state->initialized = 1;
+   state->single_line = (unsigned char)(type == NK_TEXT_EDIT_SINGLE_LINE);
+   state->mode = NK_TEXT_EDIT_MODE_VIEW;
+   state->filter = filter;
+   state->scrollbar = nk_vec2(0,0);
+}
+
+NK_API void
+nk_textedit_init_fixed(struct nk_text_edit *state, void *memory, nk_size size)
+{
+    NK_ASSERT(state);
+    NK_ASSERT(memory);
+    if (!state || !memory || !size) return;
+    NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
+    nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
+    nk_str_init_fixed(&state->string, memory, size);
+}
+
+NK_API void
+nk_textedit_init(struct nk_text_edit *state, struct nk_allocator *alloc, nk_size size)
+{
+    NK_ASSERT(state);
+    NK_ASSERT(alloc);
+    if (!state || !alloc) return;
+    NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
+    nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
+    nk_str_init(&state->string, alloc, size);
+}
+
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+NK_API void
+nk_textedit_init_default(struct nk_text_edit *state)
+{
+    NK_ASSERT(state);
+    if (!state) return;
+    NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
+    nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
+    nk_str_init_default(&state->string);
+}
+#endif
+
+NK_API void
+nk_textedit_select_all(struct nk_text_edit *state)
+{
+    NK_ASSERT(state);
+    state->select_start = 0;
+    state->select_end = state->string.len;
+}
+
+NK_API void
+nk_textedit_free(struct nk_text_edit *state)
+{
+    NK_ASSERT(state);
+    if (!state) return;
+    nk_str_free(&state->string);
+}
+
+/* ===============================================================
+ *
+ *                          TEXT WIDGET
+ *
+ * ===============================================================*/
+#define nk_widget_state_reset(s)\
+    if ((*(s)) & NK_WIDGET_STATE_MODIFIED)\
+        (*(s)) = NK_WIDGET_STATE_INACTIVE|NK_WIDGET_STATE_MODIFIED;\
+    else (*(s)) = NK_WIDGET_STATE_INACTIVE;
+
+struct nk_text {
+    struct nk_vec2 padding;
+    struct nk_color background;
+    struct nk_color text;
+};
+
+NK_INTERN void
+nk_widget_text(struct nk_command_buffer *o, struct nk_rect b,
+    const char *string, int len, const struct nk_text *t,
+    nk_flags a, const struct nk_user_font *f)
+{
+    struct nk_rect label;
+    float text_width;
+
+    NK_ASSERT(o);
+    NK_ASSERT(t);
+    if (!o || !t) return;
+
+    b.h = NK_MAX(b.h, 2 * t->padding.y);
+    label.x = 0; label.w = 0;
+    label.y = b.y + t->padding.y;
+    label.h = NK_MIN(f->height, b.h - 2 * t->padding.y);
+
+    text_width = f->width(f->userdata, f->height, (const char*)string, len);
+    text_width += (2.0f * t->padding.x);
+
+    /* align in x-axis */
+    if (a & NK_TEXT_ALIGN_LEFT) {
+        label.x = b.x + t->padding.x;
+        label.w = NK_MAX(0, b.w - 2 * t->padding.x);
+    } else if (a & NK_TEXT_ALIGN_CENTERED) {
+        label.w = NK_MAX(1, 2 * t->padding.x + (float)text_width);
+        label.x = (b.x + t->padding.x + ((b.w - 2 * t->padding.x) - label.w) / 2);
+        label.x = NK_MAX(b.x + t->padding.x, label.x);
+        label.w = NK_MIN(b.x + b.w, label.x + label.w);
+        if (label.w >= label.x) label.w -= label.x;
+    } else if (a & NK_TEXT_ALIGN_RIGHT) {
+        label.x = NK_MAX(b.x + t->padding.x, (b.x + b.w) - (2 * t->padding.x + (float)text_width));
+        label.w = (float)text_width + 2 * t->padding.x;
+    } else return;
+
+    /* align in y-axis */
+    if (a & NK_TEXT_ALIGN_MIDDLE) {
+        label.y = b.y + b.h/2.0f - (float)f->height/2.0f;
+        label.h = NK_MAX(b.h/2.0f, b.h - (b.h/2.0f + f->height/2.0f));
+    } else if (a & NK_TEXT_ALIGN_BOTTOM) {
+        label.y = b.y + b.h - f->height;
+        label.h = f->height;
+    }
+    nk_draw_text(o, label, (const char*)string,
+        len, f, t->background, t->text);
+}
+
+NK_INTERN void
+nk_widget_text_wrap(struct nk_command_buffer *o, struct nk_rect b,
+    const char *string, int len, const struct nk_text *t,
+    const struct nk_user_font *f)
+{
+    float width;
+    int glyphs = 0;
+    int fitting = 0;
+    int done = 0;
+    struct nk_rect line;
+    struct nk_text text;
+    NK_INTERN nk_rune seperator[] = {' '};
+
+    NK_ASSERT(o);
+    NK_ASSERT(t);
+    if (!o || !t) return;
+
+    text.padding = nk_vec2(0,0);
+    text.background = t->background;
+    text.text = t->text;
+
+    b.w = NK_MAX(b.w, 2 * t->padding.x);
+    b.h = NK_MAX(b.h, 2 * t->padding.y);
+    b.h = b.h - 2 * t->padding.y;
+
+    line.x = b.x + t->padding.x;
+    line.y = b.y + t->padding.y;
+    line.w = b.w - 2 * t->padding.x;
+    line.h = 2 * t->padding.y + f->height;
+
+    fitting = nk_text_clamp(f, string, len, line.w, &glyphs, &width, seperator,NK_LEN(seperator));
+    while (done < len) {
+        if (!fitting || line.y + line.h >= (b.y + b.h)) break;
+        nk_widget_text(o, line, &string[done], fitting, &text, NK_TEXT_LEFT, f);
+        done += fitting;
+        line.y += f->height + 2 * t->padding.y;
+        fitting = nk_text_clamp(f, &string[done], len - done, line.w, &glyphs, &width, seperator,NK_LEN(seperator));
+    }
+}
+
+/* ===============================================================
+ *
+ *                          BUTTON
+ *
+ * ===============================================================*/
+NK_INTERN void
+nk_draw_symbol(struct nk_command_buffer *out, enum nk_symbol_type type,
+    struct nk_rect content, struct nk_color background, struct nk_color foreground,
+    float border_width, const struct nk_user_font *font)
+{
+    switch (type) {
+    case NK_SYMBOL_X:
+    case NK_SYMBOL_UNDERSCORE:
+    case NK_SYMBOL_PLUS:
+    case NK_SYMBOL_MINUS: {
+        /* single character text symbol */
+        const char *X = (type == NK_SYMBOL_X) ? "x":
+            (type == NK_SYMBOL_UNDERSCORE) ? "_":
+            (type == NK_SYMBOL_PLUS) ? "+": "-";
+        struct nk_text text;
+        text.padding = nk_vec2(0,0);
+        text.background = background;
+        text.text = foreground;
+        nk_widget_text(out, content, X, 1, &text, NK_TEXT_CENTERED, font);
+    } break;
+    case NK_SYMBOL_CIRCLE_SOLID:
+    case NK_SYMBOL_CIRCLE_OUTLINE:
+    case NK_SYMBOL_RECT_SOLID:
+    case NK_SYMBOL_RECT_OUTLINE: {
+        /* simple empty/filled shapes */
+        if (type == NK_SYMBOL_RECT_SOLID || type == NK_SYMBOL_RECT_OUTLINE) {
+            nk_fill_rect(out, content,  0, foreground);
+            if (type == NK_SYMBOL_RECT_OUTLINE)
+                nk_fill_rect(out, nk_shrink_rect(content, border_width), 0, background);
+        } else {
+            nk_fill_circle(out, content, foreground);
+            if (type == NK_SYMBOL_CIRCLE_OUTLINE)
+                nk_fill_circle(out, nk_shrink_rect(content, 1), background);
+        }
+    } break;
+    case NK_SYMBOL_TRIANGLE_UP:
+    case NK_SYMBOL_TRIANGLE_DOWN:
+    case NK_SYMBOL_TRIANGLE_LEFT:
+    case NK_SYMBOL_TRIANGLE_RIGHT: {
+        enum nk_heading heading;
+        struct nk_vec2 points[3];
+        heading = (type == NK_SYMBOL_TRIANGLE_RIGHT) ? NK_RIGHT :
+            (type == NK_SYMBOL_TRIANGLE_LEFT) ? NK_LEFT:
+            (type == NK_SYMBOL_TRIANGLE_UP) ? NK_UP: NK_DOWN;
+        nk_triangle_from_direction(points, content, 0, 0, heading);
+        nk_fill_triangle(out, points[0].x, points[0].y, points[1].x, points[1].y,
+            points[2].x, points[2].y, foreground);
+    } break;
+    default:
+    case NK_SYMBOL_NONE:
+    case NK_SYMBOL_MAX: break;
+    }
+}
+
+NK_INTERN int
+nk_button_behavior(nk_flags *state, struct nk_rect r,
+    const struct nk_input *i, enum nk_button_behavior behavior)
+{
+    int ret = 0;
+    nk_widget_state_reset(state);
+    if (!i) return 0;
+    if (nk_input_is_mouse_hovering_rect(i, r)) {
+        *state = NK_WIDGET_STATE_HOVERED;
+        if (nk_input_is_mouse_down(i, NK_BUTTON_LEFT))
+            *state = NK_WIDGET_STATE_ACTIVE;
+        if (nk_input_has_mouse_click_in_rect(i, NK_BUTTON_LEFT, r)) {
+            ret = (behavior != NK_BUTTON_DEFAULT) ?
+                nk_input_is_mouse_down(i, NK_BUTTON_LEFT):
+#ifdef NK_BUTTON_TRIGGER_ON_RELEASE
+                nk_input_is_mouse_released(i, NK_BUTTON_LEFT);
+#else
+                nk_input_is_mouse_pressed(i, NK_BUTTON_LEFT);
+#endif
+        }
+    }
+    if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(i, r))
+        *state |= NK_WIDGET_STATE_ENTERED;
+    else if (nk_input_is_mouse_prev_hovering_rect(i, r))
+        *state |= NK_WIDGET_STATE_LEFT;
+    return ret;
+}
+
+NK_INTERN const struct nk_style_item*
+nk_draw_button(struct nk_command_buffer *out,
+    const struct nk_rect *bounds, nk_flags state,
+    const struct nk_style_button *style)
+{
+    const struct nk_style_item *background;
+    if (state & NK_WIDGET_STATE_HOVER)
+        background = &style->hover;
+    else if (state & NK_WIDGET_STATE_ACTIVED)
+        background = &style->active;
+    else background = &style->normal;
+
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        nk_draw_image(out, *bounds, &background->data.image, nk_white);
+    } else {
+        nk_fill_rect(out, *bounds, style->rounding, background->data.color);
+        nk_stroke_rect(out, *bounds, style->rounding, style->border, style->border_color);
+    }
+    return background;
+}
+
+NK_INTERN int
+nk_do_button(nk_flags *state, struct nk_command_buffer *out, struct nk_rect r,
+    const struct nk_style_button *style, const struct nk_input *in,
+    enum nk_button_behavior behavior, struct nk_rect *content)
+{
+    struct nk_rect bounds;
+    NK_ASSERT(style);
+    NK_ASSERT(state);
+    NK_ASSERT(out);
+    if (!out || !style)
+        return nk_false;
+
+    /* calculate button content space */
+    content->x = r.x + style->padding.x + style->border + style->rounding;
+    content->y = r.y + style->padding.y + style->border + style->rounding;
+    content->w = r.w - (2 * style->padding.x + style->border + style->rounding*2);
+    content->h = r.h - (2 * style->padding.y + style->border + style->rounding*2);
+
+    /* execute button behavior */
+    bounds.x = r.x - style->touch_padding.x;
+    bounds.y = r.y - style->touch_padding.y;
+    bounds.w = r.w + 2 * style->touch_padding.x;
+    bounds.h = r.h + 2 * style->touch_padding.y;
+    return nk_button_behavior(state, bounds, in, behavior);
+}
+
+NK_INTERN void
+nk_draw_button_text(struct nk_command_buffer *out,
+    const struct nk_rect *bounds, const struct nk_rect *content, nk_flags state,
+    const struct nk_style_button *style, const char *txt, int len,
+    nk_flags text_alignment, const struct nk_user_font *font)
+{
+    struct nk_text text;
+    const struct nk_style_item *background;
+    background = nk_draw_button(out, bounds, state, style);
+
+    /* select correct colors/images */
+    if (background->type == NK_STYLE_ITEM_COLOR)
+        text.background = background->data.color;
+    else text.background = style->text_background;
+    if (state & NK_WIDGET_STATE_HOVER)
+        text.text = style->text_hover;
+    else if (state & NK_WIDGET_STATE_ACTIVED)
+        text.text = style->text_active;
+    else text.text = style->text_normal;
+
+    text.padding = nk_vec2(0,0);
+    nk_widget_text(out, *content, txt, len, &text, text_alignment, font);
+}
+
+NK_INTERN int
+nk_do_button_text(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect bounds,
+    const char *string, int len, nk_flags align, enum nk_button_behavior behavior,
+    const struct nk_style_button *style, const struct nk_input *in,
+    const struct nk_user_font *font)
+{
+    struct nk_rect content;
+    int ret = nk_false;
+
+    NK_ASSERT(state);
+    NK_ASSERT(style);
+    NK_ASSERT(out);
+    NK_ASSERT(string);
+    NK_ASSERT(font);
+    if (!out || !style || !font || !string)
+        return nk_false;
+
+    ret = nk_do_button(state, out, bounds, style, in, behavior, &content);
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_button_text(out, &bounds, &content, *state, style, string, len, align, font);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return ret;
+}
+
+NK_INTERN void
+nk_draw_button_symbol(struct nk_command_buffer *out,
+    const struct nk_rect *bounds, const struct nk_rect *content,
+    nk_flags state, const struct nk_style_button *style,
+    enum nk_symbol_type type, const struct nk_user_font *font)
+{
+    struct nk_color sym, bg;
+    const struct nk_style_item *background;
+
+    /* select correct colors/images */
+    background = nk_draw_button(out, bounds, state, style);
+    if (background->type == NK_STYLE_ITEM_COLOR)
+        bg = background->data.color;
+    else bg = style->text_background;
+
+    if (state & NK_WIDGET_STATE_HOVER)
+        sym = style->text_hover;
+    else if (state & NK_WIDGET_STATE_ACTIVED)
+        sym = style->text_active;
+    else sym = style->text_normal;
+    nk_draw_symbol(out, type, *content, bg, sym, 1, font);
+}
+
+NK_INTERN int
+nk_do_button_symbol(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect bounds,
+    enum nk_symbol_type symbol, enum nk_button_behavior behavior,
+    const struct nk_style_button *style, const struct nk_input *in,
+    const struct nk_user_font *font)
+{
+    int ret;
+    struct nk_rect content;
+
+    NK_ASSERT(state);
+    NK_ASSERT(style);
+    NK_ASSERT(font);
+    NK_ASSERT(out);
+    if (!out || !style || !font || !state)
+        return nk_false;
+
+    ret = nk_do_button(state, out, bounds, style, in, behavior, &content);
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_button_symbol(out, &bounds, &content, *state, style, symbol, font);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return ret;
+}
+
+NK_INTERN void
+nk_draw_button_image(struct nk_command_buffer *out,
+    const struct nk_rect *bounds, const struct nk_rect *content,
+    nk_flags state, const struct nk_style_button *style, const struct nk_image *img)
+{
+    nk_draw_button(out, bounds, state, style);
+    nk_draw_image(out, *content, img, nk_white);
+}
+
+NK_INTERN int
+nk_do_button_image(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect bounds,
+    struct nk_image img, enum nk_button_behavior b,
+    const struct nk_style_button *style, const struct nk_input *in)
+{
+    int ret;
+    struct nk_rect content;
+
+    NK_ASSERT(state);
+    NK_ASSERT(style);
+    NK_ASSERT(out);
+    if (!out || !style || !state)
+        return nk_false;
+
+    ret = nk_do_button(state, out, bounds, style, in, b, &content);
+    content.x += style->image_padding.x;
+    content.y += style->image_padding.y;
+    content.w -= 2 * style->image_padding.x;
+    content.h -= 2 * style->image_padding.y;
+
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_button_image(out, &bounds, &content, *state, style, &img);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return ret;
+}
+
+NK_INTERN void
+nk_draw_button_text_symbol(struct nk_command_buffer *out,
+    const struct nk_rect *bounds, const struct nk_rect *label,
+    const struct nk_rect *symbol, nk_flags state, const struct nk_style_button *style,
+    const char *str, int len, enum nk_symbol_type type,
+    const struct nk_user_font *font)
+{
+    struct nk_color sym;
+    struct nk_text text;
+    const struct nk_style_item *background;
+
+    /* select correct background colors/images */
+    background = nk_draw_button(out, bounds, state, style);
+    if (background->type == NK_STYLE_ITEM_COLOR)
+        text.background = background->data.color;
+    else text.background = style->text_background;
+
+    /* select correct text colors */
+    if (state & NK_WIDGET_STATE_HOVER) {
+        sym = style->text_hover;
+        text.text = style->text_hover;
+    } else if (state & NK_WIDGET_STATE_ACTIVED) {
+        sym = style->text_active;
+        text.text = style->text_active;
+    } else {
+        sym = style->text_normal;
+        text.text = style->text_normal;
+    }
+
+    text.padding = nk_vec2(0,0);
+    nk_draw_symbol(out, type, *symbol, style->text_background, sym, 0, font);
+    nk_widget_text(out, *label, str, len, &text, NK_TEXT_CENTERED, font);
+}
+
+NK_INTERN int
+nk_do_button_text_symbol(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect bounds,
+    enum nk_symbol_type symbol, const char *str, int len, nk_flags align,
+    enum nk_button_behavior behavior, const struct nk_style_button *style,
+    const struct nk_user_font *font, const struct nk_input *in)
+{
+    int ret;
+    struct nk_rect tri = {0,0,0,0};
+    struct nk_rect content;
+
+    NK_ASSERT(style);
+    NK_ASSERT(out);
+    NK_ASSERT(font);
+    if (!out || !style || !font)
+        return nk_false;
+
+    ret = nk_do_button(state, out, bounds, style, in, behavior, &content);
+    tri.y = content.y + (content.h/2) - font->height/2;
+    tri.w = font->height; tri.h = font->height;
+    if (align & NK_TEXT_ALIGN_LEFT) {
+        tri.x = (content.x + content.w) - (2 * style->padding.x + tri.w);
+        tri.x = NK_MAX(tri.x, 0);
+    } else tri.x = content.x + 2 * style->padding.x;
+
+    /* draw button */
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_button_text_symbol(out, &bounds, &content, &tri,
+        *state, style, str, len, symbol, font);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return ret;
+}
+
+NK_INTERN void
+nk_draw_button_text_image(struct nk_command_buffer *out,
+    const struct nk_rect *bounds, const struct nk_rect *label,
+    const struct nk_rect *image, nk_flags state, const struct nk_style_button *style,
+    const char *str, int len, const struct nk_user_font *font,
+    const struct nk_image *img)
+{
+    struct nk_text text;
+    const struct nk_style_item *background;
+    background = nk_draw_button(out, bounds, state, style);
+
+    /* select correct colors */
+    if (background->type == NK_STYLE_ITEM_COLOR)
+        text.background = background->data.color;
+    else text.background = style->text_background;
+    if (state & NK_WIDGET_STATE_HOVER)
+        text.text = style->text_hover;
+    else if (state & NK_WIDGET_STATE_ACTIVED)
+        text.text = style->text_active;
+    else text.text = style->text_normal;
+
+    text.padding = nk_vec2(0,0);
+    nk_widget_text(out, *label, str, len, &text, NK_TEXT_CENTERED, font);
+    nk_draw_image(out, *image, img, nk_white);
+}
+
+NK_INTERN int
+nk_do_button_text_image(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect bounds,
+    struct nk_image img, const char* str, int len, nk_flags align,
+    enum nk_button_behavior behavior, const struct nk_style_button *style,
+    const struct nk_user_font *font, const struct nk_input *in)
+{
+    int ret;
+    struct nk_rect icon;
+    struct nk_rect content;
+
+    NK_ASSERT(style);
+    NK_ASSERT(state);
+    NK_ASSERT(font);
+    NK_ASSERT(out);
+    if (!out || !font || !style || !str)
+        return nk_false;
+
+    ret = nk_do_button(state, out, bounds, style, in, behavior, &content);
+    icon.y = bounds.y + style->padding.y;
+    icon.w = icon.h = bounds.h - 2 * style->padding.y;
+    if (align & NK_TEXT_ALIGN_LEFT) {
+        icon.x = (bounds.x + bounds.w) - (2 * style->padding.x + icon.w);
+        icon.x = NK_MAX(icon.x, 0);
+    } else icon.x = bounds.x + 2 * style->padding.x;
+
+    icon.x += style->image_padding.x;
+    icon.y += style->image_padding.y;
+    icon.w -= 2 * style->image_padding.x;
+    icon.h -= 2 * style->image_padding.y;
+
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_button_text_image(out, &bounds, &content, &icon, *state, style, str, len, font, &img);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return ret;
+}
+
+/* ===============================================================
+ *
+ *                          TOGGLE
+ *
+ * ===============================================================*/
+enum nk_toggle_type {
+    NK_TOGGLE_CHECK,
+    NK_TOGGLE_OPTION
+};
+
+NK_INTERN int
+nk_toggle_behavior(const struct nk_input *in, struct nk_rect select,
+    nk_flags *state, int active)
+{
+    nk_widget_state_reset(state);
+    if (nk_button_behavior(state, select, in, NK_BUTTON_DEFAULT)) {
+        *state = NK_WIDGET_STATE_ACTIVE;
+        active = !active;
+    }
+    if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, select))
+        *state |= NK_WIDGET_STATE_ENTERED;
+    else if (nk_input_is_mouse_prev_hovering_rect(in, select))
+        *state |= NK_WIDGET_STATE_LEFT;
+    return active;
+}
+
+NK_INTERN void
+nk_draw_checkbox(struct nk_command_buffer *out,
+    nk_flags state, const struct nk_style_toggle *style, int active,
+    const struct nk_rect *label, const struct nk_rect *selector,
+    const struct nk_rect *cursors, const char *string, int len,
+    const struct nk_user_font *font)
+{
+    const struct nk_style_item *background;
+    const struct nk_style_item *cursor;
+    struct nk_text text;
+
+    /* select correct colors/images */
+    if (state & NK_WIDGET_STATE_HOVER) {
+        background = &style->hover;
+        cursor = &style->cursor_hover;
+        text.text = style->text_hover;
+    } else if (state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->hover;
+        cursor = &style->cursor_hover;
+        text.text = style->text_active;
+    } else {
+        background = &style->normal;
+        cursor = &style->cursor_normal;
+        text.text = style->text_normal;
+    }
+
+    /* draw background and cursor */
+    if (background->type == NK_STYLE_ITEM_COLOR) {
+        nk_fill_rect(out, *selector, 0, style->border_color);
+        nk_fill_rect(out, nk_shrink_rect(*selector, style->border), 0, background->data.color);
+    } else nk_draw_image(out, *selector, &background->data.image, nk_white);
+    if (active) {
+        if (cursor->type == NK_STYLE_ITEM_IMAGE)
+            nk_draw_image(out, *cursors, &cursor->data.image, nk_white);
+        else nk_fill_rect(out, *cursors, 0, cursor->data.color);
+    }
+
+    text.padding.x = 0;
+    text.padding.y = 0;
+    text.background = style->text_background;
+    nk_widget_text(out, *label, string, len, &text, NK_TEXT_LEFT, font);
+}
+
+NK_INTERN void
+nk_draw_option(struct nk_command_buffer *out,
+    nk_flags state, const struct nk_style_toggle *style, int active,
+    const struct nk_rect *label, const struct nk_rect *selector,
+    const struct nk_rect *cursors, const char *string, int len,
+    const struct nk_user_font *font)
+{
+    const struct nk_style_item *background;
+    const struct nk_style_item *cursor;
+    struct nk_text text;
+
+    /* select correct colors/images */
+    if (state & NK_WIDGET_STATE_HOVER) {
+        background = &style->hover;
+        cursor = &style->cursor_hover;
+        text.text = style->text_hover;
+    } else if (state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->hover;
+        cursor = &style->cursor_hover;
+        text.text = style->text_active;
+    } else {
+        background = &style->normal;
+        cursor = &style->cursor_normal;
+        text.text = style->text_normal;
+    }
+
+    /* draw background and cursor */
+    if (background->type == NK_STYLE_ITEM_COLOR) {
+        nk_fill_circle(out, *selector, style->border_color);
+        nk_fill_circle(out, nk_shrink_rect(*selector, style->border), background->data.color);
+    } else nk_draw_image(out, *selector, &background->data.image, nk_white);
+    if (active) {
+        if (cursor->type == NK_STYLE_ITEM_IMAGE)
+            nk_draw_image(out, *cursors, &cursor->data.image, nk_white);
+        else nk_fill_circle(out, *cursors, cursor->data.color);
+    }
+
+    text.padding.x = 0;
+    text.padding.y = 0;
+    text.background = style->text_background;
+    nk_widget_text(out, *label, string, len, &text, NK_TEXT_LEFT, font);
+}
+
+NK_INTERN int
+nk_do_toggle(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect r,
+    int *active, const char *str, int len, enum nk_toggle_type type,
+    const struct nk_style_toggle *style, const struct nk_input *in,
+    const struct nk_user_font *font)
+{
+    int was_active;
+    struct nk_rect bounds;
+    struct nk_rect select;
+    struct nk_rect cursor;
+    struct nk_rect label;
+
+    NK_ASSERT(style);
+    NK_ASSERT(out);
+    NK_ASSERT(font);
+    if (!out || !style || !font || !active)
+        return 0;
+
+    r.w = NK_MAX(r.w, font->height + 2 * style->padding.x);
+    r.h = NK_MAX(r.h, font->height + 2 * style->padding.y);
+
+    /* add additional touch padding for touch screen devices */
+    bounds.x = r.x - style->touch_padding.x;
+    bounds.y = r.y - style->touch_padding.y;
+    bounds.w = r.w + 2 * style->touch_padding.x;
+    bounds.h = r.h + 2 * style->touch_padding.y;
+
+    /* calculate the selector space */
+    select.w = font->height;
+    select.h = select.w;
+    select.y = r.y + r.h/2.0f - select.h/2.0f;
+    select.x = r.x;
+
+    /* calculate the bounds of the cursor inside the selector */
+    cursor.x = select.x + style->padding.x + style->border;
+    cursor.y = select.y + style->padding.y + style->border;
+    cursor.w = select.w - (2 * style->padding.x + 2 * style->border);
+    cursor.h = select.h - (2 * style->padding.y + 2 * style->border);
+
+    /* label behind the selector */
+    label.x = select.x + select.w + style->spacing;
+    label.y = select.y;
+    label.w = NK_MAX(r.x + r.w, label.x) - label.x;
+    label.h = select.w;
+
+    /* update selector */
+    was_active = *active;
+    *active = nk_toggle_behavior(in, bounds, state, *active);
+
+    /* draw selector */
+    if (style->draw_begin)
+        style->draw_begin(out, style->userdata);
+    if (type == NK_TOGGLE_CHECK) {
+        nk_draw_checkbox(out, *state, style, *active, &label, &select, &cursor, str, len, font);
+    } else {
+        nk_draw_option(out, *state, style, *active, &label, &select, &cursor, str, len, font);
+    }
+    if (style->draw_end)
+        style->draw_end(out, style->userdata);
+    return (was_active != *active);
+}
+
+/* ===============================================================
+ *
+ *                          SELECTABLE
+ *
+ * ===============================================================*/
+NK_INTERN void
+nk_draw_selectable(struct nk_command_buffer *out,
+    nk_flags state, const struct nk_style_selectable *style, int active,
+    const struct nk_rect *bounds, const struct nk_rect *icon, const struct nk_image *img,
+    const char *string, int len, nk_flags align, const struct nk_user_font *font)
+{
+    const struct nk_style_item *background;
+    struct nk_text text;
+    text.padding = style->padding;
+
+    /* select correct colors/images */
+    if (!active) {
+        if (state & NK_WIDGET_STATE_ACTIVED) {
+            background = &style->pressed;
+            text.text = style->text_pressed;
+        } else if (state & NK_WIDGET_STATE_HOVER) {
+            background = &style->hover;
+            text.text = style->text_hover;
+        } else {
+            background = &style->normal;
+            text.text = style->text_normal;
+        }
+    } else {
+        if (state & NK_WIDGET_STATE_ACTIVED) {
+            background = &style->pressed_active;
+            text.text = style->text_pressed_active;
+        } else if (state & NK_WIDGET_STATE_HOVER) {
+            background = &style->hover_active;
+            text.text = style->text_hover_active;
+        } else {
+            background = &style->normal_active;
+            text.text = style->text_normal_active;
+        }
+    }
+
+
+    /* draw selectable background and text */
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        nk_draw_image(out, *bounds, &background->data.image, nk_white);
+        text.background = nk_rgba(0,0,0,0);
+    } else {
+        nk_fill_rect(out, *bounds, style->rounding, background->data.color);
+        text.background = background->data.color;
+    }
+    if (img && icon) nk_draw_image(out, *icon, img, nk_white);
+    nk_widget_text(out, *bounds, string, len, &text, align, font);
+}
+
+NK_INTERN int
+nk_do_selectable(nk_flags *state, struct nk_command_buffer *out,
+    struct nk_rect bounds, const char *str, int len, nk_flags align, int *value,
+    const struct nk_style_selectable *style, const struct nk_input *in,
+    const struct nk_user_font *font)
+{
+    int old_value;
+    struct nk_rect touch;
+
+    NK_ASSERT(state);
+    NK_ASSERT(out);
+    NK_ASSERT(str);
+    NK_ASSERT(len);
+    NK_ASSERT(value);
+    NK_ASSERT(style);
+    NK_ASSERT(font);
+
+    if (!state || !out || !str || !len || !value || !style || !font) return 0;
+    old_value = *value;
+
+    /* remove padding */
+    touch.x = bounds.x - style->touch_padding.x;
+    touch.y = bounds.y - style->touch_padding.y;
+    touch.w = bounds.w + style->touch_padding.x * 2;
+    touch.h = bounds.h + style->touch_padding.y * 2;
+
+    /* update button */
+    if (nk_button_behavior(state, touch, in, NK_BUTTON_DEFAULT))
+        *value = !(*value);
+
+    /* draw selectable */
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_selectable(out, *state, style, *value, &bounds, 0,0, str, len, align, font);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return old_value != *value;
+}
+
+NK_INTERN int
+nk_do_selectable_image(nk_flags *state, struct nk_command_buffer *out,
+    struct nk_rect bounds, const char *str, int len, nk_flags align, int *value,
+    const struct nk_image *img, const struct nk_style_selectable *style,
+    const struct nk_input *in, const struct nk_user_font *font)
+{
+    int old_value;
+    struct nk_rect touch;
+    struct nk_rect icon;
+
+    NK_ASSERT(state);
+    NK_ASSERT(out);
+    NK_ASSERT(str);
+    NK_ASSERT(len);
+    NK_ASSERT(value);
+    NK_ASSERT(style);
+    NK_ASSERT(font);
+
+    if (!state || !out || !str || !len || !value || !style || !font) return 0;
+    old_value = *value;
+
+    /* toggle behavior */
+    touch.x = bounds.x - style->touch_padding.x;
+    touch.y = bounds.y - style->touch_padding.y;
+    touch.w = bounds.w + style->touch_padding.x * 2;
+    touch.h = bounds.h + style->touch_padding.y * 2;
+    if (nk_button_behavior(state, touch, in, NK_BUTTON_DEFAULT))
+        *value = !(*value);
+
+    icon.y = bounds.y + style->padding.y;
+    icon.w = icon.h = bounds.h - 2 * style->padding.y;
+    if (align & NK_TEXT_ALIGN_LEFT) {
+        icon.x = (bounds.x + bounds.w) - (2 * style->padding.x + icon.w);
+        icon.x = NK_MAX(icon.x, 0);
+    } else icon.x = bounds.x + 2 * style->padding.x;
+
+    icon.x += style->image_padding.x;
+    icon.y += style->image_padding.y;
+    icon.w -= 2 * style->image_padding.x;
+    icon.h -= 2 * style->image_padding.y;
+
+    /* draw selectable */
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_selectable(out, *state, style, *value, &bounds, &icon, img, str, len, align, font);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return old_value != *value;
+}
+
+
+/* ===============================================================
+ *
+ *                          SLIDER
+ *
+ * ===============================================================*/
+NK_INTERN float
+nk_slider_behavior(nk_flags *state, struct nk_rect *logical_cursor,
+    struct nk_rect *visual_cursor, struct nk_input *in,
+    struct nk_rect bounds, float slider_min, float slider_max, float slider_value,
+    float slider_step, float slider_steps)
+{
+    int left_mouse_down;
+    int left_mouse_click_in_cursor;
+
+    /* check if visual cursor is being dragged */
+    nk_widget_state_reset(state);
+    left_mouse_down = in && in->mouse.buttons[NK_BUTTON_LEFT].down;
+    left_mouse_click_in_cursor = in && nk_input_has_mouse_click_down_in_rect(in,
+            NK_BUTTON_LEFT, *visual_cursor, nk_true);
+
+    if (left_mouse_down && left_mouse_click_in_cursor)
+    {
+        float ratio = 0;
+        const float d = in->mouse.pos.x - (visual_cursor->x+visual_cursor->w*0.5f);
+        const float pxstep = bounds.w / slider_steps;
+
+        /* only update value if the next slider step is reached */
+        *state = NK_WIDGET_STATE_ACTIVE;
+        if (NK_ABS(d) >= pxstep) {
+            const float steps = (float)((int)(NK_ABS(d) / pxstep));
+            slider_value += (d > 0) ? (slider_step*steps) : -(slider_step*steps);
+            slider_value = NK_CLAMP(slider_min, slider_value, slider_max);
+            ratio = (slider_value - slider_min)/slider_step;
+            logical_cursor->x = bounds.x + (logical_cursor->w * ratio);
+            in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = logical_cursor->x;
+        }
+    }
+
+    /* slider widget state */
+    if (nk_input_is_mouse_hovering_rect(in, bounds))
+        *state = NK_WIDGET_STATE_HOVERED;
+    if (*state & NK_WIDGET_STATE_HOVER &&
+        !nk_input_is_mouse_prev_hovering_rect(in, bounds))
+        *state |= NK_WIDGET_STATE_ENTERED;
+    else if (nk_input_is_mouse_prev_hovering_rect(in, bounds))
+        *state |= NK_WIDGET_STATE_LEFT;
+    return slider_value;
+}
+
+NK_INTERN void
+nk_draw_slider(struct nk_command_buffer *out, nk_flags state,
+    const struct nk_style_slider *style, const struct nk_rect *bounds,
+    const struct nk_rect *visual_cursor, float min, float value, float max)
+{
+    struct nk_rect fill;
+    struct nk_rect bar;
+    const struct nk_style_item *background;
+
+    /* select correct slider images/colors */
+    struct nk_color bar_color;
+    const struct nk_style_item *cursor;
+
+    NK_UNUSED(min);
+    NK_UNUSED(max);
+    NK_UNUSED(value);
+
+    if (state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->active;
+        bar_color = style->bar_active;
+        cursor = &style->cursor_active;
+    } else if (state & NK_WIDGET_STATE_HOVER) {
+        background = &style->hover;
+        bar_color = style->bar_hover;
+        cursor = &style->cursor_hover;
+    } else {
+        background = &style->normal;
+        bar_color = style->bar_normal;
+        cursor = &style->cursor_normal;
+    }
+
+    /* calculate slider background bar */
+    bar.x = bounds->x;
+    bar.y = (visual_cursor->y + visual_cursor->h/2) - bounds->h/12;
+    bar.w = bounds->w;
+    bar.h = bounds->h/6;
+
+    /* filled background bar style */
+    fill.w = (visual_cursor->x + (visual_cursor->w/2.0f)) - bar.x;
+    fill.x = bar.x;
+    fill.y = bar.y;
+    fill.h = bar.h;
+
+    /* draw background */
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        nk_draw_image(out, *bounds, &background->data.image, nk_white);
+    } else {
+        nk_fill_rect(out, *bounds, style->rounding, background->data.color);
+        nk_stroke_rect(out, *bounds, style->rounding, style->border, style->border_color);
+    }
+
+    /* draw slider bar */
+    nk_fill_rect(out, bar, style->rounding, bar_color);
+    nk_fill_rect(out, fill, style->rounding, style->bar_filled);
+
+    /* draw cursor */
+    if (cursor->type == NK_STYLE_ITEM_IMAGE)
+        nk_draw_image(out, *visual_cursor, &cursor->data.image, nk_white);
+    else nk_fill_circle(out, *visual_cursor, cursor->data.color);
+}
+
+NK_INTERN float
+nk_do_slider(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect bounds,
+    float min, float val, float max, float step,
+    const struct nk_style_slider *style, struct nk_input *in,
+    const struct nk_user_font *font)
+{
+    float slider_range;
+    float slider_min;
+    float slider_max;
+    float slider_value;
+    float slider_steps;
+    float cursor_offset;
+
+    struct nk_rect visual_cursor;
+    struct nk_rect logical_cursor;
+
+    NK_ASSERT(style);
+    NK_ASSERT(out);
+    if (!out || !style)
+        return 0;
+
+    /* remove padding from slider bounds */
+    bounds.x = bounds.x + style->padding.x;
+    bounds.y = bounds.y + style->padding.y;
+    bounds.h = NK_MAX(bounds.h, 2*style->padding.y);
+    bounds.w = NK_MAX(bounds.w, 2*style->padding.x + style->cursor_size.x);
+    bounds.w -= 2 * style->padding.x;
+    bounds.h -= 2 * style->padding.y;
+
+    /* optional buttons */
+    if (style->show_buttons) {
+        nk_flags ws;
+        struct nk_rect button;
+        button.y = bounds.y;
+        button.w = bounds.h;
+        button.h = bounds.h;
+
+        /* decrement button */
+        button.x = bounds.x;
+        if (nk_do_button_symbol(&ws, out, button, style->dec_symbol, NK_BUTTON_DEFAULT,
+            &style->dec_button, in, font))
+            val -= step;
+
+        /* increment button */
+        button.x = (bounds.x + bounds.w) - button.w;
+        if (nk_do_button_symbol(&ws, out, button, style->inc_symbol, NK_BUTTON_DEFAULT,
+            &style->inc_button, in, font))
+            val += step;
+
+        bounds.x = bounds.x + button.w + style->spacing.x;
+        bounds.w = bounds.w - (2*button.w + 2*style->spacing.x);
+    }
+
+    /* remove one cursor size to support visual cursor */
+    bounds.x += style->cursor_size.x*0.5f;
+    bounds.w -= style->cursor_size.x;
+
+    /* make sure the provided values are correct */
+    slider_max = NK_MAX(min, max);
+    slider_min = NK_MIN(min, max);
+    slider_value = NK_CLAMP(slider_min, val, slider_max);
+    slider_range = slider_max - slider_min;
+    slider_steps = slider_range / step;
+    cursor_offset = (slider_value - slider_min) / step;
+
+    /* calculate cursor
+    Basically you have two cursors. One for visual representation and interaction
+    and one for updating the actual cursor value. */
+    logical_cursor.h = bounds.h;
+    logical_cursor.w = bounds.w / slider_steps;
+    logical_cursor.x = bounds.x + (logical_cursor.w * cursor_offset);
+    logical_cursor.y = bounds.y;
+
+    visual_cursor.h = style->cursor_size.y;
+    visual_cursor.w = style->cursor_size.x;
+    visual_cursor.y = (bounds.y + bounds.h*0.5f) - visual_cursor.h*0.5f;
+    visual_cursor.x = logical_cursor.x - visual_cursor.w*0.5f;
+
+    slider_value = nk_slider_behavior(state, &logical_cursor, &visual_cursor,
+        in, bounds, slider_min, slider_max, slider_value, step, slider_steps);
+    visual_cursor.x = logical_cursor.x - visual_cursor.w*0.5f;
+
+    /* draw slider */
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_slider(out, *state, style, &bounds, &visual_cursor, slider_min, slider_value, slider_max);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return slider_value;
+}
+
+/* ===============================================================
+ *
+ *                          PROGRESSBAR
+ *
+ * ===============================================================*/
+NK_INTERN nk_size
+nk_progress_behavior(nk_flags *state, const struct nk_input *in,
+    struct nk_rect r, nk_size max, nk_size value, int modifiable)
+{
+    nk_widget_state_reset(state);
+    if (in && modifiable && nk_input_is_mouse_hovering_rect(in, r)) {
+        int left_mouse_down = in->mouse.buttons[NK_BUTTON_LEFT].down;
+        int left_mouse_click_in_cursor = nk_input_has_mouse_click_down_in_rect(in,
+            NK_BUTTON_LEFT, r, nk_true);
+
+        if (left_mouse_down && left_mouse_click_in_cursor) {
+            float ratio = NK_MAX(0, (float)(in->mouse.pos.x - r.x)) / (float)r.w;
+            value = (nk_size)NK_MAX(0,((float)max * ratio));
+            *state = NK_WIDGET_STATE_ACTIVE;
+        } else *state = NK_WIDGET_STATE_HOVERED;
+    }
+
+    /* set progressbar widget state */
+    if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, r))
+        *state |= NK_WIDGET_STATE_ENTERED;
+    else if (nk_input_is_mouse_prev_hovering_rect(in, r))
+        *state |= NK_WIDGET_STATE_LEFT;
+
+    if (!max) return value;
+    value = NK_MIN(value, max);
+    return value;
+}
+
+NK_INTERN void
+nk_draw_progress(struct nk_command_buffer *out, nk_flags state,
+    const struct nk_style_progress *style, const struct nk_rect *bounds,
+    const struct nk_rect *scursor, nk_size value, nk_size max)
+{
+    const struct nk_style_item *background;
+    const struct nk_style_item *cursor;
+
+    NK_UNUSED(max);
+    NK_UNUSED(value);
+
+    /* select correct colors/images to draw */
+    if (state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->active;
+        cursor = &style->cursor_active;
+    } else if (state & NK_WIDGET_STATE_HOVER){
+        background = &style->hover;
+        cursor = &style->cursor_hover;
+    } else {
+        background = &style->normal;
+        cursor = &style->cursor_normal;
+    }
+
+    /* draw background */
+    if (background->type == NK_STYLE_ITEM_COLOR) {
+        nk_fill_rect(out, *bounds, style->rounding, background->data.color);
+        nk_stroke_rect(out, *bounds, style->rounding, style->border, style->border_color);
+    } else nk_draw_image(out, *bounds, &background->data.image, nk_white);
+
+    /* draw cursor */
+    if (cursor->type == NK_STYLE_ITEM_COLOR) {
+        nk_fill_rect(out, *scursor, style->rounding, cursor->data.color);
+        nk_stroke_rect(out, *scursor, style->rounding, style->border, style->border_color);
+    } else nk_draw_image(out, *scursor, &cursor->data.image, nk_white);
+}
+
+NK_INTERN nk_size
+nk_do_progress(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect bounds,
+    nk_size value, nk_size max, int modifiable,
+    const struct nk_style_progress *style, const struct nk_input *in)
+{
+    float prog_scale;
+    nk_size prog_value;
+    struct nk_rect cursor;
+
+    NK_ASSERT(style);
+    NK_ASSERT(out);
+    if (!out || !style) return 0;
+
+    /* calculate progressbar cursor */
+    cursor.w = NK_MAX(bounds.w, 2 * style->padding.x + 2 * style->border);
+    cursor.h = NK_MAX(bounds.h, 2 * style->padding.y + 2 * style->border);
+    cursor = nk_pad_rect(bounds, nk_vec2(style->padding.x + style->border, style->padding.y + style->border));
+    prog_scale = (float)value / (float)max;
+    cursor.w = (bounds.w - 2) * prog_scale;
+
+    /* update progressbar */
+    prog_value = NK_MIN(value, max);
+    prog_value = nk_progress_behavior(state, in, bounds, max, prog_value, modifiable);
+
+    /* draw progressbar */
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_progress(out, *state, style, &bounds, &cursor, value, max);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return prog_value;
+}
+
+/* ===============================================================
+ *
+ *                          SCROLLBAR
+ *
+ * ===============================================================*/
+NK_INTERN float
+nk_scrollbar_behavior(nk_flags *state, struct nk_input *in,
+    int has_scrolling, const struct nk_rect *scroll,
+    const struct nk_rect *cursor, const struct nk_rect *empty0,
+    const struct nk_rect *empty1, float scroll_offset,
+    float target, float scroll_step, enum nk_orientation o)
+{
+    nk_flags ws = 0;
+    int left_mouse_down;
+    int left_mouse_click_in_cursor;
+    float scroll_delta;
+
+    nk_widget_state_reset(state);
+    if (!in) return scroll_offset;
+
+    left_mouse_down = in->mouse.buttons[NK_BUTTON_LEFT].down;
+    left_mouse_click_in_cursor = nk_input_has_mouse_click_down_in_rect(in,
+        NK_BUTTON_LEFT, *cursor, nk_true);
+    if (nk_input_is_mouse_hovering_rect(in, *scroll))
+        *state = NK_WIDGET_STATE_HOVERED;
+
+    scroll_delta = (o == NK_VERTICAL) ? in->mouse.scroll_delta.y: in->mouse.scroll_delta.x;
+    if (left_mouse_down && left_mouse_click_in_cursor) {
+        /* update cursor by mouse dragging */
+        float pixel, delta;
+        *state = NK_WIDGET_STATE_ACTIVE;
+        if (o == NK_VERTICAL) {
+            float cursor_y;
+            pixel = in->mouse.delta.y;
+            delta = (pixel / scroll->h) * target;
+            scroll_offset = NK_CLAMP(0, scroll_offset + delta, target - scroll->h);
+            cursor_y = scroll->y + ((scroll_offset/target) * scroll->h);
+            in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.y = cursor_y + cursor->h/2.0f;
+        } else {
+            float cursor_x;
+            pixel = in->mouse.delta.x;
+            delta = (pixel / scroll->w) * target;
+            scroll_offset = NK_CLAMP(0, scroll_offset + delta, target - scroll->w);
+            cursor_x = scroll->x + ((scroll_offset/target) * scroll->w);
+            in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = cursor_x + cursor->w/2.0f;
+        }
+    } else if ((nk_input_is_key_pressed(in, NK_KEY_SCROLL_UP) && o == NK_VERTICAL && has_scrolling)||
+            nk_button_behavior(&ws, *empty0, in, NK_BUTTON_DEFAULT)) {
+        /* scroll page up by click on empty space or shortcut */
+        if (o == NK_VERTICAL)
+            scroll_offset = NK_MAX(0, scroll_offset - scroll->h);
+        else scroll_offset = NK_MAX(0, scroll_offset - scroll->w);
+    } else if ((nk_input_is_key_pressed(in, NK_KEY_SCROLL_DOWN) && o == NK_VERTICAL && has_scrolling) ||
+        nk_button_behavior(&ws, *empty1, in, NK_BUTTON_DEFAULT)) {
+        /* scroll page down by click on empty space or shortcut */
+        if (o == NK_VERTICAL)
+            scroll_offset = NK_MIN(scroll_offset + scroll->h, target - scroll->h);
+        else scroll_offset = NK_MIN(scroll_offset + scroll->w, target - scroll->w);
+    } else if (has_scrolling) {
+        if ((scroll_delta < 0 || (scroll_delta > 0))) {
+            /* update cursor by mouse scrolling */
+            scroll_offset = scroll_offset + scroll_step * (-scroll_delta);
+            if (o == NK_VERTICAL)
+                scroll_offset = NK_CLAMP(0, scroll_offset, target - scroll->h);
+            else scroll_offset = NK_CLAMP(0, scroll_offset, target - scroll->w);
+        } else if (nk_input_is_key_pressed(in, NK_KEY_SCROLL_START)) {
+            /* update cursor to the beginning  */
+            if (o == NK_VERTICAL) scroll_offset = 0;
+        } else if (nk_input_is_key_pressed(in, NK_KEY_SCROLL_END)) {
+            /* update cursor to the end */
+            if (o == NK_VERTICAL) scroll_offset = target - scroll->h;
+        }
+    }
+    if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, *scroll))
+        *state |= NK_WIDGET_STATE_ENTERED;
+    else if (nk_input_is_mouse_prev_hovering_rect(in, *scroll))
+        *state |= NK_WIDGET_STATE_LEFT;
+    return scroll_offset;
+}
+
+NK_INTERN void
+nk_draw_scrollbar(struct nk_command_buffer *out, nk_flags state,
+    const struct nk_style_scrollbar *style, const struct nk_rect *bounds,
+    const struct nk_rect *scroll)
+{
+    const struct nk_style_item *background;
+    const struct nk_style_item *cursor;
+
+    /* select correct colors/images to draw */
+    if (state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->active;
+        cursor = &style->cursor_active;
+    } else if (state & NK_WIDGET_STATE_HOVER) {
+        background = &style->hover;
+        cursor = &style->cursor_hover;
+    } else {
+        background = &style->normal;
+        cursor = &style->cursor_normal;
+    }
+
+    /* draw background */
+    if (background->type == NK_STYLE_ITEM_COLOR) {
+        nk_fill_rect(out, *bounds, style->rounding, background->data.color);
+        nk_stroke_rect(out, *bounds, style->rounding, style->border, style->border_color);
+    } else {
+        nk_draw_image(out, *bounds, &background->data.image, nk_white);
+    }
+
+    /* draw cursor */
+    if (background->type == NK_STYLE_ITEM_COLOR) {
+        nk_fill_rect(out, *scroll, style->rounding_cursor, cursor->data.color);
+        nk_stroke_rect(out, *scroll, style->rounding_cursor, style->border_cursor, style->cursor_border_color);
+    } else nk_draw_image(out, *scroll, &cursor->data.image, nk_white);
+}
+
+NK_INTERN float
+nk_do_scrollbarv(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect scroll, int has_scrolling,
+    float offset, float target, float step, float button_pixel_inc,
+    const struct nk_style_scrollbar *style, struct nk_input *in,
+    const struct nk_user_font *font)
+{
+    struct nk_rect empty_north;
+    struct nk_rect empty_south;
+    struct nk_rect cursor;
+
+    float scroll_step;
+    float scroll_offset;
+    float scroll_off;
+    float scroll_ratio;
+
+    NK_ASSERT(out);
+    NK_ASSERT(style);
+    NK_ASSERT(state);
+    if (!out || !style) return 0;
+
+    scroll.w = NK_MAX(scroll.w, 1);
+    scroll.h = NK_MAX(scroll.h, 0);
+    if (target <= scroll.h) return 0;
+
+    /* optional scrollbar buttons */
+    if (style->show_buttons) {
+        nk_flags ws;
+        float scroll_h;
+        struct nk_rect button;
+
+        button.x = scroll.x;
+        button.w = scroll.w;
+        button.h = scroll.w;
+
+        scroll_h = NK_MAX(scroll.h - 2 * button.h,0);
+        scroll_step = NK_MIN(step, button_pixel_inc);
+
+        /* decrement button */
+        button.y = scroll.y;
+        if (nk_do_button_symbol(&ws, out, button, style->dec_symbol,
+            NK_BUTTON_REPEATER, &style->dec_button, in, font))
+            offset = offset - scroll_step;
+
+        /* increment button */
+        button.y = scroll.y + scroll.h - button.h;
+        if (nk_do_button_symbol(&ws, out, button, style->inc_symbol,
+            NK_BUTTON_REPEATER, &style->inc_button, in, font))
+            offset = offset + scroll_step;
+
+        scroll.y = scroll.y + button.h;
+        scroll.h = scroll_h;
+    }
+
+    /* calculate scrollbar constants */
+    scroll_step = NK_MIN(step, scroll.h);
+    scroll_offset = NK_CLAMP(0, offset, target - scroll.h);
+    scroll_ratio = scroll.h / target;
+    scroll_off = scroll_offset / target;
+
+    /* calculate scrollbar cursor bounds */
+    cursor.h = NK_MAX((scroll_ratio * scroll.h) - (2*style->border + 2*style->padding.y), 0);
+    cursor.y = scroll.y + (scroll_off * scroll.h) + style->border + style->padding.y;
+    cursor.w = scroll.w - (2 * style->border + 2 * style->padding.x);
+    cursor.x = scroll.x + style->border + style->padding.x;
+
+    /* calculate empty space around cursor */
+    empty_north.x = scroll.x;
+    empty_north.y = scroll.y;
+    empty_north.w = scroll.w;
+    empty_north.h = NK_MAX(cursor.y - scroll.y, 0);
+
+    empty_south.x = scroll.x;
+    empty_south.y = cursor.y + cursor.h;
+    empty_south.w = scroll.w;
+    empty_south.h = NK_MAX((scroll.y + scroll.h) - (cursor.y + cursor.h), 0);
+
+    /* update scrollbar */
+    scroll_offset = nk_scrollbar_behavior(state, in, has_scrolling, &scroll, &cursor,
+        &empty_north, &empty_south, scroll_offset, target, scroll_step, NK_VERTICAL);
+    scroll_off = scroll_offset / target;
+    cursor.y = scroll.y + (scroll_off * scroll.h) + style->border_cursor + style->padding.y;
+
+    /* draw scrollbar */
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_scrollbar(out, *state, style, &scroll, &cursor);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return scroll_offset;
+}
+
+NK_INTERN float
+nk_do_scrollbarh(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_rect scroll, int has_scrolling,
+    float offset, float target, float step, float button_pixel_inc,
+    const struct nk_style_scrollbar *style, struct nk_input *in,
+    const struct nk_user_font *font)
+{
+    struct nk_rect cursor;
+    struct nk_rect empty_west;
+    struct nk_rect empty_east;
+
+    float scroll_step;
+    float scroll_offset;
+    float scroll_off;
+    float scroll_ratio;
+
+    NK_ASSERT(out);
+    NK_ASSERT(style);
+    if (!out || !style) return 0;
+
+    /* scrollbar background */
+    scroll.h = NK_MAX(scroll.h, 1);
+    scroll.w = NK_MAX(scroll.w, 2 * scroll.h);
+    if (target <= scroll.w) return 0;
+
+    /* optional scrollbar buttons */
+    if (style->show_buttons) {
+        nk_flags ws;
+        float scroll_w;
+        struct nk_rect button;
+        button.y = scroll.y;
+        button.w = scroll.h;
+        button.h = scroll.h;
+
+        scroll_w = scroll.w - 2 * button.w;
+        scroll_step = NK_MIN(step, button_pixel_inc);
+
+        /* decrement button */
+        button.x = scroll.x;
+        if (nk_do_button_symbol(&ws, out, button, style->dec_symbol,
+            NK_BUTTON_REPEATER, &style->dec_button, in, font))
+            offset = offset - scroll_step;
+
+        /* increment button */
+        button.x = scroll.x + scroll.w - button.w;
+        if (nk_do_button_symbol(&ws, out, button, style->inc_symbol,
+            NK_BUTTON_REPEATER, &style->inc_button, in, font))
+            offset = offset + scroll_step;
+
+        scroll.x = scroll.x + button.w;
+        scroll.w = scroll_w;
+    }
+
+    /* calculate scrollbar constants */
+    scroll_step = NK_MIN(step, scroll.w);
+    scroll_offset = NK_CLAMP(0, offset, target - scroll.w);
+    scroll_ratio = scroll.w / target;
+    scroll_off = scroll_offset / target;
+
+    /* calculate cursor bounds */
+    cursor.w = (scroll_ratio * scroll.w) - (2*style->border + 2*style->padding.x);
+    cursor.x = scroll.x + (scroll_off * scroll.w) + style->border + style->padding.x;
+    cursor.h = scroll.h - (2 * style->border + 2 * style->padding.y);
+    cursor.y = scroll.y + style->border + style->padding.y;
+
+    /* calculate empty space around cursor */
+    empty_west.x = scroll.x;
+    empty_west.y = scroll.y;
+    empty_west.w = cursor.x - scroll.x;
+    empty_west.h = scroll.h;
+
+    empty_east.x = cursor.x + cursor.w;
+    empty_east.y = scroll.y;
+    empty_east.w = (scroll.x + scroll.w) - (cursor.x + cursor.w);
+    empty_east.h = scroll.h;
+
+    /* update scrollbar */
+    scroll_offset = nk_scrollbar_behavior(state, in, has_scrolling, &scroll, &cursor,
+        &empty_west, &empty_east, scroll_offset, target, scroll_step, NK_HORIZONTAL);
+    scroll_off = scroll_offset / target;
+    cursor.x = scroll.x + (scroll_off * scroll.w);
+
+    /* draw scrollbar */
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_scrollbar(out, *state, style, &scroll, &cursor);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+    return scroll_offset;
+}
+
+/* ===============================================================
+ *
+ *                          FILTER
+ *
+ * ===============================================================*/
+NK_API int nk_filter_default(const struct nk_text_edit *box, nk_rune unicode)
+{(void)unicode;NK_UNUSED(box);return nk_true;}
+
+NK_API int
+nk_filter_ascii(const struct nk_text_edit *box, nk_rune unicode)
+{
+    NK_UNUSED(box);
+    if (unicode > 128) return nk_false;
+    else return nk_true;
+}
+
+NK_API int
+nk_filter_float(const struct nk_text_edit *box, nk_rune unicode)
+{
+    NK_UNUSED(box);
+    if ((unicode < '0' || unicode > '9') && unicode != '.' && unicode != '-')
+        return nk_false;
+    else return nk_true;
+}
+
+NK_API int
+nk_filter_decimal(const struct nk_text_edit *box, nk_rune unicode)
+{
+    NK_UNUSED(box);
+    if ((unicode < '0' || unicode > '9') && unicode != '-')
+        return nk_false;
+    else return nk_true;
+}
+
+NK_API int
+nk_filter_hex(const struct nk_text_edit *box, nk_rune unicode)
+{
+    NK_UNUSED(box);
+    if ((unicode < '0' || unicode > '9') &&
+        (unicode < 'a' || unicode > 'f') &&
+        (unicode < 'A' || unicode > 'F'))
+        return nk_false;
+    else return nk_true;
+}
+
+NK_API int
+nk_filter_oct(const struct nk_text_edit *box, nk_rune unicode)
+{
+    NK_UNUSED(box);
+    if (unicode < '0' || unicode > '7')
+        return nk_false;
+    else return nk_true;
+}
+
+NK_API int
+nk_filter_binary(const struct nk_text_edit *box, nk_rune unicode)
+{
+    NK_UNUSED(box);
+    if (unicode != '0' && unicode != '1')
+        return nk_false;
+    else return nk_true;
+}
+
+/* ===============================================================
+ *
+ *                          EDIT
+ *
+ * ===============================================================*/
+NK_INTERN void
+nk_edit_draw_text(struct nk_command_buffer *out,
+    const struct nk_style_edit *style, float pos_x, float pos_y,
+    float x_offset, const char *text, int byte_len, float row_height,
+    const struct nk_user_font *font, struct nk_color background,
+    struct nk_color foreground, int is_selected)
+{
+    NK_ASSERT(out);
+    NK_ASSERT(font);
+    NK_ASSERT(style);
+    if (!text || !byte_len || !out || !style) return;
+
+    {int glyph_len = 0;
+    nk_rune unicode = 0;
+    int text_len = 0;
+    float line_width = 0;
+    float glyph_width;
+    const char *line = text;
+    float line_offset = 0;
+    int line_count = 0;
+
+    struct nk_text txt;
+    txt.padding = nk_vec2(0,0);
+    txt.background = background;
+    txt.text = foreground;
+
+    glyph_len = nk_utf_decode(text+text_len, &unicode, byte_len-text_len);
+    if (!glyph_len) return;
+    while ((text_len < byte_len) && glyph_len)
+    {
+        if (unicode == '\n') {
+            /* new line separator so draw previous line */
+            struct nk_rect label;
+            label.y = pos_y + line_offset;
+            label.h = row_height;
+            label.w = line_width;
+            label.x = pos_x;
+            if (!line_count)
+                label.x += x_offset;
+
+            if (is_selected) /* selection needs to draw different background color */
+                nk_fill_rect(out, label, 0, background);
+            nk_widget_text(out, label, line, (int)((text + text_len) - line),
+                &txt, NK_TEXT_CENTERED, font);
+
+            text_len++;
+            line_count++;
+            line_width = 0;
+            line = text + text_len;
+            line_offset += row_height;
+            glyph_len = nk_utf_decode(text + text_len, &unicode, (int)(byte_len-text_len));
+            continue;
+        }
+        if (unicode == '\r') {
+            text_len++;
+            glyph_len = nk_utf_decode(text + text_len, &unicode, byte_len-text_len);
+            continue;
+        }
+        glyph_width = font->width(font->userdata, font->height, text+text_len, glyph_len);
+        line_width += (float)glyph_width;
+        text_len += glyph_len;
+        glyph_len = nk_utf_decode(text + text_len, &unicode, byte_len-text_len);
+        continue;
+    }
+    if (line_width > 0) {
+        /* draw last line */
+        struct nk_rect label;
+        label.y = pos_y + line_offset;
+        label.h = row_height;
+        label.w = line_width;
+        label.x = pos_x;
+        if (!line_count)
+            label.x += x_offset;
+
+        if (is_selected)
+            nk_fill_rect(out, label, 0, background);
+        nk_widget_text(out, label, line, (int)((text + text_len) - line),
+            &txt, NK_TEXT_LEFT, font);
+    }}
+}
+
+NK_INTERN nk_flags
+nk_do_edit(nk_flags *state, struct nk_command_buffer *out,
+    struct nk_rect bounds, nk_flags flags, nk_plugin_filter filter,
+    struct nk_text_edit *edit, const struct nk_style_edit *style,
+    struct nk_input *in, const struct nk_user_font *font)
+{
+    struct nk_rect area;
+    nk_flags ret = 0;
+    float row_height;
+    char prev_state = 0;
+    char is_hovered = 0;
+    char select_all = 0;
+    char cursor_follow = 0;
+    struct nk_rect old_clip;
+    struct nk_rect clip;
+
+    NK_ASSERT(state);
+    NK_ASSERT(out);
+    NK_ASSERT(style);
+    if (!state || !out || !style)
+        return ret;
+
+    /* visible text area calculation */
+    area.x = bounds.x + style->padding.x + style->border;
+    area.y = bounds.y + style->padding.y + style->border;
+    area.w = bounds.w - (2.0f * style->padding.x + 2 * style->border);
+    area.h = bounds.h - (2.0f * style->padding.y + 2 * style->border);
+    if (flags & NK_EDIT_MULTILINE)
+        area.w = NK_MAX(0, area.w - style->scrollbar_size.x);
+    row_height = (flags & NK_EDIT_MULTILINE)? font->height + style->row_padding: area.h;
+
+    /* calculate clipping rectangle */
+    old_clip = out->clip;
+    nk_unify(&clip, &old_clip, area.x, area.y, area.x + area.w, area.y + area.h);
+
+    /* update edit state */
+    prev_state = (char)edit->active;
+    is_hovered = (char)nk_input_is_mouse_hovering_rect(in, bounds);
+    if (in && in->mouse.buttons[NK_BUTTON_LEFT].clicked && in->mouse.buttons[NK_BUTTON_LEFT].down) {
+        edit->active = NK_INBOX(in->mouse.pos.x, in->mouse.pos.y,
+                                bounds.x, bounds.y, bounds.w, bounds.h);
+    }
+
+    /* (de)activate text editor */
+    if (!prev_state && edit->active) {
+        const enum nk_text_edit_type type = (flags & NK_EDIT_MULTILINE) ?
+            NK_TEXT_EDIT_MULTI_LINE: NK_TEXT_EDIT_SINGLE_LINE;
+        nk_textedit_clear_state(edit, type, filter);
+        if (flags & NK_EDIT_ALWAYS_INSERT_MODE)
+            edit->mode = NK_TEXT_EDIT_MODE_INSERT;
+        if (flags & NK_EDIT_AUTO_SELECT)
+            select_all = nk_true;
+        if (flags & NK_EDIT_GOTO_END_ON_ACTIVATE) {
+            edit->cursor = edit->string.len;
+            in = 0;
+        }
+    } else if (!edit->active) edit->mode = NK_TEXT_EDIT_MODE_VIEW;
+    if (flags & NK_EDIT_READ_ONLY)
+        edit->mode = NK_TEXT_EDIT_MODE_VIEW;
+
+    ret = (edit->active) ? NK_EDIT_ACTIVE: NK_EDIT_INACTIVE;
+    if (prev_state != edit->active)
+        ret |= (edit->active) ? NK_EDIT_ACTIVATED: NK_EDIT_DEACTIVATED;
+
+    /* handle user input */
+    if (edit->active && in)
+    {
+        int shift_mod = in->keyboard.keys[NK_KEY_SHIFT].down;
+        const float mouse_x = (in->mouse.pos.x - area.x) + edit->scrollbar.x;
+        const float mouse_y = (in->mouse.pos.y - area.y) + edit->scrollbar.y;
+
+        /* mouse click handler */
+        is_hovered = (char)nk_input_is_mouse_hovering_rect(in, area);
+        if (select_all) {
+            nk_textedit_select_all(edit);
+        } else if (is_hovered && in->mouse.buttons[NK_BUTTON_LEFT].down &&
+            in->mouse.buttons[NK_BUTTON_LEFT].clicked) {
+            nk_textedit_click(edit, mouse_x, mouse_y, font, row_height);
+        } else if (is_hovered && in->mouse.buttons[NK_BUTTON_LEFT].down &&
+            (in->mouse.delta.x != 0.0f || in->mouse.delta.y != 0.0f)) {
+            nk_textedit_drag(edit, mouse_x, mouse_y, font, row_height);
+            cursor_follow = nk_true;
+        } else if (is_hovered && in->mouse.buttons[NK_BUTTON_RIGHT].clicked &&
+            in->mouse.buttons[NK_BUTTON_RIGHT].down) {
+            nk_textedit_key(edit, NK_KEY_TEXT_WORD_LEFT, nk_false, font, row_height);
+            nk_textedit_key(edit, NK_KEY_TEXT_WORD_RIGHT, nk_true, font, row_height);
+            cursor_follow = nk_true;
+        }
+
+        {int i; /* keyboard input */
+        int old_mode = edit->mode;
+        for (i = 0; i < NK_KEY_MAX; ++i) {
+            if (i == NK_KEY_ENTER || i == NK_KEY_TAB) continue; /* special case */
+            if (nk_input_is_key_pressed(in, (enum nk_keys)i)) {
+                nk_textedit_key(edit, (enum nk_keys)i, shift_mod, font, row_height);
+                cursor_follow = nk_true;
+            }
+        }
+        if (old_mode != edit->mode) {
+            in->keyboard.text_len = 0;
+        }}
+
+        /* text input */
+        edit->filter = filter;
+        if (in->keyboard.text_len) {
+            nk_textedit_text(edit, in->keyboard.text, in->keyboard.text_len);
+            cursor_follow = nk_true;
+            in->keyboard.text_len = 0;
+        }
+
+        /* enter key handler */
+        if (nk_input_is_key_pressed(in, NK_KEY_ENTER)) {
+            cursor_follow = nk_true;
+            if (flags & NK_EDIT_CTRL_ENTER_NEWLINE && shift_mod)
+                nk_textedit_text(edit, "\n", 1);
+            else if (flags & NK_EDIT_SIG_ENTER)
+                ret |= NK_EDIT_COMMITED;
+            else nk_textedit_text(edit, "\n", 1);
+        }
+
+        /* cut & copy handler */
+        {int copy= nk_input_is_key_pressed(in, NK_KEY_COPY);
+        int cut = nk_input_is_key_pressed(in, NK_KEY_CUT);
+        if ((copy || cut) && (flags & NK_EDIT_CLIPBOARD))
+        {
+            int glyph_len;
+            nk_rune unicode;
+            const char *text;
+            int b = edit->select_start;
+            int e = edit->select_end;
+
+            int begin = NK_MIN(b, e);
+            int end = NK_MAX(b, e);
+            text = nk_str_at_const(&edit->string, begin, &unicode, &glyph_len);
+            if (edit->clip.copy)
+                edit->clip.copy(edit->clip.userdata, text, end - begin);
+            if (cut && !(flags & NK_EDIT_READ_ONLY)){
+                nk_textedit_cut(edit);
+                cursor_follow = nk_true;
+            }
+        }}
+
+        /* paste handler */
+        {int paste = nk_input_is_key_pressed(in, NK_KEY_PASTE);
+        if (paste && (flags & NK_EDIT_CLIPBOARD) && edit->clip.paste) {
+            edit->clip.paste(edit->clip.userdata, edit);
+            cursor_follow = nk_true;
+        }}
+
+        /* tab handler */
+        {int tab = nk_input_is_key_pressed(in, NK_KEY_TAB);
+        if (tab && (flags & NK_EDIT_ALLOW_TAB)) {
+            nk_textedit_text(edit, "    ", 4);
+            cursor_follow = nk_true;
+        }}
+    }
+
+    /* set widget state */
+    if (edit->active)
+        *state = NK_WIDGET_STATE_ACTIVE;
+    else nk_widget_state_reset(state);
+
+    if (is_hovered)
+        *state |= NK_WIDGET_STATE_HOVERED;
+
+    /* DRAW EDIT */
+    {const char *text = nk_str_get_const(&edit->string);
+    int len = nk_str_len_char(&edit->string);
+
+    {/* select background colors/images  */
+    const struct nk_style_item *background;
+    if (*state & NK_WIDGET_STATE_ACTIVED)
+        background = &style->active;
+    else if (*state & NK_WIDGET_STATE_HOVER)
+        background = &style->hover;
+    else background = &style->normal;
+
+    /* draw background frame */
+    if (background->type == NK_STYLE_ITEM_COLOR) {
+        nk_stroke_rect(out, bounds, style->rounding, style->border, style->border_color);
+        nk_fill_rect(out, bounds, style->rounding, background->data.color);
+    } else nk_draw_image(out, bounds, &background->data.image, nk_white);}
+
+    area.w = NK_MAX(0, area.w - style->cursor_size);
+    if (edit->active)
+    {
+        int total_lines = 1;
+        struct nk_vec2 text_size = nk_vec2(0,0);
+
+        /* text pointer positions */
+        const char *cursor_ptr = 0;
+        const char *select_begin_ptr = 0;
+        const char *select_end_ptr = 0;
+
+        /* 2D pixel positions */
+        struct nk_vec2 cursor_pos = nk_vec2(0,0);
+        struct nk_vec2 selection_offset_start = nk_vec2(0,0);
+        struct nk_vec2 selection_offset_end = nk_vec2(0,0);
+
+        int selection_begin = NK_MIN(edit->select_start, edit->select_end);
+        int selection_end = NK_MAX(edit->select_start, edit->select_end);
+
+        /* calculate total line count + total space + cursor/selection position */
+        float line_width = 0.0f;
+        if (text && len)
+        {
+            /* utf8 encoding */
+            float glyph_width;
+            int glyph_len = 0;
+            nk_rune unicode = 0;
+            int text_len = 0;
+            int glyphs = 0;
+            int row_begin = 0;
+
+            glyph_len = nk_utf_decode(text, &unicode, len);
+            glyph_width = font->width(font->userdata, font->height, text, glyph_len);
+            line_width = 0;
+
+            /* iterate all lines */
+            while ((text_len < len) && glyph_len)
+            {
+                /* set cursor 2D position and line */
+                if (!cursor_ptr && glyphs == edit->cursor)
+                {
+                    int glyph_offset;
+                    struct nk_vec2 out_offset;
+                    struct nk_vec2 row_size;
+                    const char *remaining;
+
+                    /* calculate 2d position */
+                    cursor_pos.y = (float)(total_lines-1) * row_height;
+                    row_size = nk_text_calculate_text_bounds(font, text+row_begin,
+                                text_len-row_begin, row_height, &remaining,
+                                &out_offset, &glyph_offset, NK_STOP_ON_NEW_LINE);
+                    cursor_pos.x = row_size.x;
+                    cursor_ptr = text + text_len;
+                }
+
+                /* set start selection 2D position and line */
+                if (!select_begin_ptr && edit->select_start != edit->select_end &&
+                    glyphs == selection_begin)
+                {
+                    int glyph_offset;
+                    struct nk_vec2 out_offset;
+                    struct nk_vec2 row_size;
+                    const char *remaining;
+
+                    /* calculate 2d position */
+                    selection_offset_start.y = (float)(NK_MAX(total_lines-1,0)) * row_height;
+                    row_size = nk_text_calculate_text_bounds(font, text+row_begin,
+                                text_len-row_begin, row_height, &remaining,
+                                &out_offset, &glyph_offset, NK_STOP_ON_NEW_LINE);
+                    selection_offset_start.x = row_size.x;
+                    select_begin_ptr = text + text_len;
+                }
+
+                /* set end selection 2D position and line */
+                if (!select_end_ptr && edit->select_start != edit->select_end &&
+                    glyphs == selection_end)
+                {
+                    int glyph_offset;
+                    struct nk_vec2 out_offset;
+                    struct nk_vec2 row_size;
+                    const char *remaining;
+
+                    /* calculate 2d position */
+                    selection_offset_end.y = (float)(total_lines-1) * row_height;
+                    row_size = nk_text_calculate_text_bounds(font, text+row_begin,
+                                text_len-row_begin, row_height, &remaining,
+                                &out_offset, &glyph_offset, NK_STOP_ON_NEW_LINE);
+                    selection_offset_end.x = row_size.x;
+                    select_end_ptr = text + text_len;
+                }
+                if (unicode == '\n') {
+                    text_size.x = NK_MAX(text_size.x, line_width);
+                    total_lines++;
+                    line_width = 0;
+                    text_len++;
+                    glyphs++;
+                    row_begin = text_len;
+                    glyph_len = nk_utf_decode(text + text_len, &unicode, len-text_len);
+                    glyph_width = font->width(font->userdata, font->height, text+text_len, glyph_len);
+                    continue;
+                }
+
+                glyphs++;
+                text_len += glyph_len;
+                line_width += (float)glyph_width;
+
+                glyph_len = nk_utf_decode(text + text_len, &unicode, len-text_len);
+                glyph_width = font->width(font->userdata, font->height,
+                    text+text_len, glyph_len);
+                continue;
+            }
+            text_size.y = (float)total_lines * row_height;
+
+            /* handle case when cursor is at end of text buffer */
+            if (!cursor_ptr && edit->cursor == edit->string.len) {
+                cursor_pos.x = line_width;
+                cursor_pos.y = text_size.y - row_height;
+            }
+        }
+        {
+            /* scrollbar */
+            if (cursor_follow)
+            {
+                /* update scrollbar to follow cursor */
+                if (!(flags & NK_EDIT_NO_HORIZONTAL_SCROLL)) {
+                    /* horizontal scroll */
+                    const float scroll_increment = area.w * 0.25f;
+                    if (cursor_pos.x < edit->scrollbar.x)
+                        edit->scrollbar.x = (float)(int)NK_MAX(0.0f, cursor_pos.x - scroll_increment);
+                    if (cursor_pos.x >= edit->scrollbar.x + area.w)
+                        edit->scrollbar.x = (float)(int)NK_MAX(0.0f, cursor_pos.x);
+                } else edit->scrollbar.x = 0;
+
+                if (flags & NK_EDIT_MULTILINE) {
+                    /* vertical scroll */
+                    if (cursor_pos.y < edit->scrollbar.y)
+                        edit->scrollbar.y = NK_MAX(0.0f, cursor_pos.y - row_height);
+                    if (cursor_pos.y >= edit->scrollbar.y + area.h)
+                        edit->scrollbar.y = edit->scrollbar.y + row_height;
+                } else edit->scrollbar.y = 0;
+            }
+
+            /* scrollbar widget */
+            if (flags & NK_EDIT_MULTILINE)
+            {
+                nk_flags ws;
+                struct nk_rect scroll;
+                float scroll_target;
+                float scroll_offset;
+                float scroll_step;
+                float scroll_inc;
+
+                scroll = area;
+                scroll.x = (bounds.x + bounds.w - style->border) - style->scrollbar_size.x;
+                scroll.w = style->scrollbar_size.x;
+
+                scroll_offset = edit->scrollbar.y;
+                scroll_step = scroll.h * 0.10f;
+                scroll_inc = scroll.h * 0.01f;
+                scroll_target = text_size.y;
+                edit->scrollbar.y = nk_do_scrollbarv(&ws, out, scroll, 0,
+                        scroll_offset, scroll_target, scroll_step, scroll_inc,
+                        &style->scrollbar, in, font);
+            }
+        }
+
+        /* draw text */
+        {struct nk_color background_color;
+        struct nk_color text_color;
+        struct nk_color sel_background_color;
+        struct nk_color sel_text_color;
+        struct nk_color cursor_color;
+        struct nk_color cursor_text_color;
+        const struct nk_style_item *background;
+        nk_push_scissor(out, clip);
+
+        /* select correct colors to draw */
+        if (*state & NK_WIDGET_STATE_ACTIVED) {
+            background = &style->active;
+            text_color = style->text_active;
+            sel_text_color = style->selected_text_hover;
+            sel_background_color = style->selected_hover;
+            cursor_color = style->cursor_hover;
+            cursor_text_color = style->cursor_text_hover;
+        } else if (*state & NK_WIDGET_STATE_HOVER) {
+            background = &style->hover;
+            text_color = style->text_hover;
+            sel_text_color = style->selected_text_hover;
+            sel_background_color = style->selected_hover;
+            cursor_text_color = style->cursor_text_hover;
+            cursor_color = style->cursor_hover;
+        } else {
+            background = &style->normal;
+            text_color = style->text_normal;
+            sel_text_color = style->selected_text_normal;
+            sel_background_color = style->selected_normal;
+            cursor_color = style->cursor_normal;
+            cursor_text_color = style->cursor_text_normal;
+        }
+        if (background->type == NK_STYLE_ITEM_IMAGE)
+            background_color = nk_rgba(0,0,0,0);
+        else background_color = background->data.color;
+
+
+        if (edit->select_start == edit->select_end) {
+            /* no selection so just draw the complete text */
+            const char *begin = nk_str_get_const(&edit->string);
+            int l = nk_str_len_char(&edit->string);
+            nk_edit_draw_text(out, style, area.x - edit->scrollbar.x,
+                area.y - edit->scrollbar.y, 0, begin, l, row_height, font,
+                background_color, text_color, nk_false);
+        } else {
+            /* edit has selection so draw 1-3 text chunks */
+            if (edit->select_start != edit->select_end && selection_begin > 0){
+                /* draw unselected text before selection */
+                const char *begin = nk_str_get_const(&edit->string);
+                NK_ASSERT(select_begin_ptr);
+                nk_edit_draw_text(out, style, area.x - edit->scrollbar.x,
+                    area.y - edit->scrollbar.y, 0, begin, (int)(select_begin_ptr - begin),
+                    row_height, font, background_color, text_color, nk_false);
+            }
+            if (edit->select_start != edit->select_end) {
+                /* draw selected text */
+                NK_ASSERT(select_begin_ptr);
+                if (!select_end_ptr) {
+                    const char *begin = nk_str_get_const(&edit->string);
+                    select_end_ptr = begin + nk_str_len_char(&edit->string);
+                }
+                nk_edit_draw_text(out, style,
+                    area.x - edit->scrollbar.x,
+                    area.y + selection_offset_start.y - edit->scrollbar.y,
+                    selection_offset_start.x,
+                    select_begin_ptr, (int)(select_end_ptr - select_begin_ptr),
+                    row_height, font, sel_background_color, sel_text_color, nk_true);
+            }
+            if ((edit->select_start != edit->select_end &&
+                selection_end < edit->string.len))
+            {
+                /* draw unselected text after selected text */
+                const char *begin = select_end_ptr;
+                const char *end = nk_str_get_const(&edit->string) +
+                                    nk_str_len_char(&edit->string);
+                NK_ASSERT(select_end_ptr);
+                nk_edit_draw_text(out, style,
+                    area.x - edit->scrollbar.x,
+                    area.y + selection_offset_end.y - edit->scrollbar.y,
+                    selection_offset_end.x,
+                    begin, (int)(end - begin), row_height, font,
+                    background_color, text_color, nk_true);
+            }
+        }
+
+        /* cursor */
+        if (edit->select_start == edit->select_end)
+        {
+            if (edit->cursor >= nk_str_len(&edit->string) ||
+                (cursor_ptr && *cursor_ptr == '\n')) {
+                /* draw cursor at end of line */
+                struct nk_rect cursor;
+                cursor.w = style->cursor_size;
+                cursor.h = font->height;
+                cursor.x = area.x + cursor_pos.x - edit->scrollbar.x;
+                cursor.y = area.y + cursor_pos.y + row_height/2.0f - cursor.h/2.0f;
+                cursor.y -= edit->scrollbar.y;
+                nk_fill_rect(out, cursor, 0, cursor_color);
+            } else {
+                /* draw cursor inside text */
+                int glyph_len;
+                struct nk_rect label;
+                struct nk_text txt;
+
+                nk_rune unicode;
+                NK_ASSERT(cursor_ptr);
+                glyph_len = nk_utf_decode(cursor_ptr, &unicode, 4);
+
+                label.x = area.x + cursor_pos.x - edit->scrollbar.x;
+                label.y = area.y + cursor_pos.y - edit->scrollbar.y;
+                label.w = font->width(font->userdata, font->height, cursor_ptr, glyph_len);
+                label.h = row_height;
+
+                txt.padding = nk_vec2(0,0);
+                txt.background = cursor_color;;
+                txt.text = cursor_text_color;
+                nk_fill_rect(out, label, 0, cursor_color);
+                nk_widget_text(out, label, cursor_ptr, glyph_len, &txt, NK_TEXT_LEFT, font);
+            }
+        }}
+    } else {
+        /* not active so just draw text */
+        int l = nk_str_len_char(&edit->string);
+        const char *begin = nk_str_get_const(&edit->string);
+
+        const struct nk_style_item *background;
+        struct nk_color background_color;
+        struct nk_color text_color;
+        nk_push_scissor(out, clip);
+        if (*state & NK_WIDGET_STATE_ACTIVED) {
+            background = &style->active;
+            text_color = style->text_active;
+        } else if (*state & NK_WIDGET_STATE_HOVER) {
+            background = &style->hover;
+            text_color = style->text_hover;
+        } else {
+            background = &style->normal;
+            text_color = style->text_normal;
+        }
+        if (background->type == NK_STYLE_ITEM_IMAGE)
+            background_color = nk_rgba(0,0,0,0);
+        else background_color = background->data.color;
+        nk_edit_draw_text(out, style, area.x - edit->scrollbar.x,
+            area.y - edit->scrollbar.y, 0, begin, l, row_height, font,
+            background_color, text_color, nk_false);
+    }
+    nk_push_scissor(out, old_clip);}
+    return ret;
+}
+
+/* ===============================================================
+ *
+ *                          PROPERTY
+ *
+ * ===============================================================*/
+enum nk_property_status {
+    NK_PROPERTY_DEFAULT,
+    NK_PROPERTY_EDIT,
+    NK_PROPERTY_DRAG
+};
+enum nk_property_filter {
+    NK_FILTER_INT,
+    NK_FILTER_FLOAT
+};
+enum nk_property_kind {
+    NK_PROPERTY_INT,
+    NK_PROPERTY_FLOAT,
+    NK_PROPERTY_DOUBLE
+};
+union nk_property {
+    int i;
+    float f;
+    double d;
+};
+struct nk_property_variant {
+    enum nk_property_kind kind;
+    union nk_property value;
+    union nk_property min_value;
+    union nk_property max_value;
+    union nk_property step;
+};
+
+NK_INTERN void
+nk_drag_behavior(nk_flags *state, const struct nk_input *in,
+    struct nk_rect drag, struct nk_property_variant *variant,
+    float inc_per_pixel)
+{
+    int left_mouse_down = in && in->mouse.buttons[NK_BUTTON_LEFT].down;
+    int left_mouse_click_in_cursor = in &&
+        nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, drag, nk_true);
+
+    nk_widget_state_reset(state);
+    if (nk_input_is_mouse_hovering_rect(in, drag))
+        *state = NK_WIDGET_STATE_HOVERED;
+
+    if (left_mouse_down && left_mouse_click_in_cursor) {
+        float delta, pixels;
+        pixels = in->mouse.delta.x;
+        delta = pixels * inc_per_pixel;
+        switch (variant->kind) {
+        default: break;
+        case NK_PROPERTY_INT:
+            variant->value.i = variant->value.i + (int)delta;
+            variant->value.i = NK_CLAMP(variant->min_value.i, variant->value.i, variant->max_value.i);
+            break;
+        case NK_PROPERTY_FLOAT:
+            variant->value.f = variant->value.f + (float)delta;
+            variant->value.f = NK_CLAMP(variant->min_value.f, variant->value.f, variant->max_value.f);
+            break;
+        case NK_PROPERTY_DOUBLE:
+            variant->value.d = variant->value.d + (double)delta;
+            variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d, variant->max_value.d);
+            break;
+        }
+        *state = NK_WIDGET_STATE_ACTIVE;
+    }
+    if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, drag))
+        *state |= NK_WIDGET_STATE_ENTERED;
+    else if (nk_input_is_mouse_prev_hovering_rect(in, drag))
+        *state |= NK_WIDGET_STATE_LEFT;
+}
+
+NK_INTERN void
+nk_property_behavior(nk_flags *ws, const struct nk_input *in,
+    struct nk_rect property,  struct nk_rect label, struct nk_rect edit,
+    struct nk_rect empty, int *state, struct nk_property_variant *variant,
+    float inc_per_pixel)
+{
+    if (in && *state == NK_PROPERTY_DEFAULT) {
+        if (nk_button_behavior(ws, edit, in, NK_BUTTON_DEFAULT))
+            *state = NK_PROPERTY_EDIT;
+        else if (nk_input_is_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, label, nk_true))
+            *state = NK_PROPERTY_DRAG;
+        else if (nk_input_is_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, empty, nk_true))
+            *state = NK_PROPERTY_DRAG;
+    }
+    if (*state == NK_PROPERTY_DRAG) {
+        nk_drag_behavior(ws, in, property, variant, inc_per_pixel);
+        if (!(*ws & NK_WIDGET_STATE_ACTIVED)) *state = NK_PROPERTY_DEFAULT;
+    }
+}
+
+NK_INTERN void
+nk_draw_property(struct nk_command_buffer *out, const struct nk_style_property *style,
+    const struct nk_rect *bounds, const struct nk_rect *label, nk_flags state,
+    const char *name, int len, const struct nk_user_font *font)
+{
+    struct nk_text text;
+    const struct nk_style_item *background;
+
+    /* select correct background and text color */
+    if (state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->active;
+        text.text = style->label_active;
+    } else if (state & NK_WIDGET_STATE_HOVER) {
+        background = &style->hover;
+        text.text = style->label_hover;
+    } else {
+        background = &style->normal;
+        text.text = style->label_normal;
+    }
+
+    /* draw background */
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        nk_draw_image(out, *bounds, &background->data.image, nk_white);
+        text.background = nk_rgba(0,0,0,0);
+    } else {
+        text.background = background->data.color;
+        nk_fill_rect(out, *bounds, style->rounding, background->data.color);
+        nk_stroke_rect(out, *bounds, style->rounding, style->border, background->data.color);
+    }
+
+    /* draw label */
+    text.padding = nk_vec2(0,0);
+    nk_widget_text(out, *label, name, len, &text, NK_TEXT_CENTERED, font);
+}
+
+NK_INTERN void
+nk_do_property(nk_flags *ws,
+    struct nk_command_buffer *out, struct nk_rect property,
+    const char *name, struct nk_property_variant *variant,
+    float inc_per_pixel, char *buffer, int *len,
+    int *state, int *cursor, int *select_begin, int *select_end,
+    const struct nk_style_property *style,
+    enum nk_property_filter filter, struct nk_input *in,
+    const struct nk_user_font *font, struct nk_text_edit *text_edit,
+    enum nk_button_behavior behavior)
+{
+    const nk_plugin_filter filters[] = {
+        nk_filter_decimal,
+        nk_filter_float
+    };
+    int active, old;
+    int num_len, name_len;
+    char string[NK_MAX_NUMBER_BUFFER];
+    float size;
+
+    char *dst = 0;
+    int *length;
+
+    struct nk_rect left;
+    struct nk_rect right;
+    struct nk_rect label;
+    struct nk_rect edit;
+    struct nk_rect empty;
+
+    /* left decrement button */
+    left.h = font->height/2;
+    left.w = left.h;
+    left.x = property.x + style->border + style->padding.x;
+    left.y = property.y + style->border + property.h/2.0f - left.h/2;
+
+    /* text label */
+    name_len = nk_strlen(name);
+    size = font->width(font->userdata, font->height, name, name_len);
+    label.x = left.x + left.w + style->padding.x;
+    label.w = (float)size + 2 * style->padding.x;
+    label.y = property.y + style->border + style->padding.y;
+    label.h = property.h - (2 * style->border + 2 * style->padding.y);
+
+    /* right increment button */
+    right.y = left.y;
+    right.w = left.w;
+    right.h = left.h;
+    right.x = property.x + property.w - (right.w + style->padding.x);
+
+    /* edit */
+    if (*state == NK_PROPERTY_EDIT) {
+        size = font->width(font->userdata, font->height, buffer, *len);
+        size += style->edit.cursor_size;
+        length = len;
+        dst = buffer;
+    } else {
+        switch (variant->kind) {
+        default: break;
+        case NK_PROPERTY_INT:
+            nk_itoa(string, variant->value.i);
+            num_len = nk_strlen(string);
+            break;
+        case NK_PROPERTY_FLOAT:
+            nk_dtoa(string, (double)variant->value.f);
+            num_len = nk_string_float_limit(string, NK_MAX_FLOAT_PRECISION);
+            break;
+        case NK_PROPERTY_DOUBLE:
+            nk_dtoa(string, variant->value.d);
+            num_len = nk_string_float_limit(string, NK_MAX_FLOAT_PRECISION);
+            break;
+        }
+        size = font->width(font->userdata, font->height, string, num_len);
+        dst = string;
+        length = &num_len;
+    }
+
+    edit.w =  (float)size + 2 * style->padding.x;
+    edit.w = NK_MIN(edit.w, right.x - (label.x + label.w));
+    edit.x = right.x - (edit.w + style->padding.x);
+    edit.y = property.y + style->border;
+    edit.h = property.h - (2 * style->border);
+
+    /* empty left space activator */
+    empty.w = edit.x - (label.x + label.w);
+    empty.x = label.x + label.w;
+    empty.y = property.y;
+    empty.h = property.h;
+
+    /* update property */
+    old = (*state == NK_PROPERTY_EDIT);
+    nk_property_behavior(ws, in, property, label, edit, empty, state, variant, inc_per_pixel);
+
+    /* draw property */
+    if (style->draw_begin) style->draw_begin(out, style->userdata);
+    nk_draw_property(out, style, &property, &label, *ws, name, name_len, font);
+    if (style->draw_end) style->draw_end(out, style->userdata);
+
+    /* execute right button  */
+    if (nk_do_button_symbol(ws, out, left, style->sym_left, behavior, &style->dec_button, in, font)) {
+        switch (variant->kind) {
+        default: break;
+        case NK_PROPERTY_INT:
+            variant->value.i = NK_CLAMP(variant->min_value.i, variant->value.i - variant->step.i, variant->max_value.i); break;
+        case NK_PROPERTY_FLOAT:
+            variant->value.f = NK_CLAMP(variant->min_value.f, variant->value.f - variant->step.f, variant->max_value.f); break;
+        case NK_PROPERTY_DOUBLE:
+            variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d - variant->step.d, variant->max_value.d); break;
+        }
+    }
+    /* execute left button  */
+    if (nk_do_button_symbol(ws, out, right, style->sym_right, behavior, &style->inc_button, in, font)) {
+        switch (variant->kind) {
+        default: break;
+        case NK_PROPERTY_INT:
+            variant->value.i = NK_CLAMP(variant->min_value.i, variant->value.i + variant->step.i, variant->max_value.i); break;
+        case NK_PROPERTY_FLOAT:
+            variant->value.f = NK_CLAMP(variant->min_value.f, variant->value.f + variant->step.f, variant->max_value.f); break;
+        case NK_PROPERTY_DOUBLE:
+            variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d + variant->step.d, variant->max_value.d); break;
+        }
+    }
+    if (old != NK_PROPERTY_EDIT && (*state == NK_PROPERTY_EDIT)) {
+        /* property has been activated so setup buffer */
+        NK_MEMCPY(buffer, dst, (nk_size)*length);
+        *cursor = nk_utf_len(buffer, *length);
+        *len = *length;
+        length = len;
+        dst = buffer;
+        active = 0;
+    } else active = (*state == NK_PROPERTY_EDIT);
+
+    /* execute and run text edit field */
+    nk_textedit_clear_state(text_edit, NK_TEXT_EDIT_SINGLE_LINE, filters[filter]);
+    text_edit->active = (unsigned char)active;
+    text_edit->string.len = *length;
+    text_edit->cursor = NK_CLAMP(0, *cursor, *length);
+    text_edit->select_start = NK_CLAMP(0,*select_begin, *length);
+    text_edit->select_end = NK_CLAMP(0,*select_end, *length);
+    text_edit->string.buffer.allocated = (nk_size)*length;
+    text_edit->string.buffer.memory.size = NK_MAX_NUMBER_BUFFER;
+    text_edit->string.buffer.memory.ptr = dst;
+    text_edit->string.buffer.size = NK_MAX_NUMBER_BUFFER;
+    text_edit->mode = NK_TEXT_EDIT_MODE_INSERT;
+    nk_do_edit(ws, out, edit, NK_EDIT_FIELD|NK_EDIT_AUTO_SELECT,
+        filters[filter], text_edit, &style->edit, (*state == NK_PROPERTY_EDIT) ? in: 0, font);
+
+    *length = text_edit->string.len;
+    *cursor = text_edit->cursor;
+    *select_begin = text_edit->select_start;
+    *select_end = text_edit->select_end;
+    if (text_edit->active && nk_input_is_key_pressed(in, NK_KEY_ENTER))
+        text_edit->active = nk_false;
+
+    if (active && !text_edit->active) {
+        /* property is now not active so convert edit text to value*/
+        *state = NK_PROPERTY_DEFAULT;
+        buffer[*len] = '\0';
+        switch (variant->kind) {
+        default: break;
+        case NK_PROPERTY_INT:
+            variant->value.i = nk_strtoi(buffer, 0);
+            variant->value.i = NK_CLAMP(variant->min_value.i, variant->value.i, variant->max_value.i);
+            break;
+        case NK_PROPERTY_FLOAT:
+            nk_string_float_limit(buffer, NK_MAX_FLOAT_PRECISION);
+            variant->value.f = nk_strtof(buffer, 0);
+            variant->value.f = NK_CLAMP(variant->min_value.f, variant->value.f, variant->max_value.f);
+            break;
+        case NK_PROPERTY_DOUBLE:
+            nk_string_float_limit(buffer, NK_MAX_FLOAT_PRECISION);
+            variant->value.d = nk_strtod(buffer, 0);
+            variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d, variant->max_value.d);
+            break;
+        }
+    }
+}
+/* ===============================================================
+ *
+ *                          COLOR PICKER
+ *
+ * ===============================================================*/
+NK_INTERN int
+nk_color_picker_behavior(nk_flags *state,
+    const struct nk_rect *bounds, const struct nk_rect *matrix,
+    const struct nk_rect *hue_bar, const struct nk_rect *alpha_bar,
+    struct nk_color *color, const struct nk_input *in)
+{
+    float hsva[4];
+    int value_changed = 0;
+    int hsv_changed = 0;
+
+    NK_ASSERT(state);
+    NK_ASSERT(matrix);
+    NK_ASSERT(hue_bar);
+    NK_ASSERT(color);
+
+    /* color matrix */
+    nk_color_hsva_fv(hsva, *color);
+    if (nk_button_behavior(state, *matrix, in, NK_BUTTON_REPEATER)) {
+        hsva[1] = NK_SATURATE((in->mouse.pos.x - matrix->x) / (matrix->w-1));
+        hsva[2] = 1.0f - NK_SATURATE((in->mouse.pos.y - matrix->y) / (matrix->h-1));
+        value_changed = hsv_changed = 1;
+    }
+
+    /* hue bar */
+    if (nk_button_behavior(state, *hue_bar, in, NK_BUTTON_REPEATER)) {
+        hsva[0] = NK_SATURATE((in->mouse.pos.y - hue_bar->y) / (hue_bar->h-1));
+        value_changed = hsv_changed = 1;
+    }
+
+    /* alpha bar */
+    if (alpha_bar) {
+        if (nk_button_behavior(state, *alpha_bar, in, NK_BUTTON_REPEATER)) {
+            hsva[3] = 1.0f - NK_SATURATE((in->mouse.pos.y - alpha_bar->y) / (alpha_bar->h-1));
+            value_changed = 1;
+        }
+    }
+    nk_widget_state_reset(state);
+    if (hsv_changed) {
+        *color = nk_hsva_fv(hsva);
+        *state = NK_WIDGET_STATE_ACTIVE;
+    }
+    if (value_changed) {
+        color->a = (nk_byte)(hsva[3] * 255.0f);
+        *state = NK_WIDGET_STATE_ACTIVE;
+    }
+
+    /* set color picker widget state */
+    if (nk_input_is_mouse_hovering_rect(in, *bounds))
+        *state = NK_WIDGET_STATE_HOVERED;
+    if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, *bounds))
+        *state |= NK_WIDGET_STATE_ENTERED;
+    else if (nk_input_is_mouse_prev_hovering_rect(in, *bounds))
+        *state |= NK_WIDGET_STATE_LEFT;
+    return value_changed;
+}
+
+NK_INTERN void
+nk_draw_color_picker(struct nk_command_buffer *o, const struct nk_rect *matrix,
+    const struct nk_rect *hue_bar, const struct nk_rect *alpha_bar,
+    struct nk_color color)
+{
+    NK_STORAGE const struct nk_color black = {0,0,0,255};
+    NK_STORAGE const struct nk_color white = {255, 255, 255, 255};
+    NK_STORAGE const struct nk_color black_trans = {0,0,0,0};
+
+    const float crosshair_size = 7.0f;
+    struct nk_color temp;
+    float hsva[4];
+    float line_y;
+    int i;
+
+    NK_ASSERT(o);
+    NK_ASSERT(matrix);
+    NK_ASSERT(hue_bar);
+
+    /* draw hue bar */
+    nk_color_hsv_fv(hsva, color);
+    for (i = 0; i < 6; ++i) {
+        NK_GLOBAL const struct nk_color hue_colors[] = {
+            {255, 0, 0, 255},
+            {255,255,0,255},
+            {0,255,0,255},
+            {0, 255,255,255},
+            {0,0,255,255},
+            {255, 0, 255, 255},
+            {255, 0, 0, 255}
+        };
+        nk_fill_rect_multi_color(o,
+            nk_rect(hue_bar->x, hue_bar->y + (float)i * (hue_bar->h/6.0f) + 0.5f,
+                hue_bar->w, (hue_bar->h/6.0f) + 0.5f), hue_colors[i], hue_colors[i],
+                hue_colors[i+1], hue_colors[i+1]);
+    }
+    line_y = (float)(int)(hue_bar->y + hsva[0] * matrix->h + 0.5f);
+    nk_stroke_line(o, hue_bar->x-1, line_y, hue_bar->x + hue_bar->w + 2,
+        line_y, 1, nk_rgb(255,255,255));
+
+    /* draw alpha bar */
+    if (alpha_bar) {
+        float alpha = NK_SATURATE((float)color.a/255.0f);
+        line_y = (float)(int)(alpha_bar->y +  (1.0f - alpha) * matrix->h + 0.5f);
+
+        nk_fill_rect_multi_color(o, *alpha_bar, white, white, black, black);
+        nk_stroke_line(o, alpha_bar->x-1, line_y, alpha_bar->x + alpha_bar->w + 2,
+            line_y, 1, nk_rgb(255,255,255));
+    }
+
+    /* draw color matrix */
+    temp = nk_hsv_f(hsva[0], 1.0f, 1.0f);
+    nk_fill_rect_multi_color(o, *matrix, white, temp, temp, white);
+    nk_fill_rect_multi_color(o, *matrix, black_trans, black_trans, black, black);
+
+    /* draw cross-hair */
+    {struct nk_vec2 p; float S = hsva[1]; float V = hsva[2];
+    p.x = (float)(int)(matrix->x + S * matrix->w);
+    p.y = (float)(int)(matrix->y + (1.0f - V) * matrix->h);
+    nk_stroke_line(o, p.x - crosshair_size, p.y, p.x-2, p.y, 1.0f, white);
+    nk_stroke_line(o, p.x + crosshair_size + 1, p.y, p.x+3, p.y, 1.0f, white);
+    nk_stroke_line(o, p.x, p.y + crosshair_size + 1, p.x, p.y+3, 1.0f, white);
+    nk_stroke_line(o, p.x, p.y - crosshair_size, p.x, p.y-2, 1.0f, white);}
+}
+
+NK_INTERN int
+nk_do_color_picker(nk_flags *state,
+    struct nk_command_buffer *out, struct nk_color *color,
+    enum nk_color_format fmt, struct nk_rect bounds,
+    struct nk_vec2 padding, const struct nk_input *in,
+    const struct nk_user_font *font)
+{
+    int ret = 0;
+    struct nk_rect matrix;
+    struct nk_rect hue_bar;
+    struct nk_rect alpha_bar;
+    float bar_w;
+
+    NK_ASSERT(out);
+    NK_ASSERT(color);
+    NK_ASSERT(state);
+    NK_ASSERT(font);
+    if (!out || !color || !state || !font)
+        return ret;
+
+    bar_w = font->height;
+    bounds.x += padding.x;
+    bounds.y += padding.x;
+    bounds.w -= 2 * padding.x;
+    bounds.h -= 2 * padding.y;
+
+    matrix.x = bounds.x;
+    matrix.y = bounds.y;
+    matrix.h = bounds.h;
+    matrix.w = bounds.w - (3 * padding.x + 2 * bar_w);
+
+    hue_bar.w = bar_w;
+    hue_bar.y = bounds.y;
+    hue_bar.h = matrix.h;
+    hue_bar.x = matrix.x + matrix.w + padding.x;
+
+    alpha_bar.x = hue_bar.x + hue_bar.w + padding.x;
+    alpha_bar.y = bounds.y;
+    alpha_bar.w = bar_w;
+    alpha_bar.h = matrix.h;
+
+    ret = nk_color_picker_behavior(state, &bounds, &matrix, &hue_bar,
+        (fmt == NK_RGBA) ? &alpha_bar:0, color, in);
+    nk_draw_color_picker(out, &matrix, &hue_bar, (fmt == NK_RGBA) ? &alpha_bar:0, *color);
+    return ret;
+}
+
+/* ==============================================================
+ *
+ *                          STYLE
+ *
+ * ===============================================================*/
+NK_API void nk_style_default(struct nk_context *ctx){nk_style_from_table(ctx, 0);}
+#define NK_COLOR_MAP(NK_COLOR)\
+    NK_COLOR(NK_COLOR_TEXT,                 175,175,175,255) \
+    NK_COLOR(NK_COLOR_WINDOW,               45, 45, 45, 255) \
+    NK_COLOR(NK_COLOR_HEADER,               40, 40, 40, 255) \
+    NK_COLOR(NK_COLOR_BORDER,               65, 65, 65, 255) \
+    NK_COLOR(NK_COLOR_BUTTON,               50, 50, 50, 255) \
+    NK_COLOR(NK_COLOR_BUTTON_HOVER,         40, 40, 40, 255) \
+    NK_COLOR(NK_COLOR_BUTTON_ACTIVE,        35, 35, 35, 255) \
+    NK_COLOR(NK_COLOR_TOGGLE,               100,100,100,255) \
+    NK_COLOR(NK_COLOR_TOGGLE_HOVER,         120,120,120,255) \
+    NK_COLOR(NK_COLOR_TOGGLE_CURSOR,        45, 45, 45, 255) \
+    NK_COLOR(NK_COLOR_SELECT,               45, 45, 45, 255) \
+    NK_COLOR(NK_COLOR_SELECT_ACTIVE,        35, 35, 35,255) \
+    NK_COLOR(NK_COLOR_SLIDER,               38, 38, 38, 255) \
+    NK_COLOR(NK_COLOR_SLIDER_CURSOR,        100,100,100,255) \
+    NK_COLOR(NK_COLOR_SLIDER_CURSOR_HOVER,  120,120,120,255) \
+    NK_COLOR(NK_COLOR_SLIDER_CURSOR_ACTIVE, 150,150,150,255) \
+    NK_COLOR(NK_COLOR_PROPERTY,             38, 38, 38, 255) \
+    NK_COLOR(NK_COLOR_EDIT,                 38, 38, 38, 255)  \
+    NK_COLOR(NK_COLOR_EDIT_CURSOR,          175,175,175,255) \
+    NK_COLOR(NK_COLOR_COMBO,                45, 45, 45, 255) \
+    NK_COLOR(NK_COLOR_CHART,                120,120,120,255) \
+    NK_COLOR(NK_COLOR_CHART_COLOR,          45, 45, 45, 255) \
+    NK_COLOR(NK_COLOR_CHART_COLOR_HIGHLIGHT,255, 0,  0, 255) \
+    NK_COLOR(NK_COLOR_SCROLLBAR,            40, 40, 40, 255) \
+    NK_COLOR(NK_COLOR_SCROLLBAR_CURSOR,     100,100,100,255) \
+    NK_COLOR(NK_COLOR_SCROLLBAR_CURSOR_HOVER,120,120,120,255) \
+    NK_COLOR(NK_COLOR_SCROLLBAR_CURSOR_ACTIVE,150,150,150,255) \
+    NK_COLOR(NK_COLOR_TAB_HEADER,           40, 40, 40,255)
+
+NK_GLOBAL const struct nk_color
+nk_default_color_style[NK_COLOR_COUNT] = {
+#define NK_COLOR(a,b,c,d,e) {b,c,d,e},
+    NK_COLOR_MAP(NK_COLOR)
+#undef NK_COLOR
+};
+
+NK_GLOBAL const char *nk_color_names[NK_COLOR_COUNT] = {
+#define NK_COLOR(a,b,c,d,e) #a,
+    NK_COLOR_MAP(NK_COLOR)
+#undef NK_COLOR
+};
+
+NK_API const char *nk_style_get_color_by_name(enum nk_style_colors c)
+{return nk_color_names[c];}
+
+NK_API struct nk_style_item nk_style_item_image(struct nk_image img)
+{struct nk_style_item i; i.type = NK_STYLE_ITEM_IMAGE; i.data.image = img; return i;}
+
+NK_API struct nk_style_item nk_style_item_color(struct nk_color col)
+{struct nk_style_item i; i.type = NK_STYLE_ITEM_COLOR; i.data.color = col; return i;}
+
+NK_API struct nk_style_item nk_style_item_hide(void)
+{struct nk_style_item i; i.type = NK_STYLE_ITEM_COLOR; i.data.color = nk_rgba(0,0,0,0); return i;}
+
+NK_API void
+nk_style_from_table(struct nk_context *ctx, const struct nk_color *table)
+{
+    struct nk_style *style;
+    struct nk_style_text *text;
+    struct nk_style_button *button;
+    struct nk_style_toggle *toggle;
+    struct nk_style_selectable *select;
+    struct nk_style_slider *slider;
+    struct nk_style_progress *prog;
+    struct nk_style_scrollbar *scroll;
+    struct nk_style_edit *edit;
+    struct nk_style_property *property;
+    struct nk_style_combo *combo;
+    struct nk_style_chart *chart;
+    struct nk_style_tab *tab;
+    struct nk_style_window *win;
+
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    style = &ctx->style;
+    table = (!table) ? nk_default_color_style: table;
+
+    /* default text */
+    text = &style->text;
+    text->color = table[NK_COLOR_TEXT];
+    text->padding = nk_vec2(0,0);
+
+    /* default button */
+    button = &style->button;
+    nk_zero_struct(*button);
+    button->normal          = nk_style_item_color(table[NK_COLOR_BUTTON]);
+    button->hover           = nk_style_item_color(table[NK_COLOR_BUTTON_HOVER]);
+    button->active          = nk_style_item_color(table[NK_COLOR_BUTTON_ACTIVE]);
+    button->border_color    = table[NK_COLOR_BORDER];
+    button->text_background = table[NK_COLOR_BUTTON];
+    button->text_normal     = table[NK_COLOR_TEXT];
+    button->text_hover      = table[NK_COLOR_TEXT];
+    button->text_active     = table[NK_COLOR_TEXT];
+    button->padding         = nk_vec2(2.0f,2.0f);
+    button->image_padding   = nk_vec2(0.0f,0.0f);
+    button->touch_padding   = nk_vec2(0.0f, 0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 1.0f;
+    button->rounding        = 4.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+
+    /* contextual button */
+    button = &style->contextual_button;
+    nk_zero_struct(*button);
+    button->normal          = nk_style_item_color(table[NK_COLOR_WINDOW]);
+    button->hover           = nk_style_item_color(table[NK_COLOR_BUTTON_HOVER]);
+    button->active          = nk_style_item_color(table[NK_COLOR_BUTTON_ACTIVE]);
+    button->border_color    = table[NK_COLOR_WINDOW];
+    button->text_background = table[NK_COLOR_WINDOW];
+    button->text_normal     = table[NK_COLOR_TEXT];
+    button->text_hover      = table[NK_COLOR_TEXT];
+    button->text_active     = table[NK_COLOR_TEXT];
+    button->padding         = nk_vec2(2.0f,2.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 0.0f;
+    button->rounding        = 0.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+
+    /* menu button */
+    button = &style->menu_button;
+    nk_zero_struct(*button);
+    button->normal          = nk_style_item_color(table[NK_COLOR_WINDOW]);
+    button->hover           = nk_style_item_color(table[NK_COLOR_WINDOW]);
+    button->active          = nk_style_item_color(table[NK_COLOR_WINDOW]);
+    button->border_color    = table[NK_COLOR_WINDOW];
+    button->text_background = table[NK_COLOR_WINDOW];
+    button->text_normal     = table[NK_COLOR_TEXT];
+    button->text_hover      = table[NK_COLOR_TEXT];
+    button->text_active     = table[NK_COLOR_TEXT];
+    button->padding         = nk_vec2(2.0f,2.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 0.0f;
+    button->rounding        = 1.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+
+    /* checkbox toggle */
+    toggle = &style->checkbox;
+    nk_zero_struct(*toggle);
+    toggle->normal          = nk_style_item_color(table[NK_COLOR_TOGGLE]);
+    toggle->hover           = nk_style_item_color(table[NK_COLOR_TOGGLE_HOVER]);
+    toggle->active          = nk_style_item_color(table[NK_COLOR_TOGGLE_HOVER]);
+    toggle->cursor_normal   = nk_style_item_color(table[NK_COLOR_TOGGLE_CURSOR]);
+    toggle->cursor_hover    = nk_style_item_color(table[NK_COLOR_TOGGLE_CURSOR]);
+    toggle->userdata        = nk_handle_ptr(0);
+    toggle->text_background = table[NK_COLOR_WINDOW];
+    toggle->text_normal     = table[NK_COLOR_TEXT];
+    toggle->text_hover      = table[NK_COLOR_TEXT];
+    toggle->text_active     = table[NK_COLOR_TEXT];
+    toggle->padding         = nk_vec2(2.0f, 2.0f);
+    toggle->touch_padding   = nk_vec2(0,0);
+    toggle->border_color    = nk_rgba(0,0,0,0);
+    toggle->border          = 0.0f;
+    toggle->spacing         = 4;
+
+    /* option toggle */
+    toggle = &style->option;
+    nk_zero_struct(*toggle);
+    toggle->normal          = nk_style_item_color(table[NK_COLOR_TOGGLE]);
+    toggle->hover           = nk_style_item_color(table[NK_COLOR_TOGGLE_HOVER]);
+    toggle->active          = nk_style_item_color(table[NK_COLOR_TOGGLE_HOVER]);
+    toggle->cursor_normal   = nk_style_item_color(table[NK_COLOR_TOGGLE_CURSOR]);
+    toggle->cursor_hover    = nk_style_item_color(table[NK_COLOR_TOGGLE_CURSOR]);
+    toggle->userdata        = nk_handle_ptr(0);
+    toggle->text_background = table[NK_COLOR_WINDOW];
+    toggle->text_normal     = table[NK_COLOR_TEXT];
+    toggle->text_hover      = table[NK_COLOR_TEXT];
+    toggle->text_active     = table[NK_COLOR_TEXT];
+    toggle->padding         = nk_vec2(3.0f, 3.0f);
+    toggle->touch_padding   = nk_vec2(0,0);
+    toggle->border_color    = nk_rgba(0,0,0,0);
+    toggle->border          = 0.0f;
+    toggle->spacing         = 4;
+
+    /* selectable */
+    select = &style->selectable;
+    nk_zero_struct(*select);
+    select->normal          = nk_style_item_color(table[NK_COLOR_SELECT]);
+    select->hover           = nk_style_item_color(table[NK_COLOR_SELECT]);
+    select->pressed         = nk_style_item_color(table[NK_COLOR_SELECT]);
+    select->normal_active   = nk_style_item_color(table[NK_COLOR_SELECT_ACTIVE]);
+    select->hover_active    = nk_style_item_color(table[NK_COLOR_SELECT_ACTIVE]);
+    select->pressed_active  = nk_style_item_color(table[NK_COLOR_SELECT_ACTIVE]);
+    select->text_normal     = table[NK_COLOR_TEXT];
+    select->text_hover      = table[NK_COLOR_TEXT];
+    select->text_pressed    = table[NK_COLOR_TEXT];
+    select->text_normal_active  = table[NK_COLOR_TEXT];
+    select->text_hover_active   = table[NK_COLOR_TEXT];
+    select->text_pressed_active = table[NK_COLOR_TEXT];
+    select->padding         = nk_vec2(2.0f,2.0f);
+    select->touch_padding   = nk_vec2(0,0);
+    select->userdata        = nk_handle_ptr(0);
+    select->rounding        = 0.0f;
+    select->draw_begin      = 0;
+    select->draw_end        = 0;
+
+    /* slider */
+    slider = &style->slider;
+    nk_zero_struct(*slider);
+    slider->normal          = nk_style_item_hide();
+    slider->hover           = nk_style_item_hide();
+    slider->active          = nk_style_item_hide();
+    slider->bar_normal      = table[NK_COLOR_SLIDER];
+    slider->bar_hover       = table[NK_COLOR_SLIDER];
+    slider->bar_active      = table[NK_COLOR_SLIDER];
+    slider->bar_filled      = table[NK_COLOR_SLIDER_CURSOR];
+    slider->cursor_normal   = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR]);
+    slider->cursor_hover    = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR_HOVER]);
+    slider->cursor_active   = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR_ACTIVE]);
+    slider->inc_symbol      = NK_SYMBOL_TRIANGLE_RIGHT;
+    slider->dec_symbol      = NK_SYMBOL_TRIANGLE_LEFT;
+    slider->cursor_size     = nk_vec2(16,16);
+    slider->padding         = nk_vec2(2,2);
+    slider->spacing         = nk_vec2(2,2);
+    slider->userdata        = nk_handle_ptr(0);
+    slider->show_buttons    = nk_false;
+    slider->bar_height      = 8;
+    slider->rounding        = 0;
+    slider->draw_begin      = 0;
+    slider->draw_end        = 0;
+
+    /* slider buttons */
+    button = &style->slider.inc_button;
+    button->normal          = nk_style_item_color(nk_rgb(40,40,40));
+    button->hover           = nk_style_item_color(nk_rgb(42,42,42));
+    button->active          = nk_style_item_color(nk_rgb(44,44,44));
+    button->border_color    = nk_rgb(65,65,65);
+    button->text_background = nk_rgb(40,40,40);
+    button->text_normal     = nk_rgb(175,175,175);
+    button->text_hover      = nk_rgb(175,175,175);
+    button->text_active     = nk_rgb(175,175,175);
+    button->padding         = nk_vec2(8.0f,8.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 1.0f;
+    button->rounding        = 0.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+    style->slider.dec_button = style->slider.inc_button;
+
+    /* progressbar */
+    prog = &style->progress;
+    nk_zero_struct(*prog);
+    prog->normal            = nk_style_item_color(table[NK_COLOR_SLIDER]);
+    prog->hover             = nk_style_item_color(table[NK_COLOR_SLIDER]);
+    prog->active            = nk_style_item_color(table[NK_COLOR_SLIDER]);
+    prog->cursor_normal     = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR]);
+    prog->cursor_hover      = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR_HOVER]);
+    prog->cursor_active     = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR_ACTIVE]);
+    prog->border_color      = nk_rgba(0,0,0,0);
+    prog->cursor_border_color = nk_rgba(0,0,0,0);
+    prog->userdata          = nk_handle_ptr(0);
+    prog->padding           = nk_vec2(4,4);
+    prog->rounding          = 0;
+    prog->border            = 0;
+    prog->cursor_rounding   = 0;
+    prog->cursor_border     = 0;
+    prog->draw_begin        = 0;
+    prog->draw_end          = 0;
+
+    /* scrollbars */
+    scroll = &style->scrollh;
+    nk_zero_struct(*scroll);
+    scroll->normal          = nk_style_item_color(table[NK_COLOR_SCROLLBAR]);
+    scroll->hover           = nk_style_item_color(table[NK_COLOR_SCROLLBAR]);
+    scroll->active          = nk_style_item_color(table[NK_COLOR_SCROLLBAR]);
+    scroll->cursor_normal   = nk_style_item_color(table[NK_COLOR_SCROLLBAR_CURSOR]);
+    scroll->cursor_hover    = nk_style_item_color(table[NK_COLOR_SCROLLBAR_CURSOR_HOVER]);
+    scroll->cursor_active   = nk_style_item_color(table[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE]);
+    scroll->dec_symbol      = NK_SYMBOL_CIRCLE_SOLID;
+    scroll->inc_symbol      = NK_SYMBOL_CIRCLE_SOLID;
+    scroll->userdata        = nk_handle_ptr(0);
+    scroll->border_color    = table[NK_COLOR_SCROLLBAR];
+    scroll->cursor_border_color = table[NK_COLOR_SCROLLBAR];
+    scroll->padding         = nk_vec2(0,0);
+    scroll->show_buttons    = nk_false;
+    scroll->border          = 0;
+    scroll->rounding        = 0;
+    scroll->border_cursor   = 0;
+    scroll->rounding_cursor = 0;
+    scroll->draw_begin      = 0;
+    scroll->draw_end        = 0;
+    style->scrollv = style->scrollh;
+
+    /* scrollbars buttons */
+    button = &style->scrollh.inc_button;
+    button->normal          = nk_style_item_color(nk_rgb(40,40,40));
+    button->hover           = nk_style_item_color(nk_rgb(42,42,42));
+    button->active          = nk_style_item_color(nk_rgb(44,44,44));
+    button->border_color    = nk_rgb(65,65,65);
+    button->text_background = nk_rgb(40,40,40);
+    button->text_normal     = nk_rgb(175,175,175);
+    button->text_hover      = nk_rgb(175,175,175);
+    button->text_active     = nk_rgb(175,175,175);
+    button->padding         = nk_vec2(4.0f,4.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 1.0f;
+    button->rounding        = 0.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+    style->scrollh.dec_button = style->scrollh.inc_button;
+    style->scrollv.inc_button = style->scrollh.inc_button;
+    style->scrollv.dec_button = style->scrollh.inc_button;
+
+    /* edit */
+    edit = &style->edit;
+    nk_zero_struct(*edit);
+    edit->normal            = nk_style_item_color(table[NK_COLOR_EDIT]);
+    edit->hover             = nk_style_item_color(table[NK_COLOR_EDIT]);
+    edit->active            = nk_style_item_color(table[NK_COLOR_EDIT]);
+    edit->cursor_normal     = table[NK_COLOR_TEXT];
+    edit->cursor_hover      = table[NK_COLOR_TEXT];
+    edit->cursor_text_normal= table[NK_COLOR_EDIT];
+    edit->cursor_text_hover = table[NK_COLOR_EDIT];
+    edit->border_color      = table[NK_COLOR_BORDER];
+    edit->text_normal       = table[NK_COLOR_TEXT];
+    edit->text_hover        = table[NK_COLOR_TEXT];
+    edit->text_active       = table[NK_COLOR_TEXT];
+    edit->selected_normal   = table[NK_COLOR_TEXT];
+    edit->selected_hover    = table[NK_COLOR_TEXT];
+    edit->selected_text_normal  = table[NK_COLOR_EDIT];
+    edit->selected_text_hover   = table[NK_COLOR_EDIT];
+    edit->scrollbar_size    = nk_vec2(10,10);
+    edit->scrollbar         = style->scrollv;
+    edit->padding           = nk_vec2(4,4);
+    edit->row_padding       = 2;
+    edit->cursor_size       = 4;
+    edit->border            = 1;
+    edit->rounding          = 0;
+
+    /* property */
+    property = &style->property;
+    nk_zero_struct(*property);
+    property->normal        = nk_style_item_color(table[NK_COLOR_PROPERTY]);
+    property->hover         = nk_style_item_color(table[NK_COLOR_PROPERTY]);
+    property->active        = nk_style_item_color(table[NK_COLOR_PROPERTY]);
+    property->border_color  = table[NK_COLOR_BORDER];
+    property->label_normal  = table[NK_COLOR_TEXT];
+    property->label_hover   = table[NK_COLOR_TEXT];
+    property->label_active  = table[NK_COLOR_TEXT];
+    property->sym_left      = NK_SYMBOL_TRIANGLE_LEFT;
+    property->sym_right     = NK_SYMBOL_TRIANGLE_RIGHT;
+    property->userdata      = nk_handle_ptr(0);
+    property->padding       = nk_vec2(4,4);
+    property->border        = 1;
+    property->rounding      = 10;
+    property->draw_begin    = 0;
+    property->draw_end      = 0;
+
+    /* property buttons */
+    button = &style->property.dec_button;
+    nk_zero_struct(*button);
+    button->normal          = nk_style_item_color(table[NK_COLOR_PROPERTY]);
+    button->hover           = nk_style_item_color(table[NK_COLOR_PROPERTY]);
+    button->active          = nk_style_item_color(table[NK_COLOR_PROPERTY]);
+    button->border_color    = nk_rgba(0,0,0,0);
+    button->text_background = table[NK_COLOR_PROPERTY];
+    button->text_normal     = table[NK_COLOR_TEXT];
+    button->text_hover      = table[NK_COLOR_TEXT];
+    button->text_active     = table[NK_COLOR_TEXT];
+    button->padding         = nk_vec2(0.0f,0.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 0.0f;
+    button->rounding        = 0.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+    style->property.inc_button = style->property.dec_button;
+
+    /* property edit */
+    edit = &style->property.edit;
+    nk_zero_struct(*edit);
+    edit->normal            = nk_style_item_color(table[NK_COLOR_PROPERTY]);
+    edit->hover             = nk_style_item_color(table[NK_COLOR_PROPERTY]);
+    edit->active            = nk_style_item_color(table[NK_COLOR_PROPERTY]);
+    edit->border_color      = nk_rgba(0,0,0,0);
+    edit->cursor_normal     = table[NK_COLOR_TEXT];
+    edit->cursor_hover      = table[NK_COLOR_TEXT];
+    edit->cursor_text_normal= table[NK_COLOR_EDIT];
+    edit->cursor_text_hover = table[NK_COLOR_EDIT];
+    edit->text_normal       = table[NK_COLOR_TEXT];
+    edit->text_hover        = table[NK_COLOR_TEXT];
+    edit->text_active       = table[NK_COLOR_TEXT];
+    edit->selected_normal   = table[NK_COLOR_TEXT];
+    edit->selected_hover    = table[NK_COLOR_TEXT];
+    edit->selected_text_normal  = table[NK_COLOR_EDIT];
+    edit->selected_text_hover   = table[NK_COLOR_EDIT];
+    edit->padding           = nk_vec2(0,0);
+    edit->cursor_size       = 8;
+    edit->border            = 0;
+    edit->rounding          = 0;
+
+    /* chart */
+    chart = &style->chart;
+    nk_zero_struct(*chart);
+    chart->background       = nk_style_item_color(table[NK_COLOR_CHART]);
+    chart->border_color     = table[NK_COLOR_BORDER];
+    chart->selected_color   = table[NK_COLOR_CHART_COLOR_HIGHLIGHT];
+    chart->color            = table[NK_COLOR_CHART_COLOR];
+    chart->padding          = nk_vec2(4,4);
+    chart->border           = 0;
+    chart->rounding         = 0;
+
+    /* combo */
+    combo = &style->combo;
+    combo->normal           = nk_style_item_color(table[NK_COLOR_COMBO]);
+    combo->hover            = nk_style_item_color(table[NK_COLOR_COMBO]);
+    combo->active           = nk_style_item_color(table[NK_COLOR_COMBO]);
+    combo->border_color     = table[NK_COLOR_BORDER];
+    combo->label_normal     = table[NK_COLOR_TEXT];
+    combo->label_hover      = table[NK_COLOR_TEXT];
+    combo->label_active     = table[NK_COLOR_TEXT];
+    combo->sym_normal       = NK_SYMBOL_TRIANGLE_DOWN;
+    combo->sym_hover        = NK_SYMBOL_TRIANGLE_DOWN;
+    combo->sym_active       = NK_SYMBOL_TRIANGLE_DOWN;
+    combo->content_padding  = nk_vec2(4,4);
+    combo->button_padding   = nk_vec2(0,4);
+    combo->spacing          = nk_vec2(4,0);
+    combo->border           = 1;
+    combo->rounding         = 0;
+
+    /* combo button */
+    button = &style->combo.button;
+    nk_zero_struct(*button);
+    button->normal          = nk_style_item_color(table[NK_COLOR_COMBO]);
+    button->hover           = nk_style_item_color(table[NK_COLOR_COMBO]);
+    button->active          = nk_style_item_color(table[NK_COLOR_COMBO]);
+    button->border_color    = nk_rgba(0,0,0,0);
+    button->text_background = table[NK_COLOR_COMBO];
+    button->text_normal     = table[NK_COLOR_TEXT];
+    button->text_hover      = table[NK_COLOR_TEXT];
+    button->text_active     = table[NK_COLOR_TEXT];
+    button->padding         = nk_vec2(2.0f,2.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 0.0f;
+    button->rounding        = 0.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+
+    /* tab */
+    tab = &style->tab;
+    tab->background         = nk_style_item_color(table[NK_COLOR_TAB_HEADER]);
+    tab->border_color       = table[NK_COLOR_BORDER];
+    tab->text               = table[NK_COLOR_TEXT];
+    tab->sym_minimize       = NK_SYMBOL_TRIANGLE_RIGHT;
+    tab->sym_maximize       = NK_SYMBOL_TRIANGLE_DOWN;
+    tab->padding            = nk_vec2(4,4);
+    tab->spacing            = nk_vec2(4,4);
+    tab->indent             = 10.0f;
+    tab->border             = 1;
+    tab->rounding           = 0;
+
+    /* tab button */
+    button = &style->tab.tab_minimize_button;
+    nk_zero_struct(*button);
+    button->normal          = nk_style_item_color(table[NK_COLOR_TAB_HEADER]);
+    button->hover           = nk_style_item_color(table[NK_COLOR_TAB_HEADER]);
+    button->active          = nk_style_item_color(table[NK_COLOR_TAB_HEADER]);
+    button->border_color    = nk_rgba(0,0,0,0);
+    button->text_background = table[NK_COLOR_TAB_HEADER];
+    button->text_normal     = table[NK_COLOR_TEXT];
+    button->text_hover      = table[NK_COLOR_TEXT];
+    button->text_active     = table[NK_COLOR_TEXT];
+    button->padding         = nk_vec2(2.0f,2.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 0.0f;
+    button->rounding        = 0.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+    style->tab.tab_maximize_button =*button;
+
+    /* node button */
+    button = &style->tab.node_minimize_button;
+    nk_zero_struct(*button);
+    button->normal          = nk_style_item_color(table[NK_COLOR_WINDOW]);
+    button->hover           = nk_style_item_color(table[NK_COLOR_WINDOW]);
+    button->active          = nk_style_item_color(table[NK_COLOR_WINDOW]);
+    button->border_color    = nk_rgba(0,0,0,0);
+    button->text_background = table[NK_COLOR_TAB_HEADER];
+    button->text_normal     = table[NK_COLOR_TEXT];
+    button->text_hover      = table[NK_COLOR_TEXT];
+    button->text_active     = table[NK_COLOR_TEXT];
+    button->padding         = nk_vec2(2.0f,2.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 0.0f;
+    button->rounding        = 0.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+    style->tab.node_maximize_button =*button;
+
+    /* window header */
+    win = &style->window;
+    win->header.align = NK_HEADER_RIGHT;
+    win->header.close_symbol = NK_SYMBOL_X;
+    win->header.minimize_symbol = NK_SYMBOL_MINUS;
+    win->header.maximize_symbol = NK_SYMBOL_PLUS;
+    win->header.normal = nk_style_item_color(table[NK_COLOR_HEADER]);
+    win->header.hover = nk_style_item_color(table[NK_COLOR_HEADER]);
+    win->header.active = nk_style_item_color(table[NK_COLOR_HEADER]);
+    win->header.label_normal = table[NK_COLOR_TEXT];
+    win->header.label_hover = table[NK_COLOR_TEXT];
+    win->header.label_active = table[NK_COLOR_TEXT];
+    win->header.label_padding = nk_vec2(4,4);
+    win->header.padding = nk_vec2(4,4);
+    win->header.spacing = nk_vec2(0,0);
+
+    /* window header close button */
+    button = &style->window.header.close_button;
+    nk_zero_struct(*button);
+    button->normal          = nk_style_item_color(table[NK_COLOR_HEADER]);
+    button->hover           = nk_style_item_color(table[NK_COLOR_HEADER]);
+    button->active          = nk_style_item_color(table[NK_COLOR_HEADER]);
+    button->border_color    = nk_rgba(0,0,0,0);
+    button->text_background = table[NK_COLOR_HEADER];
+    button->text_normal     = table[NK_COLOR_TEXT];
+    button->text_hover      = table[NK_COLOR_TEXT];
+    button->text_active     = table[NK_COLOR_TEXT];
+    button->padding         = nk_vec2(0.0f,0.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 0.0f;
+    button->rounding        = 0.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+
+    /* window header minimize button */
+    button = &style->window.header.minimize_button;
+    nk_zero_struct(*button);
+    button->normal          = nk_style_item_color(table[NK_COLOR_HEADER]);
+    button->hover           = nk_style_item_color(table[NK_COLOR_HEADER]);
+    button->active          = nk_style_item_color(table[NK_COLOR_HEADER]);
+    button->border_color    = nk_rgba(0,0,0,0);
+    button->text_background = table[NK_COLOR_HEADER];
+    button->text_normal     = table[NK_COLOR_TEXT];
+    button->text_hover      = table[NK_COLOR_TEXT];
+    button->text_active     = table[NK_COLOR_TEXT];
+    button->padding         = nk_vec2(0.0f,0.0f);
+    button->touch_padding   = nk_vec2(0.0f,0.0f);
+    button->userdata        = nk_handle_ptr(0);
+    button->text_alignment  = NK_TEXT_CENTERED;
+    button->border          = 0.0f;
+    button->rounding        = 0.0f;
+    button->draw_begin      = 0;
+    button->draw_end        = 0;
+
+    /* window */
+    win->background = table[NK_COLOR_WINDOW];
+    win->fixed_background = nk_style_item_color(table[NK_COLOR_WINDOW]);
+    win->border_color = table[NK_COLOR_BORDER];
+    win->popup_border_color = table[NK_COLOR_BORDER];
+    win->combo_border_color = table[NK_COLOR_BORDER];
+    win->contextual_border_color = table[NK_COLOR_BORDER];
+    win->menu_border_color = table[NK_COLOR_BORDER];
+    win->group_border_color = table[NK_COLOR_BORDER];
+    win->tooltip_border_color = table[NK_COLOR_BORDER];
+    win->scaler = nk_style_item_color(table[NK_COLOR_TEXT]);
+
+    win->rounding = 0.0f;
+    win->spacing = nk_vec2(4,4);
+    win->scrollbar_size = nk_vec2(10,10);
+    win->min_size = nk_vec2(64,64);
+
+    win->combo_border = 1.0f;
+    win->contextual_border = 1.0f;
+    win->menu_border = 1.0f;
+    win->group_border = 1.0f;
+    win->tooltip_border = 1.0f;
+    win->popup_border = 1.0f;
+    win->border = 2.0f;
+    win->min_row_height_padding = 8;
+
+    win->padding = nk_vec2(4,4);
+    win->group_padding = nk_vec2(4,4);
+    win->popup_padding = nk_vec2(4,4);
+    win->combo_padding = nk_vec2(4,4);
+    win->contextual_padding = nk_vec2(4,4);
+    win->menu_padding = nk_vec2(4,4);
+    win->tooltip_padding = nk_vec2(4,4);
+}
+
+NK_API void
+nk_style_set_font(struct nk_context *ctx, const struct nk_user_font *font)
+{
+    struct nk_style *style;
+    NK_ASSERT(ctx);
+
+    if (!ctx) return;
+    style = &ctx->style;
+    style->font = font;
+    ctx->stacks.fonts.head = 0;
+    if (ctx->current)
+        nk_layout_reset_min_row_height(ctx);
+}
+
+NK_API int
+nk_style_push_font(struct nk_context *ctx, const struct nk_user_font *font)
+{
+    struct nk_config_stack_user_font *font_stack;
+    struct nk_config_stack_user_font_element *element;
+
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+
+    font_stack = &ctx->stacks.fonts;
+    NK_ASSERT(font_stack->head < (int)NK_LEN(font_stack->elements));
+    if (font_stack->head >= (int)NK_LEN(font_stack->elements))
+        return 0;
+
+    element = &font_stack->elements[font_stack->head++];
+    element->address = &ctx->style.font;
+    element->old_value = ctx->style.font;
+    ctx->style.font = font;
+    return 1;
+}
+
+NK_API int
+nk_style_pop_font(struct nk_context *ctx)
+{
+    struct nk_config_stack_user_font *font_stack;
+    struct nk_config_stack_user_font_element *element;
+
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+
+    font_stack = &ctx->stacks.fonts;
+    NK_ASSERT(font_stack->head > 0);
+    if (font_stack->head < 1)
+        return 0;
+
+    element = &font_stack->elements[--font_stack->head];
+    *element->address = element->old_value;
+    return 1;
+}
+
+#define NK_STYLE_PUSH_IMPLEMENATION(prefix, type, stack) \
+nk_style_push_##type(struct nk_context *ctx, prefix##_##type *address, prefix##_##type value)\
+{\
+    struct nk_config_stack_##type * type_stack;\
+    struct nk_config_stack_##type##_element *element;\
+    NK_ASSERT(ctx);\
+    if (!ctx) return 0;\
+    type_stack = &ctx->stacks.stack;\
+    NK_ASSERT(type_stack->head < (int)NK_LEN(type_stack->elements));\
+    if (type_stack->head >= (int)NK_LEN(type_stack->elements))\
+        return 0;\
+    element = &type_stack->elements[type_stack->head++];\
+    element->address = address;\
+    element->old_value = *address;\
+    *address = value;\
+    return 1;\
+}
+
+#define NK_STYLE_POP_IMPLEMENATION(type, stack) \
+nk_style_pop_##type(struct nk_context *ctx)\
+{\
+    struct nk_config_stack_##type *type_stack;\
+    struct nk_config_stack_##type##_element *element;\
+    NK_ASSERT(ctx);\
+    if (!ctx) return 0;\
+    type_stack = &ctx->stacks.stack;\
+    NK_ASSERT(type_stack->head > 0);\
+    if (type_stack->head < 1)\
+        return 0;\
+    element = &type_stack->elements[--type_stack->head];\
+    *element->address = element->old_value;\
+    return 1;\
+}
+
+NK_API int NK_STYLE_PUSH_IMPLEMENATION(struct nk, style_item, style_items)
+NK_API int NK_STYLE_PUSH_IMPLEMENATION(nk,float, floats)
+NK_API int NK_STYLE_PUSH_IMPLEMENATION(struct nk, vec2, vectors)
+NK_API int NK_STYLE_PUSH_IMPLEMENATION(nk,flags, flags)
+NK_API int NK_STYLE_PUSH_IMPLEMENATION(struct nk,color, colors)
+
+NK_API int NK_STYLE_POP_IMPLEMENATION(style_item, style_items)
+NK_API int NK_STYLE_POP_IMPLEMENATION(float,floats)
+NK_API int NK_STYLE_POP_IMPLEMENATION(vec2, vectors)
+NK_API int NK_STYLE_POP_IMPLEMENATION(flags,flags)
+NK_API int NK_STYLE_POP_IMPLEMENATION(color,colors)
+
+NK_API int
+nk_style_set_cursor(struct nk_context *ctx, enum nk_style_cursor c)
+{
+    struct nk_style *style;
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+    style = &ctx->style;
+    if (style->cursors[c]) {
+        style->cursor_active = style->cursors[c];
+        return 1;
+    }
+    return 0;
+}
+
+NK_API void
+nk_style_show_cursor(struct nk_context *ctx)
+{
+    ctx->style.cursor_visible = nk_true;
+}
+
+NK_API void
+nk_style_hide_cursor(struct nk_context *ctx)
+{
+    ctx->style.cursor_visible = nk_false;
+}
+
+NK_API void
+nk_style_load_cursor(struct nk_context *ctx, enum nk_style_cursor cursor,
+    const struct nk_cursor *c)
+{
+    struct nk_style *style;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    style = &ctx->style;
+    style->cursors[cursor] = c;
+}
+
+NK_API void
+nk_style_load_all_cursors(struct nk_context *ctx, struct nk_cursor *cursors)
+{
+    int i = 0;
+    struct nk_style *style;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    style = &ctx->style;
+    for (i = 0; i < NK_CURSOR_COUNT; ++i)
+        style->cursors[i] = &cursors[i];
+    style->cursor_visible = nk_true;
+}
+
+/* ===============================================================
+ *
+ *                          POOL
+ *
+ * ===============================================================*/
+NK_INTERN void
+nk_pool_init(struct nk_pool *pool, struct nk_allocator *alloc,
+    unsigned int capacity)
+{
+    nk_zero(pool, sizeof(*pool));
+    pool->alloc = *alloc;
+    pool->capacity = capacity;
+    pool->type = NK_BUFFER_DYNAMIC;
+    pool->pages = 0;
+}
+
+NK_INTERN void
+nk_pool_free(struct nk_pool *pool)
+{
+    struct nk_page *iter = pool->pages;
+    if (!pool) return;
+    if (pool->type == NK_BUFFER_FIXED) return;
+    while (iter) {
+        struct nk_page *next = iter->next;
+        pool->alloc.free(pool->alloc.userdata, iter);
+        iter = next;
+    }
+}
+
+NK_INTERN void
+nk_pool_init_fixed(struct nk_pool *pool, void *memory, nk_size size)
+{
+    nk_zero(pool, sizeof(*pool));
+    NK_ASSERT(size >= sizeof(struct nk_page));
+    if (size < sizeof(struct nk_page)) return;
+    pool->capacity = (unsigned)(size - sizeof(struct nk_page)) / sizeof(struct nk_page_element);
+    pool->pages = (struct nk_page*)memory;
+    pool->type = NK_BUFFER_FIXED;
+    pool->size = size;
+}
+
+NK_INTERN struct nk_page_element*
+nk_pool_alloc(struct nk_pool *pool)
+{
+    if (!pool->pages || pool->pages->size >= pool->capacity) {
+        /* allocate new page */
+        struct nk_page *page;
+        if (pool->type == NK_BUFFER_FIXED) {
+            if (!pool->pages) {
+                NK_ASSERT(pool->pages);
+                return 0;
+            }
+            NK_ASSERT(pool->pages->size < pool->capacity);
+            return 0;
+        } else {
+            nk_size size = sizeof(struct nk_page);
+            size += NK_POOL_DEFAULT_CAPACITY * sizeof(union nk_page_data);
+            page = (struct nk_page*)pool->alloc.alloc(pool->alloc.userdata,0, size);
+            page->next = pool->pages;
+            pool->pages = page;
+            page->size = 0;
+        }
+    }
+    return &pool->pages->win[pool->pages->size++];
+}
+
+/* ===============================================================
+ *
+ *                          CONTEXT
+ *
+ * ===============================================================*/
+NK_INTERN void* nk_create_window(struct nk_context *ctx);
+NK_INTERN void nk_remove_window(struct nk_context*, struct nk_window*);
+NK_INTERN void nk_free_window(struct nk_context *ctx, struct nk_window *win);
+NK_INTERN void nk_free_table(struct nk_context *ctx, struct nk_table *tbl);
+NK_INTERN void nk_remove_table(struct nk_window *win, struct nk_table *tbl);
+NK_INTERN void* nk_create_panel(struct nk_context *ctx);
+NK_INTERN void nk_free_panel(struct nk_context*, struct nk_panel *pan);
+
+NK_INTERN void
+nk_setup(struct nk_context *ctx, const struct nk_user_font *font)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    nk_zero_struct(*ctx);
+    nk_style_default(ctx);
+    ctx->seq = 1;
+    if (font) ctx->style.font = font;
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+    nk_draw_list_init(&ctx->draw_list);
+#endif
+}
+
+#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
+NK_API int
+nk_init_default(struct nk_context *ctx, const struct nk_user_font *font)
+{
+    struct nk_allocator alloc;
+    alloc.userdata.ptr = 0;
+    alloc.alloc = nk_malloc;
+    alloc.free = nk_mfree;
+    return nk_init(ctx, &alloc, font);
+}
+#endif
+
+NK_API int
+nk_init_fixed(struct nk_context *ctx, void *memory, nk_size size,
+    const struct nk_user_font *font)
+{
+    NK_ASSERT(memory);
+    if (!memory) return 0;
+    nk_setup(ctx, font);
+    nk_buffer_init_fixed(&ctx->memory, memory, size);
+    ctx->use_pool = nk_false;
+    return 1;
+}
+
+NK_API int
+nk_init_custom(struct nk_context *ctx, struct nk_buffer *cmds,
+    struct nk_buffer *pool, const struct nk_user_font *font)
+{
+    NK_ASSERT(cmds);
+    NK_ASSERT(pool);
+    if (!cmds || !pool) return 0;
+
+    nk_setup(ctx, font);
+    ctx->memory = *cmds;
+    if (pool->type == NK_BUFFER_FIXED) {
+        /* take memory from buffer and alloc fixed pool */
+        nk_pool_init_fixed(&ctx->pool, pool->memory.ptr, pool->memory.size);
+    } else {
+        /* create dynamic pool from buffer allocator */
+        struct nk_allocator *alloc = &pool->pool;
+        nk_pool_init(&ctx->pool, alloc, NK_POOL_DEFAULT_CAPACITY);
+    }
+    ctx->use_pool = nk_true;
+    return 1;
+}
+
+NK_API int
+nk_init(struct nk_context *ctx, struct nk_allocator *alloc,
+    const struct nk_user_font *font)
+{
+    NK_ASSERT(alloc);
+    if (!alloc) return 0;
+    nk_setup(ctx, font);
+    nk_buffer_init(&ctx->memory, alloc, NK_DEFAULT_COMMAND_BUFFER_SIZE);
+    nk_pool_init(&ctx->pool, alloc, NK_POOL_DEFAULT_CAPACITY);
+    ctx->use_pool = nk_true;
+    return 1;
+}
+
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+NK_API void
+nk_set_user_data(struct nk_context *ctx, nk_handle handle)
+{
+    if (!ctx) return;
+    ctx->userdata = handle;
+    if (ctx->current)
+        ctx->current->buffer.userdata = handle;
+}
+#endif
+
+NK_API void
+nk_free(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    nk_buffer_free(&ctx->memory);
+    if (ctx->use_pool)
+        nk_pool_free(&ctx->pool);
+
+    nk_zero(&ctx->input, sizeof(ctx->input));
+    nk_zero(&ctx->style, sizeof(ctx->style));
+    nk_zero(&ctx->memory, sizeof(ctx->memory));
+
+    ctx->seq = 0;
+    ctx->build = 0;
+    ctx->begin = 0;
+    ctx->end = 0;
+    ctx->active = 0;
+    ctx->current = 0;
+    ctx->freelist = 0;
+    ctx->count = 0;
+}
+
+NK_API void
+nk_clear(struct nk_context *ctx)
+{
+    struct nk_window *iter;
+    struct nk_window *next;
+    NK_ASSERT(ctx);
+
+    if (!ctx) return;
+    if (ctx->use_pool)
+        nk_buffer_clear(&ctx->memory);
+    else nk_buffer_reset(&ctx->memory, NK_BUFFER_FRONT);
+
+    ctx->build = 0;
+    ctx->memory.calls = 0;
+    ctx->last_widget_state = 0;
+    ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_ARROW];
+    NK_MEMSET(&ctx->overlay, 0, sizeof(ctx->overlay));
+#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+    nk_draw_list_clear(&ctx->draw_list);
+#endif
+
+    /* garbage collector */
+    iter = ctx->begin;
+    while (iter) {
+        /* make sure minimized windows do not get removed */
+        if ((iter->flags & NK_WINDOW_MINIMIZED) &&
+            !(iter->flags & NK_WINDOW_CLOSED)) {
+            iter = iter->next;
+            continue;
+        }
+        /* remove hotness from hidden or closed windows*/
+        if (((iter->flags & NK_WINDOW_HIDDEN) ||
+            (iter->flags & NK_WINDOW_CLOSED)) &&
+            iter == ctx->active) {
+            ctx->active = iter->prev;
+            ctx->end = iter->prev;
+            if (ctx->active)
+                ctx->active->flags &= ~NK_WINDOW_ROM;
+        }
+
+        /* free unused popup windows */
+        if (iter->popup.win && iter->popup.win->seq != ctx->seq) {
+            nk_free_window(ctx, iter->popup.win);
+            iter->popup.win = 0;
+        }
+        /* remove unused window state tables */
+        {struct nk_table *n, *it = iter->tables;
+        while (it) {
+            n = it->next;
+            if (it->seq != ctx->seq) {
+                nk_remove_table(iter, it);
+                nk_zero(it, sizeof(union nk_page_data));
+                nk_free_table(ctx, it);
+                if (it == iter->tables)
+                    iter->tables = n;
+            }
+            it = n;
+        }}
+        /* window itself is not used anymore so free */
+        if (iter->seq != ctx->seq || iter->flags & NK_WINDOW_CLOSED) {
+            next = iter->next;
+            nk_remove_window(ctx, iter);
+            nk_free_window(ctx, iter);
+            iter = next;
+        } else iter = iter->next;
+    }
+    ctx->seq++;
+}
+
+/* ----------------------------------------------------------------
+ *
+ *                          BUFFERING
+ *
+ * ---------------------------------------------------------------*/
+NK_INTERN void
+nk_start_buffer(struct nk_context *ctx, struct nk_command_buffer *buffer)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(buffer);
+    if (!ctx || !buffer) return;
+    buffer->begin = ctx->memory.allocated;
+    buffer->end = buffer->begin;
+    buffer->last = buffer->begin;
+    buffer->clip = nk_null_rect;
+}
+
+NK_INTERN void
+nk_start(struct nk_context *ctx, struct nk_window *win)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(win);
+    nk_start_buffer(ctx, &win->buffer);
+}
+
+NK_INTERN void
+nk_start_popup(struct nk_context *ctx, struct nk_window *win)
+{
+    struct nk_popup_buffer *buf;
+    NK_ASSERT(ctx);
+    NK_ASSERT(win);
+    if (!ctx || !win) return;
+
+    /* save buffer fill state for popup */
+    buf = &win->popup.buf;
+    buf->begin = win->buffer.end;
+    buf->end = win->buffer.end;
+    buf->parent = win->buffer.last;
+    buf->last = buf->begin;
+    buf->active = nk_true;
+}
+
+NK_INTERN void
+nk_finish_popup(struct nk_context *ctx, struct nk_window *win)
+{
+    struct nk_popup_buffer *buf;
+    NK_ASSERT(ctx);
+    NK_ASSERT(win);
+    if (!ctx || !win) return;
+
+    buf = &win->popup.buf;
+    buf->last = win->buffer.last;
+    buf->end = win->buffer.end;
+}
+
+NK_INTERN void
+nk_finish_buffer(struct nk_context *ctx, struct nk_command_buffer *buffer)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(buffer);
+    if (!ctx || !buffer) return;
+    buffer->end = ctx->memory.allocated;
+}
+
+NK_INTERN void
+nk_finish(struct nk_context *ctx, struct nk_window *win)
+{
+    struct nk_popup_buffer *buf;
+    struct nk_command *parent_last;
+    void *memory;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(win);
+    if (!ctx || !win) return;
+    nk_finish_buffer(ctx, &win->buffer);
+    if (!win->popup.buf.active) return;
+
+    buf = &win->popup.buf;
+    memory = ctx->memory.memory.ptr;
+    parent_last = nk_ptr_add(struct nk_command, memory, buf->parent);
+    parent_last->next = buf->end;
+}
+
+NK_INTERN void
+nk_build(struct nk_context *ctx)
+{
+    struct nk_window *iter = 0;
+    struct nk_command *cmd = 0;
+    nk_byte *buffer = 0;
+
+    /* draw cursor overlay */
+    if (!ctx->style.cursor_active)
+        ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_ARROW];
+    if (ctx->style.cursor_active && !ctx->input.mouse.grabbed && ctx->style.cursor_visible) {
+        struct nk_rect mouse_bounds;
+        const struct nk_cursor *cursor = ctx->style.cursor_active;
+        nk_command_buffer_init(&ctx->overlay, &ctx->memory, NK_CLIPPING_OFF);
+        nk_start_buffer(ctx, &ctx->overlay);
+
+        mouse_bounds.x = ctx->input.mouse.pos.x - cursor->offset.x;
+        mouse_bounds.y = ctx->input.mouse.pos.y - cursor->offset.y;
+        mouse_bounds.w = cursor->size.x;
+        mouse_bounds.h = cursor->size.y;
+
+        nk_draw_image(&ctx->overlay, mouse_bounds, &cursor->img, nk_white);
+        nk_finish_buffer(ctx, &ctx->overlay);
+    }
+    /* build one big draw command list out of all window buffers */
+    iter = ctx->begin;
+    buffer = (nk_byte*)ctx->memory.memory.ptr;
+    while (iter != 0) {
+        struct nk_window *next = iter->next;
+        if (iter->buffer.last == iter->buffer.begin || (iter->flags & NK_WINDOW_HIDDEN)||
+            iter->seq != ctx->seq)
+            goto cont;
+
+        cmd = nk_ptr_add(struct nk_command, buffer, iter->buffer.last);
+        while (next && ((next->buffer.last == next->buffer.begin) ||
+            (next->flags & NK_WINDOW_HIDDEN)))
+            next = next->next; /* skip empty command buffers */
+
+        if (next) cmd->next = next->buffer.begin;
+        cont: iter = next;
+    }
+    /* append all popup draw commands into lists */
+    iter = ctx->begin;
+    while (iter != 0) {
+        struct nk_window *next = iter->next;
+        struct nk_popup_buffer *buf;
+        if (!iter->popup.buf.active)
+            goto skip;
+
+        buf = &iter->popup.buf;
+        cmd->next = buf->begin;
+        cmd = nk_ptr_add(struct nk_command, buffer, buf->last);
+        buf->active = nk_false;
+        skip: iter = next;
+    }
+    /* append overlay commands */
+    if (cmd) {
+        if (ctx->overlay.end != ctx->overlay.begin)
+            cmd->next = ctx->overlay.begin;
+        else cmd->next = ctx->memory.allocated;
+    }
+}
+
+NK_API const struct nk_command*
+nk__begin(struct nk_context *ctx)
+{
+    struct nk_window *iter;
+    nk_byte *buffer;
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+    if (!ctx->count) return 0;
+
+    buffer = (nk_byte*)ctx->memory.memory.ptr;
+    if (!ctx->build) {
+        nk_build(ctx);
+        ctx->build = nk_true;
+    }
+    iter = ctx->begin;
+    while (iter && ((iter->buffer.begin == iter->buffer.end) || (iter->flags & NK_WINDOW_HIDDEN)))
+        iter = iter->next;
+    if (!iter) return 0;
+    return nk_ptr_add_const(struct nk_command, buffer, iter->buffer.begin);
+}
+
+NK_API const struct nk_command*
+nk__next(struct nk_context *ctx, const struct nk_command *cmd)
+{
+    nk_byte *buffer;
+    const struct nk_command *next;
+    NK_ASSERT(ctx);
+    if (!ctx || !cmd || !ctx->count) return 0;
+    if (cmd->next >= ctx->memory.allocated) return 0;
+    buffer = (nk_byte*)ctx->memory.memory.ptr;
+    next = nk_ptr_add_const(struct nk_command, buffer, cmd->next);
+    return next;
+}
+
+/* ----------------------------------------------------------------
+ *
+ *                          PANEL
+ *
+ * ---------------------------------------------------------------*/
+static int
+nk_panel_has_header(nk_flags flags, const char *title)
+{
+    int active = 0;
+    active = (flags & (NK_WINDOW_CLOSABLE|NK_WINDOW_MINIMIZABLE));
+    active = active || (flags & NK_WINDOW_TITLE);
+    active = active && !(flags & NK_WINDOW_HIDDEN) && title;
+    return active;
+}
+
+NK_INTERN struct nk_vec2
+nk_panel_get_padding(const struct nk_style *style, enum nk_panel_type type)
+{
+    switch (type) {
+    default:
+    case NK_PANEL_WINDOW: return style->window.padding;
+    case NK_PANEL_GROUP: return style->window.group_padding;
+    case NK_PANEL_POPUP: return style->window.popup_padding;
+    case NK_PANEL_CONTEXTUAL: return style->window.contextual_padding;
+    case NK_PANEL_COMBO: return style->window.combo_padding;
+    case NK_PANEL_MENU: return style->window.menu_padding;
+    case NK_PANEL_TOOLTIP: return style->window.menu_padding;
+    }
+}
+
+NK_INTERN float
+nk_panel_get_border(const struct nk_style *style, nk_flags flags,
+    enum nk_panel_type type)
+{
+    if (flags & NK_WINDOW_BORDER) {
+        switch (type) {
+        default:
+        case NK_PANEL_WINDOW: return style->window.border;
+        case NK_PANEL_GROUP: return style->window.group_border;
+        case NK_PANEL_POPUP: return style->window.popup_border;
+        case NK_PANEL_CONTEXTUAL: return style->window.contextual_border;
+        case NK_PANEL_COMBO: return style->window.combo_border;
+        case NK_PANEL_MENU: return style->window.menu_border;
+        case NK_PANEL_TOOLTIP: return style->window.menu_border;
+    }} else return 0;
+}
+
+NK_INTERN struct nk_color
+nk_panel_get_border_color(const struct nk_style *style, enum nk_panel_type type)
+{
+    switch (type) {
+    default:
+    case NK_PANEL_WINDOW: return style->window.border_color;
+    case NK_PANEL_GROUP: return style->window.group_border_color;
+    case NK_PANEL_POPUP: return style->window.popup_border_color;
+    case NK_PANEL_CONTEXTUAL: return style->window.contextual_border_color;
+    case NK_PANEL_COMBO: return style->window.combo_border_color;
+    case NK_PANEL_MENU: return style->window.menu_border_color;
+    case NK_PANEL_TOOLTIP: return style->window.menu_border_color;
+    }
+}
+
+NK_INTERN int
+nk_panel_is_sub(enum nk_panel_type type)
+{
+    return (type & NK_PANEL_SET_SUB)?1:0;
+}
+
+NK_INTERN int
+nk_panel_is_nonblock(enum nk_panel_type type)
+{
+    return (type & NK_PANEL_SET_NONBLOCK)?1:0;
+}
+
+NK_INTERN int
+nk_panel_begin(struct nk_context *ctx, const char *title, enum nk_panel_type panel_type)
+{
+    struct nk_input *in;
+    struct nk_window *win;
+    struct nk_panel *layout;
+    struct nk_command_buffer *out;
+    const struct nk_style *style;
+    const struct nk_user_font *font;
+
+    struct nk_vec2 scrollbar_size;
+    struct nk_vec2 panel_padding;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout) return 0;
+    nk_zero(ctx->current->layout, sizeof(*ctx->current->layout));
+    if ((ctx->current->flags & NK_WINDOW_HIDDEN) || (ctx->current->flags & NK_WINDOW_CLOSED)) {
+        nk_zero(ctx->current->layout, sizeof(struct nk_panel));
+        ctx->current->layout->type = panel_type;
+        return 0;
+    }
+    /* pull state into local stack */
+    style = &ctx->style;
+    font = style->font;
+    win = ctx->current;
+    layout = win->layout;
+    out = &win->buffer;
+    in = (win->flags & NK_WINDOW_NO_INPUT) ? 0: &ctx->input;
+#ifdef NK_INCLUDE_COMMAND_USERDATA
+    win->buffer.userdata = ctx->userdata;
+#endif
+    /* pull style configuration into local stack */
+    scrollbar_size = style->window.scrollbar_size;
+    panel_padding = nk_panel_get_padding(style, panel_type);
+
+    /* window movement */
+    if ((win->flags & NK_WINDOW_MOVABLE) && !(win->flags & NK_WINDOW_ROM)) {
+        int left_mouse_down;
+        int left_mouse_click_in_cursor;
+
+        /* calculate draggable window space */
+        struct nk_rect header;
+        header.x = win->bounds.x;
+        header.y = win->bounds.y;
+        header.w = win->bounds.w;
+        if (nk_panel_has_header(win->flags, title)) {
+            header.h = font->height + 2.0f * style->window.header.padding.y;
+            header.h += 2.0f * style->window.header.label_padding.y;
+        } else header.h = panel_padding.y;
+
+        /* window movement by dragging */
+        left_mouse_down = in->mouse.buttons[NK_BUTTON_LEFT].down;
+        left_mouse_click_in_cursor = nk_input_has_mouse_click_down_in_rect(in,
+            NK_BUTTON_LEFT, header, nk_true);
+        if (left_mouse_down && left_mouse_click_in_cursor) {
+            win->bounds.x = win->bounds.x + in->mouse.delta.x;
+            win->bounds.y = win->bounds.y + in->mouse.delta.y;
+            in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x += in->mouse.delta.x;
+            in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.y += in->mouse.delta.y;
+            ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_MOVE];
+        }
+    }
+
+    /* setup panel */
+    layout->type = panel_type;
+    layout->flags = win->flags;
+    layout->bounds = win->bounds;
+    layout->bounds.x += panel_padding.x;
+    layout->bounds.w -= 2*panel_padding.x;
+    if (win->flags & NK_WINDOW_BORDER) {
+        layout->border = nk_panel_get_border(style, win->flags, panel_type);
+        layout->bounds = nk_shrink_rect(layout->bounds, layout->border);
+    } else layout->border = 0;
+    layout->at_y = layout->bounds.y;
+    layout->at_x = layout->bounds.x;
+    layout->max_x = 0;
+    layout->header_height = 0;
+    layout->footer_height = 0;
+    nk_layout_reset_min_row_height(ctx);
+    layout->row.index = 0;
+    layout->row.columns = 0;
+    layout->row.ratio = 0;
+    layout->row.item_width = 0;
+    layout->row.tree_depth = 0;
+    layout->row.height = panel_padding.y;
+    layout->has_scrolling = nk_true;
+    if (!(win->flags & NK_WINDOW_NO_SCROLLBAR))
+        layout->bounds.w -= scrollbar_size.x;
+    if (!nk_panel_is_nonblock(panel_type)) {
+        layout->footer_height = 0;
+        if (!(win->flags & NK_WINDOW_NO_SCROLLBAR) || win->flags & NK_WINDOW_SCALABLE)
+            layout->footer_height = scrollbar_size.y;
+        layout->bounds.h -= layout->footer_height;
+    }
+
+    /* panel header */
+    if (nk_panel_has_header(win->flags, title))
+    {
+        struct nk_text text;
+        struct nk_rect header;
+        const struct nk_style_item *background = 0;
+
+        /* calculate header bounds */
+        header.x = win->bounds.x;
+        header.y = win->bounds.y;
+        header.w = win->bounds.w;
+        header.h = font->height + 2.0f * style->window.header.padding.y;
+        header.h += (2.0f * style->window.header.label_padding.y);
+
+        /* shrink panel by header */
+        layout->header_height = header.h;
+        layout->bounds.y += header.h;
+        layout->bounds.h -= header.h;
+        layout->at_y += header.h;
+
+        /* select correct header background and text color */
+        if (ctx->active == win) {
+            background = &style->window.header.active;
+            text.text = style->window.header.label_active;
+        } else if (nk_input_is_mouse_hovering_rect(&ctx->input, header)) {
+            background = &style->window.header.hover;
+            text.text = style->window.header.label_hover;
+        } else {
+            background = &style->window.header.normal;
+            text.text = style->window.header.label_normal;
+        }
+
+        /* draw header background */
+        header.h += 1.0f;
+        if (background->type == NK_STYLE_ITEM_IMAGE) {
+            text.background = nk_rgba(0,0,0,0);
+            nk_draw_image(&win->buffer, header, &background->data.image, nk_white);
+        } else {
+            text.background = background->data.color;
+            nk_fill_rect(out, header, 0, background->data.color);
+        }
+
+        /* window close button */
+        {struct nk_rect button;
+        button.y = header.y + style->window.header.padding.y;
+        button.h = header.h - 2 * style->window.header.padding.y;
+        button.w = button.h;
+        if (win->flags & NK_WINDOW_CLOSABLE) {
+            nk_flags ws = 0;
+            if (style->window.header.align == NK_HEADER_RIGHT) {
+                button.x = (header.w + header.x) - (button.w + style->window.header.padding.x);
+                header.w -= button.w + style->window.header.spacing.x + style->window.header.padding.x;
+            } else {
+                button.x = header.x + style->window.header.padding.x;
+                header.x += button.w + style->window.header.spacing.x + style->window.header.padding.x;
+            }
+
+            if (nk_do_button_symbol(&ws, &win->buffer, button,
+                style->window.header.close_symbol, NK_BUTTON_DEFAULT,
+                &style->window.header.close_button, in, style->font) && !(win->flags & NK_WINDOW_ROM))
+            {
+                layout->flags |= NK_WINDOW_HIDDEN;
+                layout->flags &= (nk_flags)~NK_WINDOW_MINIMIZED;
+            }
+        }
+
+        /* window minimize button */
+        if (win->flags & NK_WINDOW_MINIMIZABLE) {
+            nk_flags ws = 0;
+            if (style->window.header.align == NK_HEADER_RIGHT) {
+                button.x = (header.w + header.x) - button.w;
+                if (!(win->flags & NK_WINDOW_CLOSABLE)) {
+                    button.x -= style->window.header.padding.x;
+                    header.w -= style->window.header.padding.x;
+                }
+                header.w -= button.w + style->window.header.spacing.x;
+            } else {
+                button.x = header.x;
+                header.x += button.w + style->window.header.spacing.x + style->window.header.padding.x;
+            }
+            if (nk_do_button_symbol(&ws, &win->buffer, button, (layout->flags & NK_WINDOW_MINIMIZED)?
+                style->window.header.maximize_symbol: style->window.header.minimize_symbol,
+                NK_BUTTON_DEFAULT, &style->window.header.minimize_button, in, style->font) && !(win->flags & NK_WINDOW_ROM))
+                layout->flags = (layout->flags & NK_WINDOW_MINIMIZED) ?
+                    layout->flags & (nk_flags)~NK_WINDOW_MINIMIZED:
+                    layout->flags | NK_WINDOW_MINIMIZED;
+        }}
+
+        {/* window header title */
+        int text_len = nk_strlen(title);
+        struct nk_rect label = {0,0,0,0};
+        float t = font->width(font->userdata, font->height, title, text_len);
+        text.padding = nk_vec2(0,0);
+
+        label.x = header.x + style->window.header.padding.x;
+        label.x += style->window.header.label_padding.x;
+        label.y = header.y + style->window.header.label_padding.y;
+        label.h = font->height + 2 * style->window.header.label_padding.y;
+        label.w = t + 2 * style->window.header.spacing.x;
+        label.w = NK_CLAMP(0, label.w, header.x + header.w - label.x);
+        nk_widget_text(out, label,(const char*)title, text_len, &text, NK_TEXT_LEFT, font);}
+    }
+
+    /* draw window background */
+    if (!(layout->flags & NK_WINDOW_MINIMIZED) && !(layout->flags & NK_WINDOW_DYNAMIC)) {
+        struct nk_rect body;
+        body.x = win->bounds.x;
+        body.w = win->bounds.w;
+        body.y = (win->bounds.y + layout->header_height);
+        body.h = (win->bounds.h - layout->header_height);
+        if (style->window.fixed_background.type == NK_STYLE_ITEM_IMAGE)
+            nk_draw_image(out, body, &style->window.fixed_background.data.image, nk_white);
+        else nk_fill_rect(out, body, 0, style->window.fixed_background.data.color);
+    }
+
+    /* set clipping rectangle */
+    {struct nk_rect clip;
+    layout->clip = layout->bounds;
+    nk_unify(&clip, &win->buffer.clip, layout->clip.x, layout->clip.y,
+        layout->clip.x + layout->clip.w, layout->clip.y + layout->clip.h);
+    nk_push_scissor(out, clip);
+    layout->clip = clip;}
+    return !(layout->flags & NK_WINDOW_HIDDEN) && !(layout->flags & NK_WINDOW_MINIMIZED);
+}
+
+NK_INTERN void
+nk_panel_end(struct nk_context *ctx)
+{
+    struct nk_input *in;
+    struct nk_window *window;
+    struct nk_panel *layout;
+    const struct nk_style *style;
+    struct nk_command_buffer *out;
+
+    struct nk_vec2 scrollbar_size;
+    struct nk_vec2 panel_padding;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    window = ctx->current;
+    layout = window->layout;
+    style = &ctx->style;
+    out = &window->buffer;
+    in = (layout->flags & NK_WINDOW_ROM || layout->flags & NK_WINDOW_NO_INPUT) ? 0 :&ctx->input;
+    if (!nk_panel_is_sub(layout->type))
+        nk_push_scissor(out, nk_null_rect);
+
+    /* cache configuration data */
+    scrollbar_size = style->window.scrollbar_size;
+    panel_padding = nk_panel_get_padding(style, layout->type);
+
+    /* update the current cursor Y-position to point over the last added widget */
+    layout->at_y += layout->row.height;
+
+    /* dynamic panels */
+    if (layout->flags & NK_WINDOW_DYNAMIC && !(layout->flags & NK_WINDOW_MINIMIZED))
+    {
+        /* update panel height to fit dynamic growth */
+        struct nk_rect empty_space;
+        if (layout->at_y < (layout->bounds.y + layout->bounds.h))
+            layout->bounds.h = layout->at_y - layout->bounds.y;
+
+        /* fill top empty space */
+        empty_space.x = window->bounds.x;
+        empty_space.y = layout->bounds.y;
+        empty_space.h = panel_padding.y;
+        empty_space.w = window->bounds.w;
+        nk_fill_rect(out, empty_space, 0, style->window.background);
+
+        /* fill left empty space */
+        empty_space.x = window->bounds.x;
+        empty_space.y = layout->bounds.y;
+        empty_space.w = panel_padding.x + layout->border;
+        empty_space.h = layout->bounds.h;
+        nk_fill_rect(out, empty_space, 0, style->window.background);
+
+        /* fill right empty space */
+        empty_space.x = layout->bounds.x + layout->bounds.w - layout->border;
+        empty_space.y = layout->bounds.y;
+        empty_space.w = panel_padding.x + layout->border;
+        empty_space.h = layout->bounds.h;
+        if (*layout->offset_y == 0 && !(layout->flags & NK_WINDOW_NO_SCROLLBAR))
+            empty_space.w += scrollbar_size.x;
+        nk_fill_rect(out, empty_space, 0, style->window.background);
+
+        /* fill bottom empty space */
+        if (*layout->offset_x != 0 && !(layout->flags & NK_WINDOW_NO_SCROLLBAR)) {
+            empty_space.x = window->bounds.x;
+            empty_space.y = layout->bounds.y + layout->bounds.h;
+            empty_space.w = window->bounds.w;
+            empty_space.h = scrollbar_size.y;
+            nk_fill_rect(out, empty_space, 0, style->window.background);
+        }
+    }
+
+    /* scrollbars */
+    if (!(layout->flags & NK_WINDOW_NO_SCROLLBAR) &&
+        !(layout->flags & NK_WINDOW_MINIMIZED) &&
+        window->scrollbar_hiding_timer < NK_SCROLLBAR_HIDING_TIMEOUT)
+    {
+        struct nk_rect scroll;
+        int scroll_has_scrolling;
+        float scroll_target;
+        float scroll_offset;
+        float scroll_step;
+        float scroll_inc;
+
+        /* mouse wheel scrolling */
+        if (nk_panel_is_sub(layout->type))
+        {
+            /* sub-window mouse wheel scrolling */
+            struct nk_window *root_window = window;
+            struct nk_panel *root_panel = window->layout;
+            while (root_panel->parent)
+                root_panel = root_panel->parent;
+            while (root_window->parent)
+                root_window = root_window->parent;
+
+            /* only allow scrolling if parent window is active */
+            scroll_has_scrolling = 0;
+            if ((root_window == ctx->active) && layout->has_scrolling) {
+                /* and panel is being hovered and inside clip rect*/
+                if (nk_input_is_mouse_hovering_rect(in, layout->bounds) &&
+                    NK_INTERSECT(layout->bounds.x, layout->bounds.y, layout->bounds.w, layout->bounds.h,
+                        root_panel->clip.x, root_panel->clip.y, root_panel->clip.w, root_panel->clip.h))
+                {
+                    /* deactivate all parent scrolling */
+                    root_panel = window->layout;
+                    while (root_panel->parent) {
+                        root_panel->has_scrolling = nk_false;
+                        root_panel = root_panel->parent;
+                    }
+                    root_panel->has_scrolling = nk_false;
+                    scroll_has_scrolling = nk_true;
+                }
+            }
+        } else if (!nk_panel_is_sub(layout->type)) {
+            /* window mouse wheel scrolling */
+            scroll_has_scrolling = (window == ctx->active) && layout->has_scrolling;
+            if (in && (in->mouse.scroll_delta.y > 0 || in->mouse.scroll_delta.x > 0) && scroll_has_scrolling)
+                window->scrolled = nk_true;
+            else window->scrolled = nk_false;
+        } else scroll_has_scrolling = nk_false;
+
+        {
+            /* vertical scrollbar */
+            nk_flags state = 0;
+            scroll.x = layout->bounds.x + layout->bounds.w + panel_padding.x;
+            scroll.y = layout->bounds.y;
+            scroll.w = scrollbar_size.x;
+            scroll.h = layout->bounds.h;
+
+            scroll_offset = (float)*layout->offset_y;
+            scroll_step = scroll.h * 0.10f;
+            scroll_inc = scroll.h * 0.01f;
+            scroll_target = (float)(int)(layout->at_y - scroll.y);
+            scroll_offset = nk_do_scrollbarv(&state, out, scroll, scroll_has_scrolling,
+                scroll_offset, scroll_target, scroll_step, scroll_inc,
+                &ctx->style.scrollv, in, style->font);
+            *layout->offset_y = (nk_uint)scroll_offset;
+            if (in && scroll_has_scrolling)
+                in->mouse.scroll_delta.y = 0;
+        }
+        {
+            /* horizontal scrollbar */
+            nk_flags state = 0;
+            scroll.x = layout->bounds.x;
+            scroll.y = layout->bounds.y + layout->bounds.h;
+            scroll.w = layout->bounds.w;
+            scroll.h = scrollbar_size.y;
+
+            scroll_offset = (float)*layout->offset_x;
+            scroll_target = (float)(int)(layout->max_x - scroll.x);
+            scroll_step = layout->max_x * 0.05f;
+            scroll_inc = layout->max_x * 0.005f;
+            scroll_offset = nk_do_scrollbarh(&state, out, scroll, scroll_has_scrolling,
+                scroll_offset, scroll_target, scroll_step, scroll_inc,
+                &ctx->style.scrollh, in, style->font);
+            *layout->offset_x = (nk_uint)scroll_offset;
+        }
+    }
+
+    /* hide scroll if no user input */
+    if (window->flags & NK_WINDOW_SCROLL_AUTO_HIDE) {
+        int has_input = ctx->input.mouse.delta.x != 0 || ctx->input.mouse.delta.y != 0 || ctx->input.mouse.scroll_delta.y != 0;
+        int is_window_hovered = nk_window_is_hovered(ctx);
+        int any_item_active = (ctx->last_widget_state & NK_WIDGET_STATE_MODIFIED);
+        if ((!has_input && is_window_hovered) || (!is_window_hovered && !any_item_active))
+            window->scrollbar_hiding_timer += ctx->delta_time_seconds;
+        else window->scrollbar_hiding_timer = 0;
+    } else window->scrollbar_hiding_timer = 0;
+
+    /* window border */
+    if (layout->flags & NK_WINDOW_BORDER)
+    {
+        struct nk_color border_color = nk_panel_get_border_color(style, layout->type);
+        const float padding_y = (layout->flags & NK_WINDOW_MINIMIZED) ?
+            style->window.border + window->bounds.y + layout->header_height:
+            (layout->flags & NK_WINDOW_DYNAMIC)?
+            layout->bounds.y + layout->bounds.h + layout->footer_height:
+            window->bounds.y + window->bounds.h;
+
+        /* draw border top */
+        nk_stroke_line(out,window->bounds.x,window->bounds.y,
+            window->bounds.x + window->bounds.w, window->bounds.y,
+            layout->border, border_color);
+
+        /* draw bottom border */
+        nk_stroke_line(out, window->bounds.x, padding_y,
+            window->bounds.x + window->bounds.w, padding_y, layout->border, border_color);
+
+        /* draw left border */
+        nk_stroke_line(out, window->bounds.x + layout->border*0.5f,
+            window->bounds.y, window->bounds.x + layout->border*0.5f,
+            padding_y, layout->border, border_color);
+
+        /* draw right border */
+        nk_stroke_line(out, window->bounds.x + window->bounds.w - layout->border*0.5f,
+            window->bounds.y, window->bounds.x + window->bounds.w - layout->border*0.5f,
+            padding_y, layout->border, border_color);
+    }
+
+    /* scaler */
+    if ((layout->flags & NK_WINDOW_SCALABLE) && in && !(layout->flags & NK_WINDOW_MINIMIZED))
+    {
+        /* calculate scaler bounds */
+        struct nk_rect scaler;
+        scaler.w = scrollbar_size.x;
+        scaler.h = scrollbar_size.y;
+        scaler.y = layout->bounds.y + layout->bounds.h;
+        if (layout->flags & NK_WINDOW_SCALE_LEFT)
+            scaler.x = layout->bounds.x - panel_padding.x * 0.5f;
+        else scaler.x = layout->bounds.x + layout->bounds.w + panel_padding.x;
+        if (layout->flags & NK_WINDOW_NO_SCROLLBAR)
+            scaler.x -= scaler.w;
+
+        /* draw scaler */
+        {const struct nk_style_item *item = &style->window.scaler;
+        if (item->type == NK_STYLE_ITEM_IMAGE)
+            nk_draw_image(out, scaler, &item->data.image, nk_white);
+        else {
+            if (layout->flags & NK_WINDOW_SCALE_LEFT) {
+                nk_fill_triangle(out, scaler.x, scaler.y, scaler.x,
+                    scaler.y + scaler.h, scaler.x + scaler.w,
+                    scaler.y + scaler.h, item->data.color);
+            } else {
+                nk_fill_triangle(out, scaler.x + scaler.w, scaler.y, scaler.x + scaler.w,
+                    scaler.y + scaler.h, scaler.x, scaler.y + scaler.h, item->data.color);
+            }
+        }}
+
+        /* do window scaling */
+        if (!(window->flags & NK_WINDOW_ROM)) {
+            struct nk_vec2 window_size = style->window.min_size;
+            int left_mouse_down = in->mouse.buttons[NK_BUTTON_LEFT].down;
+            int left_mouse_click_in_scaler = nk_input_has_mouse_click_down_in_rect(in,
+                    NK_BUTTON_LEFT, scaler, nk_true);
+
+            if (left_mouse_down && left_mouse_click_in_scaler) {
+                float delta_x = in->mouse.delta.x;
+                if (layout->flags & NK_WINDOW_SCALE_LEFT) {
+                    delta_x = -delta_x;
+                    window->bounds.x += in->mouse.delta.x;
+                }
+                /* dragging in x-direction  */
+                if (window->bounds.w + delta_x >= window_size.x) {
+                    if ((delta_x < 0) || (delta_x > 0 && in->mouse.pos.x >= scaler.x)) {
+                        window->bounds.w = window->bounds.w + delta_x;
+                        scaler.x += in->mouse.delta.x;
+                    }
+                }
+                /* dragging in y-direction (only possible if static window) */
+                if (!(layout->flags & NK_WINDOW_DYNAMIC)) {
+                    if (window_size.y < window->bounds.h + in->mouse.delta.y) {
+                        if ((in->mouse.delta.y < 0) || (in->mouse.delta.y > 0 && in->mouse.pos.y >= scaler.y)) {
+                            window->bounds.h = window->bounds.h + in->mouse.delta.y;
+                            scaler.y += in->mouse.delta.y;
+                        }
+                    }
+                }
+                ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT];
+                in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = scaler.x + scaler.w/2.0f;
+                in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.y = scaler.y + scaler.h/2.0f;
+            }
+        }
+    }
+    if (!nk_panel_is_sub(layout->type)) {
+        /* window is hidden so clear command buffer  */
+        if (layout->flags & NK_WINDOW_HIDDEN)
+            nk_command_buffer_reset(&window->buffer);
+        /* window is visible and not tab */
+        else nk_finish(ctx, window);
+    }
+
+    /* NK_WINDOW_REMOVE_ROM flag was set so remove NK_WINDOW_ROM */
+    if (layout->flags & NK_WINDOW_REMOVE_ROM) {
+        layout->flags &= ~(nk_flags)NK_WINDOW_ROM;
+        layout->flags &= ~(nk_flags)NK_WINDOW_REMOVE_ROM;
+    }
+    window->flags = layout->flags;
+
+    /* property garbage collector */
+    if (window->property.active && window->property.old != window->property.seq &&
+        window->property.active == window->property.prev) {
+        nk_zero(&window->property, sizeof(window->property));
+    } else {
+        window->property.old = window->property.seq;
+        window->property.prev = window->property.active;
+        window->property.seq = 0;
+    }
+    /* edit garbage collector */
+    if (window->edit.active && window->edit.old != window->edit.seq &&
+       window->edit.active == window->edit.prev) {
+        nk_zero(&window->edit, sizeof(window->edit));
+    } else {
+        window->edit.old = window->edit.seq;
+        window->edit.prev = window->edit.active;
+        window->edit.seq = 0;
+    }
+    /* contextual garbage collector */
+    if (window->popup.active_con && window->popup.con_old != window->popup.con_count) {
+        window->popup.con_count = 0;
+        window->popup.con_old = 0;
+        window->popup.active_con = 0;
+    } else {
+        window->popup.con_old = window->popup.con_count;
+        window->popup.con_count = 0;
+    }
+    window->popup.combo_count = 0;
+    /* helper to make sure you have a 'nk_tree_push' for every 'nk_tree_pop' */
+    NK_ASSERT(!layout->row.tree_depth);
+}
+
+/* ----------------------------------------------------------------
+ *
+ *                          PAGE ELEMENT
+ *
+ * ---------------------------------------------------------------*/
+NK_INTERN struct nk_page_element*
+nk_create_page_element(struct nk_context *ctx)
+{
+    struct nk_page_element *elem;
+    if (ctx->freelist) {
+        /* unlink page element from free list */
+        elem = ctx->freelist;
+        ctx->freelist = elem->next;
+    } else if (ctx->use_pool) {
+        /* allocate page element from memory pool */
+        elem = nk_pool_alloc(&ctx->pool);
+        NK_ASSERT(elem);
+        if (!elem) return 0;
+    } else {
+        /* allocate new page element from back of fixed size memory buffer */
+        NK_STORAGE const nk_size size = sizeof(struct nk_page_element);
+        NK_STORAGE const nk_size align = NK_ALIGNOF(struct nk_page_element);
+        elem = (struct nk_page_element*)nk_buffer_alloc(&ctx->memory, NK_BUFFER_BACK, size, align);
+        NK_ASSERT(elem);
+        if (!elem) return 0;
+    }
+    nk_zero_struct(*elem);
+    elem->next = 0;
+    elem->prev = 0;
+    return elem;
+}
+
+NK_INTERN void
+nk_link_page_element_into_freelist(struct nk_context *ctx,
+    struct nk_page_element *elem)
+{
+    /* link table into freelist */
+    if (!ctx->freelist) {
+        ctx->freelist = elem;
+    } else {
+        elem->next = ctx->freelist;
+        ctx->freelist = elem;
+    }
+}
+
+NK_INTERN void
+nk_free_page_element(struct nk_context *ctx, struct nk_page_element *elem)
+{
+    /* we have a pool so just add to free list */
+    if (ctx->use_pool) {
+        nk_link_page_element_into_freelist(ctx, elem);
+        return;
+    }
+    /* if possible remove last element from back of fixed memory buffer */
+    {void *elem_end = (void*)(elem + 1);
+    void *buffer_end = (nk_byte*)ctx->memory.memory.ptr + ctx->memory.size;
+    if (elem_end == buffer_end)
+        ctx->memory.size -= sizeof(struct nk_page_element);
+    else nk_link_page_element_into_freelist(ctx, elem);}
+}
+
+/* ----------------------------------------------------------------
+ *
+ *                          PANEL
+ *
+ * ---------------------------------------------------------------*/
+NK_INTERN void*
+nk_create_panel(struct nk_context *ctx)
+{
+    struct nk_page_element *elem;
+    elem = nk_create_page_element(ctx);
+    if (!elem) return 0;
+    nk_zero_struct(*elem);
+    return &elem->data.pan;
+}
+
+NK_INTERN void
+nk_free_panel(struct nk_context *ctx, struct nk_panel *pan)
+{
+    union nk_page_data *pd = NK_CONTAINER_OF(pan, union nk_page_data, pan);
+    struct nk_page_element *pe = NK_CONTAINER_OF(pd, struct nk_page_element, data);
+    nk_free_page_element(ctx, pe);
+}
+
+/* ----------------------------------------------------------------
+ *
+ *                          TABLES
+ *
+ * ---------------------------------------------------------------*/
+NK_INTERN struct nk_table*
+nk_create_table(struct nk_context *ctx)
+{
+    struct nk_page_element *elem;
+    elem = nk_create_page_element(ctx);
+    if (!elem) return 0;
+    nk_zero_struct(*elem);
+    return &elem->data.tbl;
+}
+
+NK_INTERN void
+nk_free_table(struct nk_context *ctx, struct nk_table *tbl)
+{
+    union nk_page_data *pd = NK_CONTAINER_OF(tbl, union nk_page_data, tbl);
+    struct nk_page_element *pe = NK_CONTAINER_OF(pd, struct nk_page_element, data);
+    nk_free_page_element(ctx, pe);
+}
+
+NK_INTERN void
+nk_push_table(struct nk_window *win, struct nk_table *tbl)
+{
+    if (!win->tables) {
+        win->tables = tbl;
+        tbl->next = 0;
+        tbl->prev = 0;
+        tbl->size = 0;
+        win->table_count = 1;
+        return;
+    }
+    win->tables->prev = tbl;
+    tbl->next = win->tables;
+    tbl->prev = 0;
+    tbl->size = 0;
+    win->tables = tbl;
+    win->table_count++;
+}
+
+NK_INTERN void
+nk_remove_table(struct nk_window *win, struct nk_table *tbl)
+{
+    if (win->tables == tbl)
+        win->tables = tbl->next;
+    if (tbl->next)
+        tbl->next->prev = tbl->prev;
+    if (tbl->prev)
+        tbl->prev->next = tbl->next;
+    tbl->next = 0;
+    tbl->prev = 0;
+}
+
+NK_INTERN nk_uint*
+nk_add_value(struct nk_context *ctx, struct nk_window *win,
+            nk_hash name, nk_uint value)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(win);
+    if (!win || !ctx) return 0;
+    if (!win->tables || win->tables->size >= NK_VALUE_PAGE_CAPACITY) {
+        struct nk_table *tbl = nk_create_table(ctx);
+        NK_ASSERT(tbl);
+        if (!tbl) return 0;
+        nk_push_table(win, tbl);
+    }
+    win->tables->seq = win->seq;
+    win->tables->keys[win->tables->size] = name;
+    win->tables->values[win->tables->size] = value;
+    return &win->tables->values[win->tables->size++];
+}
+
+NK_INTERN nk_uint*
+nk_find_value(struct nk_window *win, nk_hash name)
+{
+    struct nk_table *iter = win->tables;
+    while (iter) {
+        unsigned int i = 0;
+        unsigned int size = iter->size;
+        for (i = 0; i < size; ++i) {
+            if (iter->keys[i] == name) {
+                iter->seq = win->seq;
+                return &iter->values[i];
+            }
+        } size = NK_VALUE_PAGE_CAPACITY;
+        iter = iter->next;
+    }
+    return 0;
+}
+
+/* ----------------------------------------------------------------
+ *
+ *                          WINDOW
+ *
+ * ---------------------------------------------------------------*/
+NK_INTERN void*
+nk_create_window(struct nk_context *ctx)
+{
+    struct nk_page_element *elem;
+    elem = nk_create_page_element(ctx);
+    if (!elem) return 0;
+    elem->data.win.seq = ctx->seq;
+    return &elem->data.win;
+}
+
+NK_INTERN void
+nk_free_window(struct nk_context *ctx, struct nk_window *win)
+{
+    /* unlink windows from list */
+    struct nk_table *it = win->tables;
+    if (win->popup.win) {
+        nk_free_window(ctx, win->popup.win);
+        win->popup.win = 0;
+    }
+    win->next = 0;
+    win->prev = 0;
+
+    while (it) {
+        /*free window state tables */
+        struct nk_table *n = it->next;
+        nk_remove_table(win, it);
+        nk_free_table(ctx, it);
+        if (it == win->tables)
+            win->tables = n;
+        it = n;
+    }
+
+    /* link windows into freelist */
+    {union nk_page_data *pd = NK_CONTAINER_OF(win, union nk_page_data, win);
+    struct nk_page_element *pe = NK_CONTAINER_OF(pd, struct nk_page_element, data);
+    nk_free_page_element(ctx, pe);}
+}
+
+NK_INTERN struct nk_window*
+nk_find_window(struct nk_context *ctx, nk_hash hash, const char *name)
+{
+    struct nk_window *iter;
+    iter = ctx->begin;
+    while (iter) {
+        NK_ASSERT(iter != iter->next);
+        if (iter->name == hash) {
+            int max_len = nk_strlen(iter->name_string);
+            if (!nk_stricmpn(iter->name_string, name, max_len))
+                return iter;
+        }
+        iter = iter->next;
+    }
+    return 0;
+}
+
+enum nk_window_insert_location {
+    NK_INSERT_BACK, /* inserts window into the back of list (front of screen) */
+    NK_INSERT_FRONT /* inserts window into the front of list (back of screen) */
+};
+NK_INTERN void
+nk_insert_window(struct nk_context *ctx, struct nk_window *win,
+    enum nk_window_insert_location loc)
+{
+    const struct nk_window *iter;
+    NK_ASSERT(ctx);
+    NK_ASSERT(win);
+    if (!win || !ctx) return;
+
+    iter = ctx->begin;
+    while (iter) {
+        NK_ASSERT(iter != iter->next);
+        NK_ASSERT(iter != win);
+        if (iter == win) return;
+        iter = iter->next;
+    }
+
+    if (!ctx->begin) {
+        win->next = 0;
+        win->prev = 0;
+        ctx->begin = win;
+        ctx->end = win;
+        ctx->count = 1;
+        return;
+    }
+    if (loc == NK_INSERT_BACK) {
+        struct nk_window *end;
+        end = ctx->end;
+        end->flags |= NK_WINDOW_ROM;
+        end->next = win;
+        win->prev = ctx->end;
+        win->next = 0;
+        ctx->end = win;
+        ctx->active = ctx->end;
+        ctx->end->flags &= ~(nk_flags)NK_WINDOW_ROM;
+    } else {
+        /*ctx->end->flags |= NK_WINDOW_ROM;*/
+        ctx->begin->prev = win;
+        win->next = ctx->begin;
+        win->prev = 0;
+        ctx->begin = win;
+        ctx->begin->flags &= ~(nk_flags)NK_WINDOW_ROM;
+    }
+    ctx->count++;
+}
+
+NK_INTERN void
+nk_remove_window(struct nk_context *ctx, struct nk_window *win)
+{
+    if (win == ctx->begin || win == ctx->end) {
+        if (win == ctx->begin) {
+            ctx->begin = win->next;
+            if (win->next)
+                win->next->prev = 0;
+        }
+        if (win == ctx->end) {
+            ctx->end = win->prev;
+            if (win->prev)
+                win->prev->next = 0;
+        }
+    } else {
+        if (win->next)
+            win->next->prev = win->prev;
+        if (win->prev)
+            win->prev->next = win->next;
+    }
+    if (win == ctx->active || !ctx->active) {
+        ctx->active = ctx->end;
+        if (ctx->end)
+            ctx->end->flags &= ~(nk_flags)NK_WINDOW_ROM;
+    }
+    win->next = 0;
+    win->prev = 0;
+    ctx->count--;
+}
+
+NK_API int
+nk_begin(struct nk_context *ctx, const char *title,
+    struct nk_rect bounds, nk_flags flags)
+{
+    return nk_begin_titled(ctx, title, title, bounds, flags);
+}
+
+NK_API int
+nk_begin_titled(struct nk_context *ctx, const char *name, const char *title,
+    struct nk_rect bounds, nk_flags flags)
+{
+    struct nk_window *win;
+    struct nk_style *style;
+    nk_hash title_hash;
+    int title_len;
+    int ret = 0;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(name);
+    NK_ASSERT(title);
+    NK_ASSERT(ctx->style.font && ctx->style.font->width && "if this triggers you forgot to add a font");
+    NK_ASSERT(!ctx->current && "if this triggers you missed a `nk_end` call");
+    if (!ctx || ctx->current || !title || !name)
+        return 0;
+
+    /* find or create window */
+    style = &ctx->style;
+    title_len = (int)nk_strlen(name);
+    title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE);
+    win = nk_find_window(ctx, title_hash, name);
+    if (!win) {
+        /* create new window */
+        nk_size name_length = (nk_size)nk_strlen(name);
+        win = (struct nk_window*)nk_create_window(ctx);
+        NK_ASSERT(win);
+        if (!win) return 0;
+
+        if (flags & NK_WINDOW_BACKGROUND)
+            nk_insert_window(ctx, win, NK_INSERT_FRONT);
+        else nk_insert_window(ctx, win, NK_INSERT_BACK);
+        nk_command_buffer_init(&win->buffer, &ctx->memory, NK_CLIPPING_ON);
+
+        win->flags = flags;
+        win->bounds = bounds;
+        win->name = title_hash;
+        name_length = NK_MIN(name_length, NK_WINDOW_MAX_NAME-1);
+        NK_MEMCPY(win->name_string, name, name_length);
+        win->name_string[name_length] = 0;
+        win->popup.win = 0;
+        if (!ctx->active)
+            ctx->active = win;
+    } else {
+        /* update window */
+        win->flags &= ~(nk_flags)(NK_WINDOW_PRIVATE-1);
+        win->flags |= flags;
+        if (!(win->flags & (NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE)))
+            win->bounds = bounds;
+        /* If this assert triggers you either:
+         *
+         * I.) Have more than one window with the same name or
+         * II.) You forgot to actually draw the window.
+         *      More specific you did not call `nk_clear` (nk_clear will be
+         *      automatically called for you if you are using one of the
+         *      provided demo backends). */
+        NK_ASSERT(win->seq != ctx->seq);
+        win->seq = ctx->seq;
+        if (!ctx->active && !(win->flags & NK_WINDOW_HIDDEN)) {
+            ctx->active = win;
+            ctx->end = win;
+        }
+    }
+    if (win->flags & NK_WINDOW_HIDDEN) {
+        ctx->current = win;
+        win->layout = 0;
+        return 0;
+    }
+
+    /* window overlapping */
+    if (!(win->flags & NK_WINDOW_HIDDEN) && !(win->flags & NK_WINDOW_NO_INPUT))
+    {
+        int inpanel, ishovered;
+        struct nk_window *iter = win;
+        float h = ctx->style.font->height + 2.0f * style->window.header.padding.y +
+            (2.0f * style->window.header.label_padding.y);
+        struct nk_rect win_bounds = (!(win->flags & NK_WINDOW_MINIMIZED))?
+            win->bounds: nk_rect(win->bounds.x, win->bounds.y, win->bounds.w, h);
+
+        /* activate window if hovered and no other window is overlapping this window */
+        nk_start(ctx, win);
+        inpanel = nk_input_has_mouse_click_down_in_rect(&ctx->input, NK_BUTTON_LEFT, win_bounds, nk_true);
+        inpanel = inpanel && ctx->input.mouse.buttons[NK_BUTTON_LEFT].clicked;
+        ishovered = nk_input_is_mouse_hovering_rect(&ctx->input, win_bounds);
+        if ((win != ctx->active) && ishovered && !ctx->input.mouse.buttons[NK_BUTTON_LEFT].down) {
+            iter = win->next;
+            while (iter) {
+                struct nk_rect iter_bounds = (!(iter->flags & NK_WINDOW_MINIMIZED))?
+                    iter->bounds: nk_rect(iter->bounds.x, iter->bounds.y, iter->bounds.w, h);
+                if (NK_INTERSECT(win_bounds.x, win_bounds.y, win_bounds.w, win_bounds.h,
+                    iter_bounds.x, iter_bounds.y, iter_bounds.w, iter_bounds.h) &&
+                    (!(iter->flags & NK_WINDOW_HIDDEN)))
+                    break;
+
+                if (iter->popup.win && iter->popup.active && !(iter->flags & NK_WINDOW_HIDDEN) &&
+                    NK_INTERSECT(win->bounds.x, win_bounds.y, win_bounds.w, win_bounds.h,
+                    iter->popup.win->bounds.x, iter->popup.win->bounds.y,
+                    iter->popup.win->bounds.w, iter->popup.win->bounds.h))
+                    break;
+                iter = iter->next;
+            }
+        }
+
+        /* activate window if clicked */
+        if (iter && inpanel && (win != ctx->end)) {
+            iter = win->next;
+            while (iter) {
+                /* try to find a panel with higher priority in the same position */
+                struct nk_rect iter_bounds = (!(iter->flags & NK_WINDOW_MINIMIZED))?
+                iter->bounds: nk_rect(iter->bounds.x, iter->bounds.y, iter->bounds.w, h);
+                if (NK_INBOX(ctx->input.mouse.pos.x, ctx->input.mouse.pos.y,
+                    iter_bounds.x, iter_bounds.y, iter_bounds.w, iter_bounds.h) &&
+                    !(iter->flags & NK_WINDOW_HIDDEN))
+                    break;
+                if (iter->popup.win && iter->popup.active && !(iter->flags & NK_WINDOW_HIDDEN) &&
+                    NK_INTERSECT(win_bounds.x, win_bounds.y, win_bounds.w, win_bounds.h,
+                    iter->popup.win->bounds.x, iter->popup.win->bounds.y,
+                    iter->popup.win->bounds.w, iter->popup.win->bounds.h))
+                    break;
+                iter = iter->next;
+            }
+        }
+        if (iter && !(win->flags & NK_WINDOW_ROM) && (win->flags & NK_WINDOW_BACKGROUND)) {
+            win->flags |= (nk_flags)NK_WINDOW_ROM;
+            iter->flags &= ~(nk_flags)NK_WINDOW_ROM;
+            ctx->active = iter;
+            if (!(iter->flags & NK_WINDOW_BACKGROUND)) {
+                /* current window is active in that position so transfer to top
+                 * at the highest priority in stack */
+                nk_remove_window(ctx, iter);
+                nk_insert_window(ctx, iter, NK_INSERT_BACK);
+            }
+        } else {
+            if (!iter && ctx->end != win) {
+                if (!(win->flags & NK_WINDOW_BACKGROUND)) {
+                    /* current window is active in that position so transfer to top
+                     * at the highest priority in stack */
+                    nk_remove_window(ctx, win);
+                    nk_insert_window(ctx, win, NK_INSERT_BACK);
+                }
+                win->flags &= ~(nk_flags)NK_WINDOW_ROM;
+                ctx->active = win;
+            }
+            if (ctx->end != win && !(win->flags & NK_WINDOW_BACKGROUND))
+                win->flags |= NK_WINDOW_ROM;
+        }
+    }
+
+    win->layout = (struct nk_panel*)nk_create_panel(ctx);
+    ctx->current = win;
+    ret = nk_panel_begin(ctx, title, NK_PANEL_WINDOW);
+    win->layout->offset_x = &win->scrollbar.x;
+    win->layout->offset_y = &win->scrollbar.y;
+    return ret;
+}
+
+NK_API void
+nk_end(struct nk_context *ctx)
+{
+    struct nk_panel *layout;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current && "if this triggers you forgot to call `nk_begin`");
+    if (!ctx || !ctx->current)
+        return;
+
+    layout = ctx->current->layout;
+    if (!layout || (layout->type == NK_PANEL_WINDOW && (ctx->current->flags & NK_WINDOW_HIDDEN))) {
+        ctx->current = 0;
+        return;
+    }
+    nk_panel_end(ctx);
+    nk_free_panel(ctx, ctx->current->layout);
+    ctx->current = 0;
+}
+
+NK_API struct nk_rect
+nk_window_get_bounds(const struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return nk_rect(0,0,0,0);
+    return ctx->current->bounds;
+}
+
+NK_API struct nk_vec2
+nk_window_get_position(const struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return nk_vec2(0,0);
+    return nk_vec2(ctx->current->bounds.x, ctx->current->bounds.y);
+}
+
+NK_API struct nk_vec2
+nk_window_get_size(const struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return nk_vec2(0,0);
+    return nk_vec2(ctx->current->bounds.w, ctx->current->bounds.h);
+}
+
+NK_API float
+nk_window_get_width(const struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return 0;
+    return ctx->current->bounds.w;
+}
+
+NK_API float
+nk_window_get_height(const struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return 0;
+    return ctx->current->bounds.h;
+}
+
+NK_API struct nk_rect
+nk_window_get_content_region(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return nk_rect(0,0,0,0);
+    return ctx->current->layout->clip;
+}
+
+NK_API struct nk_vec2
+nk_window_get_content_region_min(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current) return nk_vec2(0,0);
+    return nk_vec2(ctx->current->layout->clip.x, ctx->current->layout->clip.y);
+}
+
+NK_API struct nk_vec2
+nk_window_get_content_region_max(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current) return nk_vec2(0,0);
+    return nk_vec2(ctx->current->layout->clip.x + ctx->current->layout->clip.w,
+        ctx->current->layout->clip.y + ctx->current->layout->clip.h);
+}
+
+NK_API struct nk_vec2
+nk_window_get_content_region_size(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current) return nk_vec2(0,0);
+    return nk_vec2(ctx->current->layout->clip.w, ctx->current->layout->clip.h);
+}
+
+NK_API struct nk_command_buffer*
+nk_window_get_canvas(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current) return 0;
+    return &ctx->current->buffer;
+}
+
+NK_API struct nk_panel*
+nk_window_get_panel(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return 0;
+    return ctx->current->layout;
+}
+
+NK_API int
+nk_window_has_focus(const struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current) return 0;
+    return ctx->current == ctx->active;
+}
+
+NK_API int
+nk_window_is_hovered(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return 0;
+    if(ctx->current->flags & NK_WINDOW_HIDDEN)
+        return 0;
+    return nk_input_is_mouse_hovering_rect(&ctx->input, ctx->current->bounds);
+}
+
+NK_API int
+nk_window_is_any_hovered(struct nk_context *ctx)
+{
+    struct nk_window *iter;
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+    iter = ctx->begin;
+    while (iter) {
+        /* check if window is being hovered */
+        if(!(iter->flags & NK_WINDOW_HIDDEN)) {
+            /* check if window popup is being hovered */
+            if (iter->popup.active && iter->popup.win && nk_input_is_mouse_hovering_rect(&ctx->input, iter->popup.win->bounds))
+                return 1;
+
+            if (iter->flags & NK_WINDOW_MINIMIZED) {
+                struct nk_rect header = iter->bounds;
+                header.h = ctx->style.font->height + 2 * ctx->style.window.header.padding.y;
+                if (nk_input_is_mouse_hovering_rect(&ctx->input, header))
+                    return 1;
+            } else if (nk_input_is_mouse_hovering_rect(&ctx->input, iter->bounds)) {
+                return 1;
+            }
+        }
+        iter = iter->next;
+    }
+    return 0;
+}
+
+NK_API int
+nk_item_is_any_active(struct nk_context *ctx)
+{
+    int any_hovered = nk_window_is_any_hovered(ctx);
+    int any_active = (ctx->last_widget_state & NK_WIDGET_STATE_MODIFIED);
+    return any_hovered || any_active;
+}
+
+NK_API int
+nk_window_is_collapsed(struct nk_context *ctx, const char *name)
+{
+    int title_len;
+    nk_hash title_hash;
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+
+    title_len = (int)nk_strlen(name);
+    title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE);
+    win = nk_find_window(ctx, title_hash, name);
+    if (!win) return 0;
+    return win->flags & NK_WINDOW_MINIMIZED;
+}
+
+NK_API int
+nk_window_is_closed(struct nk_context *ctx, const char *name)
+{
+    int title_len;
+    nk_hash title_hash;
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    if (!ctx) return 1;
+
+    title_len = (int)nk_strlen(name);
+    title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE);
+    win = nk_find_window(ctx, title_hash, name);
+    if (!win) return 1;
+    return (win->flags & NK_WINDOW_CLOSED);
+}
+
+NK_API int
+nk_window_is_hidden(struct nk_context *ctx, const char *name)
+{
+    int title_len;
+    nk_hash title_hash;
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    if (!ctx) return 1;
+
+    title_len = (int)nk_strlen(name);
+    title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE);
+    win = nk_find_window(ctx, title_hash, name);
+    if (!win) return 1;
+    return (win->flags & NK_WINDOW_HIDDEN);
+}
+
+NK_API int
+nk_window_is_active(struct nk_context *ctx, const char *name)
+{
+    int title_len;
+    nk_hash title_hash;
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+
+    title_len = (int)nk_strlen(name);
+    title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE);
+    win = nk_find_window(ctx, title_hash, name);
+    if (!win) return 0;
+    return win == ctx->active;
+}
+
+NK_API struct nk_window*
+nk_window_find(struct nk_context *ctx, const char *name)
+{
+    int title_len;
+    nk_hash title_hash;
+    title_len = (int)nk_strlen(name);
+    title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE);
+    return nk_find_window(ctx, title_hash, name);
+}
+
+NK_API void
+nk_window_close(struct nk_context *ctx, const char *name)
+{
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    win = nk_window_find(ctx, name);
+    if (!win) return;
+    NK_ASSERT(ctx->current != win && "You cannot close a currently active window");
+    if (ctx->current == win) return;
+    win->flags |= NK_WINDOW_HIDDEN;
+    win->flags |= NK_WINDOW_CLOSED;
+}
+
+NK_API void
+nk_window_set_bounds(struct nk_context *ctx,
+    const char *name, struct nk_rect bounds)
+{
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    win = nk_window_find(ctx, name);
+    if (!win) return;
+    NK_ASSERT(ctx->current != win && "You cannot update a currently in procecss window");
+    win->bounds = bounds;
+}
+
+NK_API void
+nk_window_set_position(struct nk_context *ctx,
+    const char *name, struct nk_vec2 pos)
+{
+    struct nk_window *win = nk_window_find(ctx, name);
+    if (!win) return;
+    win->bounds.x = pos.x;
+    win->bounds.y = pos.y;
+}
+
+NK_API void
+nk_window_set_size(struct nk_context *ctx,
+    const char *name, struct nk_vec2 size)
+{
+    struct nk_window *win = nk_window_find(ctx, name);
+    if (!win) return;
+    win->bounds.w = size.x;
+    win->bounds.h = size.y;
+}
+
+NK_API void
+nk_window_collapse(struct nk_context *ctx, const char *name,
+                    enum nk_collapse_states c)
+{
+    int title_len;
+    nk_hash title_hash;
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+
+    title_len = (int)nk_strlen(name);
+    title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE);
+    win = nk_find_window(ctx, title_hash, name);
+    if (!win) return;
+    if (c == NK_MINIMIZED)
+        win->flags |= NK_WINDOW_MINIMIZED;
+    else win->flags &= ~(nk_flags)NK_WINDOW_MINIMIZED;
+}
+
+NK_API void
+nk_window_collapse_if(struct nk_context *ctx, const char *name,
+    enum nk_collapse_states c, int cond)
+{
+    NK_ASSERT(ctx);
+    if (!ctx || !cond) return;
+    nk_window_collapse(ctx, name, c);
+}
+
+NK_API void
+nk_window_show(struct nk_context *ctx, const char *name, enum nk_show_states s)
+{
+    int title_len;
+    nk_hash title_hash;
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+
+    title_len = (int)nk_strlen(name);
+    title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE);
+    win = nk_find_window(ctx, title_hash, name);
+    if (!win) return;
+    if (s == NK_HIDDEN) {
+        win->flags |= NK_WINDOW_HIDDEN;
+    } else win->flags &= ~(nk_flags)NK_WINDOW_HIDDEN;
+}
+
+NK_API void
+nk_window_show_if(struct nk_context *ctx, const char *name,
+    enum nk_show_states s, int cond)
+{
+    NK_ASSERT(ctx);
+    if (!ctx || !cond) return;
+    nk_window_show(ctx, name, s);
+}
+
+NK_API void
+nk_window_set_focus(struct nk_context *ctx, const char *name)
+{
+    int title_len;
+    nk_hash title_hash;
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+
+    title_len = (int)nk_strlen(name);
+    title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE);
+    win = nk_find_window(ctx, title_hash, name);
+    if (win && ctx->end != win) {
+        nk_remove_window(ctx, win);
+        nk_insert_window(ctx, win, NK_INSERT_BACK);
+    }
+    ctx->active = win;
+}
+
+/*----------------------------------------------------------------
+ *
+ *                          MENUBAR
+ *
+ * --------------------------------------------------------------*/
+NK_API void
+nk_menubar_begin(struct nk_context *ctx)
+{
+    struct nk_panel *layout;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    layout = ctx->current->layout;
+    NK_ASSERT(layout->at_y == layout->bounds.y);
+    /* if this assert triggers you allocated space between nk_begin and nk_menubar_begin.
+    If you want a menubar the first nuklear function after `nk_begin` has to be a
+    `nk_menubar_begin` call. Inside the menubar you then have to allocate space for
+    widgets (also supports multiple rows).
+    Example:
+        if (nk_begin(...)) {
+            nk_menubar_begin(...);
+                nk_layout_xxxx(...);
+                nk_button(...);
+                nk_layout_xxxx(...);
+                nk_button(...);
+            nk_menubar_end(...);
+        }
+        nk_end(...);
+    */
+    if (layout->flags & NK_WINDOW_HIDDEN || layout->flags & NK_WINDOW_MINIMIZED)
+        return;
+
+    layout->menu.x = layout->at_x;
+    layout->menu.y = layout->at_y + layout->row.height;
+    layout->menu.w = layout->bounds.w;
+    layout->menu.offset.x = *layout->offset_x;
+    layout->menu.offset.y = *layout->offset_y;
+    *layout->offset_y = 0;
+}
+
+NK_API void
+nk_menubar_end(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    struct nk_command_buffer *out;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    out = &win->buffer;
+    layout = win->layout;
+    if (layout->flags & NK_WINDOW_HIDDEN || layout->flags & NK_WINDOW_MINIMIZED)
+        return;
+
+    layout->menu.h = layout->at_y - layout->menu.y;
+    layout->bounds.y += layout->menu.h + ctx->style.window.spacing.y + layout->row.height;
+    layout->bounds.h -= layout->menu.h + ctx->style.window.spacing.y + layout->row.height;
+
+    *layout->offset_x = layout->menu.offset.x;
+    *layout->offset_y = layout->menu.offset.y;
+    layout->at_y = layout->bounds.y - layout->row.height;
+
+    layout->clip.y = layout->bounds.y;
+    layout->clip.h = layout->bounds.h;
+    nk_push_scissor(out, layout->clip);
+}
+/* -------------------------------------------------------------
+ *
+ *                          LAYOUT
+ *
+ * --------------------------------------------------------------*/
+NK_API void
+nk_layout_set_min_row_height(struct nk_context *ctx, float height)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    layout->row.min_height = height;
+}
+
+NK_API void
+nk_layout_reset_min_row_height(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    layout->row.min_height = ctx->style.font->height;
+    layout->row.min_height += ctx->style.text.padding.y*2;
+    layout->row.min_height += ctx->style.window.min_row_height_padding*2;
+}
+
+NK_INTERN float
+nk_layout_row_calculate_usable_space(const struct nk_style *style, enum nk_panel_type type,
+    float total_space, int columns)
+{
+    float panel_padding;
+    float panel_spacing;
+    float panel_space;
+
+    struct nk_vec2 spacing;
+    struct nk_vec2 padding;
+
+    spacing = style->window.spacing;
+    padding = nk_panel_get_padding(style, type);
+
+    /* calculate the usable panel space */
+    panel_padding = 2 * padding.x;
+    panel_spacing = (float)NK_MAX(columns - 1, 0) * spacing.x;
+    panel_space  = total_space - panel_padding - panel_spacing;
+    return panel_space;
+}
+
+NK_INTERN void
+nk_panel_layout(const struct nk_context *ctx, struct nk_window *win,
+    float height, int cols)
+{
+    struct nk_panel *layout;
+    const struct nk_style *style;
+    struct nk_command_buffer *out;
+
+    struct nk_vec2 item_spacing;
+    struct nk_color color;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    /* prefetch some configuration data */
+    layout = win->layout;
+    style = &ctx->style;
+    out = &win->buffer;
+    color = style->window.background;
+    item_spacing = style->window.spacing;
+
+    /*  if one of these triggers you forgot to add an `if` condition around either
+        a window, group, popup, combobox or contextual menu `begin` and `end` block.
+        Example:
+            if (nk_begin(...) {...} nk_end(...); or
+            if (nk_group_begin(...) { nk_group_end(...);} */
+    NK_ASSERT(!(layout->flags & NK_WINDOW_MINIMIZED));
+    NK_ASSERT(!(layout->flags & NK_WINDOW_HIDDEN));
+    NK_ASSERT(!(layout->flags & NK_WINDOW_CLOSED));
+
+    /* update the current row and set the current row layout */
+    layout->row.index = 0;
+    layout->at_y += layout->row.height;
+    layout->row.columns = cols;
+    if (height == 0.0f)
+        layout->row.height = NK_MAX(height, layout->row.min_height) + item_spacing.y;
+    else layout->row.height = height + item_spacing.y;
+
+    layout->row.item_offset = 0;
+    if (layout->flags & NK_WINDOW_DYNAMIC) {
+        /* draw background for dynamic panels */
+        struct nk_rect background;
+        background.x = win->bounds.x;
+        background.w = win->bounds.w;
+        background.y = layout->at_y - 1.0f;
+        background.h = layout->row.height + 1.0f;
+        nk_fill_rect(out, background, 0, color);
+    }
+}
+
+NK_INTERN void
+nk_row_layout(struct nk_context *ctx, enum nk_layout_format fmt,
+    float height, int cols, int width)
+{
+    /* update the current row and set the current row layout */
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    nk_panel_layout(ctx, win, height, cols);
+    if (fmt == NK_DYNAMIC)
+        win->layout->row.type = NK_LAYOUT_DYNAMIC_FIXED;
+    else win->layout->row.type = NK_LAYOUT_STATIC_FIXED;
+
+    win->layout->row.ratio = 0;
+    win->layout->row.filled = 0;
+    win->layout->row.item_offset = 0;
+    win->layout->row.item_width = (float)width;
+}
+
+NK_API float
+nk_layout_ratio_from_pixel(struct nk_context *ctx, float pixel_width)
+{
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    NK_ASSERT(pixel_width);
+    if (!ctx || !ctx->current || !ctx->current->layout) return 0;
+    win = ctx->current;
+    return NK_CLAMP(0.0f, pixel_width/win->bounds.x, 1.0f);
+}
+
+NK_API void
+nk_layout_row_dynamic(struct nk_context *ctx, float height, int cols)
+{
+    nk_row_layout(ctx, NK_DYNAMIC, height, cols, 0);
+}
+
+NK_API void
+nk_layout_row_static(struct nk_context *ctx, float height, int item_width, int cols)
+{
+    nk_row_layout(ctx, NK_STATIC, height, cols, item_width);
+}
+
+NK_API void
+nk_layout_row_begin(struct nk_context *ctx, enum nk_layout_format fmt,
+    float row_height, int cols)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    nk_panel_layout(ctx, win, row_height, cols);
+    if (fmt == NK_DYNAMIC)
+        layout->row.type = NK_LAYOUT_DYNAMIC_ROW;
+    else layout->row.type = NK_LAYOUT_STATIC_ROW;
+
+    layout->row.ratio = 0;
+    layout->row.filled = 0;
+    layout->row.item_width = 0;
+    layout->row.item_offset = 0;
+    layout->row.columns = cols;
+}
+
+NK_API void
+nk_layout_row_push(struct nk_context *ctx, float ratio_or_width)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    NK_ASSERT(layout->row.type == NK_LAYOUT_STATIC_ROW || layout->row.type == NK_LAYOUT_DYNAMIC_ROW);
+    if (layout->row.type != NK_LAYOUT_STATIC_ROW && layout->row.type != NK_LAYOUT_DYNAMIC_ROW)
+        return;
+
+    if (layout->row.type == NK_LAYOUT_DYNAMIC_ROW) {
+        float ratio = ratio_or_width;
+        if ((ratio + layout->row.filled) > 1.0f) return;
+        if (ratio > 0.0f)
+            layout->row.item_width = NK_SATURATE(ratio);
+        else layout->row.item_width = 1.0f - layout->row.filled;
+    } else layout->row.item_width = ratio_or_width;
+}
+
+NK_API void
+nk_layout_row_end(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    NK_ASSERT(layout->row.type == NK_LAYOUT_STATIC_ROW || layout->row.type == NK_LAYOUT_DYNAMIC_ROW);
+    if (layout->row.type != NK_LAYOUT_STATIC_ROW && layout->row.type != NK_LAYOUT_DYNAMIC_ROW)
+        return;
+    layout->row.item_width = 0;
+    layout->row.item_offset = 0;
+}
+
+NK_API void
+nk_layout_row(struct nk_context *ctx, enum nk_layout_format fmt,
+    float height, int cols, const float *ratio)
+{
+    int i;
+    int n_undef = 0;
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    nk_panel_layout(ctx, win, height, cols);
+    if (fmt == NK_DYNAMIC) {
+        /* calculate width of undefined widget ratios */
+        float r = 0;
+        layout->row.ratio = ratio;
+        for (i = 0; i < cols; ++i) {
+            if (ratio[i] < 0.0f)
+                n_undef++;
+            else r += ratio[i];
+        }
+        r = NK_SATURATE(1.0f - r);
+        layout->row.type = NK_LAYOUT_DYNAMIC;
+        layout->row.item_width = (r > 0 && n_undef > 0) ? (r / (float)n_undef):0;
+    } else {
+        layout->row.ratio = ratio;
+        layout->row.type = NK_LAYOUT_STATIC;
+        layout->row.item_width = 0;
+        layout->row.item_offset = 0;
+    }
+    layout->row.item_offset = 0;
+    layout->row.filled = 0;
+}
+
+NK_API void
+nk_layout_row_template_begin(struct nk_context *ctx, float height)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    nk_panel_layout(ctx, win, height, 1);
+    layout->row.type = NK_LAYOUT_TEMPLATE;
+    layout->row.columns = 0;
+    layout->row.ratio = 0;
+    layout->row.item_width = 0;
+    layout->row.item_height = 0;
+    layout->row.item_offset = 0;
+    layout->row.filled = 0;
+    layout->row.item.x = 0;
+    layout->row.item.y = 0;
+    layout->row.item.w = 0;
+    layout->row.item.h = 0;
+}
+
+NK_API void
+nk_layout_row_template_push_dynamic(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    NK_ASSERT(layout->row.type == NK_LAYOUT_TEMPLATE);
+    NK_ASSERT(layout->row.columns < NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS);
+    if (layout->row.type != NK_LAYOUT_TEMPLATE) return;
+    if (layout->row.columns >= NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS) return;
+    layout->row.templates[layout->row.columns++] = -1.0f;
+}
+
+NK_API void
+nk_layout_row_template_push_variable(struct nk_context *ctx, float min_width)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    NK_ASSERT(layout->row.type == NK_LAYOUT_TEMPLATE);
+    NK_ASSERT(layout->row.columns < NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS);
+    if (layout->row.type != NK_LAYOUT_TEMPLATE) return;
+    if (layout->row.columns >= NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS) return;
+    layout->row.templates[layout->row.columns++] = -min_width;
+}
+
+NK_API void
+nk_layout_row_template_push_static(struct nk_context *ctx, float width)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    NK_ASSERT(layout->row.type == NK_LAYOUT_TEMPLATE);
+    NK_ASSERT(layout->row.columns < NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS);
+    if (layout->row.type != NK_LAYOUT_TEMPLATE) return;
+    if (layout->row.columns >= NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS) return;
+    layout->row.templates[layout->row.columns++] = width;
+}
+
+NK_API void
+nk_layout_row_template_end(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    int i = 0;
+    int variable_count = 0;
+    int min_variable_count = 0;
+    float min_fixed_width = 0.0f;
+    float total_fixed_width = 0.0f;
+    float max_variable_width = 0.0f;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    NK_ASSERT(layout->row.type == NK_LAYOUT_TEMPLATE);
+    if (layout->row.type != NK_LAYOUT_TEMPLATE) return;
+    for (i = 0; i < layout->row.columns; ++i) {
+        float width = layout->row.templates[i];
+        if (width >= 0.0f) {
+            total_fixed_width += width;
+            min_fixed_width += width;
+        } else if (width < -1.0f) {
+            width = -width;
+            total_fixed_width += width;
+            max_variable_width = NK_MAX(max_variable_width, width);
+            variable_count++;
+        } else {
+            min_variable_count++;
+            variable_count++;
+        }
+    }
+    if (variable_count) {
+        float space = nk_layout_row_calculate_usable_space(&ctx->style, layout->type,
+                            layout->bounds.w, layout->row.columns);
+        float var_width = (NK_MAX(space-min_fixed_width,0.0f)) / (float)variable_count;
+        int enough_space = var_width >= max_variable_width;
+        if (!enough_space)
+            var_width = (NK_MAX(space-total_fixed_width,0)) / (float)min_variable_count;
+        for (i = 0; i < layout->row.columns; ++i) {
+            float *width = &layout->row.templates[i];
+            *width = (*width >= 0.0f)? *width: (*width < -1.0f && !enough_space)? -(*width): var_width;
+        }
+    }
+}
+
+NK_API void
+nk_layout_space_begin(struct nk_context *ctx, enum nk_layout_format fmt,
+    float height, int widget_count)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    nk_panel_layout(ctx, win, height, widget_count);
+    if (fmt == NK_STATIC)
+        layout->row.type = NK_LAYOUT_STATIC_FREE;
+    else layout->row.type = NK_LAYOUT_DYNAMIC_FREE;
+
+    layout->row.ratio = 0;
+    layout->row.filled = 0;
+    layout->row.item_width = 0;
+    layout->row.item_offset = 0;
+}
+
+NK_API void
+nk_layout_space_end(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    layout->row.item_width = 0;
+    layout->row.item_height = 0;
+    layout->row.item_offset = 0;
+    nk_zero(&layout->row.item, sizeof(layout->row.item));
+}
+
+NK_API void
+nk_layout_space_push(struct nk_context *ctx, struct nk_rect rect)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    layout->row.item = rect;
+}
+
+NK_API struct nk_rect
+nk_layout_space_bounds(struct nk_context *ctx)
+{
+    struct nk_rect ret;
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    win = ctx->current;
+    layout = win->layout;
+
+    ret.x = layout->clip.x;
+    ret.y = layout->clip.y;
+    ret.w = layout->clip.w;
+    ret.h = layout->row.height;
+    return ret;
+}
+
+NK_API struct nk_rect
+nk_layout_widget_bounds(struct nk_context *ctx)
+{
+    struct nk_rect ret;
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    win = ctx->current;
+    layout = win->layout;
+
+    ret.x = layout->at_x;
+    ret.y = layout->at_y;
+    ret.w = layout->bounds.w - NK_MAX(layout->at_x - layout->bounds.x,0);
+    ret.h = layout->row.height;
+    return ret;
+}
+
+NK_API struct nk_vec2
+nk_layout_space_to_screen(struct nk_context *ctx, struct nk_vec2 ret)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    win = ctx->current;
+    layout = win->layout;
+
+    ret.x += layout->at_x - (float)*layout->offset_x;
+    ret.y += layout->at_y - (float)*layout->offset_y;
+    return ret;
+}
+
+NK_API struct nk_vec2
+nk_layout_space_to_local(struct nk_context *ctx, struct nk_vec2 ret)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    win = ctx->current;
+    layout = win->layout;
+
+    ret.x += -layout->at_x + (float)*layout->offset_x;
+    ret.y += -layout->at_y + (float)*layout->offset_y;
+    return ret;
+}
+
+NK_API struct nk_rect
+nk_layout_space_rect_to_screen(struct nk_context *ctx, struct nk_rect ret)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    win = ctx->current;
+    layout = win->layout;
+
+    ret.x += layout->at_x - (float)*layout->offset_x;
+    ret.y += layout->at_y - (float)*layout->offset_y;
+    return ret;
+}
+
+NK_API struct nk_rect
+nk_layout_space_rect_to_local(struct nk_context *ctx, struct nk_rect ret)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    win = ctx->current;
+    layout = win->layout;
+
+    ret.x += -layout->at_x + (float)*layout->offset_x;
+    ret.y += -layout->at_y + (float)*layout->offset_y;
+    return ret;
+}
+
+NK_INTERN void
+nk_panel_alloc_row(const struct nk_context *ctx, struct nk_window *win)
+{
+    struct nk_panel *layout = win->layout;
+    struct nk_vec2 spacing = ctx->style.window.spacing;
+    const float row_height = layout->row.height - spacing.y;
+    nk_panel_layout(ctx, win, row_height, layout->row.columns);
+}
+
+NK_INTERN void
+nk_layout_widget_space(struct nk_rect *bounds, const struct nk_context *ctx,
+    struct nk_window *win, int modify)
+{
+    struct nk_panel *layout;
+    const struct nk_style *style;
+
+    struct nk_vec2 spacing;
+    struct nk_vec2 padding;
+
+    float item_offset = 0;
+    float item_width = 0;
+    float item_spacing = 0;
+    float panel_space = 0;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    style = &ctx->style;
+    NK_ASSERT(bounds);
+
+    spacing = style->window.spacing;
+    padding = nk_panel_get_padding(style, layout->type);
+    panel_space = nk_layout_row_calculate_usable_space(&ctx->style, layout->type,
+                                            layout->bounds.w, layout->row.columns);
+
+    /* calculate the width of one item inside the current layout space */
+    switch (layout->row.type) {
+    case NK_LAYOUT_DYNAMIC_FIXED: {
+        /* scaling fixed size widgets item width */
+        item_width = NK_MAX(1.0f,panel_space-1.0f) / (float)layout->row.columns;
+        item_offset = (float)layout->row.index * item_width;
+        item_spacing = (float)layout->row.index * spacing.x;
+    } break;
+    case NK_LAYOUT_DYNAMIC_ROW: {
+        /* scaling single ratio widget width */
+        item_width = layout->row.item_width * panel_space;
+        item_offset = layout->row.item_offset;
+        item_spacing = 0;
+
+        if (modify) {
+            layout->row.item_offset += item_width + spacing.x;
+            layout->row.filled += layout->row.item_width;
+            layout->row.index = 0;
+        }
+    } break;
+    case NK_LAYOUT_DYNAMIC_FREE: {
+        /* panel width depended free widget placing */
+        bounds->x = layout->at_x + (layout->bounds.w * layout->row.item.x);
+        bounds->x -= (float)*layout->offset_x;
+        bounds->y = layout->at_y + (layout->row.height * layout->row.item.y);
+        bounds->y -= (float)*layout->offset_y;
+        bounds->w = layout->bounds.w  * layout->row.item.w;
+        bounds->h = layout->row.height * layout->row.item.h;
+        return;
+    } break;
+    case NK_LAYOUT_DYNAMIC: {
+        /* scaling arrays of panel width ratios for every widget */
+        float ratio;
+        NK_ASSERT(layout->row.ratio);
+        ratio = (layout->row.ratio[layout->row.index] < 0) ?
+            layout->row.item_width : layout->row.ratio[layout->row.index];
+
+        item_spacing = (float)layout->row.index * spacing.x;
+        item_width = (ratio * panel_space);
+        item_offset = layout->row.item_offset;
+
+        if (modify) {
+            layout->row.item_offset += item_width;
+            layout->row.filled += ratio;
+        }
+    } break;
+    case NK_LAYOUT_STATIC_FIXED: {
+        /* non-scaling fixed widgets item width */
+        item_width = layout->row.item_width;
+        item_offset = (float)layout->row.index * item_width;
+        item_spacing = (float)layout->row.index * spacing.x;
+    } break;
+    case NK_LAYOUT_STATIC_ROW: {
+        /* scaling single ratio widget width */
+        item_width = layout->row.item_width;
+        item_offset = layout->row.item_offset;
+        item_spacing = (float)layout->row.index * spacing.x;
+        if (modify) layout->row.item_offset += item_width;
+    } break;
+    case NK_LAYOUT_STATIC_FREE: {
+        /* free widget placing */
+        bounds->x = layout->at_x + layout->row.item.x;
+        bounds->w = layout->row.item.w;
+        if (((bounds->x + bounds->w) > layout->max_x) && modify)
+            layout->max_x = (bounds->x + bounds->w);
+        bounds->x -= (float)*layout->offset_x;
+        bounds->y = layout->at_y + layout->row.item.y;
+        bounds->y -= (float)*layout->offset_y;
+        bounds->h = layout->row.item.h;
+        return;
+    } break;
+    case NK_LAYOUT_STATIC: {
+        /* non-scaling array of panel pixel width for every widget */
+        item_spacing = (float)layout->row.index * spacing.x;
+        item_width = layout->row.ratio[layout->row.index];
+        item_offset = layout->row.item_offset;
+        if (modify) layout->row.item_offset += item_width;
+    } break;
+    case NK_LAYOUT_TEMPLATE: {
+        /* stretchy row layout with combined dynamic/static widget width*/
+        NK_ASSERT(layout->row.index < layout->row.columns);
+        NK_ASSERT(layout->row.index < NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS);
+        item_width = layout->row.templates[layout->row.index];
+        item_offset = layout->row.item_offset;
+        item_spacing = (float)layout->row.index * spacing.x;
+        if (modify) layout->row.item_offset += item_width;
+    } break;
+    default: NK_ASSERT(0); break;
+    };
+
+    /* set the bounds of the newly allocated widget */
+    bounds->w = item_width;
+    bounds->h = layout->row.height - spacing.y;
+    bounds->y = layout->at_y - (float)*layout->offset_y;
+    bounds->x = layout->at_x + item_offset + item_spacing + padding.x;
+    if (((bounds->x + bounds->w) > layout->max_x) && modify)
+        layout->max_x = bounds->x + bounds->w;
+    bounds->x -= (float)*layout->offset_x;
+}
+
+NK_INTERN void
+nk_panel_alloc_space(struct nk_rect *bounds, const struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    /* check if the end of the row has been hit and begin new row if so */
+    win = ctx->current;
+    layout = win->layout;
+    if (layout->row.index >= layout->row.columns)
+        nk_panel_alloc_row(ctx, win);
+
+    /* calculate widget position and size */
+    nk_layout_widget_space(bounds, ctx, win, nk_true);
+    layout->row.index++;
+}
+
+NK_INTERN void
+nk_layout_peek(struct nk_rect *bounds, struct nk_context *ctx)
+{
+    float y;
+    int index;
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    y = layout->at_y;
+    index = layout->row.index;
+    if (layout->row.index >= layout->row.columns) {
+        layout->at_y += layout->row.height;
+        layout->row.index = 0;
+    }
+    nk_layout_widget_space(bounds, ctx, win, nk_false);
+    layout->at_y = y;
+    layout->row.index = index;
+}
+
+NK_INTERN int
+nk_tree_state_base(struct nk_context *ctx, enum nk_tree_type type,
+    struct nk_image *img, const char *title, enum nk_collapse_states *state)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_style *style;
+    struct nk_command_buffer *out;
+    const struct nk_input *in;
+    const struct nk_style_button *button;
+    enum nk_symbol_type symbol;
+    float row_height;
+
+    struct nk_vec2 item_spacing;
+    struct nk_rect header = {0,0,0,0};
+    struct nk_rect sym = {0,0,0,0};
+    struct nk_text text;
+
+    nk_flags ws = 0;
+    enum nk_widget_layout_states widget_state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    /* cache some data */
+    win = ctx->current;
+    layout = win->layout;
+    out = &win->buffer;
+    style = &ctx->style;
+    item_spacing = style->window.spacing;
+
+    /* calculate header bounds and draw background */
+    row_height = style->font->height + 2 * style->tab.padding.y;
+    nk_layout_set_min_row_height(ctx, row_height);
+    nk_layout_row_dynamic(ctx, row_height, 1);
+    nk_layout_reset_min_row_height(ctx);
+
+    widget_state = nk_widget(&header, ctx);
+    if (type == NK_TREE_TAB) {
+        const struct nk_style_item *background = &style->tab.background;
+        if (background->type == NK_STYLE_ITEM_IMAGE) {
+            nk_draw_image(out, header, &background->data.image, nk_white);
+            text.background = nk_rgba(0,0,0,0);
+        } else {
+            text.background = background->data.color;
+            nk_fill_rect(out, header, 0, style->tab.border_color);
+            nk_fill_rect(out, nk_shrink_rect(header, style->tab.border),
+                style->tab.rounding, background->data.color);
+        }
+    } else text.background = style->window.background;
+
+    /* update node state */
+    in = (!(layout->flags & NK_WINDOW_ROM)) ? &ctx->input: 0;
+    in = (in && widget_state == NK_WIDGET_VALID) ? &ctx->input : 0;
+    if (nk_button_behavior(&ws, header, in, NK_BUTTON_DEFAULT))
+        *state = (*state == NK_MAXIMIZED) ? NK_MINIMIZED : NK_MAXIMIZED;
+
+    /* select correct button style */
+    if (*state == NK_MAXIMIZED) {
+        symbol = style->tab.sym_maximize;
+        if (type == NK_TREE_TAB)
+            button = &style->tab.tab_maximize_button;
+        else button = &style->tab.node_maximize_button;
+    } else {
+        symbol = style->tab.sym_minimize;
+        if (type == NK_TREE_TAB)
+            button = &style->tab.tab_minimize_button;
+        else button = &style->tab.node_minimize_button;
+    }
+
+    {/* draw triangle button */
+    sym.w = sym.h = style->font->height;
+    sym.y = header.y + style->tab.padding.y;
+    sym.x = header.x + style->tab.padding.x;
+    nk_do_button_symbol(&ws, &win->buffer, sym, symbol, NK_BUTTON_DEFAULT,
+        button, 0, style->font);
+
+    if (img) {
+        /* draw optional image icon */
+        sym.x = sym.x + sym.w + 4 * item_spacing.x;
+        nk_draw_image(&win->buffer, sym, img, nk_white);
+        sym.w = style->font->height + style->tab.spacing.x;}
+    }
+
+    {/* draw label */
+    struct nk_rect label;
+    header.w = NK_MAX(header.w, sym.w + item_spacing.x);
+    label.x = sym.x + sym.w + item_spacing.x;
+    label.y = sym.y;
+    label.w = header.w - (sym.w + item_spacing.y + style->tab.indent);
+    label.h = style->font->height;
+    text.text = style->tab.text;
+    text.padding = nk_vec2(0,0);
+    nk_widget_text(out, label, title, nk_strlen(title), &text,
+        NK_TEXT_LEFT, style->font);}
+
+    /* increase x-axis cursor widget position pointer */
+    if (*state == NK_MAXIMIZED) {
+        layout->at_x = header.x + (float)*layout->offset_x + style->tab.indent;
+        layout->bounds.w = NK_MAX(layout->bounds.w, style->tab.indent);
+        layout->bounds.w -= (style->tab.indent + style->window.padding.x);
+        layout->row.tree_depth++;
+        return nk_true;
+    } else return nk_false;
+}
+
+NK_INTERN int
+nk_tree_base(struct nk_context *ctx, enum nk_tree_type type,
+    struct nk_image *img, const char *title, enum nk_collapse_states initial_state,
+    const char *hash, int len, int line)
+{
+    struct nk_window *win = ctx->current;
+    int title_len = 0;
+    nk_hash tree_hash = 0;
+    nk_uint *state = 0;
+
+    /* retrieve tree state from internal widget state tables */
+    if (!hash) {
+        title_len = (int)nk_strlen(title);
+        tree_hash = nk_murmur_hash(title, (int)title_len, (nk_hash)line);
+    } else tree_hash = nk_murmur_hash(hash, len, (nk_hash)line);
+    state = nk_find_value(win, tree_hash);
+    if (!state) {
+        state = nk_add_value(ctx, win, tree_hash, 0);
+        *state = initial_state;
+    }
+    return nk_tree_state_base(ctx, type, img, title, (enum nk_collapse_states*)state);
+}
+
+NK_API int
+nk_tree_state_push(struct nk_context *ctx, enum nk_tree_type type,
+    const char *title, enum nk_collapse_states *state)
+{return nk_tree_state_base(ctx, type, 0, title, state);}
+
+NK_API int
+nk_tree_state_image_push(struct nk_context *ctx, enum nk_tree_type type,
+    struct nk_image img, const char *title, enum nk_collapse_states *state)
+{return nk_tree_state_base(ctx, type, &img, title, state);}
+
+NK_API void
+nk_tree_state_pop(struct nk_context *ctx)
+{
+    struct nk_window *win = 0;
+    struct nk_panel *layout = 0;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    layout->at_x -= ctx->style.tab.indent + ctx->style.window.padding.x;
+    layout->bounds.w += ctx->style.tab.indent + ctx->style.window.padding.x;
+    NK_ASSERT(layout->row.tree_depth);
+    layout->row.tree_depth--;
+}
+
+NK_API int
+nk_tree_push_hashed(struct nk_context *ctx, enum nk_tree_type type,
+    const char *title, enum nk_collapse_states initial_state,
+    const char *hash, int len, int line)
+{return nk_tree_base(ctx, type, 0, title, initial_state, hash, len, line);}
+
+NK_API int
+nk_tree_image_push_hashed(struct nk_context *ctx, enum nk_tree_type type,
+    struct nk_image img, const char *title, enum nk_collapse_states initial_state,
+    const char *hash, int len,int seed)
+{return nk_tree_base(ctx, type, &img, title, initial_state, hash, len, seed);}
+
+NK_API void
+nk_tree_pop(struct nk_context *ctx)
+{nk_tree_state_pop(ctx);}
+
+/*----------------------------------------------------------------
+ *
+ *                          WIDGETS
+ *
+ * --------------------------------------------------------------*/
+NK_API struct nk_rect
+nk_widget_bounds(struct nk_context *ctx)
+{
+    struct nk_rect bounds;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current)
+        return nk_rect(0,0,0,0);
+    nk_layout_peek(&bounds, ctx);
+    return bounds;
+}
+
+NK_API struct nk_vec2
+nk_widget_position(struct nk_context *ctx)
+{
+    struct nk_rect bounds;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current)
+        return nk_vec2(0,0);
+
+    nk_layout_peek(&bounds, ctx);
+    return nk_vec2(bounds.x, bounds.y);
+}
+
+NK_API struct nk_vec2
+nk_widget_size(struct nk_context *ctx)
+{
+    struct nk_rect bounds;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current)
+        return nk_vec2(0,0);
+
+    nk_layout_peek(&bounds, ctx);
+    return nk_vec2(bounds.w, bounds.h);
+}
+
+NK_API float
+nk_widget_width(struct nk_context *ctx)
+{
+    struct nk_rect bounds;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current)
+        return 0;
+
+    nk_layout_peek(&bounds, ctx);
+    return bounds.w;
+}
+
+NK_API float
+nk_widget_height(struct nk_context *ctx)
+{
+    struct nk_rect bounds;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current)
+        return 0;
+
+    nk_layout_peek(&bounds, ctx);
+    return bounds.h;
+}
+
+NK_API int
+nk_widget_is_hovered(struct nk_context *ctx)
+{
+    struct nk_rect c, v;
+    struct nk_rect bounds;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current || ctx->active != ctx->current)
+        return 0;
+
+    c = ctx->current->layout->clip;
+    c.x = (float)((int)c.x);
+    c.y = (float)((int)c.y);
+    c.w = (float)((int)c.w);
+    c.h = (float)((int)c.h);
+
+    nk_layout_peek(&bounds, ctx);
+    nk_unify(&v, &c, bounds.x, bounds.y, bounds.x + bounds.w, bounds.y + bounds.h);
+    if (!NK_INTERSECT(c.x, c.y, c.w, c.h, bounds.x, bounds.y, bounds.w, bounds.h))
+        return 0;
+    return nk_input_is_mouse_hovering_rect(&ctx->input, bounds);
+}
+
+NK_API int
+nk_widget_is_mouse_clicked(struct nk_context *ctx, enum nk_buttons btn)
+{
+    struct nk_rect c, v;
+    struct nk_rect bounds;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current || ctx->active != ctx->current)
+        return 0;
+
+    c = ctx->current->layout->clip;
+    c.x = (float)((int)c.x);
+    c.y = (float)((int)c.y);
+    c.w = (float)((int)c.w);
+    c.h = (float)((int)c.h);
+
+    nk_layout_peek(&bounds, ctx);
+    nk_unify(&v, &c, bounds.x, bounds.y, bounds.x + bounds.w, bounds.y + bounds.h);
+    if (!NK_INTERSECT(c.x, c.y, c.w, c.h, bounds.x, bounds.y, bounds.w, bounds.h))
+        return 0;
+    return nk_input_mouse_clicked(&ctx->input, btn, bounds);
+}
+
+NK_API int
+nk_widget_has_mouse_click_down(struct nk_context *ctx, enum nk_buttons btn, int down)
+{
+    struct nk_rect c, v;
+    struct nk_rect bounds;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current || ctx->active != ctx->current)
+        return 0;
+
+    c = ctx->current->layout->clip;
+    c.x = (float)((int)c.x);
+    c.y = (float)((int)c.y);
+    c.w = (float)((int)c.w);
+    c.h = (float)((int)c.h);
+
+    nk_layout_peek(&bounds, ctx);
+    nk_unify(&v, &c, bounds.x, bounds.y, bounds.x + bounds.w, bounds.y + bounds.h);
+    if (!NK_INTERSECT(c.x, c.y, c.w, c.h, bounds.x, bounds.y, bounds.w, bounds.h))
+        return 0;
+    return nk_input_has_mouse_click_down_in_rect(&ctx->input, btn, bounds, down);
+}
+
+NK_API enum nk_widget_layout_states
+nk_widget(struct nk_rect *bounds, const struct nk_context *ctx)
+{
+    struct nk_rect c, v;
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return NK_WIDGET_INVALID;
+
+    /* allocate space and check if the widget needs to be updated and drawn */
+    nk_panel_alloc_space(bounds, ctx);
+    win = ctx->current;
+    layout = win->layout;
+    in = &ctx->input;
+    c = layout->clip;
+
+    /*  if one of these triggers you forgot to add an `if` condition around either
+        a window, group, popup, combobox or contextual menu `begin` and `end` block.
+        Example:
+            if (nk_begin(...) {...} nk_end(...); or
+            if (nk_group_begin(...) { nk_group_end(...);} */
+    NK_ASSERT(!(layout->flags & NK_WINDOW_MINIMIZED));
+    NK_ASSERT(!(layout->flags & NK_WINDOW_HIDDEN));
+    NK_ASSERT(!(layout->flags & NK_WINDOW_CLOSED));
+
+    /* need to convert to int here to remove floating point errors */
+    bounds->x = (float)((int)bounds->x);
+    bounds->y = (float)((int)bounds->y);
+    bounds->w = (float)((int)bounds->w);
+    bounds->h = (float)((int)bounds->h);
+
+    c.x = (float)((int)c.x);
+    c.y = (float)((int)c.y);
+    c.w = (float)((int)c.w);
+    c.h = (float)((int)c.h);
+
+    nk_unify(&v, &c, bounds->x, bounds->y, bounds->x + bounds->w, bounds->y + bounds->h);
+    if (!NK_INTERSECT(c.x, c.y, c.w, c.h, bounds->x, bounds->y, bounds->w, bounds->h))
+        return NK_WIDGET_INVALID;
+    if (!NK_INBOX(in->mouse.pos.x, in->mouse.pos.y, v.x, v.y, v.w, v.h))
+        return NK_WIDGET_ROM;
+    return NK_WIDGET_VALID;
+}
+
+NK_API enum nk_widget_layout_states
+nk_widget_fitting(struct nk_rect *bounds, struct nk_context *ctx,
+    struct nk_vec2 item_padding)
+{
+    /* update the bounds to stand without padding  */
+    struct nk_window *win;
+    struct nk_style *style;
+    struct nk_panel *layout;
+    enum nk_widget_layout_states state;
+    struct nk_vec2 panel_padding;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return NK_WIDGET_INVALID;
+
+    win = ctx->current;
+    style = &ctx->style;
+    layout = win->layout;
+    state = nk_widget(bounds, ctx);
+
+    panel_padding = nk_panel_get_padding(style, layout->type);
+    if (layout->row.index == 1) {
+        bounds->w += panel_padding.x;
+        bounds->x -= panel_padding.x;
+    } else bounds->x -= item_padding.x;
+
+    if (layout->row.index == layout->row.columns)
+        bounds->w += panel_padding.x;
+    else bounds->w += item_padding.x;
+    return state;
+}
+
+/*----------------------------------------------------------------
+ *
+ *                          MISC
+ *
+ * --------------------------------------------------------------*/
+NK_API void
+nk_spacing(struct nk_context *ctx, int cols)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    struct nk_rect none;
+    int i, index, rows;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    /* spacing over row boundaries */
+    win = ctx->current;
+    layout = win->layout;
+    index = (layout->row.index + cols) % layout->row.columns;
+    rows = (layout->row.index + cols) / layout->row.columns;
+    if (rows) {
+        for (i = 0; i < rows; ++i)
+            nk_panel_alloc_row(ctx, win);
+        cols = index;
+    }
+    /* non table layout need to allocate space */
+    if (layout->row.type != NK_LAYOUT_DYNAMIC_FIXED &&
+        layout->row.type != NK_LAYOUT_STATIC_FIXED) {
+        for (i = 0; i < cols; ++i)
+            nk_panel_alloc_space(&none, ctx);
+    }
+    layout->row.index = index;
+}
+
+/*----------------------------------------------------------------
+ *
+ *                          TEXT
+ *
+ * --------------------------------------------------------------*/
+NK_API void
+nk_text_colored(struct nk_context *ctx, const char *str, int len,
+    nk_flags alignment, struct nk_color color)
+{
+    struct nk_window *win;
+    const struct nk_style *style;
+
+    struct nk_vec2 item_padding;
+    struct nk_rect bounds;
+    struct nk_text text;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout) return;
+
+    win = ctx->current;
+    style = &ctx->style;
+    nk_panel_alloc_space(&bounds, ctx);
+    item_padding = style->text.padding;
+
+    text.padding.x = item_padding.x;
+    text.padding.y = item_padding.y;
+    text.background = style->window.background;
+    text.text = color;
+    nk_widget_text(&win->buffer, bounds, str, len, &text, alignment, style->font);
+}
+
+NK_API void
+nk_text_wrap_colored(struct nk_context *ctx, const char *str,
+    int len, struct nk_color color)
+{
+    struct nk_window *win;
+    const struct nk_style *style;
+
+    struct nk_vec2 item_padding;
+    struct nk_rect bounds;
+    struct nk_text text;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout) return;
+
+    win = ctx->current;
+    style = &ctx->style;
+    nk_panel_alloc_space(&bounds, ctx);
+    item_padding = style->text.padding;
+
+    text.padding.x = item_padding.x;
+    text.padding.y = item_padding.y;
+    text.background = style->window.background;
+    text.text = color;
+    nk_widget_text_wrap(&win->buffer, bounds, str, len, &text, style->font);
+}
+
+#ifdef NK_INCLUDE_STANDARD_VARARGS
+NK_API void
+nk_labelf_colored(struct nk_context *ctx, nk_flags flags,
+    struct nk_color color, const char *fmt, ...)
+{
+    char buf[256];
+    va_list args;
+    va_start(args, fmt);
+    nk_strfmt(buf, NK_LEN(buf), fmt, args);
+    nk_label_colored(ctx, buf, flags, color);
+    va_end(args);
+}
+
+NK_API void
+nk_labelf_colored_wrap(struct nk_context *ctx, struct nk_color color,
+    const char *fmt, ...)
+{
+    char buf[256];
+    va_list args;
+    va_start(args, fmt);
+    nk_strfmt(buf, NK_LEN(buf), fmt, args);
+    nk_label_colored_wrap(ctx, buf, color);
+    va_end(args);
+}
+
+NK_API void
+nk_labelf(struct nk_context *ctx, nk_flags flags, const char *fmt, ...)
+{
+    char buf[256];
+    va_list args;
+    va_start(args, fmt);
+    nk_strfmt(buf, NK_LEN(buf), fmt, args);
+    nk_label(ctx, buf, flags);
+    va_end(args);
+}
+
+NK_API void
+nk_labelf_wrap(struct nk_context *ctx, const char *fmt,...)
+{
+    char buf[256];
+    va_list args;
+    va_start(args, fmt);
+    nk_strfmt(buf, NK_LEN(buf), fmt, args);
+    nk_label_wrap(ctx, buf);
+    va_end(args);
+}
+
+NK_API void
+nk_value_bool(struct nk_context *ctx, const char *prefix, int value)
+{nk_labelf(ctx, NK_TEXT_LEFT, "%s: %s", prefix, ((value) ? "true": "false"));}
+
+NK_API void
+nk_value_int(struct nk_context *ctx, const char *prefix, int value)
+{nk_labelf(ctx, NK_TEXT_LEFT, "%s: %d", prefix, value);}
+
+NK_API void
+nk_value_uint(struct nk_context *ctx, const char *prefix, unsigned int value)
+{nk_labelf(ctx, NK_TEXT_LEFT, "%s: %u", prefix, value);}
+
+NK_API void
+nk_value_float(struct nk_context *ctx, const char *prefix, float value)
+{
+    double double_value = (double)value;
+    nk_labelf(ctx, NK_TEXT_LEFT, "%s: %.3f", prefix, double_value);
+}
+
+NK_API void
+nk_value_color_byte(struct nk_context *ctx, const char *p, struct nk_color c)
+{nk_labelf(ctx, NK_TEXT_LEFT, "%s: (%d, %d, %d, %d)", p, c.r, c.g, c.b, c.a);}
+
+NK_API void
+nk_value_color_float(struct nk_context *ctx, const char *p, struct nk_color color)
+{
+    double c[4]; nk_color_dv(c, color);
+    nk_labelf(ctx, NK_TEXT_LEFT, "%s: (%.2f, %.2f, %.2f, %.2f)",
+        p, c[0], c[1], c[2], c[3]);
+}
+
+NK_API void
+nk_value_color_hex(struct nk_context *ctx, const char *prefix, struct nk_color color)
+{
+    char hex[16];
+    nk_color_hex_rgba(hex, color);
+    nk_labelf(ctx, NK_TEXT_LEFT, "%s: %s", prefix, hex);
+}
+#endif
+
+NK_API void
+nk_text(struct nk_context *ctx, const char *str, int len, nk_flags alignment)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    nk_text_colored(ctx, str, len, alignment, ctx->style.text.color);
+}
+
+NK_API void
+nk_text_wrap(struct nk_context *ctx, const char *str, int len)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    nk_text_wrap_colored(ctx, str, len, ctx->style.text.color);
+}
+
+NK_API void
+nk_label(struct nk_context *ctx, const char *str, nk_flags alignment)
+{nk_text(ctx, str, nk_strlen(str), alignment);}
+
+NK_API void
+nk_label_colored(struct nk_context *ctx, const char *str, nk_flags align,
+    struct nk_color color)
+{nk_text_colored(ctx, str, nk_strlen(str), align, color);}
+
+NK_API void
+nk_label_wrap(struct nk_context *ctx, const char *str)
+{nk_text_wrap(ctx, str, nk_strlen(str));}
+
+NK_API void
+nk_label_colored_wrap(struct nk_context *ctx, const char *str, struct nk_color color)
+{nk_text_wrap_colored(ctx, str, nk_strlen(str), color);}
+
+NK_API void
+nk_image(struct nk_context *ctx, struct nk_image img)
+{
+    struct nk_window *win;
+    struct nk_rect bounds;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout) return;
+
+    win = ctx->current;
+    if (!nk_widget(&bounds, ctx)) return;
+    nk_draw_image(&win->buffer, bounds, &img, nk_white);
+}
+
+/*----------------------------------------------------------------
+ *
+ *                          BUTTON
+ *
+ * --------------------------------------------------------------*/
+NK_API void
+nk_button_set_behavior(struct nk_context *ctx, enum nk_button_behavior behavior)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return;
+    ctx->button_behavior = behavior;
+}
+
+NK_API int
+nk_button_push_behavior(struct nk_context *ctx, enum nk_button_behavior behavior)
+{
+    struct nk_config_stack_button_behavior *button_stack;
+    struct nk_config_stack_button_behavior_element *element;
+
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+
+    button_stack = &ctx->stacks.button_behaviors;
+    NK_ASSERT(button_stack->head < (int)NK_LEN(button_stack->elements));
+    if (button_stack->head >= (int)NK_LEN(button_stack->elements))
+        return 0;
+
+    element = &button_stack->elements[button_stack->head++];
+    element->address = &ctx->button_behavior;
+    element->old_value = ctx->button_behavior;
+    ctx->button_behavior = behavior;
+    return 1;
+}
+
+NK_API int
+nk_button_pop_behavior(struct nk_context *ctx)
+{
+    struct nk_config_stack_button_behavior *button_stack;
+    struct nk_config_stack_button_behavior_element *element;
+
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+
+    button_stack = &ctx->stacks.button_behaviors;
+    NK_ASSERT(button_stack->head > 0);
+    if (button_stack->head < 1)
+        return 0;
+
+    element = &button_stack->elements[--button_stack->head];
+    *element->address = element->old_value;
+    return 1;
+}
+
+NK_API int
+nk_button_text_styled(struct nk_context *ctx,
+    const struct nk_style_button *style, const char *title, int len)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(style);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!style || !ctx || !ctx->current || !ctx->current->layout) return 0;
+
+    win = ctx->current;
+    layout = win->layout;
+    state = nk_widget(&bounds, ctx);
+
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    return nk_do_button_text(&ctx->last_widget_state, &win->buffer, bounds,
+                    title, len, style->text_alignment, ctx->button_behavior,
+                    style, in, ctx->style.font);
+}
+
+NK_API int
+nk_button_text(struct nk_context *ctx, const char *title, int len)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+    return nk_button_text_styled(ctx, &ctx->style.button, title, len);
+}
+
+NK_API int nk_button_label_styled(struct nk_context *ctx,
+    const struct nk_style_button *style, const char *title)
+{return nk_button_text_styled(ctx, style, title, nk_strlen(title));}
+
+NK_API int nk_button_label(struct nk_context *ctx, const char *title)
+{return nk_button_text(ctx, title, nk_strlen(title));}
+
+NK_API int
+nk_button_color(struct nk_context *ctx, struct nk_color color)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+    struct nk_style_button button;
+
+    int ret = 0;
+    struct nk_rect bounds;
+    struct nk_rect content;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    layout = win->layout;
+
+    state = nk_widget(&bounds, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+
+    button = ctx->style.button;
+    button.normal = nk_style_item_color(color);
+    button.hover = nk_style_item_color(color);
+    button.active = nk_style_item_color(color);
+    ret = nk_do_button(&ctx->last_widget_state, &win->buffer, bounds,
+                &button, in, ctx->button_behavior, &content);
+    nk_draw_button(&win->buffer, &bounds, ctx->last_widget_state, &button);
+    return ret;
+}
+
+NK_API int
+nk_button_symbol_styled(struct nk_context *ctx,
+    const struct nk_style_button *style, enum nk_symbol_type symbol)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    layout = win->layout;
+    state = nk_widget(&bounds, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    return nk_do_button_symbol(&ctx->last_widget_state, &win->buffer, bounds,
+            symbol, ctx->button_behavior, style, in, ctx->style.font);
+}
+
+NK_API int
+nk_button_symbol(struct nk_context *ctx, enum nk_symbol_type symbol)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+    return nk_button_symbol_styled(ctx, &ctx->style.button, symbol);
+}
+
+NK_API int
+nk_button_image_styled(struct nk_context *ctx, const struct nk_style_button *style,
+    struct nk_image img)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    layout = win->layout;
+
+    state = nk_widget(&bounds, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    return nk_do_button_image(&ctx->last_widget_state, &win->buffer, bounds,
+                img, ctx->button_behavior, style, in);
+}
+
+NK_API int
+nk_button_image(struct nk_context *ctx, struct nk_image img)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+    return nk_button_image_styled(ctx, &ctx->style.button, img);
+}
+
+NK_API int
+nk_button_symbol_text_styled(struct nk_context *ctx,
+    const struct nk_style_button *style, enum nk_symbol_type symbol,
+    const char *text, int len, nk_flags align)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    layout = win->layout;
+
+    state = nk_widget(&bounds, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    return nk_do_button_text_symbol(&ctx->last_widget_state, &win->buffer, bounds,
+                symbol, text, len, align, ctx->button_behavior,
+                style, ctx->style.font, in);
+}
+
+NK_API int
+nk_button_symbol_text(struct nk_context *ctx, enum nk_symbol_type symbol,
+    const char* text, int len, nk_flags align)
+{
+    NK_ASSERT(ctx);
+    if (!ctx) return 0;
+    return nk_button_symbol_text_styled(ctx, &ctx->style.button, symbol, text, len, align);
+}
+
+NK_API int nk_button_symbol_label(struct nk_context *ctx, enum nk_symbol_type symbol,
+    const char *label, nk_flags align)
+{return nk_button_symbol_text(ctx, symbol, label, nk_strlen(label), align);}
+
+NK_API int nk_button_symbol_label_styled(struct nk_context *ctx,
+    const struct nk_style_button *style, enum nk_symbol_type symbol,
+    const char *title, nk_flags align)
+{return nk_button_symbol_text_styled(ctx, style, symbol, title, nk_strlen(title), align);}
+
+NK_API int
+nk_button_image_text_styled(struct nk_context *ctx,
+    const struct nk_style_button *style, struct nk_image img, const char *text,
+    int len, nk_flags align)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    layout = win->layout;
+
+    state = nk_widget(&bounds, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    return nk_do_button_text_image(&ctx->last_widget_state, &win->buffer,
+            bounds, img, text, len, align, ctx->button_behavior,
+            style, ctx->style.font, in);
+}
+
+NK_API int
+nk_button_image_text(struct nk_context *ctx, struct nk_image img,
+    const char *text, int len, nk_flags align)
+{return nk_button_image_text_styled(ctx, &ctx->style.button,img, text, len, align);}
+
+
+NK_API int nk_button_image_label(struct nk_context *ctx, struct nk_image img,
+    const char *label, nk_flags align)
+{return nk_button_image_text(ctx, img, label, nk_strlen(label), align);}
+
+NK_API int nk_button_image_label_styled(struct nk_context *ctx,
+    const struct nk_style_button *style, struct nk_image img,
+    const char *label, nk_flags text_alignment)
+{return nk_button_image_text_styled(ctx, style, img, label, nk_strlen(label), text_alignment);}
+
+/*----------------------------------------------------------------
+ *
+ *                          SELECTABLE
+ *
+ * --------------------------------------------------------------*/
+NK_API int
+nk_selectable_text(struct nk_context *ctx, const char *str, int len,
+    nk_flags align, int *value)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+    const struct nk_style *style;
+
+    enum nk_widget_layout_states state;
+    struct nk_rect bounds;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(value);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout || !value)
+        return 0;
+
+    win = ctx->current;
+    layout = win->layout;
+    style = &ctx->style;
+
+    state = nk_widget(&bounds, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    return nk_do_selectable(&ctx->last_widget_state, &win->buffer, bounds,
+                str, len, align, value, &style->selectable, in, style->font);
+}
+
+NK_API int
+nk_selectable_image_text(struct nk_context *ctx, struct nk_image img,
+    const char *str, int len, nk_flags align, int *value)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+    const struct nk_style *style;
+
+    enum nk_widget_layout_states state;
+    struct nk_rect bounds;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(value);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout || !value)
+        return 0;
+
+    win = ctx->current;
+    layout = win->layout;
+    style = &ctx->style;
+
+    state = nk_widget(&bounds, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    return nk_do_selectable_image(&ctx->last_widget_state, &win->buffer, bounds,
+                str, len, align, value, &img, &style->selectable, in, style->font);
+}
+
+NK_API int nk_select_text(struct nk_context *ctx, const char *str, int len,
+    nk_flags align, int value)
+{nk_selectable_text(ctx, str, len, align, &value);return value;}
+
+NK_API int nk_selectable_label(struct nk_context *ctx, const char *str, nk_flags align, int *value)
+{return nk_selectable_text(ctx, str, nk_strlen(str), align, value);}
+
+NK_API int nk_selectable_image_label(struct nk_context *ctx,struct nk_image img,
+    const char *str, nk_flags align, int *value)
+{return nk_selectable_image_text(ctx, img, str, nk_strlen(str), align, value);}
+
+NK_API int nk_select_label(struct nk_context *ctx, const char *str, nk_flags align, int value)
+{nk_selectable_text(ctx, str, nk_strlen(str), align, &value);return value;}
+
+NK_API int nk_select_image_label(struct nk_context *ctx, struct nk_image img,
+    const char *str, nk_flags align, int value)
+{nk_selectable_image_text(ctx, img, str, nk_strlen(str), align, &value);return value;}
+
+NK_API int nk_select_image_text(struct nk_context *ctx, struct nk_image img,
+    const char *str, int len, nk_flags align, int value)
+{nk_selectable_image_text(ctx, img, str, len, align, &value);return value;}
+
+/*----------------------------------------------------------------
+ *
+ *                          CHECKBOX
+ *
+ * --------------------------------------------------------------*/
+NK_API int
+nk_check_text(struct nk_context *ctx, const char *text, int len, int active)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+    const struct nk_style *style;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return active;
+
+    win = ctx->current;
+    style = &ctx->style;
+    layout = win->layout;
+
+    state = nk_widget(&bounds, ctx);
+    if (!state) return active;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    nk_do_toggle(&ctx->last_widget_state, &win->buffer, bounds, &active,
+        text, len, NK_TOGGLE_CHECK, &style->checkbox, in, style->font);
+    return active;
+}
+
+NK_API unsigned int
+nk_check_flags_text(struct nk_context *ctx, const char *text, int len,
+    unsigned int flags, unsigned int value)
+{
+    int old_active;
+    NK_ASSERT(ctx);
+    NK_ASSERT(text);
+    if (!ctx || !text) return flags;
+    old_active = (int)((flags & value) & value);
+    if (nk_check_text(ctx, text, len, old_active))
+        flags |= value;
+    else flags &= ~value;
+    return flags;
+}
+
+NK_API int
+nk_checkbox_text(struct nk_context *ctx, const char *text, int len, int *active)
+{
+    int old_val;
+    NK_ASSERT(ctx);
+    NK_ASSERT(text);
+    NK_ASSERT(active);
+    if (!ctx || !text || !active) return 0;
+    old_val = *active;
+    *active = nk_check_text(ctx, text, len, *active);
+    return old_val != *active;
+}
+
+NK_API int
+nk_checkbox_flags_text(struct nk_context *ctx, const char *text, int len,
+    unsigned int *flags, unsigned int value)
+{
+    int active;
+    NK_ASSERT(ctx);
+    NK_ASSERT(text);
+    NK_ASSERT(flags);
+    if (!ctx || !text || !flags) return 0;
+
+    active = (int)((*flags & value) & value);
+    if (nk_checkbox_text(ctx, text, len, &active)) {
+        if (active) *flags |= value;
+        else *flags &= ~value;
+        return 1;
+    }
+    return 0;
+}
+
+NK_API int nk_check_label(struct nk_context *ctx, const char *label, int active)
+{return nk_check_text(ctx, label, nk_strlen(label), active);}
+
+NK_API unsigned int nk_check_flags_label(struct nk_context *ctx, const char *label,
+    unsigned int flags, unsigned int value)
+{return nk_check_flags_text(ctx, label, nk_strlen(label), flags, value);}
+
+NK_API int nk_checkbox_label(struct nk_context *ctx, const char *label, int *active)
+{return nk_checkbox_text(ctx, label, nk_strlen(label), active);}
+
+NK_API int nk_checkbox_flags_label(struct nk_context *ctx, const char *label,
+    unsigned int *flags, unsigned int value)
+{return nk_checkbox_flags_text(ctx, label, nk_strlen(label), flags, value);}
+
+/*----------------------------------------------------------------
+ *
+ *                          OPTION
+ *
+ * --------------------------------------------------------------*/
+NK_API int
+nk_option_text(struct nk_context *ctx, const char *text, int len, int is_active)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_input *in;
+    const struct nk_style *style;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return is_active;
+
+    win = ctx->current;
+    style = &ctx->style;
+    layout = win->layout;
+
+    state = nk_widget(&bounds, ctx);
+    if (!state) return state;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    nk_do_toggle(&ctx->last_widget_state, &win->buffer, bounds, &is_active,
+        text, len, NK_TOGGLE_OPTION, &style->option, in, style->font);
+    return is_active;
+}
+
+NK_API int
+nk_radio_text(struct nk_context *ctx, const char *text, int len, int *active)
+{
+    int old_value;
+    NK_ASSERT(ctx);
+    NK_ASSERT(text);
+    NK_ASSERT(active);
+    if (!ctx || !text || !active) return 0;
+    old_value = *active;
+    *active = nk_option_text(ctx, text, len, old_value);
+    return old_value != *active;
+}
+
+NK_API int
+nk_option_label(struct nk_context *ctx, const char *label, int active)
+{return nk_option_text(ctx, label, nk_strlen(label), active);}
+
+NK_API int
+nk_radio_label(struct nk_context *ctx, const char *label, int *active)
+{return nk_radio_text(ctx, label, nk_strlen(label), active);}
+
+/*----------------------------------------------------------------
+ *
+ *                          SLIDER
+ *
+ * --------------------------------------------------------------*/
+NK_API int
+nk_slider_float(struct nk_context *ctx, float min_value, float *value, float max_value,
+    float value_step)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    struct nk_input *in;
+    const struct nk_style *style;
+
+    int ret = 0;
+    float old_value;
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    NK_ASSERT(value);
+    if (!ctx || !ctx->current || !ctx->current->layout || !value)
+        return ret;
+
+    win = ctx->current;
+    style = &ctx->style;
+    layout = win->layout;
+
+    state = nk_widget(&bounds, ctx);
+    if (!state) return ret;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+
+    old_value = *value;
+    *value = nk_do_slider(&ctx->last_widget_state, &win->buffer, bounds, min_value,
+                old_value, max_value, value_step, &style->slider, in, style->font);
+    return (old_value > *value || old_value < *value);
+}
+
+NK_API float
+nk_slide_float(struct nk_context *ctx, float min, float val, float max, float step)
+{
+    nk_slider_float(ctx, min, &val, max, step); return val;
+}
+
+NK_API int
+nk_slide_int(struct nk_context *ctx, int min, int val, int max, int step)
+{
+    float value = (float)val;
+    nk_slider_float(ctx, (float)min, &value, (float)max, (float)step);
+    return (int)value;
+}
+
+NK_API int
+nk_slider_int(struct nk_context *ctx, int min, int *val, int max, int step)
+{
+    int ret;
+    float value = (float)*val;
+    ret = nk_slider_float(ctx, (float)min, &value, (float)max, (float)step);
+    *val =  (int)value;
+    return ret;
+}
+
+/*----------------------------------------------------------------
+ *
+ *                          PROGRESSBAR
+ *
+ * --------------------------------------------------------------*/
+NK_API int
+nk_progress(struct nk_context *ctx, nk_size *cur, nk_size max, int is_modifyable)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_style *style;
+    const struct nk_input *in;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+    nk_size old_value;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(cur);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout || !cur)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    layout = win->layout;
+    state = nk_widget(&bounds, ctx);
+    if (!state) return 0;
+
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    old_value = *cur;
+    *cur = nk_do_progress(&ctx->last_widget_state, &win->buffer, bounds,
+            *cur, max, is_modifyable, &style->progress, in);
+    return (*cur != old_value);
+}
+
+NK_API nk_size nk_prog(struct nk_context *ctx, nk_size cur, nk_size max, int modifyable)
+{nk_progress(ctx, &cur, max, modifyable);return cur;}
+
+/*----------------------------------------------------------------
+ *
+ *                          EDIT
+ *
+ * --------------------------------------------------------------*/
+NK_API void
+nk_edit_focus(struct nk_context *ctx, nk_flags flags)
+{
+    nk_hash hash;
+    struct nk_window *win;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return;
+
+    win = ctx->current;
+    hash = win->edit.seq;
+    win->edit.active = nk_true;
+    win->edit.name = hash;
+    if (flags & NK_EDIT_ALWAYS_INSERT_MODE)
+        win->edit.mode = NK_TEXT_EDIT_MODE_INSERT;
+}
+
+NK_API void
+nk_edit_unfocus(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return;
+
+    win = ctx->current;
+    win->edit.active = nk_false;
+    win->edit.name = 0;
+}
+
+NK_API nk_flags
+nk_edit_string(struct nk_context *ctx, nk_flags flags,
+    char *memory, int *len, int max, nk_plugin_filter filter)
+{
+    nk_hash hash;
+    nk_flags state;
+    struct nk_text_edit *edit;
+    struct nk_window *win;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(memory);
+    NK_ASSERT(len);
+    if (!ctx || !memory || !len)
+        return 0;
+
+    filter = (!filter) ? nk_filter_default: filter;
+    win = ctx->current;
+    hash = win->edit.seq;
+    edit = &ctx->text_edit;
+    nk_textedit_clear_state(&ctx->text_edit, (flags & NK_EDIT_MULTILINE)?
+        NK_TEXT_EDIT_MULTI_LINE: NK_TEXT_EDIT_SINGLE_LINE, filter);
+
+    if (win->edit.active && hash == win->edit.name) {
+        if (flags & NK_EDIT_NO_CURSOR)
+            edit->cursor = nk_utf_len(memory, *len);
+        else edit->cursor = win->edit.cursor;
+        if (!(flags & NK_EDIT_SELECTABLE)) {
+            edit->select_start = win->edit.cursor;
+            edit->select_end = win->edit.cursor;
+        } else {
+            edit->select_start = win->edit.sel_start;
+            edit->select_end = win->edit.sel_end;
+        }
+        edit->mode = win->edit.mode;
+        edit->scrollbar.x = (float)win->edit.scrollbar.x;
+        edit->scrollbar.y = (float)win->edit.scrollbar.y;
+        edit->active = nk_true;
+    } else edit->active = nk_false;
+
+    max = NK_MAX(1, max);
+    *len = NK_MIN(*len, max-1);
+    nk_str_init_fixed(&edit->string, memory, (nk_size)max);
+    edit->string.buffer.allocated = (nk_size)*len;
+    edit->string.len = nk_utf_len(memory, *len);
+    state = nk_edit_buffer(ctx, flags, edit, filter);
+    *len = (int)edit->string.buffer.allocated;
+
+    if (edit->active) {
+        win->edit.cursor = edit->cursor;
+        win->edit.sel_start = edit->select_start;
+        win->edit.sel_end = edit->select_end;
+        win->edit.mode = edit->mode;
+        win->edit.scrollbar.x = (nk_uint)edit->scrollbar.x;
+        win->edit.scrollbar.y = (nk_uint)edit->scrollbar.y;
+    }
+    return state;
+}
+
+NK_API nk_flags
+nk_edit_buffer(struct nk_context *ctx, nk_flags flags,
+    struct nk_text_edit *edit, nk_plugin_filter filter)
+{
+    struct nk_window *win;
+    struct nk_style *style;
+    struct nk_input *in;
+
+    enum nk_widget_layout_states state;
+    struct nk_rect bounds;
+
+    nk_flags ret_flags = 0;
+    unsigned char prev_state;
+    nk_hash hash;
+
+    /* make sure correct values */
+    NK_ASSERT(ctx);
+    NK_ASSERT(edit);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    state = nk_widget(&bounds, ctx);
+    if (!state) return state;
+    in = (win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+
+    /* check if edit is currently hot item */
+    hash = win->edit.seq++;
+    if (win->edit.active && hash == win->edit.name) {
+        if (flags & NK_EDIT_NO_CURSOR)
+            edit->cursor = edit->string.len;
+        if (!(flags & NK_EDIT_SELECTABLE)) {
+            edit->select_start = edit->cursor;
+            edit->select_end = edit->cursor;
+        }
+        if (flags & NK_EDIT_CLIPBOARD)
+            edit->clip = ctx->clip;
+        edit->active = win->edit.active;
+    } else edit->active = nk_false;
+    edit->mode = win->edit.mode;
+
+    filter = (!filter) ? nk_filter_default: filter;
+    prev_state = (unsigned char)edit->active;
+    in = (flags & NK_EDIT_READ_ONLY) ? 0: in;
+    ret_flags = nk_do_edit(&ctx->last_widget_state, &win->buffer, bounds, flags,
+                    filter, edit, &style->edit, in, style->font);
+
+    if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER)
+        ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_TEXT];
+    if (edit->active && prev_state != edit->active) {
+        /* current edit is now hot */
+        win->edit.active = nk_true;
+        win->edit.name = hash;
+    } else if (prev_state && !edit->active) {
+        /* current edit is now cold */
+        win->edit.active = nk_false;
+    }
+    return ret_flags;
+}
+
+NK_API nk_flags
+nk_edit_string_zero_terminated(struct nk_context *ctx, nk_flags flags,
+    char *buffer, int max, nk_plugin_filter filter)
+{
+    nk_flags result;
+    int len = nk_strlen(buffer);
+    result = nk_edit_string(ctx, flags, buffer, &len, max, filter);
+    buffer[NK_MIN(NK_MAX(max-1,0), len)] = '\0';
+    return result;
+}
+
+/*----------------------------------------------------------------
+ *
+ *                          PROPERTY
+ *
+ * --------------------------------------------------------------*/
+NK_INTERN struct nk_property_variant
+nk_property_variant_int(int value, int min_value, int max_value, int step)
+{
+    struct nk_property_variant result;
+    result.kind = NK_PROPERTY_INT;
+    result.value.i = value;
+    result.min_value.i = min_value;
+    result.max_value.i = max_value;
+    result.step.i = step;
+    return result;
+}
+
+NK_INTERN struct nk_property_variant
+nk_property_variant_float(float value, float min_value, float max_value, float step)
+{
+    struct nk_property_variant result;
+    result.kind = NK_PROPERTY_FLOAT;
+    result.value.f = value;
+    result.min_value.f = min_value;
+    result.max_value.f = max_value;
+    result.step.f = step;
+    return result;
+}
+
+NK_INTERN struct nk_property_variant
+nk_property_variant_double(double value, double min_value, double max_value,
+    double step)
+{
+    struct nk_property_variant result;
+    result.kind = NK_PROPERTY_DOUBLE;
+    result.value.d = value;
+    result.min_value.d = min_value;
+    result.max_value.d = max_value;
+    result.step.d = step;
+    return result;
+}
+
+NK_INTERN void
+nk_property(struct nk_context *ctx, const char *name, struct nk_property_variant *variant,
+    float inc_per_pixel, const enum nk_property_filter filter)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    struct nk_input *in;
+    const struct nk_style *style;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states s;
+
+    int *state = 0;
+    nk_hash hash = 0;
+    char *buffer = 0;
+    int *len = 0;
+    int *cursor = 0;
+    int *select_begin = 0;
+    int *select_end = 0;
+    int old_state;
+
+    char dummy_buffer[NK_MAX_NUMBER_BUFFER];
+    int dummy_state = NK_PROPERTY_DEFAULT;
+    int dummy_length = 0;
+    int dummy_cursor = 0;
+    int dummy_select_begin = 0;
+    int dummy_select_end = 0;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    win = ctx->current;
+    layout = win->layout;
+    style = &ctx->style;
+    s = nk_widget(&bounds, ctx);
+    if (!s) return;
+
+    /* calculate hash from name */
+    if (name[0] == '#') {
+        hash = nk_murmur_hash(name, (int)nk_strlen(name), win->property.seq++);
+        name++; /* special number hash */
+    } else hash = nk_murmur_hash(name, (int)nk_strlen(name), 42);
+
+    /* check if property is currently hot item */
+    if (win->property.active && hash == win->property.name) {
+        buffer = win->property.buffer;
+        len = &win->property.length;
+        cursor = &win->property.cursor;
+        state = &win->property.state;
+        select_begin = &win->property.select_start;
+        select_end = &win->property.select_end;
+    } else {
+        buffer = dummy_buffer;
+        len = &dummy_length;
+        cursor = &dummy_cursor;
+        state = &dummy_state;
+        select_begin =  &dummy_select_begin;
+        select_end = &dummy_select_end;
+    }
+
+    /* execute property widget */
+    old_state = *state;
+    ctx->text_edit.clip = ctx->clip;
+    in = ((s == NK_WIDGET_ROM && !win->property.active) ||
+        layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    nk_do_property(&ctx->last_widget_state, &win->buffer, bounds, name,
+        variant, inc_per_pixel, buffer, len, state, cursor, select_begin,
+        select_end, &style->property, filter, in, style->font, &ctx->text_edit,
+        ctx->button_behavior);
+
+    if (in && *state != NK_PROPERTY_DEFAULT && !win->property.active) {
+        /* current property is now hot */
+        win->property.active = 1;
+        NK_MEMCPY(win->property.buffer, buffer, (nk_size)*len);
+        win->property.length = *len;
+        win->property.cursor = *cursor;
+        win->property.state = *state;
+        win->property.name = hash;
+        win->property.select_start = *select_begin;
+        win->property.select_end = *select_end;
+        if (*state == NK_PROPERTY_DRAG) {
+            ctx->input.mouse.grab = nk_true;
+            ctx->input.mouse.grabbed = nk_true;
+        }
+    }
+    /* check if previously active property is now inactive */
+    if (*state == NK_PROPERTY_DEFAULT && old_state != NK_PROPERTY_DEFAULT) {
+        if (old_state == NK_PROPERTY_DRAG) {
+            ctx->input.mouse.grab = nk_false;
+            ctx->input.mouse.grabbed = nk_false;
+            ctx->input.mouse.ungrab = nk_true;
+        }
+        win->property.select_start = 0;
+        win->property.select_end = 0;
+        win->property.active = 0;
+    }
+}
+
+NK_API void
+nk_property_int(struct nk_context *ctx, const char *name,
+    int min, int *val, int max, int step, float inc_per_pixel)
+{
+    struct nk_property_variant variant;
+    NK_ASSERT(ctx);
+    NK_ASSERT(name);
+    NK_ASSERT(val);
+
+    if (!ctx || !ctx->current || !name || !val) return;
+    variant = nk_property_variant_int(*val, min, max, step);
+    nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_INT);
+    *val = variant.value.i;
+}
+
+NK_API void
+nk_property_float(struct nk_context *ctx, const char *name,
+    float min, float *val, float max, float step, float inc_per_pixel)
+{
+    struct nk_property_variant variant;
+    NK_ASSERT(ctx);
+    NK_ASSERT(name);
+    NK_ASSERT(val);
+
+    if (!ctx || !ctx->current || !name || !val) return;
+    variant = nk_property_variant_float(*val, min, max, step);
+    nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_FLOAT);
+    *val = variant.value.f;
+}
+
+NK_API void
+nk_property_double(struct nk_context *ctx, const char *name,
+    double min, double *val, double max, double step, float inc_per_pixel)
+{
+    struct nk_property_variant variant;
+    NK_ASSERT(ctx);
+    NK_ASSERT(name);
+    NK_ASSERT(val);
+
+    if (!ctx || !ctx->current || !name || !val) return;
+    variant = nk_property_variant_double(*val, min, max, step);
+    nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_FLOAT);
+    *val = variant.value.d;
+}
+
+NK_API int
+nk_propertyi(struct nk_context *ctx, const char *name, int min, int val,
+    int max, int step, float inc_per_pixel)
+{
+    struct nk_property_variant variant;
+    NK_ASSERT(ctx);
+    NK_ASSERT(name);
+
+    if (!ctx || !ctx->current || !name) return val;
+    variant = nk_property_variant_int(val, min, max, step);
+    nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_INT);
+    val = variant.value.i;
+    return val;
+}
+
+NK_API float
+nk_propertyf(struct nk_context *ctx, const char *name, float min,
+    float val, float max, float step, float inc_per_pixel)
+{
+    struct nk_property_variant variant;
+    NK_ASSERT(ctx);
+    NK_ASSERT(name);
+
+    if (!ctx || !ctx->current || !name) return val;
+    variant = nk_property_variant_float(val, min, max, step);
+    nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_FLOAT);
+    val = variant.value.f;
+    return val;
+}
+
+NK_API double
+nk_propertyd(struct nk_context *ctx, const char *name, double min,
+    double val, double max, double step, float inc_per_pixel)
+{
+    struct nk_property_variant variant;
+    NK_ASSERT(ctx);
+    NK_ASSERT(name);
+
+    if (!ctx || !ctx->current || !name) return val;
+    variant = nk_property_variant_double(val, min, max, step);
+    nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_FLOAT);
+    val = variant.value.d;
+    return val;
+}
+
+/*----------------------------------------------------------------
+ *
+ *                          COLOR PICKER
+ *
+ * --------------------------------------------------------------*/
+NK_API int
+nk_color_pick(struct nk_context * ctx, struct nk_color *color,
+    enum nk_color_format fmt)
+{
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_style *config;
+    const struct nk_input *in;
+
+    enum nk_widget_layout_states state;
+    struct nk_rect bounds;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(color);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout || !color)
+        return 0;
+
+    win = ctx->current;
+    config = &ctx->style;
+    layout = win->layout;
+    state = nk_widget(&bounds, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    return nk_do_color_picker(&ctx->last_widget_state, &win->buffer, color, fmt, bounds,
+                nk_vec2(0,0), in, config->font);
+}
+
+NK_API struct nk_color
+nk_color_picker(struct nk_context *ctx, struct nk_color color,
+    enum nk_color_format fmt)
+{
+    nk_color_pick(ctx, &color, fmt);
+    return color;
+}
+
+/* -------------------------------------------------------------
+ *
+ *                          CHART
+ *
+ * --------------------------------------------------------------*/
+NK_API int
+nk_chart_begin_colored(struct nk_context *ctx, enum nk_chart_type type,
+    struct nk_color color, struct nk_color highlight,
+    int count, float min_value, float max_value)
+{
+    struct nk_window *win;
+    struct nk_chart *chart;
+    const struct nk_style *config;
+    const struct nk_style_chart *style;
+
+    const struct nk_style_item *background;
+    struct nk_rect bounds = {0, 0, 0, 0};
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+
+    if (!ctx || !ctx->current || !ctx->current->layout) return 0;
+    if (!nk_widget(&bounds, ctx)) {
+        chart = &ctx->current->layout->chart;
+        nk_zero(chart, sizeof(*chart));
+        return 0;
+    }
+
+    win = ctx->current;
+    config = &ctx->style;
+    chart = &win->layout->chart;
+    style = &config->chart;
+
+    /* setup basic generic chart  */
+    nk_zero(chart, sizeof(*chart));
+    chart->x = bounds.x + style->padding.x;
+    chart->y = bounds.y + style->padding.y;
+    chart->w = bounds.w - 2 * style->padding.x;
+    chart->h = bounds.h - 2 * style->padding.y;
+    chart->w = NK_MAX(chart->w, 2 * style->padding.x);
+    chart->h = NK_MAX(chart->h, 2 * style->padding.y);
+
+    /* add first slot into chart */
+    {struct nk_chart_slot *slot = &chart->slots[chart->slot++];
+    slot->type = type;
+    slot->count = count;
+    slot->color = color;
+    slot->highlight = highlight;
+    slot->min = NK_MIN(min_value, max_value);
+    slot->max = NK_MAX(min_value, max_value);
+    slot->range = slot->max - slot->min;}
+
+    /* draw chart background */
+    background = &style->background;
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        nk_draw_image(&win->buffer, bounds, &background->data.image, nk_white);
+    } else {
+        nk_fill_rect(&win->buffer, bounds, style->rounding, style->border_color);
+        nk_fill_rect(&win->buffer, nk_shrink_rect(bounds, style->border),
+            style->rounding, style->background.data.color);
+    }
+    return 1;
+}
+
+NK_API int
+nk_chart_begin(struct nk_context *ctx, const enum nk_chart_type type,
+    int count, float min_value, float max_value)
+{return nk_chart_begin_colored(ctx, type, ctx->style.chart.color, ctx->style.chart.selected_color, count, min_value, max_value);}
+
+NK_API void
+nk_chart_add_slot_colored(struct nk_context *ctx, const enum nk_chart_type type,
+    struct nk_color color, struct nk_color highlight,
+    int count, float min_value, float max_value)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    NK_ASSERT(ctx->current->layout->chart.slot < NK_CHART_MAX_SLOT);
+    if (!ctx || !ctx->current || !ctx->current->layout) return;
+    if (ctx->current->layout->chart.slot >= NK_CHART_MAX_SLOT) return;
+
+    /* add another slot into the graph */
+    {struct nk_chart *chart = &ctx->current->layout->chart;
+    struct nk_chart_slot *slot = &chart->slots[chart->slot++];
+    slot->type = type;
+    slot->count = count;
+    slot->color = color;
+    slot->highlight = highlight;
+    slot->min = NK_MIN(min_value, max_value);
+    slot->max = NK_MAX(min_value, max_value);
+    slot->range = slot->max - slot->min;}
+}
+
+NK_API void
+nk_chart_add_slot(struct nk_context *ctx, const enum nk_chart_type type,
+    int count, float min_value, float max_value)
+{nk_chart_add_slot_colored(ctx, type, ctx->style.chart.color, ctx->style.chart.selected_color, count, min_value, max_value);}
+
+NK_INTERN nk_flags
+nk_chart_push_line(struct nk_context *ctx, struct nk_window *win,
+    struct nk_chart *g, float value, int slot)
+{
+    struct nk_panel *layout = win->layout;
+    const struct nk_input *i = &ctx->input;
+    struct nk_command_buffer *out = &win->buffer;
+
+    nk_flags ret = 0;
+    struct nk_vec2 cur;
+    struct nk_rect bounds;
+    struct nk_color color;
+    float step;
+    float range;
+    float ratio;
+
+    NK_ASSERT(slot >= 0 && slot < NK_CHART_MAX_SLOT);
+    step = g->w / (float)g->slots[slot].count;
+    range = g->slots[slot].max - g->slots[slot].min;
+    ratio = (value - g->slots[slot].min) / range;
+
+    if (g->slots[slot].index == 0) {
+        /* first data point does not have a connection */
+        g->slots[slot].last.x = g->x;
+        g->slots[slot].last.y = (g->y + g->h) - ratio * (float)g->h;
+
+        bounds.x = g->slots[slot].last.x - 2;
+        bounds.y = g->slots[slot].last.y - 2;
+        bounds.w = bounds.h = 4;
+
+        color = g->slots[slot].color;
+        if (!(layout->flags & NK_WINDOW_ROM) &&
+            NK_INBOX(i->mouse.pos.x,i->mouse.pos.y, g->slots[slot].last.x-3, g->slots[slot].last.y-3, 6, 6)){
+            ret = nk_input_is_mouse_hovering_rect(i, bounds) ? NK_CHART_HOVERING : 0;
+            ret |= (i->mouse.buttons[NK_BUTTON_LEFT].down &&
+                i->mouse.buttons[NK_BUTTON_LEFT].clicked) ? NK_CHART_CLICKED: 0;
+            color = g->slots[slot].highlight;
+        }
+        nk_fill_rect(out, bounds, 0, color);
+        g->slots[slot].index += 1;
+        return ret;
+    }
+
+    /* draw a line between the last data point and the new one */
+    color = g->slots[slot].color;
+    cur.x = g->x + (float)(step * (float)g->slots[slot].index);
+    cur.y = (g->y + g->h) - (ratio * (float)g->h);
+    nk_stroke_line(out, g->slots[slot].last.x, g->slots[slot].last.y, cur.x, cur.y, 1.0f, color);
+
+    bounds.x = cur.x - 3;
+    bounds.y = cur.y - 3;
+    bounds.w = bounds.h = 6;
+
+    /* user selection of current data point */
+    if (!(layout->flags & NK_WINDOW_ROM)) {
+        if (nk_input_is_mouse_hovering_rect(i, bounds)) {
+            ret = NK_CHART_HOVERING;
+            ret |= (!i->mouse.buttons[NK_BUTTON_LEFT].down &&
+                i->mouse.buttons[NK_BUTTON_LEFT].clicked) ? NK_CHART_CLICKED: 0;
+            color = g->slots[slot].highlight;
+        }
+    }
+    nk_fill_rect(out, nk_rect(cur.x - 2, cur.y - 2, 4, 4), 0, color);
+
+    /* save current data point position */
+    g->slots[slot].last.x = cur.x;
+    g->slots[slot].last.y = cur.y;
+    g->slots[slot].index  += 1;
+    return ret;
+}
+
+NK_INTERN nk_flags
+nk_chart_push_column(const struct nk_context *ctx, struct nk_window *win,
+    struct nk_chart *chart, float value, int slot)
+{
+    struct nk_command_buffer *out = &win->buffer;
+    const struct nk_input *in = &ctx->input;
+    struct nk_panel *layout = win->layout;
+
+    float ratio;
+    nk_flags ret = 0;
+    struct nk_color color;
+    struct nk_rect item = {0,0,0,0};
+
+    NK_ASSERT(slot >= 0 && slot < NK_CHART_MAX_SLOT);
+    if (chart->slots[slot].index  >= chart->slots[slot].count)
+        return nk_false;
+    if (chart->slots[slot].count) {
+        float padding = (float)(chart->slots[slot].count-1);
+        item.w = (chart->w - padding) / (float)(chart->slots[slot].count);
+    }
+
+    /* calculate bounds of current bar chart entry */
+    color = chart->slots[slot].color;;
+    item.h = chart->h * NK_ABS((value/chart->slots[slot].range));
+    if (value >= 0) {
+        ratio = (value + NK_ABS(chart->slots[slot].min)) / NK_ABS(chart->slots[slot].range);
+        item.y = (chart->y + chart->h) - chart->h * ratio;
+    } else {
+        ratio = (value - chart->slots[slot].max) / chart->slots[slot].range;
+        item.y = chart->y + (chart->h * NK_ABS(ratio)) - item.h;
+    }
+    item.x = chart->x + ((float)chart->slots[slot].index * item.w);
+    item.x = item.x + ((float)chart->slots[slot].index);
+
+    /* user chart bar selection */
+    if (!(layout->flags & NK_WINDOW_ROM) &&
+        NK_INBOX(in->mouse.pos.x,in->mouse.pos.y,item.x,item.y,item.w,item.h)) {
+        ret = NK_CHART_HOVERING;
+        ret |= (!in->mouse.buttons[NK_BUTTON_LEFT].down &&
+                in->mouse.buttons[NK_BUTTON_LEFT].clicked) ? NK_CHART_CLICKED: 0;
+        color = chart->slots[slot].highlight;
+    }
+    nk_fill_rect(out, item, 0, color);
+    chart->slots[slot].index += 1;
+    return ret;
+}
+
+NK_API nk_flags
+nk_chart_push_slot(struct nk_context *ctx, float value, int slot)
+{
+    nk_flags flags;
+    struct nk_window *win;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(slot >= 0 && slot < NK_CHART_MAX_SLOT);
+    NK_ASSERT(slot < ctx->current->layout->chart.slot);
+    if (!ctx || !ctx->current || slot >= NK_CHART_MAX_SLOT) return nk_false;
+    if (slot >= ctx->current->layout->chart.slot) return nk_false;
+
+    win = ctx->current;
+    if (win->layout->chart.slot < slot) return nk_false;
+    switch (win->layout->chart.slots[slot].type) {
+    case NK_CHART_LINES:
+        flags = nk_chart_push_line(ctx, win, &win->layout->chart, value, slot); break;
+    case NK_CHART_COLUMN:
+        flags = nk_chart_push_column(ctx, win, &win->layout->chart, value, slot); break;
+    default:
+    case NK_CHART_MAX:
+        flags = 0;
+    }
+    return flags;
+}
+
+NK_API nk_flags
+nk_chart_push(struct nk_context *ctx, float value)
+{return nk_chart_push_slot(ctx, value, 0);}
+
+NK_API void
+nk_chart_end(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_chart *chart;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current)
+        return;
+
+    win = ctx->current;
+    chart = &win->layout->chart;
+    NK_MEMSET(chart, 0, sizeof(*chart));
+    return;
+}
+
+NK_API void
+nk_plot(struct nk_context *ctx, enum nk_chart_type type, const float *values,
+    int count, int offset)
+{
+    int i = 0;
+    float min_value;
+    float max_value;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(values);
+    if (!ctx || !values || !count) return;
+
+    min_value = values[offset];
+    max_value = values[offset];
+    for (i = 0; i < count; ++i) {
+        min_value = NK_MIN(values[i + offset], min_value);
+        max_value = NK_MAX(values[i + offset], max_value);
+    }
+
+    if (nk_chart_begin(ctx, type, count, min_value, max_value)) {
+        for (i = 0; i < count; ++i)
+            nk_chart_push(ctx, values[i + offset]);
+        nk_chart_end(ctx);
+    }
+}
+
+NK_API void
+nk_plot_function(struct nk_context *ctx, enum nk_chart_type type, void *userdata,
+    float(*value_getter)(void* user, int index), int count, int offset)
+{
+    int i = 0;
+    float min_value;
+    float max_value;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(value_getter);
+    if (!ctx || !value_getter || !count) return;
+
+    max_value = min_value = value_getter(userdata, offset);
+    for (i = 0; i < count; ++i) {
+        float value = value_getter(userdata, i + offset);
+        min_value = NK_MIN(value, min_value);
+        max_value = NK_MAX(value, max_value);
+    }
+
+    if (nk_chart_begin(ctx, type, count, min_value, max_value)) {
+        for (i = 0; i < count; ++i)
+            nk_chart_push(ctx, value_getter(userdata, i + offset));
+        nk_chart_end(ctx);
+    }
+}
+
+/* -------------------------------------------------------------
+ *
+ *                          GROUP
+ *
+ * --------------------------------------------------------------*/
+NK_API int
+nk_group_scrolled_offset_begin(struct nk_context *ctx,
+    nk_uint *x_offset, nk_uint *y_offset, const char *title, nk_flags flags)
+{
+    struct nk_rect bounds;
+    struct nk_window panel;
+    struct nk_window *win;
+
+    win = ctx->current;
+    nk_panel_alloc_space(&bounds, ctx);
+    {const struct nk_rect *c = &win->layout->clip;
+    if (!NK_INTERSECT(c->x, c->y, c->w, c->h, bounds.x, bounds.y, bounds.w, bounds.h) &&
+        !(flags & NK_WINDOW_MOVABLE)) {
+        return 0;
+    }}
+    if (win->flags & NK_WINDOW_ROM)
+        flags |= NK_WINDOW_ROM;
+
+    /* initialize a fake window to create the panel from */
+    nk_zero(&panel, sizeof(panel));
+    panel.bounds = bounds;
+    panel.flags = flags;
+    panel.scrollbar.x = *x_offset;
+    panel.scrollbar.y = *y_offset;
+    panel.buffer = win->buffer;
+    panel.layout = (struct nk_panel*)nk_create_panel(ctx);
+    ctx->current = &panel;
+    nk_panel_begin(ctx, (flags & NK_WINDOW_TITLE) ? title: 0, NK_PANEL_GROUP);
+
+    win->buffer = panel.buffer;
+    win->buffer.clip = panel.layout->clip;
+    panel.layout->offset_x = x_offset;
+    panel.layout->offset_y = y_offset;
+    panel.layout->parent = win->layout;
+    win->layout = panel.layout;
+
+    ctx->current = win;
+    if ((panel.layout->flags & NK_WINDOW_CLOSED) ||
+        (panel.layout->flags & NK_WINDOW_MINIMIZED))
+    {
+        nk_flags f = panel.layout->flags;
+        nk_group_scrolled_end(ctx);
+        if (f & NK_WINDOW_CLOSED)
+            return NK_WINDOW_CLOSED;
+        if (f & NK_WINDOW_MINIMIZED)
+            return NK_WINDOW_MINIMIZED;
+    }
+    return 1;
+}
+
+NK_API void
+nk_group_scrolled_end(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_panel *parent;
+    struct nk_panel *g;
+
+    struct nk_rect clip;
+    struct nk_window pan;
+    struct nk_vec2 panel_padding;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current)
+        return;
+
+    /* make sure nk_group_begin was called correctly */
+    NK_ASSERT(ctx->current);
+    win = ctx->current;
+    NK_ASSERT(win->layout);
+    g = win->layout;
+    NK_ASSERT(g->parent);
+    parent = g->parent;
+
+    /* dummy window */
+    nk_zero_struct(pan);
+    panel_padding = nk_panel_get_padding(&ctx->style, NK_PANEL_GROUP);
+    pan.bounds.y = g->bounds.y - (g->header_height + g->menu.h);
+    pan.bounds.x = g->bounds.x - panel_padding.x;
+    pan.bounds.w = g->bounds.w + 2 * panel_padding.x;
+    pan.bounds.h = g->bounds.h + g->header_height + g->menu.h;
+    if (g->flags & NK_WINDOW_BORDER) {
+        pan.bounds.x -= g->border;
+        pan.bounds.y -= g->border;
+        pan.bounds.w += 2*g->border;
+        pan.bounds.h += 2*g->border;
+    }
+    if (!(g->flags & NK_WINDOW_NO_SCROLLBAR)) {
+        pan.bounds.w += ctx->style.window.scrollbar_size.x;
+        pan.bounds.h += ctx->style.window.scrollbar_size.y;
+    }
+    pan.scrollbar.x = *g->offset_x;
+    pan.scrollbar.y = *g->offset_y;
+    pan.flags = g->flags;
+    pan.buffer = win->buffer;
+    pan.layout = g;
+    pan.parent = win;
+    ctx->current = &pan;
+
+    /* make sure group has correct clipping rectangle */
+    nk_unify(&clip, &parent->clip, pan.bounds.x, pan.bounds.y,
+        pan.bounds.x + pan.bounds.w, pan.bounds.y + pan.bounds.h + panel_padding.x);
+    nk_push_scissor(&pan.buffer, clip);
+    nk_end(ctx);
+
+    win->buffer = pan.buffer;
+    nk_push_scissor(&win->buffer, parent->clip);
+    ctx->current = win;
+    win->layout = parent;
+    g->bounds = pan.bounds;
+    return;
+}
+
+NK_API int
+nk_group_scrolled_begin(struct nk_context *ctx,
+    struct nk_scroll *scroll, const char *title, nk_flags flags)
+{return nk_group_scrolled_offset_begin(ctx, &scroll->x, &scroll->y, title, flags);}
+
+NK_API int
+nk_group_begin(struct nk_context *ctx, const char *title, nk_flags flags)
+{
+    int title_len;
+    nk_hash title_hash;
+    struct nk_window *win;
+    nk_uint *x_offset;
+    nk_uint *y_offset;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(title);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout || !title)
+        return 0;
+
+    /* find persistent group scrollbar value */
+    win = ctx->current;
+    title_len = (int)nk_strlen(title);
+    title_hash = nk_murmur_hash(title, (int)title_len, NK_PANEL_GROUP);
+    x_offset = nk_find_value(win, title_hash);
+    if (!x_offset) {
+        x_offset = nk_add_value(ctx, win, title_hash, 0);
+        y_offset = nk_add_value(ctx, win, title_hash+1, 0);
+
+        NK_ASSERT(x_offset);
+        NK_ASSERT(y_offset);
+        if (!x_offset || !y_offset) return 0;
+        *x_offset = *y_offset = 0;
+    } else y_offset = nk_find_value(win, title_hash+1);
+    return nk_group_scrolled_offset_begin(ctx, x_offset, y_offset, title, flags);
+}
+
+NK_API void
+nk_group_end(struct nk_context *ctx)
+{nk_group_scrolled_end(ctx);}
+
+NK_API int
+nk_list_view_begin(struct nk_context *ctx, struct nk_list_view *view,
+    const char *title, nk_flags flags, int row_height, int row_count)
+{
+    int title_len;
+    nk_hash title_hash;
+    nk_uint *x_offset;
+    nk_uint *y_offset;
+
+    int result;
+    struct nk_window *win;
+    struct nk_panel *layout;
+    const struct nk_style *style;
+    struct nk_vec2 item_spacing;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(view);
+    NK_ASSERT(title);
+    if (!ctx || !view || !title) return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    item_spacing = style->window.spacing;
+    row_height += NK_MAX(0, (int)item_spacing.y);
+
+    /* find persistent list view scrollbar offset */
+    title_len = (int)nk_strlen(title);
+    title_hash = nk_murmur_hash(title, (int)title_len, NK_PANEL_GROUP);
+    x_offset = nk_find_value(win, title_hash);
+    if (!x_offset) {
+        x_offset = nk_add_value(ctx, win, title_hash, 0);
+        y_offset = nk_add_value(ctx, win, title_hash+1, 0);
+
+        NK_ASSERT(x_offset);
+        NK_ASSERT(y_offset);
+        if (!x_offset || !y_offset) return 0;
+        *x_offset = *y_offset = 0;
+    } else y_offset = nk_find_value(win, title_hash+1);
+    view->scroll_value = *y_offset;
+    view->scroll_pointer = y_offset;
+
+    *y_offset = 0;
+    result = nk_group_scrolled_offset_begin(ctx, x_offset, y_offset, title, flags);
+    win = ctx->current;
+    layout = win->layout;
+
+    view->total_height = row_height * NK_MAX(row_count,1);
+    view->begin = (int)NK_MAX(((float)view->scroll_value / (float)row_height), 0.0f);
+    view->count = (int)NK_MAX(nk_iceilf((layout->clip.h)/(float)row_height), 0);
+    view->end = view->begin + view->count;
+    view->ctx = ctx;
+    return result;
+}
+
+NK_API void
+nk_list_view_end(struct nk_list_view *view)
+{
+    struct nk_context *ctx;
+    struct nk_window *win;
+    struct nk_panel *layout;
+
+    NK_ASSERT(view);
+    NK_ASSERT(view->ctx);
+    NK_ASSERT(view->scroll_pointer);
+    if (!view || !view->ctx) return;
+
+    ctx = view->ctx;
+    win = ctx->current;
+    layout = win->layout;
+    layout->at_y = layout->bounds.y + (float)view->total_height;
+    *view->scroll_pointer = *view->scroll_pointer + view->scroll_value;
+    nk_group_end(view->ctx);
+}
+
+/* --------------------------------------------------------------
+ *
+ *                          POPUP
+ *
+ * --------------------------------------------------------------*/
+NK_API int
+nk_popup_begin(struct nk_context *ctx, enum nk_popup_type type,
+    const char *title, nk_flags flags, struct nk_rect rect)
+{
+    struct nk_window *popup;
+    struct nk_window *win;
+    struct nk_panel *panel;
+
+    int title_len;
+    nk_hash title_hash;
+    nk_size allocated;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(title);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    panel = win->layout;
+    NK_ASSERT(!(panel->type & NK_PANEL_SET_POPUP) && "popups are not allowed to have popups");
+    (void)panel;
+    title_len = (int)nk_strlen(title);
+    title_hash = nk_murmur_hash(title, (int)title_len, NK_PANEL_POPUP);
+
+    popup = win->popup.win;
+    if (!popup) {
+        popup = (struct nk_window*)nk_create_window(ctx);
+        popup->parent = win;
+        win->popup.win = popup;
+        win->popup.active = 0;
+        win->popup.type = NK_PANEL_POPUP;
+    }
+
+    /* make sure we have correct popup */
+    if (win->popup.name != title_hash) {
+        if (!win->popup.active) {
+            nk_zero(popup, sizeof(*popup));
+            win->popup.name = title_hash;
+            win->popup.active = 1;
+            win->popup.type = NK_PANEL_POPUP;
+        } else return 0;
+    }
+
+    /* popup position is local to window */
+    ctx->current = popup;
+    rect.x += win->layout->clip.x;
+    rect.y += win->layout->clip.y;
+
+    /* setup popup data */
+    popup->parent = win;
+    popup->bounds = rect;
+    popup->seq = ctx->seq;
+    popup->layout = (struct nk_panel*)nk_create_panel(ctx);
+    popup->flags = flags;
+    popup->flags |= NK_WINDOW_BORDER;
+    if (type == NK_POPUP_DYNAMIC)
+        popup->flags |= NK_WINDOW_DYNAMIC;
+
+    popup->buffer = win->buffer;
+    nk_start_popup(ctx, win);
+    allocated = ctx->memory.allocated;
+    nk_push_scissor(&popup->buffer, nk_null_rect);
+
+    if (nk_panel_begin(ctx, title, NK_PANEL_POPUP)) {
+        /* popup is running therefore invalidate parent panels */
+        struct nk_panel *root;
+        root = win->layout;
+        while (root) {
+            root->flags |= NK_WINDOW_ROM;
+            root->flags &= ~(nk_flags)NK_WINDOW_REMOVE_ROM;
+            root = root->parent;
+        }
+        win->popup.active = 1;
+        popup->layout->offset_x = &popup->scrollbar.x;
+        popup->layout->offset_y = &popup->scrollbar.y;
+        popup->layout->parent = win->layout;
+        return 1;
+    } else {
+        /* popup was closed/is invalid so cleanup */
+        struct nk_panel *root;
+        root = win->layout;
+        while (root) {
+            root->flags |= NK_WINDOW_REMOVE_ROM;
+            root = root->parent;
+        }
+        win->popup.buf.active = 0;
+        win->popup.active = 0;
+        ctx->memory.allocated = allocated;
+        ctx->current = win;
+        nk_free_panel(ctx, popup->layout);
+        popup->layout = 0;
+        return 0;
+    }
+}
+
+NK_INTERN int
+nk_nonblock_begin(struct nk_context *ctx,
+    nk_flags flags, struct nk_rect body, struct nk_rect header,
+    enum nk_panel_type panel_type)
+{
+    struct nk_window *popup;
+    struct nk_window *win;
+    struct nk_panel *panel;
+    int is_active = nk_true;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    /* popups cannot have popups */
+    win = ctx->current;
+    panel = win->layout;
+    NK_ASSERT(!(panel->type & NK_PANEL_SET_POPUP));
+    (void)panel;
+    popup = win->popup.win;
+    if (!popup) {
+        /* create window for nonblocking popup */
+        popup = (struct nk_window*)nk_create_window(ctx);
+        popup->parent = win;
+        win->popup.win = popup;
+        win->popup.type = panel_type;
+        nk_command_buffer_init(&popup->buffer, &ctx->memory, NK_CLIPPING_ON);
+    } else {
+        /* close the popup if user pressed outside or in the header */
+        int pressed, in_body, in_header;
+        pressed = nk_input_is_mouse_pressed(&ctx->input, NK_BUTTON_LEFT);
+        in_body = nk_input_is_mouse_hovering_rect(&ctx->input, body);
+        in_header = nk_input_is_mouse_hovering_rect(&ctx->input, header);
+        if (pressed && (!in_body || in_header))
+            is_active = nk_false;
+    }
+    win->popup.header = header;
+
+    if (!is_active) {
+        /* remove read only mode from all parent panels */
+        struct nk_panel *root = win->layout;
+        while (root) {
+            root->flags |= NK_WINDOW_REMOVE_ROM;
+            root = root->parent;
+        }
+        return is_active;
+    }
+
+    popup->bounds = body;
+    popup->parent = win;
+    popup->layout = (struct nk_panel*)nk_create_panel(ctx);
+    popup->flags = flags;
+    popup->flags |= NK_WINDOW_BORDER;
+    popup->flags |= NK_WINDOW_DYNAMIC;
+    popup->seq = ctx->seq;
+    win->popup.active = 1;
+    NK_ASSERT(popup->layout);
+
+    nk_start_popup(ctx, win);
+    popup->buffer = win->buffer;
+    nk_push_scissor(&popup->buffer, nk_null_rect);
+    ctx->current = popup;
+
+    nk_panel_begin(ctx, 0, panel_type);
+    win->buffer = popup->buffer;
+    popup->layout->parent = win->layout;
+    popup->layout->offset_x = &popup->scrollbar.x;
+    popup->layout->offset_y = &popup->scrollbar.y;
+
+    /* set read only mode to all parent panels */
+    {struct nk_panel *root;
+    root = win->layout;
+    while (root) {
+        root->flags |= NK_WINDOW_ROM;
+        root = root->parent;
+    }}
+    return is_active;
+}
+
+NK_API void
+nk_popup_close(struct nk_context *ctx)
+{
+    struct nk_window *popup;
+    NK_ASSERT(ctx);
+    if (!ctx || !ctx->current) return;
+
+    popup = ctx->current;
+    NK_ASSERT(popup->parent);
+    NK_ASSERT(popup->layout->type & NK_PANEL_SET_POPUP);
+    popup->flags |= NK_WINDOW_HIDDEN;
+}
+
+NK_API void
+nk_popup_end(struct nk_context *ctx)
+{
+    struct nk_window *win;
+    struct nk_window *popup;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return;
+
+    popup = ctx->current;
+    if (!popup->parent) return;
+    win = popup->parent;
+    if (popup->flags & NK_WINDOW_HIDDEN) {
+        struct nk_panel *root;
+        root = win->layout;
+        while (root) {
+            root->flags |= NK_WINDOW_REMOVE_ROM;
+            root = root->parent;
+        }
+        win->popup.active = 0;
+    }
+    nk_push_scissor(&popup->buffer, nk_null_rect);
+    nk_end(ctx);
+
+    win->buffer = popup->buffer;
+    nk_finish_popup(ctx, win);
+    ctx->current = win;
+    nk_push_scissor(&win->buffer, win->layout->clip);
+}
+/* -------------------------------------------------------------
+ *
+ *                          TOOLTIP
+ *
+ * -------------------------------------------------------------- */
+NK_API int
+nk_tooltip_begin(struct nk_context *ctx, float width)
+{
+    struct nk_window *win;
+    const struct nk_input *in;
+    struct nk_rect bounds;
+    int ret;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    /* make sure that no nonblocking popup is currently active */
+    win = ctx->current;
+    in = &ctx->input;
+    if (win->popup.win && (win->popup.type & NK_PANEL_SET_NONBLOCK))
+        return 0;
+
+    bounds.w = width;
+    bounds.h = nk_null_rect.h;
+    bounds.x = (in->mouse.pos.x + 1) - win->layout->clip.x;
+    bounds.y = (in->mouse.pos.y + 1) - win->layout->clip.y;
+
+    ret = nk_popup_begin(ctx, NK_POPUP_DYNAMIC,
+        "__##Tooltip##__", NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER, bounds);
+    if (ret) win->layout->flags &= ~(nk_flags)NK_WINDOW_ROM;
+    win->popup.type = NK_PANEL_TOOLTIP;
+    ctx->current->layout->type = NK_PANEL_TOOLTIP;
+    return ret;
+}
+
+NK_API void
+nk_tooltip_end(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return;
+    ctx->current->seq--;
+    nk_popup_close(ctx);
+    nk_popup_end(ctx);
+}
+
+NK_API void
+nk_tooltip(struct nk_context *ctx, const char *text)
+{
+    const struct nk_style *style;
+    struct nk_vec2 padding;
+
+    int text_len;
+    float text_width;
+    float text_height;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    NK_ASSERT(text);
+    if (!ctx || !ctx->current || !ctx->current->layout || !text)
+        return;
+
+    /* fetch configuration data */
+    style = &ctx->style;
+    padding = style->window.padding;
+
+    /* calculate size of the text and tooltip */
+    text_len = nk_strlen(text);
+    text_width = style->font->width(style->font->userdata,
+                    style->font->height, text, text_len);
+    text_width += (4 * padding.x);
+    text_height = (style->font->height + 2 * padding.y);
+
+    /* execute tooltip and fill with text */
+    if (nk_tooltip_begin(ctx, (float)text_width)) {
+        nk_layout_row_dynamic(ctx, (float)text_height, 1);
+        nk_text(ctx, text, text_len, NK_TEXT_LEFT);
+        nk_tooltip_end(ctx);
+    }
+}
+/* -------------------------------------------------------------
+ *
+ *                          CONTEXTUAL
+ *
+ * -------------------------------------------------------------- */
+NK_API int
+nk_contextual_begin(struct nk_context *ctx, nk_flags flags, struct nk_vec2 size,
+    struct nk_rect trigger_bounds)
+{
+    struct nk_window *win;
+    struct nk_window *popup;
+    struct nk_rect body;
+
+    NK_STORAGE const struct nk_rect null_rect = {0,0,0,0};
+    int is_clicked = 0;
+    int is_active = 0;
+    int is_open = 0;
+    int ret = 0;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    ++win->popup.con_count;
+
+    /* check if currently active contextual is active */
+    popup = win->popup.win;
+    is_open = (popup && win->popup.type == NK_PANEL_CONTEXTUAL);
+    is_clicked = nk_input_mouse_clicked(&ctx->input, NK_BUTTON_RIGHT, trigger_bounds);
+    if (win->popup.active_con && win->popup.con_count != win->popup.active_con)
+        return 0;
+    if ((is_clicked && is_open && !is_active) || (!is_open && !is_active && !is_clicked))
+        return 0;
+
+    /* calculate contextual position on click */
+    win->popup.active_con = win->popup.con_count;
+    if (is_clicked) {
+        body.x = ctx->input.mouse.pos.x;
+        body.y = ctx->input.mouse.pos.y;
+    } else {
+        body.x = popup->bounds.x;
+        body.y = popup->bounds.y;
+    }
+    body.w = size.x;
+    body.h = size.y;
+
+    /* start nonblocking contextual popup */
+    ret = nk_nonblock_begin(ctx, flags|NK_WINDOW_NO_SCROLLBAR, body,
+            null_rect, NK_PANEL_CONTEXTUAL);
+    if (ret) win->popup.type = NK_PANEL_CONTEXTUAL;
+    else {
+        win->popup.active_con = 0;
+        if (win->popup.win)
+            win->popup.win->flags = 0;
+    }
+    return ret;
+}
+
+NK_API int
+nk_contextual_item_text(struct nk_context *ctx, const char *text, int len,
+    nk_flags alignment)
+{
+    struct nk_window *win;
+    const struct nk_input *in;
+    const struct nk_style *style;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    state = nk_widget_fitting(&bounds, ctx, style->contextual_button.padding);
+    if (!state) return nk_false;
+
+    in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    if (nk_do_button_text(&ctx->last_widget_state, &win->buffer, bounds,
+        text, len, alignment, NK_BUTTON_DEFAULT, &style->contextual_button, in, style->font)) {
+        nk_contextual_close(ctx);
+        return nk_true;
+    }
+    return nk_false;
+}
+
+NK_API int nk_contextual_item_label(struct nk_context *ctx, const char *label, nk_flags align)
+{return nk_contextual_item_text(ctx, label, nk_strlen(label), align);}
+
+NK_API int
+nk_contextual_item_image_text(struct nk_context *ctx, struct nk_image img,
+    const char *text, int len, nk_flags align)
+{
+    struct nk_window *win;
+    const struct nk_input *in;
+    const struct nk_style *style;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    state = nk_widget_fitting(&bounds, ctx, style->contextual_button.padding);
+    if (!state) return nk_false;
+
+    in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    if (nk_do_button_text_image(&ctx->last_widget_state, &win->buffer, bounds,
+        img, text, len, align, NK_BUTTON_DEFAULT, &style->contextual_button, style->font, in)){
+        nk_contextual_close(ctx);
+        return nk_true;
+    }
+    return nk_false;
+}
+
+NK_API int nk_contextual_item_image_label(struct nk_context *ctx, struct nk_image img,
+    const char *label, nk_flags align)
+{return nk_contextual_item_image_text(ctx, img, label, nk_strlen(label), align);}
+
+NK_API int
+nk_contextual_item_symbol_text(struct nk_context *ctx, enum nk_symbol_type symbol,
+    const char *text, int len, nk_flags align)
+{
+    struct nk_window *win;
+    const struct nk_input *in;
+    const struct nk_style *style;
+
+    struct nk_rect bounds;
+    enum nk_widget_layout_states state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    state = nk_widget_fitting(&bounds, ctx, style->contextual_button.padding);
+    if (!state) return nk_false;
+
+    in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    if (nk_do_button_text_symbol(&ctx->last_widget_state, &win->buffer, bounds,
+        symbol, text, len, align, NK_BUTTON_DEFAULT, &style->contextual_button, style->font, in)) {
+        nk_contextual_close(ctx);
+        return nk_true;
+    }
+    return nk_false;
+}
+
+NK_API int nk_contextual_item_symbol_label(struct nk_context *ctx, enum nk_symbol_type symbol,
+    const char *text, nk_flags align)
+{return nk_contextual_item_symbol_text(ctx, symbol, text, nk_strlen(text), align);}
+
+NK_API void
+nk_contextual_close(struct nk_context *ctx)
+{
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout) return;
+    nk_popup_close(ctx);
+}
+
+NK_API void
+nk_contextual_end(struct nk_context *ctx)
+{
+    struct nk_window *popup;
+    struct nk_panel *panel;
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !ctx->current) return;
+
+    popup = ctx->current;
+    panel = popup->layout;
+    NK_ASSERT(popup->parent);
+    NK_ASSERT(panel->type & NK_PANEL_SET_POPUP);
+    if (panel->flags & NK_WINDOW_DYNAMIC) {
+        /* Close behavior
+        This is a bit of a hack solution since we do not know before we end our popup
+        how big it will be. We therefore do not directly know when a
+        click outside the non-blocking popup must close it at that direct frame.
+        Instead it will be closed in the next frame.*/
+        struct nk_rect body = {0,0,0,0};
+        if (panel->at_y < (panel->bounds.y + panel->bounds.h)) {
+            struct nk_vec2 padding = nk_panel_get_padding(&ctx->style, panel->type);
+            body = panel->bounds;
+            body.y = (panel->at_y + panel->footer_height + panel->border + padding.y + panel->row.height);
+            body.h = (panel->bounds.y + panel->bounds.h) - body.y;
+        }
+        {int pressed = nk_input_is_mouse_pressed(&ctx->input, NK_BUTTON_LEFT);
+        int in_body = nk_input_is_mouse_hovering_rect(&ctx->input, body);
+        if (pressed && in_body)
+            popup->flags |= NK_WINDOW_HIDDEN;
+        }
+    }
+    if (popup->flags & NK_WINDOW_HIDDEN)
+        popup->seq = 0;
+    nk_popup_end(ctx);
+    return;
+}
+/* -------------------------------------------------------------
+ *
+ *                          COMBO
+ *
+ * --------------------------------------------------------------*/
+NK_INTERN int
+nk_combo_begin(struct nk_context *ctx, struct nk_window *win,
+    struct nk_vec2 size, int is_clicked, struct nk_rect header)
+{
+    struct nk_window *popup;
+    int is_open = 0;
+    int is_active = 0;
+    struct nk_rect body;
+    nk_hash hash;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    popup = win->popup.win;
+    body.x = header.x;
+    body.w = size.x;
+    body.y = header.y + header.h-ctx->style.window.combo_border;
+    body.h = size.y;
+
+    hash = win->popup.combo_count++;
+    is_open = (popup) ? nk_true:nk_false;
+    is_active = (popup && (win->popup.name == hash) && win->popup.type == NK_PANEL_COMBO);
+    if ((is_clicked && is_open && !is_active) || (is_open && !is_active) ||
+        (!is_open && !is_active && !is_clicked)) return 0;
+    if (!nk_nonblock_begin(ctx, 0, body,
+        (is_clicked && is_open)?nk_rect(0,0,0,0):header, NK_PANEL_COMBO)) return 0;
+
+    win->popup.type = NK_PANEL_COMBO;
+    win->popup.name = hash;
+    return 1;
+}
+
+NK_API int
+nk_combo_begin_text(struct nk_context *ctx, const char *selected, int len,
+    struct nk_vec2 size)
+{
+    const struct nk_input *in;
+    struct nk_window *win;
+    struct nk_style *style;
+
+    enum nk_widget_layout_states s;
+    int is_clicked = nk_false;
+    struct nk_rect header;
+    const struct nk_style_item *background;
+    struct nk_text text;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(selected);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout || !selected)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    s = nk_widget(&header, ctx);
+    if (s == NK_WIDGET_INVALID)
+        return 0;
+
+    in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input;
+    if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT))
+        is_clicked = nk_true;
+
+    /* draw combo box header background and border */
+    if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->combo.active;
+        text.text = style->combo.label_active;
+    } else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) {
+        background = &style->combo.hover;
+        text.text = style->combo.label_hover;
+    } else {
+        background = &style->combo.normal;
+        text.text = style->combo.label_normal;
+    }
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        text.background = nk_rgba(0,0,0,0);
+        nk_draw_image(&win->buffer, header, &background->data.image, nk_white);
+    } else {
+        text.background = background->data.color;
+        nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color);
+        nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color);
+    }
+    {
+        /* print currently selected text item */
+        struct nk_rect label;
+        struct nk_rect button;
+        struct nk_rect content;
+
+        enum nk_symbol_type sym;
+        if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER)
+            sym = style->combo.sym_hover;
+        else if (is_clicked)
+            sym = style->combo.sym_active;
+        else sym = style->combo.sym_normal;
+
+        /* calculate button */
+        button.w = header.h - 2 * style->combo.button_padding.y;
+        button.x = (header.x + header.w - header.h) - style->combo.button_padding.x;
+        button.y = header.y + style->combo.button_padding.y;
+        button.h = button.w;
+
+        content.x = button.x + style->combo.button.padding.x;
+        content.y = button.y + style->combo.button.padding.y;
+        content.w = button.w - 2 * style->combo.button.padding.x;
+        content.h = button.h - 2 * style->combo.button.padding.y;
+
+        /* draw selected label */
+        text.padding = nk_vec2(0,0);
+        label.x = header.x + style->combo.content_padding.x;
+        label.y = header.y + style->combo.content_padding.y;
+        label.w = button.x - (style->combo.content_padding.x + style->combo.spacing.x) - label.x;;
+        label.h = header.h - 2 * style->combo.content_padding.y;
+        nk_widget_text(&win->buffer, label, selected, len, &text,
+            NK_TEXT_LEFT, ctx->style.font);
+
+        /* draw open/close button */
+        nk_draw_button_symbol(&win->buffer, &button, &content, ctx->last_widget_state,
+            &ctx->style.combo.button, sym, style->font);
+    }
+    return nk_combo_begin(ctx, win, size, is_clicked, header);
+}
+
+NK_API int nk_combo_begin_label(struct nk_context *ctx, const char *selected, struct nk_vec2 size)
+{return nk_combo_begin_text(ctx, selected, nk_strlen(selected), size);}
+
+NK_API int
+nk_combo_begin_color(struct nk_context *ctx, struct nk_color color, struct nk_vec2 size)
+{
+    struct nk_window *win;
+    struct nk_style *style;
+    const struct nk_input *in;
+
+    struct nk_rect header;
+    int is_clicked = nk_false;
+    enum nk_widget_layout_states s;
+    const struct nk_style_item *background;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    s = nk_widget(&header, ctx);
+    if (s == NK_WIDGET_INVALID)
+        return 0;
+
+    in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input;
+    if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT))
+        is_clicked = nk_true;
+
+    /* draw combo box header background and border */
+    if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED)
+        background = &style->combo.active;
+    else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER)
+        background = &style->combo.hover;
+    else background = &style->combo.normal;
+
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        nk_draw_image(&win->buffer, header, &background->data.image,nk_white);
+    } else {
+        nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color);
+        nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color);
+    }
+    {
+        struct nk_rect content;
+        struct nk_rect button;
+        struct nk_rect bounds;
+
+        enum nk_symbol_type sym;
+        if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER)
+            sym = style->combo.sym_hover;
+        else if (is_clicked)
+            sym = style->combo.sym_active;
+        else sym = style->combo.sym_normal;
+
+        /* calculate button */
+        button.w = header.h - 2 * style->combo.button_padding.y;
+        button.x = (header.x + header.w - header.h) - style->combo.button_padding.x;
+        button.y = header.y + style->combo.button_padding.y;
+        button.h = button.w;
+
+        content.x = button.x + style->combo.button.padding.x;
+        content.y = button.y + style->combo.button.padding.y;
+        content.w = button.w - 2 * style->combo.button.padding.x;
+        content.h = button.h - 2 * style->combo.button.padding.y;
+
+        /* draw color */
+        bounds.h = header.h - 4 * style->combo.content_padding.y;
+        bounds.y = header.y + 2 * style->combo.content_padding.y;
+        bounds.x = header.x + 2 * style->combo.content_padding.x;
+        bounds.w = (button.x - (style->combo.content_padding.x + style->combo.spacing.x)) - bounds.x;
+        nk_fill_rect(&win->buffer, bounds, 0, color);
+
+        /* draw open/close button */
+        nk_draw_button_symbol(&win->buffer, &button, &content, ctx->last_widget_state,
+            &ctx->style.combo.button, sym, style->font);
+    }
+    return nk_combo_begin(ctx, win, size, is_clicked, header);
+}
+
+NK_API int
+nk_combo_begin_symbol(struct nk_context *ctx, enum nk_symbol_type symbol, struct nk_vec2 size)
+{
+    struct nk_window *win;
+    struct nk_style *style;
+    const struct nk_input *in;
+
+    struct nk_rect header;
+    int is_clicked = nk_false;
+    enum nk_widget_layout_states s;
+    const struct nk_style_item *background;
+    struct nk_color sym_background;
+    struct nk_color symbol_color;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    s = nk_widget(&header, ctx);
+    if (s == NK_WIDGET_INVALID)
+        return 0;
+
+    in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input;
+    if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT))
+        is_clicked = nk_true;
+
+    /* draw combo box header background and border */
+    if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->combo.active;
+        symbol_color = style->combo.symbol_active;
+    } else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) {
+        background = &style->combo.hover;
+        symbol_color = style->combo.symbol_hover;
+    } else {
+        background = &style->combo.normal;
+        symbol_color = style->combo.symbol_hover;
+    }
+
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        sym_background = nk_rgba(0,0,0,0);
+        nk_draw_image(&win->buffer, header, &background->data.image, nk_white);
+    } else {
+        sym_background = background->data.color;
+        nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color);
+        nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color);
+    }
+    {
+        struct nk_rect bounds = {0,0,0,0};
+        struct nk_rect content;
+        struct nk_rect button;
+
+        enum nk_symbol_type sym;
+        if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER)
+            sym = style->combo.sym_hover;
+        else if (is_clicked)
+            sym = style->combo.sym_active;
+        else sym = style->combo.sym_normal;
+
+        /* calculate button */
+        button.w = header.h - 2 * style->combo.button_padding.y;
+        button.x = (header.x + header.w - header.h) - style->combo.button_padding.y;
+        button.y = header.y + style->combo.button_padding.y;
+        button.h = button.w;
+
+        content.x = button.x + style->combo.button.padding.x;
+        content.y = button.y + style->combo.button.padding.y;
+        content.w = button.w - 2 * style->combo.button.padding.x;
+        content.h = button.h - 2 * style->combo.button.padding.y;
+
+        /* draw symbol */
+        bounds.h = header.h - 2 * style->combo.content_padding.y;
+        bounds.y = header.y + style->combo.content_padding.y;
+        bounds.x = header.x + style->combo.content_padding.x;
+        bounds.w = (button.x - style->combo.content_padding.y) - bounds.x;
+        nk_draw_symbol(&win->buffer, symbol, bounds, sym_background, symbol_color,
+            1.0f, style->font);
+
+        /* draw open/close button */
+        nk_draw_button_symbol(&win->buffer, &bounds, &content, ctx->last_widget_state,
+            &ctx->style.combo.button, sym, style->font);
+    }
+    return nk_combo_begin(ctx, win, size, is_clicked, header);
+}
+
+NK_API int
+nk_combo_begin_symbol_text(struct nk_context *ctx, const char *selected, int len,
+    enum nk_symbol_type symbol, struct nk_vec2 size)
+{
+    struct nk_window *win;
+    struct nk_style *style;
+    struct nk_input *in;
+
+    struct nk_rect header;
+    int is_clicked = nk_false;
+    enum nk_widget_layout_states s;
+    const struct nk_style_item *background;
+    struct nk_color symbol_color;
+    struct nk_text text;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    s = nk_widget(&header, ctx);
+    if (!s) return 0;
+
+    in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input;
+    if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT))
+        is_clicked = nk_true;
+
+    /* draw combo box header background and border */
+    if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->combo.active;
+        symbol_color = style->combo.symbol_active;
+        text.text = style->combo.label_active;
+    } else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) {
+        background = &style->combo.hover;
+        symbol_color = style->combo.symbol_hover;
+        text.text = style->combo.label_hover;
+    } else {
+        background = &style->combo.normal;
+        symbol_color = style->combo.symbol_normal;
+        text.text = style->combo.label_normal;
+    }
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        text.background = nk_rgba(0,0,0,0);
+        nk_draw_image(&win->buffer, header, &background->data.image, nk_white);
+    } else {
+        text.background = background->data.color;
+        nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color);
+        nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color);
+    }
+    {
+        struct nk_rect content;
+        struct nk_rect button;
+        struct nk_rect label;
+        struct nk_rect image;
+
+        enum nk_symbol_type sym;
+        if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER)
+            sym = style->combo.sym_hover;
+        else if (is_clicked)
+            sym = style->combo.sym_active;
+        else sym = style->combo.sym_normal;
+
+        /* calculate button */
+        button.w = header.h - 2 * style->combo.button_padding.y;
+        button.x = (header.x + header.w - header.h) - style->combo.button_padding.x;
+        button.y = header.y + style->combo.button_padding.y;
+        button.h = button.w;
+
+        content.x = button.x + style->combo.button.padding.x;
+        content.y = button.y + style->combo.button.padding.y;
+        content.w = button.w - 2 * style->combo.button.padding.x;
+        content.h = button.h - 2 * style->combo.button.padding.y;
+        nk_draw_button_symbol(&win->buffer, &button, &content, ctx->last_widget_state,
+            &ctx->style.combo.button, sym, style->font);
+
+        /* draw symbol */
+        image.x = header.x + style->combo.content_padding.x;
+        image.y = header.y + style->combo.content_padding.y;
+        image.h = header.h - 2 * style->combo.content_padding.y;
+        image.w = image.h;
+        nk_draw_symbol(&win->buffer, symbol, image, text.background, symbol_color,
+            1.0f, style->font);
+
+        /* draw label */
+        text.padding = nk_vec2(0,0);
+        label.x = image.x + image.w + style->combo.spacing.x + style->combo.content_padding.x;
+        label.y = header.y + style->combo.content_padding.y;
+        label.w = (button.x - style->combo.content_padding.x) - label.x;
+        label.h = header.h - 2 * style->combo.content_padding.y;
+        nk_widget_text(&win->buffer, label, selected, len, &text, NK_TEXT_LEFT, style->font);
+    }
+    return nk_combo_begin(ctx, win, size, is_clicked, header);
+}
+
+NK_API int
+nk_combo_begin_image(struct nk_context *ctx, struct nk_image img, struct nk_vec2 size)
+{
+    struct nk_window *win;
+    struct nk_style *style;
+    const struct nk_input *in;
+
+    struct nk_rect header;
+    int is_clicked = nk_false;
+    enum nk_widget_layout_states s;
+    const struct nk_style_item *background;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    s = nk_widget(&header, ctx);
+    if (s == NK_WIDGET_INVALID)
+        return 0;
+
+    in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input;
+    if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT))
+        is_clicked = nk_true;
+
+    /* draw combo box header background and border */
+    if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED)
+        background = &style->combo.active;
+    else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER)
+        background = &style->combo.hover;
+    else background = &style->combo.normal;
+
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        nk_draw_image(&win->buffer, header, &background->data.image, nk_white);
+    } else {
+        nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color);
+        nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color);
+    }
+    {
+        struct nk_rect bounds = {0,0,0,0};
+        struct nk_rect content;
+        struct nk_rect button;
+
+        enum nk_symbol_type sym;
+        if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER)
+            sym = style->combo.sym_hover;
+        else if (is_clicked)
+            sym = style->combo.sym_active;
+        else sym = style->combo.sym_normal;
+
+        /* calculate button */
+        button.w = header.h - 2 * style->combo.button_padding.y;
+        button.x = (header.x + header.w - header.h) - style->combo.button_padding.y;
+        button.y = header.y + style->combo.button_padding.y;
+        button.h = button.w;
+
+        content.x = button.x + style->combo.button.padding.x;
+        content.y = button.y + style->combo.button.padding.y;
+        content.w = button.w - 2 * style->combo.button.padding.x;
+        content.h = button.h - 2 * style->combo.button.padding.y;
+
+        /* draw image */
+        bounds.h = header.h - 2 * style->combo.content_padding.y;
+        bounds.y = header.y + style->combo.content_padding.y;
+        bounds.x = header.x + style->combo.content_padding.x;
+        bounds.w = (button.x - style->combo.content_padding.y) - bounds.x;
+        nk_draw_image(&win->buffer, bounds, &img, nk_white);
+
+        /* draw open/close button */
+        nk_draw_button_symbol(&win->buffer, &bounds, &content, ctx->last_widget_state,
+            &ctx->style.combo.button, sym, style->font);
+    }
+    return nk_combo_begin(ctx, win, size, is_clicked, header);
+}
+
+NK_API int
+nk_combo_begin_image_text(struct nk_context *ctx, const char *selected, int len,
+    struct nk_image img, struct nk_vec2 size)
+{
+    struct nk_window *win;
+    struct nk_style *style;
+    struct nk_input *in;
+
+    struct nk_rect header;
+    int is_clicked = nk_false;
+    enum nk_widget_layout_states s;
+    const struct nk_style_item *background;
+    struct nk_text text;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    style = &ctx->style;
+    s = nk_widget(&header, ctx);
+    if (!s) return 0;
+
+    in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input;
+    if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT))
+        is_clicked = nk_true;
+
+    /* draw combo box header background and border */
+    if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) {
+        background = &style->combo.active;
+        text.text = style->combo.label_active;
+    } else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) {
+        background = &style->combo.hover;
+        text.text = style->combo.label_hover;
+    } else {
+        background = &style->combo.normal;
+        text.text = style->combo.label_normal;
+    }
+    if (background->type == NK_STYLE_ITEM_IMAGE) {
+        text.background = nk_rgba(0,0,0,0);
+        nk_draw_image(&win->buffer, header, &background->data.image, nk_white);
+    } else {
+        text.background = background->data.color;
+        nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color);
+        nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color);
+    }
+    {
+        struct nk_rect content;
+        struct nk_rect button;
+        struct nk_rect label;
+        struct nk_rect image;
+
+        enum nk_symbol_type sym;
+        if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER)
+            sym = style->combo.sym_hover;
+        else if (is_clicked)
+            sym = style->combo.sym_active;
+        else sym = style->combo.sym_normal;
+
+        /* calculate button */
+        button.w = header.h - 2 * style->combo.button_padding.y;
+        button.x = (header.x + header.w - header.h) - style->combo.button_padding.x;
+        button.y = header.y + style->combo.button_padding.y;
+        button.h = button.w;
+
+        content.x = button.x + style->combo.button.padding.x;
+        content.y = button.y + style->combo.button.padding.y;
+        content.w = button.w - 2 * style->combo.button.padding.x;
+        content.h = button.h - 2 * style->combo.button.padding.y;
+        nk_draw_button_symbol(&win->buffer, &button, &content, ctx->last_widget_state,
+            &ctx->style.combo.button, sym, style->font);
+
+        /* draw image */
+        image.x = header.x + style->combo.content_padding.x;
+        image.y = header.y + style->combo.content_padding.y;
+        image.h = header.h - 2 * style->combo.content_padding.y;
+        image.w = image.h;
+        nk_draw_image(&win->buffer, image, &img, nk_white);
+
+        /* draw label */
+        text.padding = nk_vec2(0,0);
+        label.x = image.x + image.w + style->combo.spacing.x + style->combo.content_padding.x;
+        label.y = header.y + style->combo.content_padding.y;
+        label.w = (button.x - style->combo.content_padding.x) - label.x;
+        label.h = header.h - 2 * style->combo.content_padding.y;
+        nk_widget_text(&win->buffer, label, selected, len, &text, NK_TEXT_LEFT, style->font);
+    }
+    return nk_combo_begin(ctx, win, size, is_clicked, header);
+}
+
+NK_API int nk_combo_begin_symbol_label(struct nk_context *ctx,
+    const char *selected, enum nk_symbol_type type, struct nk_vec2 size)
+{return nk_combo_begin_symbol_text(ctx, selected, nk_strlen(selected), type, size);}
+
+NK_API int nk_combo_begin_image_label(struct nk_context *ctx,
+    const char *selected, struct nk_image img, struct nk_vec2 size)
+{return nk_combo_begin_image_text(ctx, selected, nk_strlen(selected), img, size);}
+
+NK_API int nk_combo_item_text(struct nk_context *ctx, const char *text, int len,nk_flags align)
+{return nk_contextual_item_text(ctx, text, len, align);}
+
+NK_API int nk_combo_item_label(struct nk_context *ctx, const char *label, nk_flags align)
+{return nk_contextual_item_label(ctx, label, align);}
+
+NK_API int nk_combo_item_image_text(struct nk_context *ctx, struct nk_image img, const char *text,
+    int len, nk_flags alignment)
+{return nk_contextual_item_image_text(ctx, img, text, len, alignment);}
+
+NK_API int nk_combo_item_image_label(struct nk_context *ctx, struct nk_image img,
+    const char *text, nk_flags alignment)
+{return nk_contextual_item_image_label(ctx, img, text, alignment);}
+
+NK_API int nk_combo_item_symbol_text(struct nk_context *ctx, enum nk_symbol_type sym,
+    const char *text, int len, nk_flags alignment)
+{return nk_contextual_item_symbol_text(ctx, sym, text, len, alignment);}
+
+NK_API int nk_combo_item_symbol_label(struct nk_context *ctx, enum nk_symbol_type sym,
+    const char *label, nk_flags alignment)
+{return nk_contextual_item_symbol_label(ctx, sym, label, alignment);}
+
+NK_API void nk_combo_end(struct nk_context *ctx)
+{nk_contextual_end(ctx);}
+
+NK_API void nk_combo_close(struct nk_context *ctx)
+{nk_contextual_close(ctx);}
+
+NK_API int
+nk_combo(struct nk_context *ctx, const char **items, int count,
+    int selected, int item_height, struct nk_vec2 size)
+{
+    int i = 0;
+    int max_height;
+    struct nk_vec2 item_spacing;
+    struct nk_vec2 window_padding;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(items);
+    NK_ASSERT(ctx->current);
+    if (!ctx || !items ||!count)
+        return selected;
+
+    item_spacing = ctx->style.window.spacing;
+    window_padding = nk_panel_get_padding(&ctx->style, ctx->current->layout->type);
+    max_height = count * item_height + count * (int)item_spacing.y;
+    max_height += (int)item_spacing.y * 2 + (int)window_padding.y * 2;
+    size.y = NK_MIN(size.y, (float)max_height);
+    if (nk_combo_begin_label(ctx, items[selected], size)) {
+        nk_layout_row_dynamic(ctx, (float)item_height, 1);
+        for (i = 0; i < count; ++i) {
+            if (nk_combo_item_label(ctx, items[i], NK_TEXT_LEFT))
+                selected = i;
+        }
+        nk_combo_end(ctx);
+    }
+    return selected;
+}
+
+NK_API int
+nk_combo_separator(struct nk_context *ctx, const char *items_separated_by_separator,
+    int separator, int selected, int count, int item_height, struct nk_vec2 size)
+{
+    int i;
+    int max_height;
+    struct nk_vec2 item_spacing;
+    struct nk_vec2 window_padding;
+    const char *current_item;
+    const char *iter;
+    int length = 0;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(items_separated_by_separator);
+    if (!ctx || !items_separated_by_separator)
+        return selected;
+
+    /* calculate popup window */
+    item_spacing = ctx->style.window.spacing;
+    window_padding = nk_panel_get_padding(&ctx->style, ctx->current->layout->type);
+    max_height = count * item_height + count * (int)item_spacing.y;
+    max_height += (int)item_spacing.y * 2 + (int)window_padding.y * 2;
+    size.y = NK_MIN(size.y, (float)max_height);
+
+    /* find selected item */
+    current_item = items_separated_by_separator;
+    for (i = 0; i < count; ++i) {
+        iter = current_item;
+        while (*iter && *iter != separator) iter++;
+        length = (int)(iter - current_item);
+        if (i == selected) break;
+        current_item = iter + 1;
+    }
+
+    if (nk_combo_begin_text(ctx, current_item, length, size)) {
+        current_item = items_separated_by_separator;
+        nk_layout_row_dynamic(ctx, (float)item_height, 1);
+        for (i = 0; i < count; ++i) {
+            iter = current_item;
+            while (*iter && *iter != separator) iter++;
+            length = (int)(iter - current_item);
+            if (nk_combo_item_text(ctx, current_item, length, NK_TEXT_LEFT))
+                selected = i;
+            current_item = current_item + length + 1;
+        }
+        nk_combo_end(ctx);
+    }
+    return selected;
+}
+
+NK_API int
+nk_combo_string(struct nk_context *ctx, const char *items_separated_by_zeros,
+    int selected, int count, int item_height, struct nk_vec2 size)
+{return nk_combo_separator(ctx, items_separated_by_zeros, '\0', selected, count, item_height, size);}
+
+NK_API int
+nk_combo_callback(struct nk_context *ctx, void(*item_getter)(void*, int, const char**),
+    void *userdata, int selected, int count, int item_height, struct nk_vec2 size)
+{
+    int i;
+    int max_height;
+    struct nk_vec2 item_spacing;
+    struct nk_vec2 window_padding;
+    const char *item;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(item_getter);
+    if (!ctx || !item_getter)
+        return selected;
+
+    /* calculate popup window */
+    item_spacing = ctx->style.window.spacing;
+    window_padding = nk_panel_get_padding(&ctx->style, ctx->current->layout->type);
+    max_height = count * item_height + count * (int)item_spacing.y;
+    max_height += (int)item_spacing.y * 2 + (int)window_padding.y * 2;
+    size.y = NK_MIN(size.y, (float)max_height);
+
+    item_getter(userdata, selected, &item);
+    if (nk_combo_begin_label(ctx, item, size)) {
+        nk_layout_row_dynamic(ctx, (float)item_height, 1);
+        for (i = 0; i < count; ++i) {
+            item_getter(userdata, i, &item);
+            if (nk_combo_item_label(ctx, item, NK_TEXT_LEFT))
+                selected = i;
+        }
+        nk_combo_end(ctx);
+    }
+    return selected;
+}
+
+NK_API void nk_combobox(struct nk_context *ctx, const char **items, int count,
+    int *selected, int item_height, struct nk_vec2 size)
+{*selected = nk_combo(ctx, items, count, *selected, item_height, size);}
+
+NK_API void nk_combobox_string(struct nk_context *ctx, const char *items_separated_by_zeros,
+    int *selected, int count, int item_height, struct nk_vec2 size)
+{*selected = nk_combo_string(ctx, items_separated_by_zeros, *selected, count, item_height, size);}
+
+NK_API void nk_combobox_separator(struct nk_context *ctx, const char *items_separated_by_separator,
+    int separator,int *selected, int count, int item_height, struct nk_vec2 size)
+{*selected = nk_combo_separator(ctx, items_separated_by_separator, separator,
+    *selected, count, item_height, size);}
+
+NK_API void nk_combobox_callback(struct nk_context *ctx,
+    void(*item_getter)(void* data, int id, const char **out_text),
+    void *userdata, int *selected, int count, int item_height, struct nk_vec2 size)
+{*selected = nk_combo_callback(ctx, item_getter, userdata,  *selected, count, item_height, size);}
+
+/*
+ * -------------------------------------------------------------
+ *
+ *                          MENU
+ *
+ * --------------------------------------------------------------
+ */
+NK_INTERN int
+nk_menu_begin(struct nk_context *ctx, struct nk_window *win,
+    const char *id, int is_clicked, struct nk_rect header, struct nk_vec2 size)
+{
+    int is_open = 0;
+    int is_active = 0;
+    struct nk_rect body;
+    struct nk_window *popup;
+    nk_hash hash = nk_murmur_hash(id, (int)nk_strlen(id), NK_PANEL_MENU);
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    body.x = header.x;
+    body.w = size.x;
+    body.y = header.y + header.h;
+    body.h = size.y;
+
+    popup = win->popup.win;
+    is_open = popup ? nk_true : nk_false;
+    is_active = (popup && (win->popup.name == hash) && win->popup.type == NK_PANEL_MENU);
+    if ((is_clicked && is_open && !is_active) || (is_open && !is_active) ||
+        (!is_open && !is_active && !is_clicked)) return 0;
+    if (!nk_nonblock_begin(ctx, NK_WINDOW_NO_SCROLLBAR, body, header, NK_PANEL_MENU))
+        return 0;
+
+    win->popup.type = NK_PANEL_MENU;
+    win->popup.name = hash;
+    return 1;
+}
+
+NK_API int
+nk_menu_begin_text(struct nk_context *ctx, const char *title, int len,
+    nk_flags align, struct nk_vec2 size)
+{
+    struct nk_window *win;
+    const struct nk_input *in;
+    struct nk_rect header;
+    int is_clicked = nk_false;
+    nk_flags state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    state = nk_widget(&header, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || win->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    if (nk_do_button_text(&ctx->last_widget_state, &win->buffer, header,
+        title, len, align, NK_BUTTON_DEFAULT, &ctx->style.menu_button, in, ctx->style.font))
+        is_clicked = nk_true;
+    return nk_menu_begin(ctx, win, title, is_clicked, header, size);
+}
+
+NK_API int nk_menu_begin_label(struct nk_context *ctx,
+    const char *text, nk_flags align, struct nk_vec2 size)
+{return nk_menu_begin_text(ctx, text, nk_strlen(text), align, size);}
+
+NK_API int
+nk_menu_begin_image(struct nk_context *ctx, const char *id, struct nk_image img,
+    struct nk_vec2 size)
+{
+    struct nk_window *win;
+    struct nk_rect header;
+    const struct nk_input *in;
+    int is_clicked = nk_false;
+    nk_flags state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    state = nk_widget(&header, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    if (nk_do_button_image(&ctx->last_widget_state, &win->buffer, header,
+        img, NK_BUTTON_DEFAULT, &ctx->style.menu_button, in))
+        is_clicked = nk_true;
+    return nk_menu_begin(ctx, win, id, is_clicked, header, size);
+}
+
+NK_API int
+nk_menu_begin_symbol(struct nk_context *ctx, const char *id,
+    enum nk_symbol_type sym, struct nk_vec2 size)
+{
+    struct nk_window *win;
+    const struct nk_input *in;
+    struct nk_rect header;
+    int is_clicked = nk_false;
+    nk_flags state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    state = nk_widget(&header, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    if (nk_do_button_symbol(&ctx->last_widget_state,  &win->buffer, header,
+        sym, NK_BUTTON_DEFAULT, &ctx->style.menu_button, in, ctx->style.font))
+        is_clicked = nk_true;
+    return nk_menu_begin(ctx, win, id, is_clicked, header, size);
+}
+
+NK_API int
+nk_menu_begin_image_text(struct nk_context *ctx, const char *title, int len,
+    nk_flags align, struct nk_image img, struct nk_vec2 size)
+{
+    struct nk_window *win;
+    struct nk_rect header;
+    const struct nk_input *in;
+    int is_clicked = nk_false;
+    nk_flags state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    state = nk_widget(&header, ctx);
+    if (!state) return 0;
+    in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    if (nk_do_button_text_image(&ctx->last_widget_state, &win->buffer,
+        header, img, title, len, align, NK_BUTTON_DEFAULT, &ctx->style.menu_button,
+        ctx->style.font, in))
+        is_clicked = nk_true;
+    return nk_menu_begin(ctx, win, title, is_clicked, header, size);
+}
+
+NK_API int nk_menu_begin_image_label(struct nk_context *ctx,
+    const char *title, nk_flags align, struct nk_image img, struct nk_vec2 size)
+{return nk_menu_begin_image_text(ctx, title, nk_strlen(title), align, img, size);}
+
+NK_API int
+nk_menu_begin_symbol_text(struct nk_context *ctx, const char *title, int len,
+    nk_flags align, enum nk_symbol_type sym, struct nk_vec2 size)
+{
+    struct nk_window *win;
+    struct nk_rect header;
+    const struct nk_input *in;
+    int is_clicked = nk_false;
+    nk_flags state;
+
+    NK_ASSERT(ctx);
+    NK_ASSERT(ctx->current);
+    NK_ASSERT(ctx->current->layout);
+    if (!ctx || !ctx->current || !ctx->current->layout)
+        return 0;
+
+    win = ctx->current;
+    state = nk_widget(&header, ctx);
+    if (!state) return 0;
+
+    in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
+    if (nk_do_button_text_symbol(&ctx->last_widget_state, &win->buffer,
+        header, sym, title, len, align, NK_BUTTON_DEFAULT, &ctx->style.menu_button,
+        ctx->style.font, in)) is_clicked = nk_true;
+    return nk_menu_begin(ctx, win, title, is_clicked, header, size);
+}
+
+NK_API int nk_menu_begin_symbol_label(struct nk_context *ctx,
+    const char *title, nk_flags align, enum nk_symbol_type sym, struct nk_vec2 size )
+{return nk_menu_begin_symbol_text(ctx, title, nk_strlen(title), align,sym,size);}
+
+NK_API int nk_menu_item_text(struct nk_context *ctx, const char *title, int len, nk_flags align)
+{return nk_contextual_item_text(ctx, title, len, align);}
+
+NK_API int nk_menu_item_label(struct nk_context *ctx, const char *label, nk_flags align)
+{return nk_contextual_item_label(ctx, label, align);}
+
+NK_API int nk_menu_item_image_label(struct nk_context *ctx, struct nk_image img,
+    const char *label, nk_flags align)
+{return nk_contextual_item_image_label(ctx, img, label, align);}
+
+NK_API int nk_menu_item_image_text(struct nk_context *ctx, struct nk_image img,
+    const char *text, int len, nk_flags align)
+{return nk_contextual_item_image_text(ctx, img, text, len, align);}
+
+NK_API int nk_menu_item_symbol_text(struct nk_context *ctx, enum nk_symbol_type sym,
+    const char *text, int len, nk_flags align)
+{return nk_contextual_item_symbol_text(ctx, sym, text, len, align);}
+
+NK_API int nk_menu_item_symbol_label(struct nk_context *ctx, enum nk_symbol_type sym,
+    const char *label, nk_flags align)
+{return nk_contextual_item_symbol_label(ctx, sym, label, align);}
+
+NK_API void nk_menu_close(struct nk_context *ctx)
+{nk_contextual_close(ctx);}
+
+NK_API void
+nk_menu_end(struct nk_context *ctx)
+{nk_contextual_end(ctx);}
+
+#endif /* NK_IMPLEMENTATION */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/nuklear_sdl_gles2.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,454 @@
+/*
+ * Nuklear - 1.40.8 - public domain
+ * no warrenty implied; use at your own risk.
+ * authored from 2015-2017 by Micha Mettke
+ * emscripten from 2016 by Chris Willcocks
+ * OpenGL ES 2.0 from 2017 by Dmitry Hrabrov a.k.a. DeXPeriX
+ */
+/*
+ * ==============================================================
+ *
+ *                              API
+ *
+ * ===============================================================
+ */
+#ifndef NK_SDL_GLES2_H_
+#define NK_SDL_GLES2_H_
+
+#include <SDL.h>
+#ifndef DISABLE_OPENGL
+#ifdef USE_GLES
+#include <SDL_opengles2.h>
+#else
+#include <GL/glew.h>
+#endif
+#endif
+
+
+NK_API struct nk_context*   nk_sdl_init(SDL_Window *win);
+NK_API void                 nk_sdl_font_stash_begin(struct nk_font_atlas **atlas);
+NK_API void                 nk_sdl_font_stash_end(void);
+NK_API int                  nk_sdl_handle_event(SDL_Event *evt);
+NK_API void                 nk_sdl_render(enum nk_anti_aliasing , int max_vertex_buffer, int max_element_buffer);
+NK_API void                 nk_sdl_shutdown(void);
+NK_API void                 nk_sdl_device_destroy(void);
+NK_API void                 nk_sdl_device_create(void);
+
+#endif
+
+/*
+ * ==============================================================
+ *
+ *                          IMPLEMENTATION
+ *
+ * ===============================================================
+ */
+#ifdef NK_SDL_GLES2_IMPLEMENTATION
+
+#include <string.h>
+
+struct nk_sdl_device {
+    struct nk_buffer cmds;
+    struct nk_draw_null_texture null;
+    GLuint vbo, ebo;
+    GLuint prog;
+    GLuint vert_shdr;
+    GLuint frag_shdr;
+    GLint attrib_pos;
+    GLint attrib_uv;
+    GLint attrib_col;
+    GLint uniform_tex;
+    GLint uniform_proj;
+    GLuint font_tex;
+    GLsizei vs;
+    size_t vp, vt, vc;
+};
+
+struct nk_sdl_vertex {
+    GLfloat position[2];
+    GLfloat uv[2];
+    nk_byte col[4];
+};
+
+static struct nk_sdl {
+    SDL_Window *win;
+    struct nk_sdl_device ogl;
+    struct nk_context ctx;
+    struct nk_font_atlas atlas;
+} sdl;
+
+#ifdef USE_GLES
+#define NK_SHADER_VERSION "#version 100\n"
+#define DECLARE_PRECISION "precision mediump float;\n"
+#else
+#define NK_SHADER_VERSION "#version 110\n"
+#define DECLARE_PRECISION
+#endif
+
+
+NK_API void
+nk_sdl_device_create(void)
+{
+    GLint status;
+    static const GLchar *vertex_shader =
+        NK_SHADER_VERSION
+        "uniform mat4 ProjMtx;\n"
+        "attribute vec2 Position;\n"
+        "attribute vec2 TexCoord;\n"
+        "attribute vec4 Color;\n"
+        "varying vec2 Frag_UV;\n"
+        "varying vec4 Frag_Color;\n"
+        "void main() {\n"
+        "   Frag_UV = TexCoord;\n"
+        "   Frag_Color = Color;\n"
+        "   gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n"
+        "}\n";
+    static const GLchar *fragment_shader =
+        NK_SHADER_VERSION
+        DECLARE_PRECISION
+        "uniform sampler2D Texture;\n"
+        "varying vec2 Frag_UV;\n"
+        "varying vec4 Frag_Color;\n"
+        "void main(){\n"
+        "   gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV);\n"
+        "}\n";
+
+    struct nk_sdl_device *dev = &sdl.ogl;
+    
+    nk_buffer_init_default(&dev->cmds);
+    dev->prog = glCreateProgram();
+    dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER);
+    dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER);
+    glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0);
+    glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0);
+    glCompileShader(dev->vert_shdr);
+    glCompileShader(dev->frag_shdr);
+    glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status);
+    assert(status == GL_TRUE);
+    glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status);
+    assert(status == GL_TRUE);
+    glAttachShader(dev->prog, dev->vert_shdr);
+    glAttachShader(dev->prog, dev->frag_shdr);
+    glLinkProgram(dev->prog);
+    glGetProgramiv(dev->prog, GL_LINK_STATUS, &status);
+    assert(status == GL_TRUE);
+
+
+    dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture");
+    dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx");
+    dev->attrib_pos = glGetAttribLocation(dev->prog, "Position");
+    dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord");
+    dev->attrib_col = glGetAttribLocation(dev->prog, "Color");
+    {
+        dev->vs = sizeof(struct nk_sdl_vertex);
+        dev->vp = offsetof(struct nk_sdl_vertex, position);
+        dev->vt = offsetof(struct nk_sdl_vertex, uv);
+        dev->vc = offsetof(struct nk_sdl_vertex, col);
+        
+        /* Allocate buffers */
+        glGenBuffers(1, &dev->vbo);
+        glGenBuffers(1, &dev->ebo);
+    }
+    glBindTexture(GL_TEXTURE_2D, 0);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+NK_INTERN void
+nk_sdl_device_upload_atlas(const void *image, int width, int height)
+{
+    struct nk_sdl_device *dev = &sdl.ogl;
+    glGenTextures(1, &dev->font_tex);
+    glBindTexture(GL_TEXTURE_2D, dev->font_tex);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0,
+                GL_RGBA, GL_UNSIGNED_BYTE, image);
+}
+
+NK_API void
+nk_sdl_device_destroy(void)
+{
+    struct nk_sdl_device *dev = &sdl.ogl;
+    glDetachShader(dev->prog, dev->vert_shdr);
+    glDetachShader(dev->prog, dev->frag_shdr);
+    glDeleteShader(dev->vert_shdr);
+    glDeleteShader(dev->frag_shdr);
+    glDeleteProgram(dev->prog);
+    glDeleteTextures(1, &dev->font_tex);
+    glDeleteBuffers(1, &dev->vbo);
+    glDeleteBuffers(1, &dev->ebo);
+    nk_buffer_free(&dev->cmds);
+}
+
+NK_API void
+nk_sdl_render(enum nk_anti_aliasing AA, int max_vertex_buffer, int max_element_buffer)
+{
+    struct nk_sdl_device *dev = &sdl.ogl;
+    int width, height;
+    int display_width, display_height;
+    struct nk_vec2 scale;
+    GLfloat ortho[4][4] = {
+        {2.0f, 0.0f, 0.0f, 0.0f},
+        {0.0f,-2.0f, 0.0f, 0.0f},
+        {0.0f, 0.0f,-1.0f, 0.0f},
+        {-1.0f,1.0f, 0.0f, 1.0f},
+    };
+    SDL_GetWindowSize(sdl.win, &width, &height);
+    SDL_GL_GetDrawableSize(sdl.win, &display_width, &display_height);
+    ortho[0][0] /= (GLfloat)width;
+    ortho[1][1] /= (GLfloat)height;
+
+    scale.x = (float)display_width/(float)width;
+    scale.y = (float)display_height/(float)height;
+
+    /* setup global state */
+    glViewport(0,0,display_width,display_height);
+    glEnable(GL_BLEND);
+    glBlendEquation(GL_FUNC_ADD);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    glDisable(GL_CULL_FACE);
+    glDisable(GL_DEPTH_TEST);
+    glEnable(GL_SCISSOR_TEST);
+    glActiveTexture(GL_TEXTURE0);
+
+    /* setup program */
+    glUseProgram(dev->prog);
+    glUniform1i(dev->uniform_tex, 0);
+    glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]);
+    {
+        /* convert from command queue into draw list and draw to screen */
+        const struct nk_draw_command *cmd;
+        void *vertices, *elements;
+        const nk_draw_index *offset = NULL;
+
+        /* Bind buffers */
+        glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
+        
+        {
+            /* buffer setup */
+            glEnableVertexAttribArray((GLuint)dev->attrib_pos);
+            glEnableVertexAttribArray((GLuint)dev->attrib_uv);
+            glEnableVertexAttribArray((GLuint)dev->attrib_col);
+
+            glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, dev->vs, (void*)dev->vp);
+            glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, dev->vs, (void*)dev->vt);
+            glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, dev->vs, (void*)dev->vc);
+        }
+
+        glBufferData(GL_ARRAY_BUFFER, max_vertex_buffer, NULL, GL_STREAM_DRAW);
+        glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_element_buffer, NULL, GL_STREAM_DRAW);
+
+        /* load vertices/elements directly into vertex/element buffer */
+        vertices = malloc((size_t)max_vertex_buffer);
+        elements = malloc((size_t)max_element_buffer);
+        {
+            /* fill convert configuration */
+            struct nk_convert_config config;
+            static const struct nk_draw_vertex_layout_element vertex_layout[] = {
+                {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_sdl_vertex, position)},
+                {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_sdl_vertex, uv)},
+                {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct nk_sdl_vertex, col)},
+                {NK_VERTEX_LAYOUT_END}
+            };
+            NK_MEMSET(&config, 0, sizeof(config));
+            config.vertex_layout = vertex_layout;
+            config.vertex_size = sizeof(struct nk_sdl_vertex);
+            config.vertex_alignment = NK_ALIGNOF(struct nk_sdl_vertex);
+            config.null = dev->null;
+            config.circle_segment_count = 22;
+            config.curve_segment_count = 22;
+            config.arc_segment_count = 22;
+            config.global_alpha = 1.0f;
+            config.shape_AA = AA;
+            config.line_AA = AA;
+
+            /* setup buffers to load vertices and elements */
+            {struct nk_buffer vbuf, ebuf;
+            nk_buffer_init_fixed(&vbuf, vertices, (nk_size)max_vertex_buffer);
+            nk_buffer_init_fixed(&ebuf, elements, (nk_size)max_element_buffer);
+            nk_convert(&sdl.ctx, &dev->cmds, &vbuf, &ebuf, &config);}
+        }
+        glBufferSubData(GL_ARRAY_BUFFER, 0, (size_t)max_vertex_buffer, vertices);
+        glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, (size_t)max_element_buffer, elements);
+        free(vertices);
+        free(elements);
+
+        /* iterate over and execute each draw command */
+        nk_draw_foreach(cmd, &sdl.ctx, &dev->cmds) {
+            if (!cmd->elem_count) continue;
+            glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id);
+            glScissor((GLint)(cmd->clip_rect.x * scale.x),
+                (GLint)((height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h)) * scale.y),
+                (GLint)(cmd->clip_rect.w * scale.x),
+                (GLint)(cmd->clip_rect.h * scale.y));
+            glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset);
+            offset += cmd->elem_count;
+        }
+        nk_clear(&sdl.ctx);
+    }
+
+    glUseProgram(0);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+    glDisable(GL_BLEND);
+    glDisable(GL_SCISSOR_TEST);
+}
+
+static void
+nk_sdl_clipbard_paste(nk_handle usr, struct nk_text_edit *edit)
+{
+    const char *text = SDL_GetClipboardText();
+    if (text) nk_textedit_paste(edit, text, nk_strlen(text));
+    (void)usr;
+}
+
+static void
+nk_sdl_clipbard_copy(nk_handle usr, const char *text, int len)
+{
+    char *str = 0;
+    (void)usr;
+    if (!len) return;
+    str = (char*)malloc((size_t)len+1);
+    if (!str) return;
+    memcpy(str, text, (size_t)len);
+    str[len] = '\0';
+    SDL_SetClipboardText(str);
+    free(str);
+}
+
+NK_API struct nk_context*
+nk_sdl_init(SDL_Window *win)
+{
+    sdl.win = win;
+    nk_init_default(&sdl.ctx, 0);
+    sdl.ctx.clip.copy = nk_sdl_clipbard_copy;
+    sdl.ctx.clip.paste = nk_sdl_clipbard_paste;
+    sdl.ctx.clip.userdata = nk_handle_ptr(0);
+    nk_sdl_device_create();
+    return &sdl.ctx;
+}
+
+NK_API void
+nk_sdl_font_stash_begin(struct nk_font_atlas **atlas)
+{
+    nk_font_atlas_init_default(&sdl.atlas);
+    nk_font_atlas_begin(&sdl.atlas);
+    *atlas = &sdl.atlas;
+}
+
+NK_API void
+nk_sdl_font_stash_end(void)
+{
+    const void *image; int w, h;
+    image = nk_font_atlas_bake(&sdl.atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
+    nk_sdl_device_upload_atlas(image, w, h);
+    nk_font_atlas_end(&sdl.atlas, nk_handle_id((int)sdl.ogl.font_tex), &sdl.ogl.null);
+    if (sdl.atlas.default_font)
+        nk_style_set_font(&sdl.ctx, &sdl.atlas.default_font->handle);
+
+}
+
+NK_API int
+nk_sdl_handle_event(SDL_Event *evt)
+{
+    struct nk_context *ctx = &sdl.ctx;
+    if (evt->type == SDL_KEYUP || evt->type == SDL_KEYDOWN) {
+        /* key events */
+        int down = evt->type == SDL_KEYDOWN;
+        const Uint8* state = SDL_GetKeyboardState(0);
+        SDL_Keycode sym = evt->key.keysym.sym;
+        if (sym == SDLK_RSHIFT || sym == SDLK_LSHIFT)
+            nk_input_key(ctx, NK_KEY_SHIFT, down);
+        else if (sym == SDLK_DELETE)
+            nk_input_key(ctx, NK_KEY_DEL, down);
+        else if (sym == SDLK_RETURN)
+            nk_input_key(ctx, NK_KEY_ENTER, down);
+        else if (sym == SDLK_TAB)
+            nk_input_key(ctx, NK_KEY_TAB, down);
+        else if (sym == SDLK_BACKSPACE)
+            nk_input_key(ctx, NK_KEY_BACKSPACE, down);
+        else if (sym == SDLK_HOME) {
+            nk_input_key(ctx, NK_KEY_TEXT_START, down);
+            nk_input_key(ctx, NK_KEY_SCROLL_START, down);
+        } else if (sym == SDLK_END) {
+            nk_input_key(ctx, NK_KEY_TEXT_END, down);
+            nk_input_key(ctx, NK_KEY_SCROLL_END, down);
+        } else if (sym == SDLK_PAGEDOWN) {
+            nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down);
+        } else if (sym == SDLK_PAGEUP) {
+            nk_input_key(ctx, NK_KEY_SCROLL_UP, down);
+        } else if (sym == SDLK_z)
+            nk_input_key(ctx, NK_KEY_TEXT_UNDO, down && state[SDL_SCANCODE_LCTRL]);
+        else if (sym == SDLK_r)
+            nk_input_key(ctx, NK_KEY_TEXT_REDO, down && state[SDL_SCANCODE_LCTRL]);
+        else if (sym == SDLK_c)
+            nk_input_key(ctx, NK_KEY_COPY, down && state[SDL_SCANCODE_LCTRL]);
+        else if (sym == SDLK_v)
+            nk_input_key(ctx, NK_KEY_PASTE, down && state[SDL_SCANCODE_LCTRL]);
+        else if (sym == SDLK_x)
+            nk_input_key(ctx, NK_KEY_CUT, down && state[SDL_SCANCODE_LCTRL]);
+        else if (sym == SDLK_b)
+            nk_input_key(ctx, NK_KEY_TEXT_LINE_START, down && state[SDL_SCANCODE_LCTRL]);
+        else if (sym == SDLK_e)
+            nk_input_key(ctx, NK_KEY_TEXT_LINE_END, down && state[SDL_SCANCODE_LCTRL]);
+        else if (sym == SDLK_UP)
+            nk_input_key(ctx, NK_KEY_UP, down);
+        else if (sym == SDLK_DOWN)
+            nk_input_key(ctx, NK_KEY_DOWN, down);
+        else if (sym == SDLK_LEFT) {
+            if (state[SDL_SCANCODE_LCTRL])
+                nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down);
+            else nk_input_key(ctx, NK_KEY_LEFT, down);
+        } else if (sym == SDLK_RIGHT) {
+            if (state[SDL_SCANCODE_LCTRL])
+                nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down);
+            else nk_input_key(ctx, NK_KEY_RIGHT, down);
+        } else return 0;
+        return 1;
+    } else if (evt->type == SDL_MOUSEBUTTONDOWN || evt->type == SDL_MOUSEBUTTONUP) {
+        /* mouse button */
+        int down = evt->type == SDL_MOUSEBUTTONDOWN;
+        const int x = evt->button.x, y = evt->button.y;
+        if (evt->button.button == SDL_BUTTON_LEFT) {
+            if (evt->button.clicks > 1)
+                nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, down);
+            nk_input_button(ctx, NK_BUTTON_LEFT, x, y, down);
+        } else if (evt->button.button == SDL_BUTTON_MIDDLE)
+            nk_input_button(ctx, NK_BUTTON_MIDDLE, x, y, down);
+        else if (evt->button.button == SDL_BUTTON_RIGHT)
+            nk_input_button(ctx, NK_BUTTON_RIGHT, x, y, down);
+        return 1;
+    } else if (evt->type == SDL_MOUSEMOTION) {
+        /* mouse motion */
+        if (ctx->input.mouse.grabbed) {
+            int x = (int)ctx->input.mouse.prev.x, y = (int)ctx->input.mouse.prev.y;
+            nk_input_motion(ctx, x + evt->motion.xrel, y + evt->motion.yrel);
+        } else nk_input_motion(ctx, evt->motion.x, evt->motion.y);
+        return 1;
+    } else if (evt->type == SDL_TEXTINPUT) {
+        /* text input */
+        nk_glyph glyph;
+        memcpy(glyph, evt->text.text, NK_UTF_SIZE);
+        nk_input_glyph(ctx, glyph);
+        return 1;
+    } else if (evt->type == SDL_MOUSEWHEEL) {
+        /* mouse wheel */
+        nk_input_scroll(ctx,nk_vec2((float)evt->wheel.x,(float)evt->wheel.y));
+        return 1;
+    }
+    return 0;
+}
+
+NK_API
+void nk_sdl_shutdown(void)
+{
+    nk_font_atlas_clear(&sdl.atlas);
+    nk_free(&sdl.ctx);
+    nk_sdl_device_destroy();
+    memset(&sdl, 0, sizeof(sdl));
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/sfnt.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,273 @@
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include "sfnt.h"
+#include "../util.h"
+
+static uint32_t big32(uint8_t *src)
+{
+	uint32_t ret = *(src++) << 24;
+	ret |= *(src++) << 16;
+	ret |= *(src++) << 8;
+	ret |= *src;
+	return ret;
+}
+
+static uint32_t big16(uint8_t *src)
+{
+	uint32_t ret = *(src++) << 8;
+	ret |= *src;
+	return ret;
+}
+
+#define MIN_RESOURCE_MAP_SIZE (16 + 12 + 2 + 8)
+
+sfnt_container *load_sfnt(uint8_t *buffer, uint32_t size)
+{
+	if (size < 0x100) {
+		return NULL;
+	}
+	uint32_t sfnt_res_count, sfnt_res_offset, res_offset;
+	uint8_t type;
+	if (!memcmp(buffer, "true", 4) || !memcmp(buffer, "OTTO", 4) || !memcmp(buffer, "typ1", 4) || !memcmp(buffer, "\0\x01\0\0", 4)) {
+		type = CONTAINER_TTF;
+	} else if (!memcmp(buffer, "ttcf", 4)) {
+		type = CONTAINER_TTC;
+	} else {
+		static uint8_t all_zeroes[16];
+		uint32_t resource_map_off = big32(buffer + 4);
+		if (resource_map_off + MIN_RESOURCE_MAP_SIZE > size) {
+			return NULL;
+		}
+		//first 16 bytes of map should match header or be all zeroes
+		if (memcmp(buffer, buffer + resource_map_off, 16) && memcmp(all_zeroes, buffer + resource_map_off, 16)) {
+			return NULL;
+		}
+		uint32_t type_start_off = resource_map_off + big16(buffer + resource_map_off + 24);
+		if (type_start_off + sizeof(uint16_t) > size) {
+			return NULL;
+		}
+		uint32_t num_types = 1 + big16(buffer + type_start_off);
+		if (type_start_off + sizeof(uint16_t) + 8 * num_types > size) {
+			return NULL;
+		}
+		res_offset = big32(buffer);
+		if (res_offset > size) {
+			return NULL;
+		}
+		uint8_t *cur = buffer + type_start_off + 2;
+		sfnt_res_count = 0;
+		for (uint32_t i = 0; i < num_types; i++, cur += 8)
+		{
+			if (!memcmp("sfnt", cur, 4)) {
+				sfnt_res_count = 1 + big16(cur + 4);
+				sfnt_res_offset = type_start_off + big16(cur + 6);
+				if (sfnt_res_offset + sfnt_res_count * 12 > size) {
+					return NULL;
+				}
+				type = CONTAINER_DFONT;
+				break;
+			}
+		}
+		if (!sfnt_res_count) {
+			//No "sfnt" resources in this dfont
+			return NULL;
+		}
+	}
+	sfnt_container *sfnt = calloc(1, sizeof(sfnt_container));
+	sfnt->blob = buffer;
+	sfnt->size = size;
+	sfnt->container_type = type;
+	switch (type)
+	{
+	case CONTAINER_TTF:
+		sfnt->num_fonts = 1;
+		sfnt->tables = calloc(1, sizeof(sfnt_table));
+		sfnt->tables->container = sfnt;
+		sfnt->tables->data = buffer + 0xC;
+		sfnt->tables->num_entries = big16(buffer + 4);
+		sfnt->tables->offset = 0;
+		break;
+	case CONTAINER_TTC: {
+		sfnt->num_fonts = big32(buffer+8);
+		sfnt->tables = calloc(sfnt->num_fonts, sizeof(sfnt_table));
+		uint8_t *offsets = buffer + 0xC;
+		for (int i = 0; i < sfnt->num_fonts; i++, offsets += sizeof(uint32_t))
+		{
+			uint32_t offset = big32(offsets);
+			sfnt->tables[i].data = buffer + offset + 0xC;
+			sfnt->tables[i].container = sfnt;
+			sfnt->tables[i].num_entries = big16(buffer + offset + 4);
+			sfnt->tables[i].offset = 0;
+		}
+		break;
+	}
+	case CONTAINER_DFONT:{
+		sfnt->num_fonts = sfnt_res_count;
+		sfnt->tables = calloc(sfnt->num_fonts, sizeof(sfnt_table));
+		uint8_t *cur = buffer + sfnt_res_offset;
+		for (int i = 0; i < sfnt->num_fonts; i++, cur += 12)
+		{
+			uint32_t offset = res_offset + (big32(cur + 4) & 0xFFFFFF);
+			if (offset + 4 > size) {
+				sfnt->tables[i].num_entries = 0;
+				sfnt->tables[i].data = NULL;
+				continue;
+			}
+			uint32_t res_size = big32(buffer + offset);
+			if (offset + 4 + res_size > size || res_size < 0xC) {
+				sfnt->tables[i].num_entries = 0;
+				sfnt->tables[i].data = NULL;
+				continue;
+			}
+			sfnt->tables[i].container = sfnt;
+			sfnt->tables[i].data = buffer + offset + 4 + 0xC;
+			sfnt->tables[i].num_entries = big16(buffer + offset + 4 + 4);
+			sfnt->tables[i].offset = offset + 4;
+		}
+		break;
+	}
+	}
+	return sfnt;
+}
+
+uint8_t *sfnt_find_table(sfnt_table *sfnt, char *table, uint32_t *size_out)
+{
+	uint8_t *entry = sfnt->data;
+	for (int i = 0; i < sfnt->num_entries; i++, entry += 16)
+	{
+		if (!strncmp(entry, table, 4)) {
+			if (size_out) {
+				*size_out = big32(entry + 12);
+			}
+			return sfnt->container->blob + sfnt->offset + big32(entry + 8);
+		}
+	}
+	return NULL;
+}
+
+char *sfnt_name(sfnt_table *sfnt, uint16_t name_type)
+{
+	uint32_t name_size;
+	uint8_t *name_table = sfnt_find_table(sfnt, "name", &name_size);
+	if (!name_table) {
+		return NULL;
+	}
+	uint16_t num_names = big16(name_table + 2);
+	if ((6 + num_names *12) > name_size) {
+		//count is too big for the name table size, abort
+		return NULL;
+	}
+	uint8_t *entry = name_table + 6;
+	uint16_t name_length = 0, name_offset;
+	uint8_t *unicode_entry = NULL, *macroman_entry = NULL, *winunicode_entry = NULL;
+	for (uint16_t i = 0; i < num_names; i++, entry += 12)
+	{
+		if (big16(entry + 6) != name_type) {
+			continue;
+		}
+		uint16_t language_id = big16(entry + 4);
+		if (language_id >= 0x8000) {
+			//ingore language tag records
+			continue;
+		}
+		uint16_t platform_id = big16(entry);
+		if (platform_id == 0) {
+			//prefer Unicode first
+			unicode_entry = entry;
+			break;
+		} else if (platform_id == 3 && big16(entry + 2) < 2) {
+			if (!winunicode_entry || (language_id & 0xFF) == 0x09) {
+				winunicode_entry = entry;
+			}
+		} else if (platform_id == 1 && big16(entry + 2) == 0) {
+			if (!macroman_entry || (language_id == 0)) {
+				macroman_entry = entry;
+			}
+		}
+	}
+	entry = unicode_entry ? unicode_entry : winunicode_entry ? winunicode_entry : macroman_entry;
+	if (entry) {
+		name_length = big16(entry + 8);
+		name_offset = big16(entry + 10);
+	}
+	if (!name_length) {
+		return NULL;
+	}
+	uint32_t full_off = name_offset + big16(name_table + 4);
+	if ((full_off + name_length) > name_size) {
+		return NULL;
+	}
+	if (entry == macroman_entry) {
+		//TODO: convert these properly to UTF-8
+		char *ret = malloc(name_length + 1);
+		memcpy(ret, name_table + full_off, name_length);
+		ret[name_length] = 0;
+		return ret;
+	} else {
+		return utf16be_to_utf8(name_table + full_off, name_length/2);
+	}
+}
+
+uint8_t *sfnt_flatten(sfnt_table *sfnt, uint32_t *size_out)
+{
+	uint8_t *ret = NULL;;
+	sfnt_container *cont = sfnt->container;
+	switch(cont->container_type)
+	{
+	case CONTAINER_TTF:
+		ret = cont->blob;
+		if (size_out) {
+			*size_out = cont->size;
+		}
+		break;
+	case CONTAINER_TTC:
+		memmove(cont->blob, sfnt->data - 0xC, 0xC + sfnt->num_entries * 12);
+		ret = cont->blob;
+		if (size_out) {
+			*size_out = cont->size;
+		}
+		break;
+	case CONTAINER_DFONT:{
+		uint8_t * start = sfnt->data - 0xC;
+		uint32_t size = big32(start - 4);
+		if (size + (start-cont->blob) > cont->size) {
+			size = cont->size - (start-cont->blob);
+		}
+		ret = malloc(size);
+		memcpy(ret, start, size);
+		free(cont->blob);
+		if (size_out) {
+			*size_out = size;
+		}
+		break;
+	}
+	}
+	free(cont->tables);
+	free(cont);
+	return ret;
+}
+
+sfnt_table *sfnt_subfamily_by_names(sfnt_container *sfnt, const char **names)
+{
+	for (int i = 0; i < sfnt->num_fonts; i++)
+	{
+		for (const char **name = names; *name; name++)
+		{
+			char *font_subfam = sfnt_name(sfnt->tables + i, SFNT_SUBFAMILY);
+			if (font_subfam && !strcasecmp(*name, font_subfam)) {
+				free(font_subfam);
+				return sfnt->tables + i;
+			}
+			free(font_subfam);
+		}
+	}
+	return NULL;
+}
+
+void sfnt_free(sfnt_container *sfnt)
+{
+	free(sfnt->tables);
+	free(sfnt->blob);
+	free(sfnt);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nuklear_ui/sfnt.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,44 @@
+#ifndef SFNT_H_
+#define SFNT_H_
+
+#include <stdint.h>
+enum {
+	CONTAINER_TTF,
+	CONTAINER_TTC,
+	CONTAINER_DFONT
+};
+
+enum {
+	SFNT_COPYRIGHT,
+	SFNT_FAMILY,
+	SFNT_SUBFAMILY,
+	SFNT_SUBFAMILY_UNIQUE,
+	SFNT_FULL_NAME,
+	SFNT_VERSION,
+	SFNT_POSTSCRIPT,
+	//TODO: add the rest of the name IDs
+};
+
+typedef struct sfnt_container sfnt_container;
+typedef struct {
+	uint8_t        *data;
+	sfnt_container *container;
+	uint32_t       offset;
+	uint16_t       num_entries;
+} sfnt_table;
+
+struct sfnt_container {
+	uint8_t    *blob;
+	sfnt_table *tables;
+	uint32_t   size;
+	uint8_t    num_fonts;
+	uint8_t    container_type;
+};
+
+sfnt_container *load_sfnt(uint8_t *buffer, uint32_t size);
+char *sfnt_name(sfnt_table *sfnt, uint16_t name_type);
+uint8_t *sfnt_flatten(sfnt_table *sfnt, uint32_t *size_out);
+sfnt_table *sfnt_subfamily_by_names(sfnt_container *sfnt, const char **names);
+void sfnt_free(sfnt_container *sfnt);
+
+#endif // SFNT_H_
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/paths.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,141 @@
+#include <string.h>
+#include <stdlib.h>
+#include "blastem.h"
+#include "util.h"
+
+static char **current_path;
+
+static void persist_path(void)
+{
+	char *pathfname = alloc_concat(get_userdata_dir(), PATH_SEP "blastem" PATH_SEP "sticky_path");
+	FILE *f = fopen(pathfname, "wb");
+	if (f) {
+		if (fwrite(*current_path, 1, strlen(*current_path), f) != strlen(*current_path)) {
+			warning("Failed to save menu path");
+		}
+		fclose(f);
+	} else {
+		warning("Failed to save menu path: Could not open %s for writing\n", pathfname);
+		
+	}
+	free(pathfname);
+}
+
+#ifdef __ANDROID__
+#include <SDL.h>
+#include <jni.h>
+static char *get_external_storage_path()
+{
+	static char *ret;
+	if (ret) {
+		return ret;
+	}
+	JNIEnv *env = SDL_AndroidGetJNIEnv();
+	if ((*env)->PushLocalFrame(env, 8) < 0) {
+		return NULL;
+	}
+
+	jclass Environment = (*env)->FindClass(env, "android/os/Environment");
+	jmethodID getExternalStorageDirectory =
+		(*env)->GetStaticMethodID(env, Environment, "getExternalStorageDirectory", "()Ljava/io/File;");
+	jobject file = (*env)->CallStaticObjectMethod(env, Environment, getExternalStorageDirectory);
+	if (!file) {
+		goto cleanup;
+	}
+
+	jmethodID getAbsolutePath = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, file),
+		"getAbsolutePath", "()Ljava/lang/String;");
+	jstring path = (*env)->CallObjectMethod(env, file, getAbsolutePath);
+
+	char const *tmp = (*env)->GetStringUTFChars(env, path, NULL);
+	ret = strdup(tmp);
+	(*env)->ReleaseStringUTFChars(env, path, tmp);
+
+cleanup:
+	(*env)->PopLocalFrame(env, NULL);
+	return ret;
+}
+#endif
+
+void get_initial_browse_path(char **dst)
+{
+	*dst = NULL;
+	char *remember_path = tern_find_path(config, "ui\0remember_path\0", TVAL_PTR).ptrval;
+	if (!remember_path || !strcmp("on", remember_path)) {
+		char *pathfname = alloc_concat(get_userdata_dir(), PATH_SEP "blastem" PATH_SEP "sticky_path");
+		FILE *f = fopen(pathfname, "rb");
+		if (f) {
+			long pathsize = file_size(f);
+			if (pathsize > 0) {
+				*dst = malloc(pathsize + 1);
+				if (fread(*dst, 1, pathsize, f) != pathsize) {
+					warning("Error restoring saved file browser path");
+					free(*dst);
+					*dst = NULL;
+				} else {
+					(*dst)[pathsize] = 0;
+				}
+			}
+			fclose(f);
+		}
+		free(pathfname);
+		if (!current_path) {
+			atexit(persist_path);
+			current_path = dst;
+		}
+	}
+	if (!*dst) {
+		*dst = tern_find_path(config, "ui\0initial_path\0", TVAL_PTR).ptrval;
+	}
+	if (!*dst){
+#ifdef __ANDROID__
+		*dst = get_external_storage_path();
+#else
+		*dst = "$HOME";
+#endif
+	}
+	tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
+	vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
+	*dst = replace_vars(*dst, vars, 1);
+	tern_free(vars);
+}
+
+char *path_append(const char *base, const char *suffix)
+{
+	if (!strcmp(suffix, "..")) {
+#ifdef _WIN32
+		//handle transition from root of a drive to virtual root
+		if (base[1] == ':' && !base[2]) {
+			return strdup(PATH_SEP);
+		}
+#endif
+		size_t len = strlen(base);
+		while (len > 0) {
+			--len;
+			if (is_path_sep(base[len])) {
+				if (!len) {
+					//special handling for /
+					len++;
+				}
+				char *ret = malloc(len+1);
+				memcpy(ret, base, len);
+				ret[len] = 0;
+				return ret;
+			}
+		}
+		return strdup(PATH_SEP);
+	} else {
+#ifdef _WIN32
+		if (base[0] == PATH_SEP[0] && !base[1]) {
+			//handle transition from virtual root to root of a drive
+			return strdup(suffix);
+		}
+#endif
+		if (is_path_sep(base[strlen(base) - 1])) {
+			return alloc_concat(base, suffix);
+		} else {
+			char const *pieces[] = {base, PATH_SEP, suffix};
+			return alloc_concat_m(3, pieces);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/paths.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,7 @@
+#ifndef PATHS_H_
+#define PATHS_H_
+
+void get_initial_browse_path(char **dst);
+char *path_append(const char *base, const char *suffix);
+
+#endif //PATHS_H_
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/png.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,447 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "zlib/zlib.h"
+
+static const char png_magic[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'};
+static const char ihdr[] = {'I', 'H', 'D', 'R'};
+static const char plte[] = {'P', 'L', 'T', 'E'};
+static const char idat[] = {'I', 'D', 'A', 'T'};
+static const char iend[] = {'I', 'E', 'N', 'D'};
+
+enum {
+	COLOR_GRAY,
+	COLOR_TRUE = 2,
+	COLOR_INDEXED,
+	COLOR_GRAY_ALPHA,
+	COLOR_TRUE_ALPHA=6
+};
+
+static void write_chunk(FILE *f, const char*id, uint8_t *buffer, uint32_t size)
+{
+	uint8_t tmp[4] = {size >> 24, size >> 16, size >> 8, size};
+	uint8_t warn = 0;
+	warn = warn || (sizeof(tmp) != fwrite(tmp, 1, sizeof(tmp), f));
+	warn = warn || (4 != fwrite(id, 1, 4, f));
+	if (size) {
+		warn = warn || (size != fwrite(buffer, 1, size, f));
+	}
+	
+	uint32_t crc = crc32(0, NULL, 0);
+	crc = crc32(crc, id, 4);
+	if (size) {
+		crc = crc32(crc, buffer, size);
+	}
+	tmp[0] = crc >> 24;
+	tmp[1] = crc >> 16;
+	tmp[2] = crc >> 8;
+	tmp[3] = crc;
+	warn = warn || (sizeof(tmp) != fwrite(tmp, 1, sizeof(tmp), f));
+	if (warn) {
+		fprintf(stderr, "Failure during write of %c%c%c%c chunk\n", id[0], id[1], id[2], id[3]);
+	}
+}
+
+static void write_header(FILE *f, uint32_t width, uint32_t height, uint8_t color_type)
+{
+	uint8_t chunk[13] = {
+		width >> 24, width >> 16, width >> 8, width,
+		height >> 24, height >> 16, height >> 8, height,
+		8, color_type, 0, 0, 0
+	};
+	if (sizeof(png_magic) != fwrite(png_magic, 1, sizeof(png_magic), f)) {
+		fputs("Error writing PNG magic\n", stderr);
+	}
+	write_chunk(f, ihdr, chunk, sizeof(chunk));
+}
+
+void save_png24(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch)
+{
+	uint32_t idat_size = (1 + width*3) * height;
+	uint8_t *idat_buffer = malloc(idat_size);
+	uint32_t *pixel = buffer;
+	uint8_t *cur = idat_buffer;
+	for (uint32_t y = 0; y < height; y++)
+	{
+		//save filter type
+		*(cur++) = 0;
+		uint32_t *start = pixel;
+		for (uint32_t x = 0; x < width; x++, pixel++)
+		{
+			uint32_t value = *pixel;
+			*(cur++) = value >> 16;
+			*(cur++) = value >> 8;
+			*(cur++) = value;
+		}
+		pixel = start + pitch / sizeof(uint32_t);
+	}
+	write_header(f, width, height, COLOR_TRUE);
+	uLongf compress_buffer_size = idat_size + 5 * (idat_size/16383 + 1) + 3;
+	uint8_t *compressed = malloc(compress_buffer_size);
+	compress(compressed, &compress_buffer_size, idat_buffer, idat_size);
+	free(idat_buffer);
+	write_chunk(f, idat, compressed, compress_buffer_size);
+	write_chunk(f, iend, NULL, 0);
+	free(compressed);
+}
+
+void save_png(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch)
+{
+	uint32_t palette[256];
+	uint8_t pal_buffer[256*3];
+	uint32_t num_pal = 0;
+	uint32_t index_size = (1 + width) * height;
+	uint8_t *index_buffer = malloc(index_size);
+	uint8_t *cur = index_buffer;
+	uint32_t *pixel = buffer;
+	for (uint32_t y = 0; y < height; y++)
+	{
+		//save filter type
+		*(cur++) = 0;
+		uint32_t *start = pixel;
+		for (uint32_t x = 0; x < width; x++, pixel++, cur++)
+		{
+			uint32_t value = (*pixel) & 0xFFFFFF;
+			uint32_t i;
+			for (i = 0; i < num_pal; i++)
+			{
+				if (palette[i] == value) {
+					break;
+				}
+			}
+			if (i == num_pal) {
+				if (num_pal == 256) {
+					free(index_buffer);
+					save_png24(f, buffer, width, height, pitch);
+					return;
+				}
+				palette[i] = value;
+				num_pal++;
+			}
+			*cur = i;
+		}
+		pixel = start + pitch / sizeof(uint32_t);
+	}
+	write_header(f, width, height, COLOR_INDEXED);
+	cur = pal_buffer;
+	for (uint32_t i = 0; i < num_pal; i++)
+	{
+		*(cur++) = palette[i] >> 16;
+		*(cur++) = palette[i] >> 8;
+		*(cur++) = palette[i];
+	}
+	write_chunk(f, plte, pal_buffer, num_pal * 3);
+	uLongf compress_buffer_size = index_size + 5 * (index_size/16383 + 1) + 3;
+	uint8_t *compressed = malloc(compress_buffer_size);
+	compress(compressed, &compress_buffer_size, index_buffer, index_size);
+	free(index_buffer);
+	write_chunk(f, idat, compressed, compress_buffer_size);
+	write_chunk(f, iend, NULL, 0);
+	free(compressed);
+}
+
+typedef uint8_t (*filter_fun)(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x);
+typedef uint32_t (*pixel_fun)(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun);
+
+static uint8_t filter_none(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x)
+{
+	return *cur;
+}
+
+static uint8_t filter_sub(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x)
+{
+	if (x) {
+		return *cur + *(cur - bpp);
+	} else {
+		return *cur;
+	}
+}
+
+static uint8_t filter_up(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x)
+{
+	if (last) {
+		return *cur + *last;
+	} else {
+		return *cur;
+	}
+}
+
+static uint8_t filter_avg(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x)
+{
+	uint8_t prev = x ? *(cur - bpp) : 0;
+	uint8_t prior = last ? *last : 0;
+	return *cur + ((prev + prior) >> 1);
+}
+
+static uint8_t paeth(uint8_t a, uint8_t b, uint8_t c)
+{
+	int32_t p = a + b - c;
+	int32_t pa = abs(p - a);
+	int32_t pb = abs(p - b);
+	int32_t pc = abs(p - c);
+	if (pa <= pb && pa <= pc) {
+		return a;
+	}
+	if (pb <= pc) {
+		return b;
+	}
+	return c;
+}
+
+static uint8_t filter_paeth(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x)
+{
+	uint8_t prev, prev_prior;
+	if (x) {
+		prev = *(cur - bpp);
+		prev_prior = *(last - bpp);
+	} else {
+		prev = prev_prior = 0;
+	}
+	uint8_t prior = last ? *last : 0;
+	return *cur + paeth(prev, prior, prev_prior);
+}
+
+static uint32_t pixel_gray(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter)
+{
+	uint8_t value = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	return 0xFF000000 | value << 16 | value << 8 | value;
+}
+
+static uint32_t pixel_true(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter)
+{
+	uint8_t red = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	uint8_t green = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	uint8_t blue = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	return 0xFF000000 | red << 16 | green << 8 | blue;
+}
+
+static uint32_t pixel_gray_alpha(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter)
+{
+	uint8_t value = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	uint8_t alpha = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	return alpha << 24 | value << 16 | value << 8 | value;
+}
+
+static uint32_t pixel_true_alpha(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter)
+{
+	uint8_t red = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	uint8_t green = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	uint8_t blue = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	uint8_t alpha = filter(*cur, *last, bpp, x);
+	(*cur)++;
+	if (*last) {
+		(*last)++;
+	}
+	return alpha << 24 | red << 16 | green << 8 | blue;
+}
+
+static filter_fun filters[] = {filter_none, filter_sub, filter_up, filter_avg, filter_paeth};
+
+#define MIN_CHUNK_SIZE 12
+#define MIN_IHDR_SIZE 0xD
+#define MAX_SUPPORTED_DIM 32767 //chosen to avoid possibility of overflow when calculating uncompressed size
+uint32_t *load_png(uint8_t *buffer, uint32_t buf_size, uint32_t *width, uint32_t *height)
+{
+	if (buf_size < sizeof(png_magic) || memcmp(buffer, png_magic, sizeof(png_magic))) {
+		return NULL;
+	}
+	uint32_t cur = sizeof(png_magic);
+	uint8_t has_header = 0;
+	uint8_t bits, color_type, comp_type, filter_type, interlace;
+	uint8_t *idat_buf = NULL;
+	uint8_t idat_needs_free = 0;
+	uint32_t idat_size;
+	uint32_t *out = NULL;
+	uint32_t *palette = NULL;
+	while(cur + MIN_CHUNK_SIZE <= buf_size)
+	{
+		uint32_t chunk_size = buffer[cur++] << 24;
+		chunk_size |=  buffer[cur++] << 16;
+		chunk_size |=  buffer[cur++] << 8;
+		chunk_size |=  buffer[cur++];
+		if (!memcmp(ihdr, buffer + cur, sizeof(ihdr))) {
+			if (chunk_size < MIN_IHDR_SIZE || cur + MIN_IHDR_SIZE > buf_size) {
+				return NULL;
+			}
+			cur += sizeof(ihdr);
+			*width = buffer[cur++] << 24;
+			*width |=  buffer[cur++] << 16;
+			*width |=  buffer[cur++] << 8;
+			*width |=  buffer[cur++];
+			*height = buffer[cur++] << 24;
+			*height |=  buffer[cur++] << 16;
+			*height |=  buffer[cur++] << 8;
+			*height |=  buffer[cur++];
+			if (*width > MAX_SUPPORTED_DIM || *height > MAX_SUPPORTED_DIM) {
+				return NULL;
+			}
+			bits = buffer[cur++];
+			if (bits != 8) {
+				//only support 8-bits per element for now
+				return NULL;
+			}
+			color_type = buffer[cur++];
+			if (color_type > COLOR_TRUE_ALPHA || color_type == 1 || color_type == 5) {
+				//reject invalid color type
+				return NULL;
+			}
+			comp_type = buffer[cur++];
+			if (comp_type) {
+				//only compression type 0 is defined by the spec
+				return NULL;
+			}
+			filter_type = buffer[cur++];
+			interlace = buffer[cur++];
+			if (interlace) {
+				//interlacing not supported for now
+				return NULL;
+			}
+			cur += chunk_size - MIN_IHDR_SIZE;
+			has_header = 1;
+		} else {
+			if (!has_header) {
+				//IHDR is required to be the first chunk, fail if it isn't
+				break;
+			}
+			if (!memcmp(plte, buffer + cur, sizeof(plte))) {
+				//TODO: implement paletted images
+			} else if (!memcmp(idat, buffer + cur, sizeof(idat))) {
+				cur += sizeof(idat);
+				if (idat_buf) {
+					if (idat_needs_free) {
+						idat_buf = realloc(idat_buf, idat_size + chunk_size);
+					} else {
+						uint8_t *tmp = idat_buf;
+						idat_buf = malloc(idat_size + chunk_size);
+						memcpy(idat_buf, tmp, idat_size);
+					}
+					memcpy(idat_buf + idat_size, buffer + cur, chunk_size);
+					idat_size += chunk_size;
+				} else {
+					idat_buf = buffer + cur;
+					idat_size = chunk_size;
+				}
+				cur += chunk_size;
+			} else if (!memcmp(iend, buffer + cur, sizeof(iend))) {
+				if (!idat_buf) {
+					break;
+				}
+				if (!palette && color_type == COLOR_INDEXED) {
+					//indexed color, but no PLTE chunk found
+					return NULL;
+				}
+				uLongf uncompressed_size = *width * *height;
+				uint8_t bpp;
+				pixel_fun pixel;
+				switch (color_type)
+				{
+				case COLOR_GRAY:
+					uncompressed_size *= bits / 8;
+					bpp = bits/8;
+					pixel = pixel_gray;
+					break;
+				case COLOR_TRUE:
+					uncompressed_size *= 3 * bits / 8;
+					bpp = 3 * bits/8;
+					pixel = pixel_true;
+					break;
+				case COLOR_INDEXED: {
+					uint32_t pixels_per_byte = 8 / bits;
+					uncompressed_size = (*width / pixels_per_byte) * *height;
+					if (*width % pixels_per_byte) {
+						uncompressed_size += *height;
+					}
+					bpp = 1;
+					break;
+				}
+				case COLOR_GRAY_ALPHA:
+					uncompressed_size *= bits / 4;
+					bpp = bits / 4;
+					pixel = pixel_gray_alpha;
+					break;
+				case COLOR_TRUE_ALPHA:
+					uncompressed_size *= bits / 2;
+					bpp = bits / 2;
+					pixel = pixel_true_alpha;
+					break;
+				}
+				//add filter type byte
+				uncompressed_size += *height;
+				uint8_t *decomp_buffer = malloc(uncompressed_size);
+				if (Z_OK != uncompress(decomp_buffer, &uncompressed_size, idat_buf, idat_size)) {
+					free(decomp_buffer);
+					break;
+				}
+				out = calloc(*width * *height, sizeof(uint32_t));
+				uint32_t *cur_pixel = out;
+				uint8_t *cur_byte = decomp_buffer;
+				uint8_t *last_line = NULL;
+				for (uint32_t y = 0; y < *height; y++)
+				{
+					uint8_t filter_type = *(cur_byte++);
+					if (filter_type >= sizeof(filters)/sizeof(*filters)) {
+						free(out);
+						out = NULL;
+						free(decomp_buffer);
+						break;
+					}
+					filter_fun filter = filters[filter_type];
+					uint8_t *line_start = cur_byte;
+					for (uint32_t x = 0; x < *width; x++)
+					{
+						*(cur_pixel++) = pixel(&cur_byte, &last_line, bpp, x, filter);
+					}
+					last_line = line_start;
+				}
+				free(decomp_buffer);
+			} else {
+				//skip uncrecognized chunks
+				cur += 4 + chunk_size;
+			}
+		}
+		//skip CRC for now
+		cur += sizeof(uint32_t);
+	}
+	if (idat_needs_free) {
+		free(idat_buf);
+	}
+	free(palette);
+	return out;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/png.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,8 @@
+#ifndef PNG_H_
+#define PNG_H_
+
+void save_png24(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch);
+void save_png(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch);
+uint32_t *load_png(uint8_t *buffer, uint32_t buf_size, uint32_t *width, uint32_t *height);
+
+#endif //PNG_H_
--- a/psg.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/psg.c	Sat Jan 05 00:58:08 2019 -0800
@@ -10,19 +10,11 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <math.h>
-void psg_init(psg_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t samples_frame, uint32_t lowpass_cutoff)
+void psg_init(psg_context * context, uint32_t master_clock, uint32_t clock_div)
 {
 	memset(context, 0, sizeof(*context));
-	context->audio_buffer = malloc(sizeof(*context->audio_buffer) * samples_frame);
-	context->back_buffer = malloc(sizeof(*context->audio_buffer) * samples_frame);
+	context->audio = render_audio_source(master_clock, clock_div, 1);
 	context->clock_inc = clock_div;
-	context->sample_rate = sample_rate;
-	context->samples_frame = samples_frame;
-	double rc = (1.0 / (double)lowpass_cutoff) / (2.0 * M_PI);
-	double dt = 1.0 / ((double)master_clock / (double)clock_div);
-	double alpha = dt / (dt + rc);
-	context->lowpass_alpha = (int32_t)(((double)0x10000) * alpha);
-	psg_adjust_master_clock(context, master_clock);
 	for (int i = 0; i < 4; i++) {
 		context->volume[i] = 0xF;
 	}
@@ -30,19 +22,13 @@
 
 void psg_free(psg_context *context)
 {
-	free(context->audio_buffer);
-	//TODO: Figure out how to make this 100% safe
-	//audio thread could still be using this
-	free(context->back_buffer);
+	render_free_source(context->audio);
 	free(context);
 }
 
-#define BUFFER_INC_RES 0x40000000UL
-
 void psg_adjust_master_clock(psg_context * context, uint32_t master_clock)
 {
-	uint64_t old_inc = context->buffer_inc;
-	context->buffer_inc = ((BUFFER_INC_RES * (uint64_t)context->sample_rate) / (uint64_t)master_clock) * (uint64_t)context->clock_inc;
+	render_audio_adjust_clock(context->audio, master_clock, context->clock_inc);
 }
 
 void psg_write(psg_context * context, uint8_t value)
@@ -120,33 +106,19 @@
 			}
 		}
 
-		context->last_sample = context->accum;
-		context->accum = 0;
+		int16_t accum = 0;
 		
 		for (int i = 0; i < 3; i++) {
 			if (context->output_state[i]) {
-				context->accum += volume_table[context->volume[i]];
+				accum += volume_table[context->volume[i]];
 			}
 		}
 		if (context->noise_out) {
-			context->accum += volume_table[context->volume[3]];
+			accum += volume_table[context->volume[3]];
 		}
-		int32_t tmp = context->accum * context->lowpass_alpha + context->last_sample * (0x10000 - context->lowpass_alpha);
-		context->accum = tmp >> 16;
+		
+		render_put_mono_sample(context->audio, accum);
 
-		context->buffer_fraction += context->buffer_inc;
-		while (context->buffer_fraction >= BUFFER_INC_RES) {
-			context->buffer_fraction -= BUFFER_INC_RES;
-			int32_t tmp = context->last_sample * ((context->buffer_fraction << 16) / context->buffer_inc);
-			tmp += context->accum * (0x10000 - ((context->buffer_fraction << 16) / context->buffer_inc));
-			context->audio_buffer[context->buffer_pos++] = tmp >> 16;
-			
-			if (context->buffer_pos == context->samples_frame) {
-				if (!headless) {
-					render_wait_psg(context);
-				}
-			}
-		}
 		context->cycles += context->clock_inc;
 	}
 }
--- a/psg.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/psg.h	Sat Jan 05 00:58:08 2019 -0800
@@ -8,23 +8,15 @@
 
 #include <stdint.h>
 #include "serialize.h"
+#include "render.h"
 
 typedef struct {
-	int16_t  *audio_buffer;
-	int16_t  *back_buffer;
-	uint64_t buffer_fraction;
-	uint64_t buffer_inc;
-	uint32_t buffer_pos;
+	audio_source *audio;
 	uint32_t clock_inc;
 	uint32_t cycles;
-	uint32_t sample_rate;
-	uint32_t samples_frame;
-	int32_t lowpass_alpha;
 	uint16_t lsfr;
 	uint16_t counter_load[4];
 	uint16_t counters[4];
-	int16_t  accum;
-	int16_t  last_sample;
 	uint8_t  volume[4];
 	uint8_t  output_state[4];
 	uint8_t  noise_out;
@@ -34,7 +26,7 @@
 } psg_context;
 
 
-void psg_init(psg_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t samples_frame, uint32_t lowpass_cutoff);
+void psg_init(psg_context * context, uint32_t master_clock, uint32_t clock_div);
 void psg_free(psg_context *context);
 void psg_adjust_master_clock(psg_context * context, uint32_t master_clock);
 void psg_write(psg_context * context, uint8_t value);
--- a/render.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/render.h	Sat Jan 05 00:58:08 2019 -0800
@@ -40,6 +40,22 @@
 #define RENDERKEY_PLAY     SDLK_AUDIOPLAY
 #define RENDERKEY_SEARCH   SDLK_AC_SEARCH
 #define RENDERKEY_BACK     SDLK_AC_BACK
+#define RENDERKEY_NP0      SDLK_KP_0
+#define RENDERKEY_NP1      SDLK_KP_1
+#define RENDERKEY_NP2      SDLK_KP_2
+#define RENDERKEY_NP3      SDLK_KP_3
+#define RENDERKEY_NP4      SDLK_KP_4
+#define RENDERKEY_NP5      SDLK_KP_5
+#define RENDERKEY_NP6      SDLK_KP_6
+#define RENDERKEY_NP7      SDLK_KP_7
+#define RENDERKEY_NP8      SDLK_KP_8
+#define RENDERKEY_NP9      SDLK_KP_9
+#define RENDERKEY_NP_DIV   SDLK_KP_DIVIDE
+#define RENDERKEY_NP_MUL   SDLK_KP_MULTIPLY
+#define RENDERKEY_NP_MIN   SDLK_KP_MINUS
+#define RENDERKEY_NP_PLUS  SDLK_KP_PLUS
+#define RENDERKEY_NP_ENTER SDLK_KP_ENTER
+#define RENDERKEY_NP_STOP  SDLK_KP_PERIOD
 #define RENDER_DPAD_UP     SDL_HAT_UP
 #define RENDER_DPAD_DOWN   SDL_HAT_DOWN
 #define RENDER_DPAD_LEFT   SDL_HAT_LEFT
@@ -52,10 +68,9 @@
 
 #define FRAMEBUFFER_ODD 0
 #define FRAMEBUFFER_EVEN 1
+#define FRAMEBUFFER_USER_START 2
 
 #include "vdp.h"
-#include "psg.h"
-#include "ym2612.h"
 
 typedef enum {
 	VID_NTSC,
@@ -69,21 +84,23 @@
 #define RENDER_NOT_MAPPED -2
 #define RENDER_NOT_PLUGGED_IN -3
 
+typedef struct audio_source audio_source;
 typedef void (*drop_handler)(const char *filename);
+typedef void (*window_close_handler)(uint8_t which);
 
 uint32_t render_map_color(uint8_t r, uint8_t g, uint8_t b);
 void render_save_screenshot(char *path);
+uint8_t render_create_window(char *caption, uint32_t width, uint32_t height, window_close_handler close_handler);
+void render_destroy_window(uint8_t which);
 uint32_t *render_get_framebuffer(uint8_t which, int *pitch);
 void render_framebuffer_updated(uint8_t which, int width);
+//returns the framebuffer index associated with the Window that has focus
+uint8_t render_get_active_framebuffer(void);
 void render_init(int width, int height, char * title, uint8_t fullscreen);
 void render_set_video_standard(vid_std std);
 void render_toggle_fullscreen();
 void render_update_caption(char *title);
 void render_wait_quit(vdp_context * context);
-void render_wait_psg(psg_context * context);
-void render_wait_ym(ym2612_context * context);
-void render_disable_ym();
-void render_enable_ym();
 uint32_t render_audio_buffer();
 uint32_t render_sample_rate();
 void process_events();
@@ -96,6 +113,7 @@
 int32_t render_dpad_part(int32_t input);
 int32_t render_axis_part(int32_t input);
 uint8_t render_direction_part(int32_t input);
+char* render_joystick_type_id(int index);
 void render_errorbox(char *title, char *message);
 void render_warnbox(char *title, char *message);
 void render_infobox(char *title, char *message);
@@ -103,6 +121,17 @@
 uint32_t render_emulated_height();
 uint32_t render_overscan_top();
 uint32_t render_overscan_left();
+uint32_t render_elapsed_ms(void);
+void render_sleep_ms(uint32_t delay);
+uint8_t render_has_gl(void);
+audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels);
+void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider);
+void render_put_mono_sample(audio_source *src, int16_t value);
+void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right);
+void render_pause_source(audio_source *src);
+void render_resume_source(audio_source *src);
+void render_free_source(audio_source *src);
+void render_config_updated(void);
 
 #endif //RENDER_H_
 
--- a/render_sdl.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/render_sdl.c	Sat Jan 05 00:58:08 2019 -0800
@@ -8,21 +8,32 @@
 #include <string.h>
 #include <math.h>
 #include "render.h"
+#include "render_sdl.h"
 #include "blastem.h"
 #include "genesis.h"
-#include "io.h"
+#include "bindings.h"
 #include "util.h"
 #include "ppm.h"
+#include "png.h"
+#include "config.h"
+#include "controller_info.h"
 
 #ifndef DISABLE_OPENGL
+#ifdef USE_GLES
+#include <SDL_opengles2.h>
+#else
 #include <GL/glew.h>
 #endif
+#endif
 
 #define MAX_EVENT_POLL_PER_FRAME 2
 
 static SDL_Window *main_window;
+static SDL_Window **extra_windows;
 static SDL_Renderer *main_renderer;
+static SDL_Renderer **extra_renderers;
 static SDL_Texture  **sdl_textures;
+static window_close_handler *close_handlers;
 static uint8_t num_textures;
 static SDL_Rect      main_clip;
 static SDL_GLContext *main_context;
@@ -34,72 +45,193 @@
 
 static uint32_t last_frame = 0;
 
-static int16_t * current_psg = NULL;
-static int16_t * current_ym = NULL;
-
 static uint32_t buffer_samples, sample_rate;
 static uint32_t missing_count;
 
 static SDL_mutex * audio_mutex;
 static SDL_cond * audio_ready;
-static SDL_cond * psg_cond;
-static SDL_cond * ym_cond;
 static uint8_t quitting = 0;
-static uint8_t ym_enabled = 1;
+
+struct audio_source {
+	SDL_cond *cond;
+	int16_t  *front;
+	int16_t  *back;
+	double   dt;
+	uint64_t buffer_fraction;
+	uint64_t buffer_inc;
+	uint32_t buffer_pos;
+	uint32_t read_start;
+	uint32_t read_end;
+	uint32_t lowpass_alpha;
+	uint32_t mask;
+	int16_t  last_left;
+	int16_t  last_right;
+	uint8_t  num_channels;
+	uint8_t  front_populated;
+};
+
+static audio_source *audio_sources[8];
+static audio_source *inactive_audio_sources[8];
+static uint8_t num_audio_sources;
+static uint8_t num_inactive_audio_sources;
+static uint8_t sync_to_audio;
+static uint32_t min_buffered;
+
+typedef int32_t (*mix_func)(audio_source *audio, void *vstream, int len);
+
+static int32_t mix_s16(audio_source *audio, void *vstream, int len)
+{
+	int samples = len/(sizeof(int16_t)*2);
+	int16_t *stream = vstream;
+	int16_t *end = stream + 2*samples;
+	int16_t *src = audio->front;
+	uint32_t i = audio->read_start;
+	uint32_t i_end = audio->read_end;
+	int16_t *cur = stream;
+	if (audio->num_channels == 1) {
+		while (cur < end && i != i_end)
+		{
+			*(cur++) += src[i];
+			*(cur++) += src[i++];
+			i &= audio->mask;
+		}
+	} else {
+		while (cur < end && i != i_end)
+		{
+			*(cur++) += src[i++];
+			*(cur++) += src[i++];
+			i &= audio->mask;
+		}
+	}
+	
+	if (cur != end) {
+		printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask);
+	}
+	if (!sync_to_audio) {
+		audio->read_start = i;
+	}
+	if (cur != end) {
+		//printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask);
+		return (cur-end)/2;
+	} else {
+		return ((i_end - i) & audio->mask) / audio->num_channels;
+	}
+}
+
+static int32_t mix_f32(audio_source *audio, void *vstream, int len)
+{
+	int samples = len/(sizeof(float)*2);
+	float *stream = vstream;
+	float *end = stream + 2*samples;
+	int16_t *src = audio->front;
+	uint32_t i = audio->read_start;
+	uint32_t i_end = audio->read_end;
+	float *cur = stream;
+	if (audio->num_channels == 1) {
+		while (cur < end && i != i_end)
+		{
+			*(cur++) += ((float)src[i]) / 0x7FFF;
+			*(cur++) += ((float)src[i++]) / 0x7FFF;
+			i &= audio->mask;
+		}
+	} else {
+		while(cur < end && i != i_end)
+		{
+			*(cur++) += ((float)src[i++]) / 0x7FFF;
+			*(cur++) += ((float)src[i++]) / 0x7FFF;
+			i &= audio->mask;
+		}
+	}
+	if (!sync_to_audio) {
+		audio->read_start = i;
+	}
+	if (cur != end) {
+		printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask);
+		return (cur-end)/2;
+	} else {
+		return ((i_end - i) & audio->mask) / audio->num_channels;
+	}
+}
+
+static int32_t mix_null(audio_source *audio, void *vstream, int len)
+{
+	return 0;
+}
+
+static mix_func mix;
 
 static void audio_callback(void * userdata, uint8_t *byte_stream, int len)
 {
-	//puts("audio_callback");
-	int16_t * stream = (int16_t *)byte_stream;
-	int samples = len/(sizeof(int16_t)*2);
-	int16_t * psg_buf, * ym_buf;
-	uint8_t local_quit;
+	uint8_t num_populated;
+	memset(byte_stream, 0, len);
 	SDL_LockMutex(audio_mutex);
-		psg_buf = NULL;
-		ym_buf = NULL;
 		do {
-			if (!psg_buf) {
-				psg_buf = current_psg;
-				current_psg = NULL;
-				SDL_CondSignal(psg_cond);
+			num_populated = 0;
+			for (uint8_t i = 0; i < num_audio_sources; i++)
+			{
+				if (audio_sources[i]->front_populated) {
+					num_populated++;
+				}
 			}
-			if (ym_enabled && !ym_buf) {
-				ym_buf = current_ym;
-				current_ym = NULL;
-				SDL_CondSignal(ym_cond);
-			}
-			if (!quitting && (!psg_buf || (ym_enabled && !ym_buf))) {
+			if (!quitting && num_populated < num_audio_sources) {
+				fflush(stdout);
 				SDL_CondWait(audio_ready, audio_mutex);
 			}
-		} while(!quitting && (!psg_buf || (ym_enabled && !ym_buf)));
-
-		local_quit = quitting;
-	SDL_UnlockMutex(audio_mutex);
-	if (!local_quit) {
-		if (ym_enabled) {
-			for (int i = 0; i < samples; i++)
+		} while(!quitting && num_populated < num_audio_sources);
+		if (!quitting) {
+			for (uint8_t i = 0; i < num_audio_sources; i++)
 			{
-				*(stream++) = psg_buf[i] + *(ym_buf++);
-				*(stream++) = psg_buf[i] + *(ym_buf++);
-			}
-		} else {
-			for (int i = 0; i < samples; i++)
-			{
-				*(stream++) = psg_buf[i];
-				*(stream++) = psg_buf[i];
+				mix(audio_sources[i], byte_stream, len);
+				audio_sources[i]->front_populated = 0;
+				SDL_CondSignal(audio_sources[i]->cond);
 			}
 		}
+	SDL_UnlockMutex(audio_mutex);
+}
+
+#define NO_LAST_BUFFERED -2000000000
+static int32_t last_buffered = NO_LAST_BUFFERED;
+static float average_change;
+#define BUFFER_FRAMES_THRESHOLD 6
+#define BASE_MAX_ADJUST 0.0125
+static float max_adjust;
+static int32_t cur_min_buffered;
+static uint32_t min_remaining_buffer;
+static void audio_callback_drc(void *userData, uint8_t *byte_stream, int len)
+{
+	memset(byte_stream, 0, len);
+	if (cur_min_buffered < 0) {
+		//underflow last frame, but main thread hasn't gotten a chance to call SDL_PauseAudio yet
+		return;
+	}
+	cur_min_buffered = 0x7FFFFFFF;
+	min_remaining_buffer = 0xFFFFFFFF;
+	for (uint8_t i = 0; i < num_audio_sources; i++)
+	{
+		
+		int32_t buffered = mix(audio_sources[i], byte_stream, len);
+		cur_min_buffered = buffered < cur_min_buffered ? buffered : cur_min_buffered;
+		uint32_t remaining = (audio_sources[i]->mask + 1)/audio_sources[i]->num_channels - buffered;
+		min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer;
 	}
 }
 
-void render_disable_ym()
+static void lock_audio()
 {
-	ym_enabled = 0;
+	if (sync_to_audio) {
+		SDL_LockMutex(audio_mutex);
+	} else {
+		SDL_LockAudio();
+	}
 }
 
-void render_enable_ym()
+static void unlock_audio()
 {
-	ym_enabled = 1;
+	if (sync_to_audio) {
+		SDL_UnlockMutex(audio_mutex);
+	} else {
+		SDL_UnlockAudio();
+	}
 }
 
 static void render_close_audio()
@@ -111,6 +243,184 @@
 	SDL_CloseAudio();
 }
 
+#define BUFFER_INC_RES 0x40000000UL
+
+void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider)
+{
+	src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider;
+}
+
+audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels)
+{
+	audio_source *ret = NULL;
+	uint32_t alloc_size = sync_to_audio ? channels * buffer_samples : nearest_pow2(min_buffered * 4 * channels);
+	lock_audio();
+		if (num_audio_sources < 8) {
+			ret = malloc(sizeof(audio_source));
+			ret->back = malloc(alloc_size * sizeof(int16_t));
+			ret->front = sync_to_audio ? malloc(alloc_size * sizeof(int16_t)) : ret->back;
+			ret->front_populated = 0;
+			ret->cond = SDL_CreateCond();
+			ret->num_channels = channels;
+			audio_sources[num_audio_sources++] = ret;
+		}
+	unlock_audio();
+	if (!ret) {
+		fatal_error("Too many audio sources!");
+	} else {
+		render_audio_adjust_clock(ret, master_clock, sample_divider);
+		double lowpass_cutoff = get_lowpass_cutoff(config);
+		double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI);
+		ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider));
+		double alpha = ret->dt / (ret->dt + rc);
+		ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha);
+		ret->buffer_pos = 0;
+		ret->buffer_fraction = 0;
+		ret->last_left = ret->last_right = 0;
+		ret->read_start = 0;
+		ret->read_end = sync_to_audio ? buffer_samples * channels : 0;
+		ret->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1;
+	}
+	if (sync_to_audio && SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) {
+		SDL_PauseAudio(0);
+	}
+	return ret;
+}
+
+void render_pause_source(audio_source *src)
+{
+	uint8_t need_pause = 0;
+	lock_audio();
+		for (uint8_t i = 0; i < num_audio_sources; i++)
+		{
+			if (audio_sources[i] == src) {
+				audio_sources[i] = audio_sources[--num_audio_sources];
+				if (sync_to_audio) {
+					SDL_CondSignal(audio_ready);
+				}
+				break;
+			}
+		}
+		if (!num_audio_sources) {
+			need_pause = 1;
+		}
+	unlock_audio();
+	if (need_pause) {
+		SDL_PauseAudio(1);
+	}
+	inactive_audio_sources[num_inactive_audio_sources++] = src;
+}
+
+void render_resume_source(audio_source *src)
+{
+	lock_audio();
+		if (num_audio_sources < 8) {
+			audio_sources[num_audio_sources++] = src;
+		}
+	unlock_audio();
+	for (uint8_t i = 0; i < num_inactive_audio_sources; i++)
+	{
+		if (inactive_audio_sources[i] == src) {
+			inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources];
+		}
+	}
+	if (sync_to_audio) {
+		SDL_PauseAudio(0);
+	}
+}
+
+void render_free_source(audio_source *src)
+{
+	render_pause_source(src);
+	
+	free(src->front);
+	if (sync_to_audio) {
+		free(src->back);
+		SDL_DestroyCond(src->cond);
+	}
+	free(src);
+}
+static uint32_t sync_samples;
+static void do_audio_ready(audio_source *src)
+{
+	if (sync_to_audio) {
+		SDL_LockMutex(audio_mutex);
+			while (src->front_populated) {
+				SDL_CondWait(src->cond, audio_mutex);
+			}
+			int16_t *tmp = src->front;
+			src->front = src->back;
+			src->back = tmp;
+			src->front_populated = 1;
+			src->buffer_pos = 0;
+			SDL_CondSignal(audio_ready);
+		SDL_UnlockMutex(audio_mutex);
+	} else {
+		uint32_t num_buffered;
+		SDL_LockAudio();
+			src->read_end = src->buffer_pos;
+			num_buffered = ((src->read_end - src->read_start) & src->mask) / src->num_channels;
+		SDL_UnlockAudio();
+		if (num_buffered >= min_buffered && SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) {
+			SDL_PauseAudio(0);
+		}
+	}
+}
+
+static int16_t lowpass_sample(audio_source *src, int16_t last, int16_t current)
+{
+	int32_t tmp = current * src->lowpass_alpha + last * (0x10000 - src->lowpass_alpha);
+	current = tmp >> 16;
+	return current;
+}
+
+static void interp_sample(audio_source *src, int16_t last, int16_t current)
+{
+	int64_t tmp = last * ((src->buffer_fraction << 16) / src->buffer_inc);
+	tmp += current * (0x10000 - ((src->buffer_fraction << 16) / src->buffer_inc));
+	src->back[src->buffer_pos++] = tmp >> 16;
+}
+
+void render_put_mono_sample(audio_source *src, int16_t value)
+{
+	value = lowpass_sample(src, src->last_left, value);
+	src->buffer_fraction += src->buffer_inc;
+	uint32_t base = sync_to_audio ? 0 : src->read_end;
+	while (src->buffer_fraction > BUFFER_INC_RES)
+	{
+		src->buffer_fraction -= BUFFER_INC_RES;
+		interp_sample(src, src->last_left, value);
+		
+		if (((src->buffer_pos - base) & src->mask) >= sync_samples) {
+			do_audio_ready(src);
+		}
+		src->buffer_pos &= src->mask;
+	}
+	src->last_left = value;
+}
+
+void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right)
+{
+	left = lowpass_sample(src, src->last_left, left);
+	right = lowpass_sample(src, src->last_right, right);
+	src->buffer_fraction += src->buffer_inc;
+	uint32_t base = sync_to_audio ? 0 : src->read_end;
+	while (src->buffer_fraction > BUFFER_INC_RES)
+	{
+		src->buffer_fraction -= BUFFER_INC_RES;
+		
+		interp_sample(src, src->last_left, left);
+		interp_sample(src, src->last_right, right);
+		
+		if (((src->buffer_pos - base) & src->mask)/2 >= sync_samples) {
+			do_audio_ready(src);
+		}
+		src->buffer_pos &= src->mask;
+	}
+	src->last_left = left;
+	src->last_right = right;
+}
+
 static SDL_Joystick * joysticks[MAX_JOYSTICKS];
 static int joystick_sdl_index[MAX_JOYSTICKS];
 
@@ -131,7 +441,11 @@
 
 uint32_t render_map_color(uint8_t r, uint8_t g, uint8_t b)
 {
+#ifdef USE_GLES
+	return 255 << 24 | b << 16 | g << 8 | r;
+#else
 	return 255 << 24 | r << 16 | g << 8 | b;
+#endif
 }
 
 #ifndef DISABLE_OPENGL
@@ -148,6 +462,16 @@
 
 static const GLushort element_data[] = {0, 1, 2, 3};
 
+static const GLchar shader_prefix[] =
+#ifdef USE_GLES
+	"#version 100\n";
+#else
+	"#version 110\n"
+	"#define lowp\n"
+	"#define mediump\n"
+	"#define highp\n";
+#endif
+
 static GLuint load_shader(char * fname, GLenum shader_type)
 {
 	char const * parts[] = {get_home_dir(), "/.config/blastem/shaders/", fname};
@@ -172,6 +496,12 @@
 		free(text);
 		return 0;
 	}
+	if (strncmp(text, "#version", strlen("#version"))) {
+		GLchar *tmp = text;
+		text = alloc_concat(shader_prefix, tmp);
+		free(tmp);
+		fsize += strlen(shader_prefix);
+	}
 	GLuint ret = glCreateShader(shader_type);
 	glShaderSource(ret, 1, (const GLchar **)&text, (const GLint *)&fsize);
 	free(text);
@@ -193,6 +523,15 @@
 
 static uint32_t texture_buf[512 * 513];
 #ifndef DISABLE_OPENGL
+#ifdef USE_GLES
+#define INTERNAL_FORMAT GL_RGBA
+#define SRC_FORMAT GL_RGBA
+#define RENDER_FORMAT SDL_PIXELFORMAT_ABGR8888
+#else
+#define INTERNAL_FORMAT GL_RGBA8
+#define SRC_FORMAT GL_BGRA
+#define RENDER_FORMAT SDL_PIXELFORMAT_ARGB8888
+#endif
 static void gl_setup()
 {
 	tern_val def = {.ptrval = "linear"};
@@ -208,10 +547,10 @@
 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 		if (i < 2) {
 			//TODO: Fixme for PAL + invalid display mode
-			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf);
+			glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, 512, 512, 0, SRC_FORMAT, GL_UNSIGNED_BYTE, texture_buf);
 		} else {
 			uint32_t blank = 255 << 24;
-			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_BGRA, GL_UNSIGNED_BYTE, &blank);
+			glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, 1, 1, 0, SRC_FORMAT, GL_UNSIGNED_BYTE, &blank);
 		}
 	}
 	glGenBuffers(2, buffers);
@@ -239,12 +578,20 @@
 	un_height = glGetUniformLocation(program, "height");
 	at_pos = glGetAttribLocation(program, "pos");
 }
+
+static void gl_teardown()
+{
+	glDeleteProgram(program);
+	glDeleteShader(vshader);
+	glDeleteShader(fshader);
+	glDeleteBuffers(2, buffers);
+	glDeleteTextures(3, textures);
+}
 #endif
 
+static uint8_t texture_init;
 static void render_alloc_surfaces()
 {
-	static uint8_t texture_init;
-
 	if (texture_init) {
 		return;
 	}
@@ -261,24 +608,38 @@
 		char *scaling = tern_find_path_default(config, "video\0scaling\0", def, TVAL_PTR).ptrval;
 		SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, scaling);
 		//TODO: Fixme for invalid display mode
-		sdl_textures[0] = sdl_textures[1] = SDL_CreateTexture(main_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, LINEBUF_SIZE, 588);
+		sdl_textures[0] = sdl_textures[1] = SDL_CreateTexture(main_renderer, RENDER_FORMAT, SDL_TEXTUREACCESS_STREAMING, LINEBUF_SIZE, 588);
 #ifndef DISABLE_OPENGL
 	}
 #endif
 }
 
+static void free_surfaces(void)
+{
+	for (int i = 0; i < num_textures; i++)
+	{
+		if (sdl_textures[i]) {
+			SDL_DestroyTexture(sdl_textures[i]);
+		}
+	}
+	free(sdl_textures);
+	sdl_textures = NULL;
+	texture_init = 0;
+}
+
 static char * caption = NULL;
 static char * fps_caption = NULL;
 
 static void render_quit()
 {
 	render_close_audio();
-	for (int i = 0; i < num_textures; i++)
-	{
-		if (sdl_textures[i]) {
-			SDL_DestroyTexture(sdl_textures[i]);
-		}
+	free_surfaces();
+#ifndef DISABLE_OPENGL
+	if (render_gl) {
+		gl_teardown();
+		SDL_GL_DeleteContext(main_context);
 	}
+#endif
 }
 
 static float config_aspect()
@@ -338,520 +699,11 @@
 	}
 }
 
-static uint32_t overscan_top[NUM_VID_STD] = {2, 21};
-static uint32_t overscan_bot[NUM_VID_STD] = {1, 17};
-static uint32_t overscan_left[NUM_VID_STD] = {13, 13};
-static uint32_t overscan_right[NUM_VID_STD] = {14, 14};
-static vid_std video_standard = VID_NTSC;
-static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"};
-void render_init(int width, int height, char * title, uint8_t fullscreen)
-{
-	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
-		fatal_error("Unable to init SDL: %s\n", SDL_GetError());
-	}
-	atexit(SDL_Quit);
-	if (height <= 0) {
-		float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f;
-		height = ((float)width / aspect) + 0.5f;
-	}
-	printf("width: %d, height: %d\n", width, height);
-	windowed_width = width;
-	windowed_height = height;
-	
-	uint32_t flags = SDL_WINDOW_RESIZABLE;
-
-	if (fullscreen) {
-		flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
-		SDL_DisplayMode mode;
-		//TODO: Multiple monitor support
-		SDL_GetCurrentDisplayMode(0, &mode);
-		//the SDL2 migration guide suggests setting width and height to 0 when using SDL_WINDOW_FULLSCREEN_DESKTOP
-		//but that doesn't seem to work right when using OpenGL, at least on Linux anyway
-		width = mode.w;
-		height = mode.h;
-	}
-	main_width = width;
-	main_height = height;
-	is_fullscreen = fullscreen;
-
-	render_gl = 0;
-	tern_val def = {.ptrval = "off"};
-	char *vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval;
-	
-	tern_node *video = tern_find_node(config, "video");
-	if (video)
-	{
-		for (int i = 0; i < NUM_VID_STD; i++)
-		{
-			tern_node *std_settings = tern_find_node(video, vid_std_names[i]);
-			if (std_settings) {
-				char *val = tern_find_path_default(std_settings, "overscan\0top\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval;
-				if (val) {
-					overscan_top[i] = atoi(val);
-				}
-				val = tern_find_path_default(std_settings, "overscan\0bottom\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval;
-				if (val) {
-					overscan_bot[i] = atoi(val);
-				}
-				val = tern_find_path_default(std_settings, "overscan\0left\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval;
-				if (val) {
-					overscan_left[i] = atoi(val);
-				}
-				val = tern_find_path_default(std_settings, "overscan\0right\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval;
-				if (val) {
-					overscan_right[i] = atoi(val);
-				}
-			}
-		}
-	}
-
-#ifndef DISABLE_OPENGL
-	char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval;
-	uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0;
-	if (gl_enabled)
-	{
-		flags |= SDL_WINDOW_OPENGL;
-		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
-		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
-		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
-		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
-		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
-	}
-#endif
-	main_window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags);
-	if (!main_window) {
-		fatal_error("Unable to create SDL window: %s\n", SDL_GetError());
-	}
-#ifndef DISABLE_OPENGL
-	if (gl_enabled)
-	{
-		main_context = SDL_GL_CreateContext(main_window);
-		GLenum res = glewInit();
-		if (res != GLEW_OK) {
-			warning("Initialization of GLEW failed with code %d\n", res);
-		}
-
-		if (res == GLEW_OK && GLEW_VERSION_2_0) {
-			render_gl = 1;
-			if (!strcmp("tear", vsync)) {
-				if (SDL_GL_SetSwapInterval(-1) < 0) {
-					warning("late tear is not available (%s), using normal vsync\n", SDL_GetError());
-					vsync = "on";
-				} else {
-					vsync = NULL;
-				}
-			}
-			if (vsync) {
-				if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) {
-					warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError());
-				}
-			}
-		} else {
-			warning("OpenGL 2.0 is unavailable, falling back to SDL2 renderer\n");
-		}
-	}
-	if (!render_gl) {
-#endif
-		flags = SDL_RENDERER_ACCELERATED;
-		if (!strcmp("on", vsync) || !strcmp("tear", vsync)) {
-			flags |= SDL_RENDERER_PRESENTVSYNC;
-		}
-		main_renderer = SDL_CreateRenderer(main_window, -1, flags);
-
-		if (!main_renderer) {
-			fatal_error("unable to create SDL renderer: %s\n", SDL_GetError());
-		}
-		main_clip.x = main_clip.y = 0;
-		main_clip.w = width;
-		main_clip.h = height;
-#ifndef DISABLE_OPENGL
-	}
-#endif
-
-	SDL_GetWindowSize(main_window, &main_width, &main_height);
-	printf("Window created with size: %d x %d\n", main_width, main_height);
-	update_aspect();
-	render_alloc_surfaces();
-	def.ptrval = "off";
-	scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def, TVAL_PTR).ptrval, "on");
-
-	caption = title;
-
-	audio_mutex = SDL_CreateMutex();
-	psg_cond = SDL_CreateCond();
-	ym_cond = SDL_CreateCond();
-	audio_ready = SDL_CreateCond();
-
-	SDL_AudioSpec desired, actual;
-    char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval;
-   	int rate = rate_str ? atoi(rate_str) : 0;
-   	if (!rate) {
-   		rate = 48000;
-   	}
-    desired.freq = rate;
-	desired.format = AUDIO_S16SYS;
-	desired.channels = 2;
-    char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval;
-   	int samples = samples_str ? atoi(samples_str) : 0;
-   	if (!samples) {
-   		samples = 512;
-   	}
-    printf("config says: %d\n", samples);
-    desired.samples = samples*2;
-	desired.callback = audio_callback;
-	desired.userdata = NULL;
-
-	if (SDL_OpenAudio(&desired, &actual) < 0) {
-		fatal_error("Unable to open SDL audio: %s\n", SDL_GetError());
-	}
-	buffer_samples = actual.samples;
-	sample_rate = actual.freq;
-	printf("Initialized audio at frequency %d with a %d sample buffer\n", actual.freq, actual.samples);
-	SDL_PauseAudio(0);
-	
-	uint32_t db_size;
-	char *db_data = read_bundled_file("gamecontrollerdb.txt", &db_size);
-	if (db_data) {
-		int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1);
-		free(db_data);
-		printf("Added %d game controller mappings from gamecontrollerdb.txt\n", added);
-	}
-	
-	SDL_JoystickEventState(SDL_ENABLE);
-
-	atexit(render_quit);
-}
-
-void render_set_video_standard(vid_std std)
-{
-	video_standard = std;
-}
-
-void render_update_caption(char *title)
-{
-	caption = title;
-	free(fps_caption);
-	fps_caption = NULL;
-}
-
-static char *screenshot_path;
-void render_save_screenshot(char *path)
-{
-	if (screenshot_path) {
-		free(screenshot_path);
-	}
-	screenshot_path = path;
-}
-
-uint32_t *locked_pixels;
-uint32_t locked_pitch;
-uint32_t *render_get_framebuffer(uint8_t which, int *pitch)
-{
-#ifndef DISABLE_OPENGL
-	if (render_gl && which <= FRAMEBUFFER_EVEN) {
-		*pitch = LINEBUF_SIZE * sizeof(uint32_t);
-		return texture_buf;
-	} else {
-#endif
-		if (which >= num_textures) {
-			warning("Request for invalid framebuffer number %d\n", which);
-			return NULL;
-		}
-		void *pixels;
-		if (SDL_LockTexture(sdl_textures[which], NULL, &pixels, pitch) < 0) {
-			warning("Failed to lock texture: %s\n", SDL_GetError());
-			return NULL;
-		}
-		static uint8_t last;
-		if (which <= FRAMEBUFFER_EVEN) {
-			locked_pixels = pixels;
-			if (which == FRAMEBUFFER_EVEN) {
-				pixels += *pitch;
-			}
-			locked_pitch = *pitch;
-			if (which != last) {
-				*pitch *= 2;
-			}
-			last = which;
-		}
-		return pixels;
-#ifndef DISABLE_OPENGL
-	}
-#endif
-}
-
-uint8_t events_processed;
-#ifdef __ANDROID__
-#define FPS_INTERVAL 10000
-#else
-#define FPS_INTERVAL 1000
-#endif
-
-static uint32_t last_width;
-void render_framebuffer_updated(uint8_t which, int width)
+static ui_render_fun on_context_destroyed, on_context_created;
+void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create)
 {
-	static uint8_t last;
-	last_width = width;
-	uint32_t height = which <= FRAMEBUFFER_EVEN 
-		? (video_standard == VID_NTSC ? 243 : 294) - (overscan_top[video_standard] + overscan_bot[video_standard])
-		: 240;
-	FILE *screenshot_file = NULL;
-	uint32_t shot_height, shot_width;
-	if (screenshot_path && which == FRAMEBUFFER_ODD) {
-		screenshot_file = fopen(screenshot_path, "wb");
-		if (screenshot_file) {
-			info_message("Saving screenshot to %s\n", screenshot_path);
-		} else {
-			warning("Failed to open screenshot file %s for writing\n", screenshot_path);
-		}
-		free(screenshot_path);
-		screenshot_path = NULL;
-		shot_height = video_standard == VID_NTSC ? 243 : 294;
-		shot_width = width;
-	}
-	width -= overscan_left[video_standard] + overscan_right[video_standard];
-#ifndef DISABLE_OPENGL
-	if (render_gl && which <= FRAMEBUFFER_EVEN) {
-		glBindTexture(GL_TEXTURE_2D, textures[which]);
-		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LINEBUF_SIZE, height, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]);
-
-		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-		glClear(GL_COLOR_BUFFER_BIT);
-
-		glUseProgram(program);
-		glActiveTexture(GL_TEXTURE0);
-		glBindTexture(GL_TEXTURE_2D, textures[0]);
-		glUniform1i(un_textures[0], 0);
-
-		glActiveTexture(GL_TEXTURE1);
-		glBindTexture(GL_TEXTURE_2D, textures[last != which ? 1 : scanlines ? 2 : 0]);
-		glUniform1i(un_textures[1], 1);
-
-		glUniform1f(un_width, width);
-		glUniform1f(un_height, height);
-
-		glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
-		glVertexAttribPointer(at_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat[2]), (void *)0);
-		glEnableVertexAttribArray(at_pos);
-
-		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
-		glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void *)0);
-
-		glDisableVertexAttribArray(at_pos);
-
-		SDL_GL_SwapWindow(main_window);
-		
-		if (screenshot_file) {
-			//properly supporting interlaced modes here is non-trivial, so only save the odd field for now
-			save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t));
-		}
-	} else {
-#endif
-		if (which <= FRAMEBUFFER_EVEN && last != which) {
-			uint8_t *cur_dst = (uint8_t *)locked_pixels;
-			uint8_t *cur_saved = (uint8_t *)texture_buf;
-			uint32_t dst_off = which == FRAMEBUFFER_EVEN ? 0 : locked_pitch;
-			uint32_t src_off = which == FRAMEBUFFER_EVEN ? locked_pitch : 0;
-			for (int i = 0; i < height; ++i)
-			{
-				//copy saved line from other field
-				memcpy(cur_dst + dst_off, cur_saved, locked_pitch);
-				//save line from this field to buffer for next frame
-				memcpy(cur_saved, cur_dst + src_off, locked_pitch);
-				cur_dst += locked_pitch * 2;
-				cur_saved += locked_pitch;
-			}
-			height = 480;
-		}
-		if (screenshot_file) {
-			uint32_t shot_pitch = locked_pitch;
-			if (which == FRAMEBUFFER_EVEN) {
-				shot_height *= 2;
-			} else {
-				shot_pitch *= 2;
-			}
-			save_ppm(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch);
-		}
-		SDL_UnlockTexture(sdl_textures[which]);
-		SDL_Rect src_clip = {
-			.x = overscan_left[video_standard],
-			.y = overscan_top[video_standard],
-			.w = width,
-			.h = height
-		};
-		SDL_SetRenderDrawColor(main_renderer, 0, 0, 0, 255);
-		SDL_RenderClear(main_renderer);
-		SDL_RenderCopy(main_renderer, sdl_textures[which], &src_clip, &main_clip);
-		SDL_RenderPresent(main_renderer);
-#ifndef DISABLE_OPENGL
-	}
-#endif
-	if (screenshot_file) {
-		fclose(screenshot_file);
-	}
-	if (which <= FRAMEBUFFER_EVEN) {
-		last = which;
-		static uint32_t frame_counter, start;
-		frame_counter++;
-		last_frame= SDL_GetTicks();
-		if ((last_frame - start) > FPS_INTERVAL) {
-			if (start && (last_frame-start)) {
-	#ifdef __ANDROID__
-				info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0));
-	#else
-				if (!fps_caption) {
-					fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1);
-				}
-				sprintf(fps_caption, "%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0));
-				SDL_SetWindowTitle(main_window, fps_caption);
-	#endif
-			}
-			start = last_frame;
-			frame_counter = 0;
-		}
-	}
-	if (!events_processed) {
-		process_events();
-	}
-	events_processed = 0;
-}
-
-uint32_t render_emulated_width()
-{
-	return last_width - overscan_left[video_standard] - overscan_right[video_standard];
-}
-
-uint32_t render_emulated_height()
-{
-	return (video_standard == VID_NTSC ? 243 : 294) - overscan_top[video_standard] - overscan_bot[video_standard];
-}
-
-uint32_t render_overscan_left()
-{
-	return overscan_left[video_standard];
-}
-
-uint32_t render_overscan_top()
-{
-	return overscan_top[video_standard];
-}
-
-void render_wait_quit(vdp_context * context)
-{
-	SDL_Event event;
-	while(SDL_WaitEvent(&event)) {
-		switch (event.type) {
-		case SDL_QUIT:
-			return;
-		}
-	}
-}
-
-static int find_joystick_index(SDL_JoystickID instanceID)
-{
-	for (int i = 0; i < MAX_JOYSTICKS; i++) {
-		if (joysticks[i] && SDL_JoystickInstanceID(joysticks[i]) == instanceID) {
-			return i;
-		}
-	}
-	return -1;
-}
-
-static int lowest_unused_joystick_index()
-{
-	for (int i = 0; i < MAX_JOYSTICKS; i++) {
-		if (!joysticks[i]) {
-			return i;
-		}
-	}
-	return -1;
-}
-
-int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis)
-{
-	static tern_node *button_lookup, *axis_lookup;
-	if (controller > MAX_JOYSTICKS || !joysticks[controller]) {
-		return RENDER_NOT_PLUGGED_IN;
-	}
-	
-	if (!SDL_IsGameController(joystick_sdl_index[controller])) {
-		return RENDER_NOT_MAPPED;
-	}
-	SDL_GameController *control = SDL_GameControllerOpen(joystick_sdl_index[controller]);
-	if (!control) {
-		warning("Failed to open game controller %d: %s\n", controller, SDL_GetError());
-		return RENDER_NOT_PLUGGED_IN;
-	}
-	
-	SDL_GameControllerButtonBind cbind;
-	if (is_axis) {
-		if (!axis_lookup) {
-			for (int i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++)
-			{
-				axis_lookup = tern_insert_int(axis_lookup, SDL_GameControllerGetStringForAxis(i), i);
-			}
-			//alternative Playstation-style names
-			axis_lookup = tern_insert_int(axis_lookup, "l2", SDL_CONTROLLER_AXIS_TRIGGERLEFT);
-			axis_lookup = tern_insert_int(axis_lookup, "r2", SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
-		}
-		intptr_t sdl_axis = tern_find_int(axis_lookup, name, SDL_CONTROLLER_AXIS_INVALID);
-		if (sdl_axis == SDL_CONTROLLER_AXIS_INVALID) {
-			SDL_GameControllerClose(control);
-			return RENDER_INVALID_NAME;
-		}
-		cbind = SDL_GameControllerGetBindForAxis(control, sdl_axis);
-	} else {
-		if (!button_lookup) {
-			for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++)
-			{
-				button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i);
-			}
-			//alternative Playstation-style names
-			button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A);
-			button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B);
-			button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X);
-			button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y);
-			button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK);
-			button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK);
-			button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START);
-			button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
-			button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
-			button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK);
-			button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK);
-		}
-		intptr_t sdl_button = tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID);
-		if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) {
-			SDL_GameControllerClose(control);
-			return RENDER_INVALID_NAME;
-		}
-		cbind = SDL_GameControllerGetBindForButton(control, sdl_button);
-	}
-	SDL_GameControllerClose(control);
-	switch (cbind.bindType)
-	{
-	case SDL_CONTROLLER_BINDTYPE_BUTTON:
-		return cbind.value.button;
-	case SDL_CONTROLLER_BINDTYPE_AXIS:
-		return RENDER_AXIS_BIT | cbind.value.axis;
-	case SDL_CONTROLLER_BINDTYPE_HAT:
-		return RENDER_DPAD_BIT | (cbind.value.hat.hat << 4) | cbind.value.hat.hat_mask;
-	}
-	return RENDER_NOT_MAPPED;
-}
-
-int32_t render_dpad_part(int32_t input)
-{
-	return input >> 4 & 0xFFFFFF;
-}
-
-uint8_t render_direction_part(int32_t input)
-{
-	return input & 0xF;
-}
-
-int32_t render_axis_part(int32_t input)
-{
-	return input & 0xFFFFFFF;
+	on_context_destroyed = destroy;
+	on_context_created = create;
 }
 
 static uint8_t scancode_map[SDL_NUM_SCANCODES] = {
@@ -964,8 +816,70 @@
 	drag_drop_handler = handler;
 }
 
+static event_handler custom_event_handler;
+void render_set_event_handler(event_handler handler)
+{
+	custom_event_handler = handler;
+}
+
+static int find_joystick_index(SDL_JoystickID instanceID)
+{
+	for (int i = 0; i < MAX_JOYSTICKS; i++) {
+		if (joysticks[i] && SDL_JoystickInstanceID(joysticks[i]) == instanceID) {
+			return i;
+		}
+	}
+	return -1;
+}
+
+static int lowest_unused_joystick_index()
+{
+	for (int i = 0; i < MAX_JOYSTICKS; i++) {
+		if (!joysticks[i]) {
+			return i;
+		}
+	}
+	return -1;
+}
+
+SDL_Joystick *render_get_joystick(int index)
+{
+	if (index >= MAX_JOYSTICKS) {
+		return NULL;
+	}
+	return joysticks[index];
+}
+
+char* render_joystick_type_id(int index)
+{
+	SDL_Joystick *stick = render_get_joystick(index);
+	if (!stick) {
+		return NULL;
+	}
+	char *guid_string = malloc(33);
+	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(stick), guid_string, 33);
+	return guid_string;
+}
+
+SDL_GameController *render_get_controller(int index)
+{
+	if (index >= MAX_JOYSTICKS) {
+		return NULL;
+	}
+	return SDL_GameControllerOpen(joystick_sdl_index[index]);
+}
+
+static uint32_t overscan_top[NUM_VID_STD] = {2, 21};
+static uint32_t overscan_bot[NUM_VID_STD] = {1, 17};
+static uint32_t overscan_left[NUM_VID_STD] = {13, 13};
+static uint32_t overscan_right[NUM_VID_STD] = {14, 14};
+static vid_std video_standard = VID_NTSC;
+
 static int32_t handle_event(SDL_Event *event)
 {
+	if (custom_event_handler) {
+		custom_event_handler(event);
+	}
 	switch (event->type) {
 	case SDL_KEYDOWN:
 		handle_keydown(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]);
@@ -980,7 +894,7 @@
 		handle_joyup(find_joystick_index(event->jbutton.which), event->jbutton.button);
 		break;
 	case SDL_JOYHATMOTION:
-		handle_joy_dpad(find_joystick_index(event->jbutton.which), event->jhat.hat, event->jhat.value);
+		handle_joy_dpad(find_joystick_index(event->jhat.which), event->jhat.hat, event->jhat.value);
 		break;
 	case SDL_JOYAXISMOTION:
 		handle_joy_axis(find_joystick_index(event->jaxis.which), event->jaxis.axis, event->jaxis.value);
@@ -1028,12 +942,34 @@
 			update_aspect();
 #ifndef DISABLE_OPENGL
 			if (render_gl) {
+				if (on_context_destroyed) {
+					on_context_destroyed();
+				}
+				gl_teardown();
 				SDL_GL_DeleteContext(main_context);
 				main_context = SDL_GL_CreateContext(main_window);
 				gl_setup();
+				if (on_context_created) {
+					on_context_created();
+				}
 			}
 #endif
 			break;
+		case SDL_WINDOWEVENT_CLOSE:
+			if (SDL_GetWindowID(main_window) == event->window.windowID) {
+				exit(0);
+			} else {
+				for (int i = 0; i < num_textures - FRAMEBUFFER_USER_START; i++)
+				{
+					if (SDL_GetWindowID(extra_windows[i]) == event->window.windowID) {
+						if (close_handlers[i]) {
+							close_handlers[i](i + FRAMEBUFFER_USER_START);
+						}
+						break;
+					}
+				}
+			}
+			break;
 		}
 		break;
 	case SDL_DROPFILE:
@@ -1058,6 +994,885 @@
 	}
 }
 
+static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"};
+static int display_hz;
+static int source_hz;
+static int source_frame;
+static int source_frame_count;
+static int frame_repeat[60];
+
+static void init_audio()
+{
+	SDL_AudioSpec desired, actual;
+    char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval;
+   	int rate = rate_str ? atoi(rate_str) : 0;
+   	if (!rate) {
+   		rate = 48000;
+   	}
+    desired.freq = rate;
+	desired.format = AUDIO_S16SYS;
+	desired.channels = 2;
+    char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval;
+   	int samples = samples_str ? atoi(samples_str) : 0;
+   	if (!samples) {
+   		samples = 512;
+   	}
+    printf("config says: %d\n", samples);
+    desired.samples = samples*2;
+	desired.callback = sync_to_audio ? audio_callback : audio_callback_drc;
+	desired.userdata = NULL;
+
+	if (SDL_OpenAudio(&desired, &actual) < 0) {
+		fatal_error("Unable to open SDL audio: %s\n", SDL_GetError());
+	}
+	buffer_samples = actual.samples;
+	sample_rate = actual.freq;
+	printf("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples);
+	if (actual.format == AUDIO_S16SYS) {
+		puts("signed 16-bit int format");
+		mix = mix_s16;
+	} else if (actual.format == AUDIO_F32SYS) {
+		puts("32-bit float format");
+		mix = mix_f32;
+	} else {
+		printf("unsupported format %X\n", actual.format);
+		warning("Unsupported audio sample format: %X\n", actual.format);
+		mix = mix_null;
+	}
+}
+
+void window_setup(void)
+{
+	uint32_t flags = SDL_WINDOW_RESIZABLE;
+	if (is_fullscreen) {
+		flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
+	}
+	
+	tern_val def = {.ptrval = "audio"};
+	char *sync_src = tern_find_path_default(config, "system\0sync_source\0", def, TVAL_PTR).ptrval;
+	sync_to_audio = !strcmp(sync_src, "audio");
+	
+	const char *vsync;
+	if (sync_to_audio) {
+		def.ptrval = "off";
+		vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval;
+	} else {
+		vsync = "on";
+	}
+	
+	tern_node *video = tern_find_node(config, "video");
+	if (video)
+	{
+		for (int i = 0; i < NUM_VID_STD; i++)
+		{
+			tern_node *std_settings = tern_find_node(video, vid_std_names[i]);
+			if (std_settings) {
+				char *val = tern_find_path_default(std_settings, "overscan\0top\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval;
+				if (val) {
+					overscan_top[i] = atoi(val);
+				}
+				val = tern_find_path_default(std_settings, "overscan\0bottom\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval;
+				if (val) {
+					overscan_bot[i] = atoi(val);
+				}
+				val = tern_find_path_default(std_settings, "overscan\0left\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval;
+				if (val) {
+					overscan_left[i] = atoi(val);
+				}
+				val = tern_find_path_default(std_settings, "overscan\0right\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval;
+				if (val) {
+					overscan_right[i] = atoi(val);
+				}
+			}
+		}
+	}
+	render_gl = 0;
+	
+#ifndef DISABLE_OPENGL
+	char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval;
+	uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0;
+	if (gl_enabled)
+	{
+		flags |= SDL_WINDOW_OPENGL;
+		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
+		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
+		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
+		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
+		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+#ifdef USE_GLES
+		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
+		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+#endif
+	}
+#endif
+	main_window = SDL_CreateWindow(caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, main_width, main_height, flags);
+	if (!main_window) {
+		fatal_error("Unable to create SDL window: %s\n", SDL_GetError());
+	}
+#ifndef DISABLE_OPENGL
+	if (gl_enabled)
+	{
+		main_context = SDL_GL_CreateContext(main_window);
+#ifdef USE_GLES
+		int major_version;
+		if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major_version) == 0 && major_version >= 2) {
+#else
+		GLenum res = glewInit();
+		if (res != GLEW_OK) {
+			warning("Initialization of GLEW failed with code %d\n", res);
+		}
+
+		if (res == GLEW_OK && GLEW_VERSION_2_0) {
+#endif
+			render_gl = 1;
+			SDL_GL_MakeCurrent(main_window, main_context);
+			if (!strcmp("tear", vsync)) {
+				if (SDL_GL_SetSwapInterval(-1) < 0) {
+					warning("late tear is not available (%s), using normal vsync\n", SDL_GetError());
+					vsync = "on";
+				} else {
+					vsync = NULL;
+				}
+			}
+			if (vsync) {
+				if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) {
+					warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError());
+				}
+			}
+		} else {
+			warning("OpenGL 2.0 is unavailable, falling back to SDL2 renderer\n");
+		}
+	}
+	if (!render_gl) {
+#endif
+		flags = SDL_RENDERER_ACCELERATED;
+		if (!strcmp("on", vsync) || !strcmp("tear", vsync)) {
+			flags |= SDL_RENDERER_PRESENTVSYNC;
+		}
+		main_renderer = SDL_CreateRenderer(main_window, -1, flags);
+
+		if (!main_renderer) {
+			fatal_error("unable to create SDL renderer: %s\n", SDL_GetError());
+		}
+		SDL_RendererInfo rinfo;
+		SDL_GetRendererInfo(main_renderer, &rinfo);
+		printf("SDL2 Render Driver: %s\n", rinfo.name);
+		main_clip.x = main_clip.y = 0;
+		main_clip.w = main_width;
+		main_clip.h = main_height;
+#ifndef DISABLE_OPENGL
+	}
+#endif
+
+	SDL_GetWindowSize(main_window, &main_width, &main_height);
+	printf("Window created with size: %d x %d\n", main_width, main_height);
+	update_aspect();
+	render_alloc_surfaces();
+	def.ptrval = "off";
+	scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def, TVAL_PTR).ptrval, "on");
+}
+
+void render_init(int width, int height, char * title, uint8_t fullscreen)
+{
+	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
+		fatal_error("Unable to init SDL: %s\n", SDL_GetError());
+	}
+	atexit(SDL_Quit);
+	if (height <= 0) {
+		float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f;
+		height = ((float)width / aspect) + 0.5f;
+	}
+	printf("width: %d, height: %d\n", width, height);
+	windowed_width = width;
+	windowed_height = height;
+	
+	SDL_DisplayMode mode;
+	//TODO: Explicit multiple monitor support
+	SDL_GetCurrentDisplayMode(0, &mode);
+	display_hz = mode.refresh_rate;
+
+	if (fullscreen) {
+		//the SDL2 migration guide suggests setting width and height to 0 when using SDL_WINDOW_FULLSCREEN_DESKTOP
+		//but that doesn't seem to work right when using OpenGL, at least on Linux anyway
+		width = mode.w;
+		height = mode.h;
+	}
+	main_width = width;
+	main_height = height;
+	is_fullscreen = fullscreen;
+	
+	caption = title;
+	
+	window_setup();
+
+	audio_mutex = SDL_CreateMutex();
+	audio_ready = SDL_CreateCond();
+	
+	init_audio();
+	
+	uint32_t db_size;
+	char *db_data = read_bundled_file("gamecontrollerdb.txt", &db_size);
+	if (db_data) {
+		int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1);
+		free(db_data);
+		printf("Added %d game controller mappings from gamecontrollerdb.txt\n", added);
+	}
+	
+	controller_add_mappings();
+	
+	SDL_JoystickEventState(SDL_ENABLE);
+	
+	render_set_video_standard(VID_NTSC);
+
+	atexit(render_quit);
+}
+#include<unistd.h>
+static int in_toggle;
+static void update_source(audio_source *src, double rc, uint8_t sync_changed)
+{
+	double alpha = src->dt / (src->dt + rc);
+	int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha);
+	src->lowpass_alpha = lowpass_alpha;
+	if (sync_changed) {
+		uint32_t alloc_size = sync_to_audio ? src->num_channels * buffer_samples : nearest_pow2(min_buffered * 4 * src->num_channels);
+		src->back = realloc(src->back, alloc_size * sizeof(int16_t));
+		if (sync_to_audio) {
+			src->front = malloc(alloc_size * sizeof(int16_t));
+		} else {
+			free(src->front);
+			src->front = src->back;
+		}
+		src->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1;
+		src->read_start = 0;
+		src->read_end = sync_to_audio ? buffer_samples * src->num_channels : 0;
+		src->buffer_pos = 0;
+	}
+}
+
+void render_config_updated(void)
+{
+	uint8_t old_sync_to_audio = sync_to_audio;
+	
+	free_surfaces();
+#ifndef DISABLE_OPENGL
+	if (render_gl) {
+		if (on_context_destroyed) {
+			on_context_destroyed();
+		}
+		gl_teardown();
+		SDL_GL_DeleteContext(main_context);
+	} else {
+#endif
+		SDL_DestroyRenderer(main_renderer);
+#ifndef DISABLE_OPENGL
+	}
+#endif
+	in_toggle = 1;
+	SDL_DestroyWindow(main_window);
+	drain_events();
+	
+	char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval;
+	if (config_width) {
+		windowed_width = atoi(config_width);
+	}
+	char *config_height = tern_find_path(config, "video\0height\0", TVAL_PTR).ptrval;
+	if (config_height) {
+		windowed_height = atoi(config_height);
+	} else {
+		float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f;
+		windowed_height = ((float)windowed_width / aspect) + 0.5f;
+	}
+	char *config_fullscreen = tern_find_path(config, "video\0fullscreen\0", TVAL_PTR).ptrval;
+	is_fullscreen = config_fullscreen && !strcmp("on", config_fullscreen);
+	if (is_fullscreen) {
+		SDL_DisplayMode mode;
+		//TODO: Multiple monitor support
+		SDL_GetCurrentDisplayMode(0, &mode);
+		main_width = mode.w;
+		main_height = mode.h;
+	} else {
+		main_width = windowed_width;
+		main_height = windowed_height;
+	}
+	
+	window_setup();
+	update_aspect();
+#ifndef DISABLE_OPENGL
+	//need to check render_gl again after window_setup as render option could have changed
+	if (render_gl && on_context_created) {
+		on_context_created();
+	}
+#endif
+
+	uint8_t was_paused = SDL_GetAudioStatus() == SDL_AUDIO_PAUSED;
+	render_close_audio();
+	quitting = 0;
+	init_audio();
+	render_set_video_standard(video_standard);
+	
+	double lowpass_cutoff = get_lowpass_cutoff(config);
+	double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI);
+	lock_audio();
+		for (uint8_t i = 0; i < num_audio_sources; i++)
+		{
+			update_source(audio_sources[i], rc, old_sync_to_audio != sync_to_audio);
+		}
+	unlock_audio();
+	for (uint8_t i = 0; i < num_inactive_audio_sources; i++)
+	{
+		update_source(inactive_audio_sources[i], rc, old_sync_to_audio != sync_to_audio);
+	}
+	drain_events();
+	in_toggle = 0;
+	if (!was_paused) {
+		SDL_PauseAudio(0);
+	}
+}
+
+SDL_Window *render_get_window(void)
+{
+	return main_window;
+}
+
+void render_set_video_standard(vid_std std)
+{
+	video_standard = std;
+	source_hz = std == VID_PAL ? 50 : 60;
+	uint32_t max_repeat = 0;
+	if (abs(source_hz - display_hz) < 2) {
+		memset(frame_repeat, 0, sizeof(int)*display_hz);
+	} else {
+		int inc = display_hz * 100000 / source_hz;
+		int accum = 0;
+		int dst_frames = 0;
+		for (int src_frame = 0; src_frame < source_hz; src_frame++)
+		{
+			frame_repeat[src_frame] = -1;
+			accum += inc;
+			while (accum > 100000)
+			{
+				accum -= 100000;
+				frame_repeat[src_frame]++;
+				max_repeat = frame_repeat[src_frame] > max_repeat ? frame_repeat[src_frame] : max_repeat;
+				dst_frames++;
+			}
+		}
+		if (dst_frames != display_hz) {
+			frame_repeat[source_hz-1] += display_hz - dst_frames;
+		}
+	}
+	source_frame = 0;
+	source_frame_count = frame_repeat[0];
+	//sync samples with audio thread approximately every 8 lines
+	sync_samples = sync_to_audio ? buffer_samples : 8 * sample_rate / (source_hz * (VID_PAL ? 313 : 262));
+	max_repeat++;
+	min_buffered = (((float)max_repeat * (float)sample_rate/(float)source_hz)/* / (float)buffer_samples*/);// + 0.9999;
+	//min_buffered *= buffer_samples;
+	printf("Min samples buffered before audio start: %d\n", min_buffered);
+	max_adjust = BASE_MAX_ADJUST / source_hz;
+}
+
+void render_update_caption(char *title)
+{
+	caption = title;
+	free(fps_caption);
+	fps_caption = NULL;
+}
+
+static char *screenshot_path;
+void render_save_screenshot(char *path)
+{
+	if (screenshot_path) {
+		free(screenshot_path);
+	}
+	screenshot_path = path;
+}
+
+uint8_t render_create_window(char *caption, uint32_t width, uint32_t height, window_close_handler close_handler)
+{
+	uint8_t win_idx = 0xFF;
+	for (int i = 0; i < num_textures - FRAMEBUFFER_USER_START; i++)
+	{
+		if (!extra_windows[i]) {
+			win_idx = i;
+			break;
+		}
+	}
+	
+	if (win_idx == 0xFF) {
+		num_textures++;
+		sdl_textures = realloc(sdl_textures, num_textures * sizeof(*sdl_textures));
+		extra_windows = realloc(extra_windows, (num_textures - FRAMEBUFFER_USER_START) * sizeof(*extra_windows));
+		extra_renderers = realloc(extra_renderers, (num_textures - FRAMEBUFFER_USER_START) * sizeof(*extra_renderers));
+		close_handlers = realloc(close_handlers, (num_textures - FRAMEBUFFER_USER_START) * sizeof(*close_handlers));
+		win_idx = num_textures - FRAMEBUFFER_USER_START - 1;
+	}
+	extra_windows[win_idx] = SDL_CreateWindow(caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0);
+	if (!extra_windows[win_idx]) {
+		goto fail_window;
+	}
+	extra_renderers[win_idx] = SDL_CreateRenderer(extra_windows[win_idx], -1, SDL_RENDERER_ACCELERATED);
+	if (!extra_renderers[win_idx]) {
+		goto fail_renderer;
+	}
+	uint8_t texture_idx = win_idx + FRAMEBUFFER_USER_START;
+	sdl_textures[texture_idx] = SDL_CreateTexture(extra_renderers[win_idx], SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
+	if (!sdl_textures[texture_idx]) {
+		goto fail_texture;
+	}
+	close_handlers[win_idx] = close_handler;
+	return texture_idx;
+	
+fail_texture:
+	SDL_DestroyRenderer(extra_renderers[win_idx]);
+fail_renderer:
+	SDL_DestroyWindow(extra_windows[win_idx]);
+fail_window:
+	num_textures--;
+	return 0;
+}
+
+void render_destroy_window(uint8_t which)
+{
+	uint8_t win_idx = which - FRAMEBUFFER_USER_START;
+	//Destroying the renderers also frees the textures
+	SDL_DestroyRenderer(extra_renderers[win_idx]);
+	SDL_DestroyWindow(extra_windows[win_idx]);
+	
+	extra_renderers[win_idx] = NULL;
+	extra_windows[win_idx] = NULL;
+}
+
+uint32_t *locked_pixels;
+uint32_t locked_pitch;
+uint32_t *render_get_framebuffer(uint8_t which, int *pitch)
+{
+#ifndef DISABLE_OPENGL
+	if (render_gl && which <= FRAMEBUFFER_EVEN) {
+		*pitch = LINEBUF_SIZE * sizeof(uint32_t);
+		return texture_buf;
+	} else {
+#endif
+		if (which >= num_textures) {
+			warning("Request for invalid framebuffer number %d\n", which);
+			return NULL;
+		}
+		void *pixels;
+		if (SDL_LockTexture(sdl_textures[which], NULL, &pixels, pitch) < 0) {
+			warning("Failed to lock texture: %s\n", SDL_GetError());
+			return NULL;
+		}
+		static uint8_t last;
+		if (which <= FRAMEBUFFER_EVEN) {
+			locked_pixels = pixels;
+			if (which == FRAMEBUFFER_EVEN) {
+				pixels += *pitch;
+			}
+			locked_pitch = *pitch;
+			if (which != last) {
+				*pitch *= 2;
+			}
+			last = which;
+		}
+		return pixels;
+#ifndef DISABLE_OPENGL
+	}
+#endif
+}
+
+uint8_t events_processed;
+#ifdef __ANDROID__
+#define FPS_INTERVAL 10000
+#else
+#define FPS_INTERVAL 1000
+#endif
+
+static uint32_t last_width, last_height;
+static uint8_t interlaced;
+void render_framebuffer_updated(uint8_t which, int width)
+{
+	static uint8_t last;
+	if (!sync_to_audio && which <= FRAMEBUFFER_EVEN && source_frame_count < 0) {
+		source_frame++;
+		if (source_frame >= source_hz) {
+			source_frame = 0;
+		}
+		source_frame_count = frame_repeat[source_frame];
+		//TODO: Figure out what to do about SDL Render API texture locking
+		return;
+	}
+	
+	last_width = width;
+	uint32_t height = which <= FRAMEBUFFER_EVEN 
+		? (video_standard == VID_NTSC ? 243 : 294) - (overscan_top[video_standard] + overscan_bot[video_standard])
+		: 240;
+	FILE *screenshot_file = NULL;
+	uint32_t shot_height, shot_width;
+	char *ext;
+	if (screenshot_path && which == FRAMEBUFFER_ODD) {
+		screenshot_file = fopen(screenshot_path, "wb");
+		if (screenshot_file) {
+#ifndef DISABLE_ZLIB
+			ext = path_extension(screenshot_path);
+#endif
+			info_message("Saving screenshot to %s\n", screenshot_path);
+		} else {
+			warning("Failed to open screenshot file %s for writing\n", screenshot_path);
+		}
+		free(screenshot_path);
+		screenshot_path = NULL;
+		shot_height = video_standard == VID_NTSC ? 243 : 294;
+		shot_width = width;
+	}
+	interlaced = last != which;
+	width -= overscan_left[video_standard] + overscan_right[video_standard];
+#ifndef DISABLE_OPENGL
+	if (render_gl && which <= FRAMEBUFFER_EVEN) {
+		SDL_GL_MakeCurrent(main_window, main_context);
+		glBindTexture(GL_TEXTURE_2D, textures[which]);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LINEBUF_SIZE, height, SRC_FORMAT, GL_UNSIGNED_BYTE, texture_buf + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]);
+		
+		if (screenshot_file) {
+			//properly supporting interlaced modes here is non-trivial, so only save the odd field for now
+#ifndef DISABLE_ZLIB
+			if (!strcasecmp(ext, "png")) {
+				free(ext);
+				save_png(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t));
+			} else {
+				free(ext);
+#endif
+				save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t));
+#ifndef DISABLE_ZLIB
+			}
+#endif
+		}
+	} else {
+#endif
+		if (which <= FRAMEBUFFER_EVEN && last != which) {
+			uint8_t *cur_dst = (uint8_t *)locked_pixels;
+			uint8_t *cur_saved = (uint8_t *)texture_buf;
+			uint32_t dst_off = which == FRAMEBUFFER_EVEN ? 0 : locked_pitch;
+			uint32_t src_off = which == FRAMEBUFFER_EVEN ? locked_pitch : 0;
+			for (int i = 0; i < height; ++i)
+			{
+				//copy saved line from other field
+				memcpy(cur_dst + dst_off, cur_saved, locked_pitch);
+				//save line from this field to buffer for next frame
+				memcpy(cur_saved, cur_dst + src_off, locked_pitch);
+				cur_dst += locked_pitch * 2;
+				cur_saved += locked_pitch;
+			}
+			height = 480;
+		}
+		if (screenshot_file) {
+			uint32_t shot_pitch = locked_pitch;
+			if (which == FRAMEBUFFER_EVEN) {
+				shot_height *= 2;
+			} else {
+				shot_pitch *= 2;
+			}
+#ifndef DISABLE_ZLIB
+			if (!strcasecmp(ext, "png")) {
+				free(ext);
+				save_png(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch);
+			} else {
+				free(ext);
+#endif
+				save_ppm(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch);
+#ifndef DISABLE_ZLIB
+			}
+#endif
+		}
+		SDL_UnlockTexture(sdl_textures[which]);
+#ifndef DISABLE_OPENGL
+	}
+#endif
+	last_height = height;
+	if (which <= FRAMEBUFFER_EVEN) {
+		render_update_display();
+	} else {
+		SDL_RenderCopy(extra_renderers[which - FRAMEBUFFER_USER_START], sdl_textures[which], NULL, NULL);
+		SDL_RenderPresent(extra_renderers[which - FRAMEBUFFER_USER_START]);
+	}
+	if (screenshot_file) {
+		fclose(screenshot_file);
+	}
+	if (which <= FRAMEBUFFER_EVEN) {
+		last = which;
+		static uint32_t frame_counter, start;
+		frame_counter++;
+		last_frame= SDL_GetTicks();
+		if ((last_frame - start) > FPS_INTERVAL) {
+			if (start && (last_frame-start)) {
+	#ifdef __ANDROID__
+				info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0));
+	#else
+				if (!fps_caption) {
+					fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1);
+				}
+				sprintf(fps_caption, "%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0));
+				SDL_SetWindowTitle(main_window, fps_caption);
+	#endif
+			}
+			start = last_frame;
+			frame_counter = 0;
+		}
+	}
+	if (!sync_to_audio) {
+		int32_t local_cur_min, local_min_remaining;
+		SDL_LockAudio();
+			if (last_buffered > NO_LAST_BUFFERED) {
+				average_change *= 0.9f;
+				average_change += (cur_min_buffered - last_buffered) * 0.1f;
+			}
+			local_cur_min = cur_min_buffered;
+			local_min_remaining = min_remaining_buffer;
+			last_buffered = cur_min_buffered;
+		SDL_UnlockAudio();
+		float frames_to_problem;
+		if (average_change < 0) {
+			frames_to_problem = (float)local_cur_min / -average_change;
+		} else {
+			frames_to_problem = (float)local_min_remaining / average_change;
+		}
+		float adjust_ratio = 0.0f;
+		if (
+			frames_to_problem < BUFFER_FRAMES_THRESHOLD
+			|| (average_change < 0 && local_cur_min < 3*min_buffered / 4)
+			|| (average_change >0 && local_cur_min > 5 * min_buffered / 4)
+			|| cur_min_buffered < 0
+		) {
+			
+			if (cur_min_buffered < 0) {
+				adjust_ratio = max_adjust;
+				SDL_PauseAudio(1);
+				last_buffered = NO_LAST_BUFFERED;
+				cur_min_buffered = 0;
+			} else {
+				adjust_ratio = -1.0 * average_change / ((float)sample_rate / (float)source_hz);
+				adjust_ratio /= 2.5 * source_hz;
+				if (fabsf(adjust_ratio) > max_adjust) {
+					adjust_ratio = adjust_ratio > 0 ? max_adjust : -max_adjust;
+				}
+			}
+		} else if (local_cur_min < min_buffered / 2) {
+			adjust_ratio = max_adjust;
+		}
+		if (adjust_ratio != 0.0f) {
+			average_change = 0;
+			for (uint8_t i = 0; i < num_audio_sources; i++)
+			{
+				audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5;
+			}
+		}
+		while (source_frame_count > 0)
+		{
+			render_update_display();
+			source_frame_count--;
+		}
+		source_frame++;
+		if (source_frame >= source_hz) {
+			source_frame = 0;
+		}
+		source_frame_count = frame_repeat[source_frame];
+	}
+}
+
+static ui_render_fun render_ui;
+void render_set_ui_render_fun(ui_render_fun fun)
+{
+	render_ui = fun;
+}
+
+void render_update_display()
+{
+#ifndef DISABLE_OPENGL
+	if (render_gl) {
+		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+		glClear(GL_COLOR_BUFFER_BIT);
+
+		glUseProgram(program);
+		glActiveTexture(GL_TEXTURE0);
+		glBindTexture(GL_TEXTURE_2D, textures[0]);
+		glUniform1i(un_textures[0], 0);
+
+		glActiveTexture(GL_TEXTURE1);
+		glBindTexture(GL_TEXTURE_2D, textures[interlaced ? 1 : scanlines ? 2 : 0]);
+		glUniform1i(un_textures[1], 1);
+
+		glUniform1f(un_width, render_emulated_width());
+		glUniform1f(un_height, last_height);
+
+		glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
+		glVertexAttribPointer(at_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat[2]), (void *)0);
+		glEnableVertexAttribArray(at_pos);
+
+		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
+		glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void *)0);
+
+		glDisableVertexAttribArray(at_pos);
+		
+		if (render_ui) {
+			render_ui();
+		}
+
+		SDL_GL_SwapWindow(main_window);
+	} else {
+#endif
+		SDL_Rect src_clip = {
+			.x = overscan_left[video_standard],
+			.y = overscan_top[video_standard],
+			.w = render_emulated_width(),
+			.h = last_height
+		};
+		SDL_SetRenderDrawColor(main_renderer, 0, 0, 0, 255);
+		SDL_RenderClear(main_renderer);
+		SDL_RenderCopy(main_renderer, sdl_textures[FRAMEBUFFER_ODD], &src_clip, &main_clip);
+		if (render_ui) {
+			render_ui();
+		}
+		SDL_RenderPresent(main_renderer);
+#ifndef DISABLE_OPENGL
+	}
+#endif
+	if (!events_processed) {
+		process_events();
+	}
+	events_processed = 0;
+}
+
+uint32_t render_emulated_width()
+{
+	return last_width - overscan_left[video_standard] - overscan_right[video_standard];
+}
+
+uint32_t render_emulated_height()
+{
+	return (video_standard == VID_NTSC ? 243 : 294) - overscan_top[video_standard] - overscan_bot[video_standard];
+}
+
+uint32_t render_overscan_left()
+{
+	return overscan_left[video_standard];
+}
+
+uint32_t render_overscan_top()
+{
+	return overscan_top[video_standard];
+}
+
+void render_wait_quit(vdp_context * context)
+{
+	SDL_Event event;
+	while(SDL_WaitEvent(&event)) {
+		switch (event.type) {
+		case SDL_QUIT:
+			return;
+		}
+	}
+}
+
+int render_lookup_button(char *name)
+{
+	static tern_node *button_lookup;
+	if (!button_lookup) {
+		for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++)
+		{
+			button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i);
+		}
+		//alternative Playstation-style names
+		button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A);
+		button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B);
+		button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X);
+		button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y);
+		button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK);
+		button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK);
+		button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START);
+		button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
+		button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
+		button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK);
+		button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK);
+	}
+	return (int)tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID);
+}
+
+int render_lookup_axis(char *name)
+{
+	static tern_node *axis_lookup;
+	if (!axis_lookup) {
+		for (int i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++)
+		{
+			axis_lookup = tern_insert_int(axis_lookup, SDL_GameControllerGetStringForAxis(i), i);
+		}
+		//alternative Playstation-style names
+		axis_lookup = tern_insert_int(axis_lookup, "l2", SDL_CONTROLLER_AXIS_TRIGGERLEFT);
+		axis_lookup = tern_insert_int(axis_lookup, "r2", SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
+	}
+	return (int)tern_find_int(axis_lookup, name, SDL_CONTROLLER_AXIS_INVALID);
+}
+
+int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis)
+{
+	tern_node *button_lookup, *axis_lookup;
+	if (controller > MAX_JOYSTICKS || !joysticks[controller]) {
+		return RENDER_NOT_PLUGGED_IN;
+	}
+	
+	if (!SDL_IsGameController(joystick_sdl_index[controller])) {
+		return RENDER_NOT_MAPPED;
+	}
+	SDL_GameController *control = SDL_GameControllerOpen(joystick_sdl_index[controller]);
+	if (!control) {
+		warning("Failed to open game controller %d: %s\n", controller, SDL_GetError());
+		return RENDER_NOT_PLUGGED_IN;
+	}
+	
+	SDL_GameControllerButtonBind cbind;
+	if (is_axis) {
+		
+		int sdl_axis = render_lookup_axis(name);
+		if (sdl_axis == SDL_CONTROLLER_AXIS_INVALID) {
+			SDL_GameControllerClose(control);
+			return RENDER_INVALID_NAME;
+		}
+		cbind = SDL_GameControllerGetBindForAxis(control, sdl_axis);
+	} else {
+		int sdl_button = render_lookup_button(name);
+		if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) {
+			SDL_GameControllerClose(control);
+			return RENDER_INVALID_NAME;
+		}
+		cbind = SDL_GameControllerGetBindForButton(control, sdl_button);
+	}
+	SDL_GameControllerClose(control);
+	switch (cbind.bindType)
+	{
+	case SDL_CONTROLLER_BINDTYPE_BUTTON:
+		return cbind.value.button;
+	case SDL_CONTROLLER_BINDTYPE_AXIS:
+		return RENDER_AXIS_BIT | cbind.value.axis;
+	case SDL_CONTROLLER_BINDTYPE_HAT:
+		return RENDER_DPAD_BIT | (cbind.value.hat.hat << 4) | cbind.value.hat.hat_mask;
+	}
+	return RENDER_NOT_MAPPED;
+}
+
+int32_t render_dpad_part(int32_t input)
+{
+	return input >> 4 & 0xFFFFFF;
+}
+
+uint8_t render_direction_part(int32_t input)
+{
+	return input & 0xF;
+}
+
+int32_t render_axis_part(int32_t input)
+{
+	return input & 0xFFFFFFF;
+}
+
 void process_events()
 {
 	if (events_processed > MAX_EVENT_POLL_PER_FRAME) {
@@ -1070,7 +1885,6 @@
 #define TOGGLE_MIN_DELAY 250
 void render_toggle_fullscreen()
 {
-	static int in_toggle;
 	//protect against event processing causing us to attempt to toggle while still toggling
 	if (in_toggle) {
 		return;
@@ -1110,36 +1924,6 @@
 	in_toggle = 0;
 }
 
-void render_wait_psg(psg_context * context)
-{
-	SDL_LockMutex(audio_mutex);
-		while (current_psg != NULL) {
-			SDL_CondWait(psg_cond, audio_mutex);
-		}
-		current_psg = context->audio_buffer;
-		SDL_CondSignal(audio_ready);
-
-		context->audio_buffer = context->back_buffer;
-		context->back_buffer = current_psg;
-	SDL_UnlockMutex(audio_mutex);
-	context->buffer_pos = 0;
-}
-
-void render_wait_ym(ym2612_context * context)
-{
-	SDL_LockMutex(audio_mutex);
-		while (current_ym != NULL) {
-			SDL_CondWait(ym_cond, audio_mutex);
-		}
-		current_ym = context->audio_buffer;
-		SDL_CondSignal(audio_ready);
-
-		context->audio_buffer = context->back_buffer;
-		context->back_buffer = current_ym;
-	SDL_UnlockMutex(audio_mutex);
-	context->buffer_pos = 0;
-}
-
 uint32_t render_audio_buffer()
 {
 	return buffer_samples;
@@ -1165,3 +1949,31 @@
 	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, title, message, NULL);
 }
 
+uint32_t render_elapsed_ms(void)
+{
+	return SDL_GetTicks();
+}
+
+void render_sleep_ms(uint32_t delay)
+{
+	return SDL_Delay(delay);
+}
+
+uint8_t render_has_gl(void)
+{
+	return render_gl;
+}
+
+uint8_t render_get_active_framebuffer(void)
+{
+	if (SDL_GetWindowFlags(main_window) & SDL_WINDOW_INPUT_FOCUS) {
+		return FRAMEBUFFER_ODD;
+	}
+	for (int i = 0; i < num_textures - 2; i++)
+	{
+		if (extra_windows[i] && (SDL_GetWindowFlags(extra_windows[i]) & SDL_WINDOW_INPUT_FOCUS)) {
+			return FRAMEBUFFER_USER_START + i; 
+		}
+	}
+	return 0xFF;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_sdl.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,18 @@
+#ifndef RENDER_SDL_H_
+#define RENDER_SDL_H_
+
+#include <SDL.h>
+
+SDL_Window *render_get_window(void);
+typedef void (*ui_render_fun)(void);
+typedef void (*event_handler)(SDL_Event *);
+void render_update_display(void);
+void render_set_ui_render_fun(ui_render_fun);
+void render_set_event_handler(event_handler handler);
+void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create);
+SDL_Joystick *render_get_joystick(int index);
+SDL_GameController *render_get_controller(int index);
+int render_lookup_button(char *name);
+int render_lookup_axis(char *name);
+
+#endif //RENDER_SDL_H_
--- a/rom.db	Tue Dec 19 00:49:13 2017 -0800
+++ b/rom.db	Sat Jan 05 00:58:08 2019 -0800
@@ -565,11 +565,15 @@
 		}
 		380000 {
 			device EEPROM
-			last 3FFFFF
+			last 387FFF
 			bits_read {
 				7 sda
 			}
 		}
+		388000 {
+			device jcart
+			last 38FFFF
+		}
 	}
 }
 9f47fcc7bb2f5921cb1c3beb06b668ffb292cb08 {
@@ -623,11 +627,15 @@
 		}
 		380000 {
 			device EEPROM
-			last 3FFFFF
+			last 387FFF
 			bits_read {
 				7 sda
 			}
 		}
+		388000 {
+			device jcart
+			last 38FFFF
+		}
 	}
 }
 T-120096 {
@@ -652,11 +660,15 @@
 		}
 		380000 {
 			device EEPROM
-			last 3FFFFF
+			last 387FFF
 			bits_read {
 				7 sda
 			}
 		}
+		388000 {
+			device jcart
+			last 38FFFF
+		}
 	}
 }
 MK-12056 {
@@ -1203,6 +1215,21 @@
 		}
 	}
 }
+2a561b6e47c93272fe5947084837d9f6f514ed38 {
+	name Squirrel King
+	map {
+		0 {
+			device ROM
+			last 3FFFFF
+		}
+		400000 {
+			device RAM
+			size 2
+			bus both
+			last 7FFFFF
+		}
+	}
+}
 
 #This entry is used by the GUI ROM
 BlstMenu {
@@ -1287,3 +1314,66 @@
 		}
 	}
 }
+31c66bd13abf4ae8271c09ec5286a0ee0289dbbc {
+	#Designed to run on Sega Channel hardware which is RAM-based
+	#writes to the this RAM qutie a bit to select games
+	name Game no Kanzume Otokuyou
+	map {
+		0 {
+			device ROM
+			last 3FFFFF
+			writeable yes
+		}
+	}
+}
+cda73e4caf53cbc8f0750b69e5e7f394ad3735d1 {
+	name MegaWiFi Bootloader
+	NOR {
+		size 4194304
+		page_size 128
+		product_id DA45
+		bus both
+		init ROM
+		cmd_address1 AAB
+		cmd_address2 555
+	}
+	map {
+		0 {
+			device NOR
+			last 3FFFFF
+		}
+		A130C0 {
+			device megawifi
+			last A130CF
+		}
+	}
+}
+222a66cdb8865a7f89e5a72418413888bb400176 {
+	#I've personally confirmed this version had a J-Cart
+	#release, but unlike the other revision it runs without it
+	name Pete Sampras Tennis
+	map {
+		0 {
+			device ROM
+			last 1FFFFF
+		}
+		200000 {
+			device jcart
+			last 3FFFFF
+		}
+	}
+}
+4c830ace4590294bb374b4cab71ebebf44d9a07a {
+	#This version will not accept input if J-Cart hardware is missing
+	name Pete Sampras Tennis
+	map {
+		0 {
+			device ROM
+			last 1FFFFF
+		}
+		200000 {
+			device jcart
+			last 3FFFFF
+		}
+	}
+}
--- a/romdb.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/romdb.c	Sat Jan 05 00:58:08 2019 -0800
@@ -11,6 +11,9 @@
 #include "nor.h"
 #include "sega_mapper.h"
 #include "multi_game.h"
+#include "megawifi.h"
+#include "jcart.h"
+#include "blastem.h"
 
 #define DOM_TITLE_START 0x120
 #define DOM_TITLE_END 0x150
@@ -52,6 +55,8 @@
 		free(info->save_buffer);
 		if (info->save_type == SAVE_I2C) {
 			free(info->eeprom_map);
+		} else if (info->save_type == SAVE_NOR) {
+			free(info->nor);
 		}
 	}
 	free(info->map);
@@ -502,15 +507,15 @@
 		if (!page_size) {
 			fatal_error("ROM DB map entry %d with address %s has device type NOR, but the NOR page size is not defined\n", state->index, key);
 		}
-		state->info->save_page_size = atoi(size);
-		if (!state->info->save_page_size) {
-			fatal_error("NOR page size %s is invalid\n", size);
+		uint32_t save_page_size = atoi(page_size);
+		if (!save_page_size) {
+			fatal_error("NOR page size %s is invalid\n", page_size);
 		}
 		char *product_id = tern_find_path(state->root, "NOR\0product_id\0", TVAL_PTR).ptrval;
 		if (!product_id) {
 			fatal_error("ROM DB map entry %d with address %s has device type NOR, but the NOR product ID is not defined\n", state->index, key);
 		}
-		state->info->save_product_id = strtol(product_id, NULL, 16);
+		uint16_t save_product_id = strtol(product_id, NULL, 16);
 		char *bus = tern_find_path(state->root, "NOR\0bus\0", TVAL_PTR).ptrval;
 		if (!strcmp(bus, "odd")) {
 			state->info->save_bus = RAM_FLAG_ODD;
@@ -521,7 +526,26 @@
 		}
 		state->info->save_type = SAVE_NOR;
 		state->info->save_buffer = malloc(state->info->save_size);
-		memset(state->info->save_buffer, 0xFF, state->info->save_size);
+		char *init = tern_find_path_default(state->root, "NOR\0init\0", (tern_val){.ptrval="FF"}, TVAL_PTR).ptrval;
+		if (!strcmp(init, "ROM")) {
+			uint32_t init_size = state->rom_size > state->info->save_size ? state->info->save_size : state->rom_size;
+			memcpy(state->info->save_buffer, state->rom, init_size);
+			if (state->info->save_bus == RAM_FLAG_BOTH) {
+				byteswap_rom(state->info->save_size, (uint16_t *)state->info->save_buffer);
+			}
+		} else {
+			memset(state->info->save_buffer, strtol(init, NULL, 16), state->info->save_size);
+		}
+		state->info->nor = calloc(1, sizeof(nor_state));
+		nor_flash_init(state->info->nor, state->info->save_buffer, state->info->save_size, save_page_size, save_product_id, state->info->save_bus);
+		char *cmd1 = tern_find_path(state->root, "NOR\0cmd_address1\0", TVAL_PTR).ptrval;
+		if (cmd1) {
+			state->info->nor->cmd_address1 = strtol(cmd1, NULL, 16);
+		}
+		char *cmd2 = tern_find_path(state->root, "NOR\0cmd_address2\0", TVAL_PTR).ptrval;
+		if (cmd2) {
+			state->info->nor->cmd_address2 = strtol(cmd2, NULL, 16);
+		}
 	}
 }
 
@@ -562,8 +586,12 @@
 	map->end = end + 1;
 	if (!strcmp(dtype, "ROM")) {
 		map->buffer = state->rom + offset;
-		map->flags = MMAP_READ;
 		map->mask = calc_mask(state->rom_size - offset, start, end);
+		if (strcmp(tern_find_ptr_default(node, "writeable", "no"), "yes")) {
+			map->flags = MMAP_READ;
+		} else {
+			map->flags = MMAP_READ | MMAP_WRITE | MMAP_CODE;
+		}
 	} else if (!strcmp(dtype, "LOCK-ON")) {
 		rom_info lock_info;
 		if (state->lock_on) {
@@ -584,6 +612,7 @@
 		}
 		if (matching_chunks == 0) {
 			//Nothing mapped in the relevant range for the lock-on cart, ignore this mapping
+			free_rom_info(&lock_info);
 			return;
 		} else if (matching_chunks > 1) {
 			state->info->map_chunks += matching_chunks - 1;
@@ -614,8 +643,7 @@
 			state->info->save_buffer = lock_info.save_buffer;
 			state->info->save_size = lock_info.save_size;
 			state->info->save_mask = lock_info.save_mask;
-			state->info->save_page_size = lock_info.save_page_size;
-			state->info->save_product_id = lock_info.save_product_id;
+			state->info->nor = lock_info.nor;
 			state->info->save_type = lock_info.save_type;
 			state->info->save_bus = lock_info.save_bus;
 			lock_info.save_buffer = NULL;
@@ -665,6 +693,10 @@
 		map->write_8 = nor_flash_write_b;
 		map->read_16 = nor_flash_read_w;
 		map->read_8 = nor_flash_read_b;
+		if (state->info->save_bus == RAM_FLAG_BOTH) {
+			map->flags |= MMAP_READ_CODE | MMAP_CODE;
+			map->buffer = state->info->save_buffer;
+		}
 		map->mask = 0xFFFFFF;
 	} else if (!strcmp(dtype, "Sega mapper")) {
 		state->info->mapper_type = MAPPER_SEGA;
@@ -777,6 +809,27 @@
 		map->mask = 0xFF;
 		map->write_16 = write_multi_game_w;
 		map->write_8 = write_multi_game_b;
+	} else if (!strcmp(dtype, "megawifi")) {
+		if (!strcmp(
+			"on", 
+			tern_find_path_default(config, "system\0megawifi\0", (tern_val){.ptrval="off"}, TVAL_PTR).ptrval)
+		) {
+			map->write_16 = megawifi_write_w;
+			map->write_8 = megawifi_write_b;
+			map->read_16 = megawifi_read_w;
+			map->read_8 = megawifi_read_b;
+			map->mask = 0xFFFFFF;
+		} else {
+			warning("ROM uses MegaWiFi, but it is disabled\n");
+			return;
+		}
+	} else if (!strcmp(dtype, "jcart")) {
+		state->info->mapper_type = MAPPER_JCART;
+		map->write_16 = jcart_write_w;
+		map->write_8 = jcart_write_b;
+		map->read_16 = jcart_read_w;
+		map->read_8 = jcart_read_b;
+		map->mask = 0xFFFFFF;
 	} else {
 		fatal_error("Invalid device type %s for ROM DB map entry %d with address %s\n", dtype, state->index, key);
 	}
--- a/romdb.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/romdb.h	Sat Jan 05 00:58:08 2019 -0800
@@ -31,6 +31,8 @@
 	uint32_t    page_size;
 	uint32_t    current_page;
 	uint32_t    last_write_cycle;
+	uint32_t    cmd_address1;
+	uint32_t    cmd_address2;
 	uint16_t    product_id;
 	uint8_t     mode;
 	uint8_t     cmd_state;
@@ -43,13 +45,14 @@
 	MAPPER_SEGA,
 	MAPPER_REALTEC,
 	MAPPER_XBAND,
-	MAPPER_MULTI_GAME
+	MAPPER_MULTI_GAME,
+	MAPPER_JCART
 };
 
 
 typedef struct rom_info rom_info;
 
-#include "backend.h"
+#include "memmap.h"
 
 struct rom_info {
 	char          *name;
@@ -61,13 +64,12 @@
 	char          *port2_override;
 	char          *ext_override;
 	char          *mouse_mode;
+	nor_state     *nor;
 	uint32_t      num_eeprom;
 	uint32_t      map_chunks;
 	uint32_t      rom_size;
 	uint32_t      save_size;
 	uint32_t      save_mask;
-	uint32_t      save_page_size;
-	uint16_t      save_product_id;
 	uint16_t      mapper_start_index;
 	uint8_t       save_type;
 	uint8_t       save_bus; //only used for NOR currently
@@ -87,6 +89,7 @@
 //Note: free_rom_info only frees things pointed to by a rom_info struct, not the struct itself
 //this is because rom_info structs are typically stack allocated
 void free_rom_info(rom_info *info);
+typedef struct system_header system_header;
 void cart_serialize(system_header *sys, serialize_buffer *buf);
 void cart_deserialize(deserialize_buffer *buf, void *vcontext);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/saves.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,77 @@
+#include <string.h>
+#include <stdlib.h>
+#include "saves.h"
+#include "util.h"
+
+#ifdef _WIN32
+#define localtime_r(a,b) localtime(a)
+#include <windows.h>
+#endif
+//0123456789012345678901234678
+//Slot N - December 31st, XXXX
+#define MAX_DESC_SIZE 40
+
+char *get_slot_name(system_header *system, uint32_t slot_index, char *ext)
+{
+	if (!system->save_dir) {
+		return NULL;
+	}
+	char *fname;
+	if (slot_index < 10) {
+		size_t name_len = strlen("slot_N.") + strlen(ext) + 1;
+		fname = malloc(name_len);
+		snprintf(fname, name_len, "slot_%d.%s", slot_index, ext);
+	} else {
+		size_t name_len = strlen("quicksave.") + strlen(ext) + 1;
+		fname = malloc(name_len);
+		snprintf(fname, name_len, "quicksave.%s", ext);
+	}
+	char const *parts[] = {system->save_dir, PATH_SEP, fname};
+	char *ret = alloc_concat_m(3, parts);
+	free(fname);
+	return ret;
+}
+
+save_slot_info *get_slot_info(system_header *system, uint32_t *num_out)
+{
+	save_slot_info *dst = calloc(11, sizeof(save_slot_info));
+	time_t modtime;
+	struct tm ltime;
+	for (uint32_t i = 0; i <= QUICK_SAVE_SLOT; i++)
+	{
+		char * cur = dst[i].desc = malloc(MAX_DESC_SIZE);
+		char * fname = get_slot_name(system, i, "state");
+		modtime = get_modification_time(fname);
+		free(fname);
+		if (!modtime && system->type == SYSTEM_GENESIS) {
+			fname = get_slot_name(system, i, "gst");
+			modtime = get_modification_time(fname);
+			free(fname);
+		}
+		if (i == QUICK_SAVE_SLOT) {
+			cur += snprintf(cur, MAX_DESC_SIZE, "Quick - ");
+		} else {
+			cur += snprintf(cur, MAX_DESC_SIZE, "Slot %d - ", i);
+		}
+		if (modtime) {
+			strftime(cur, MAX_DESC_SIZE - (cur - dst->desc), "%c", localtime_r(&modtime, &ltime));
+		} else {
+			strcpy(cur, "EMPTY");
+		}
+		dst[i].modification_time = modtime;
+	}
+	*num_out = QUICK_SAVE_SLOT + 1;
+	return dst;
+}
+
+void free_slot_info(save_slot_info *slots)
+{
+	if (!slots) {
+		return;
+	}
+	for (uint32_t i = 0; i <= QUICK_SAVE_SLOT; i++)
+	{
+		free(slots[i].desc);
+	}
+	free(slots);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/saves.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,19 @@
+#ifndef SAVES_H_
+#define SAVES_H_
+
+#include <time.h>
+#include <stdint.h>
+#include "system.h"
+
+#define QUICK_SAVE_SLOT 10
+
+typedef struct {
+	char   *desc;
+	time_t modification_time;
+} save_slot_info;
+
+char *get_slot_name(system_header *system, uint32_t slot_index, char *ext);
+save_slot_info *get_slot_info(system_header *system, uint32_t *num_out);
+void free_slot_info(save_slot_info *slots);
+
+#endif //SAVES_H_
--- a/shaders/default.f.glsl	Tue Dec 19 00:49:13 2017 -0800
+++ b/shaders/default.f.glsl	Sat Jan 05 00:58:08 2019 -0800
@@ -1,13 +1,12 @@
-#version 110
 
 uniform sampler2D textures[2];
 
-varying vec2 texcoord;
+varying mediump vec2 texcoord;
 
 void main()
 {
-	vec2 modifiedCoord0 = vec2(texcoord.x, (floor(texcoord.y * 512.0 + 0.25) + 0.5)/512.0);
-	vec2 modifiedCoord1 = vec2(texcoord.x, (floor(texcoord.y * 512.0 - 0.25) + 0.5)/512.0);
+	mediump vec2 modifiedCoord0 = vec2(texcoord.x, (floor(texcoord.y * 512.0 + 0.25) + 0.5)/512.0);
+	mediump vec2 modifiedCoord1 = vec2(texcoord.x, (floor(texcoord.y * 512.0 - 0.25) + 0.5)/512.0);
 	gl_FragColor = mix(
 		texture2D(textures[1], modifiedCoord1),
 		texture2D(textures[0], modifiedCoord0),
--- a/shaders/default.v.glsl	Tue Dec 19 00:49:13 2017 -0800
+++ b/shaders/default.v.glsl	Sat Jan 05 00:58:08 2019 -0800
@@ -1,11 +1,10 @@
-#version 110
 
 attribute vec2 pos;
-varying vec2 texcoord;
-uniform float width, height;
+varying mediump vec2 texcoord;
+uniform mediump float width, height;
 
 void main()
 {
 	gl_Position = vec4(pos, 0.0, 1.0);
-	texcoord = sign(pos) * vec2(width/1024.0, height/-1024.0) + vec2(width/1024.0, height/1024.0);
+	texcoord = sign(pos) * vec2(width / 1024.0, height / -1024.0) + vec2(width / 1024.0, height / 1024.0);
 }
--- a/sms.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/sms.c	Sat Jan 05 00:58:08 2019 -0800
@@ -6,6 +6,8 @@
 #include "render.h"
 #include "util.h"
 #include "debug.h"
+#include "saves.h"
+#include "bindings.h"
 
 static void *memory_io_write(uint32_t location, void *vcontext, uint8_t value)
 {
@@ -292,20 +294,13 @@
 
 static void save_state(sms_context *sms, uint8_t slot)
 {
-	char *save_path;
-	if (slot == QUICK_SAVE_SLOT) {
-		save_path = save_state_path;
-	} else {
-		char slotname[] = "slot_0.state";
-		slotname[5] = '0' + slot;
-		char const *parts[] = {sms->header.save_dir, PATH_SEP, slotname};
-		save_path = alloc_concat_m(3, parts);
-	}
+	char *save_path = get_slot_name(&sms->header, slot, "state");
 	serialize_buffer state;
 	init_serialize(&state);
 	sms_serialize(sms, &state);
 	save_to_file(&state, save_path);
 	printf("Saved state to %s\n", save_path);
+	free(save_path);
 	free(state.data);
 }
 
@@ -324,30 +319,35 @@
 static uint8_t load_state(system_header *system, uint8_t slot)
 {
 	sms_context *sms = (sms_context *)system;
-	char numslotname[] = "slot_0.state";
-	char *slotname;
-	if (slot == QUICK_SAVE_SLOT) {
-		slotname = "quicksave.state";
-	} else {
-		numslotname[5] = '0' + slot;
-		slotname = numslotname;
+	char *statepath = get_slot_name(system, slot, "state");
+	uint8_t ret;
+	if (!sms->z80->native_pc) {
+		ret = get_modification_time(statepath) != 0;
+		if (ret) {
+			system->delayed_load_slot = slot + 1;
+		}
+		goto done;
+		
 	}
-	char const *parts[] = {sms->header.save_dir, PATH_SEP, slotname};
-	char *statepath = alloc_concat_m(3, parts);
-	uint8_t ret = load_state_path(sms, statepath);
+	ret = load_state_path(sms, statepath);
+done:
 	free(statepath);
 	return ret;
 }
 
 static void run_sms(system_header *system)
 {
-	render_disable_ym();
 	sms_context *sms = (sms_context *)system;
 	uint32_t target_cycle = sms->z80->current_cycle + 3420*16;
 	//TODO: PAL support
 	render_set_video_standard(VID_NTSC);
 	while (!sms->should_return)
 	{
+		if (system->delayed_load_slot) {
+			load_state(system, system->delayed_load_slot - 1);
+			system->delayed_load_slot = 0;
+			
+		}
 		if (system->enter_debugger && sms->z80->pc) {
 			system->enter_debugger = 0;
 			zdebugger(sms->z80, sms->z80->pc);
@@ -386,22 +386,24 @@
 			target_cycle -= adjust;
 		}
 	}
+	bindings_release_capture();
 	vdp_release_framebuffer(sms->vdp);
+	render_pause_source(sms->psg->audio);
 	sms->should_return = 0;
-	render_enable_ym();
 }
 
 static void resume_sms(system_header *system)
 {
 	sms_context *sms = (sms_context *)system;
+	bindings_reacquire_capture();
 	vdp_reacquire_framebuffer(sms->vdp);
+	render_resume_source(sms->psg->audio);
 	run_sms(system);
 }
 
 static void start_sms(system_header *system, char *statefile)
 {
 	sms_context *sms = (sms_context *)system;
-	set_keybindings(&sms->io);
 	
 	z80_assert_reset(sms->z80, 0);
 	z80_clear_reset(sms->z80, 128*15);
@@ -449,19 +451,7 @@
 static void inc_debug_mode(system_header *system)
 {
 	sms_context *sms = (sms_context *)system;
-	sms->vdp->debug++;
-	if (sms->vdp->debug == 7) {
-		sms->vdp->debug = 0;
-	}
-}
-
-static void inc_debug_pal(system_header *system)
-{
-	sms_context *sms = (sms_context *)system;
-	sms->vdp->debug_pal++;
-	if (sms->vdp->debug_pal == 4) {
-		sms->vdp->debug_pal = 0;
-	}
+	vdp_inc_debug_mode(sms->vdp);
 }
 
 static void load_save(system_header *system)
@@ -474,14 +464,74 @@
 	//TODO: Implement me
 }
 
-sms_context *alloc_configure_sms(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info_out)
+static void gamepad_down(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	sms_context *sms = (sms_context *)system;
+	if (gamepad_num == GAMEPAD_MAIN_UNIT) {
+		if (button == MAIN_UNIT_PAUSE) {
+			vdp_pbc_pause(sms->vdp);
+		}
+	} else {
+		io_gamepad_down(&sms->io, gamepad_num, button);
+	}
+}
+
+static void gamepad_up(system_header *system, uint8_t gamepad_num, uint8_t button)
+{
+	sms_context *sms = (sms_context *)system;
+	io_gamepad_up(&sms->io, gamepad_num, button);
+}
+
+static void mouse_down(system_header *system, uint8_t mouse_num, uint8_t button)
+{
+	sms_context *sms = (sms_context *)system;
+	io_mouse_down(&sms->io, mouse_num, button);
+}
+
+static void mouse_up(system_header *system, uint8_t mouse_num, uint8_t button)
+{
+	sms_context *sms = (sms_context *)system;
+	io_mouse_up(&sms->io, mouse_num, button);
+}
+
+static void mouse_motion_absolute(system_header *system, uint8_t mouse_num, uint16_t x, uint16_t y)
 {
-	memset(info_out, 0, sizeof(*info_out));
+	sms_context *sms = (sms_context *)system;
+	io_mouse_motion_absolute(&sms->io, mouse_num, x, y);
+}
+
+static void mouse_motion_relative(system_header *system, uint8_t mouse_num, int32_t x, int32_t y)
+{
+	sms_context *sms = (sms_context *)system;
+	io_mouse_motion_relative(&sms->io, mouse_num, x, y);
+}
+
+static void keyboard_down(system_header *system, uint8_t scancode)
+{
+	sms_context *sms = (sms_context *)system;
+	io_keyboard_down(&sms->io, scancode);
+}
+
+static void keyboard_up(system_header *system, uint8_t scancode)
+{
+	sms_context *sms = (sms_context *)system;
+	io_keyboard_up(&sms->io, scancode);
+}
+
+static void config_updated(system_header *system)
+{
+	sms_context *sms = (sms_context *)system;
+	setup_io_devices(config, &system->info, &sms->io);
+}
+
+
+sms_context *alloc_configure_sms(system_media *media, uint32_t opts, uint8_t force_region)
+{
 	sms_context *sms = calloc(1, sizeof(sms_context));
 	uint32_t rom_size = nearest_pow2(media->size);
 	memmap_chunk memory_map[6];
 	if (media->size > 0xC000)  {
-		info_out->map_chunks = 6;
+		sms->header.info.map_chunks = 6;
 		uint8_t *ram_reg_overlap = sms->ram + sizeof(sms->ram) - 4;
 		memory_map[0] = (memmap_chunk){0x0000, 0x0400,  0xFFFF,             0, 0, MMAP_READ,                        media->buffer, NULL, NULL, NULL, NULL};
 		memory_map[1] = (memmap_chunk){0x0400, 0x4000,  0xFFFF,             0, 0, MMAP_READ|MMAP_PTR_IDX|MMAP_CODE, NULL,     NULL, NULL, NULL, NULL};
@@ -490,21 +540,21 @@
 		memory_map[4] = (memmap_chunk){0xC000, 0xFFFC,  sizeof(sms->ram)-1, 0, 0, MMAP_READ|MMAP_WRITE|MMAP_CODE,   sms->ram, NULL, NULL, NULL, NULL};
 		memory_map[5] = (memmap_chunk){0xFFFC, 0x10000, 0x0003,             0, 0, MMAP_READ,                        ram_reg_overlap, NULL, NULL, NULL, mapper_write};
 	} else {
-		info_out->map_chunks = 2;
+		sms->header.info.map_chunks = 2;
 		memory_map[0] = (memmap_chunk){0x0000, 0xC000,  rom_size-1,         0, 0, MMAP_READ,                      media->buffer,  NULL, NULL, NULL, NULL};
 		memory_map[1] = (memmap_chunk){0xC000, 0x10000, sizeof(sms->ram)-1, 0, 0, MMAP_READ|MMAP_WRITE|MMAP_CODE, sms->ram, NULL, NULL, NULL, NULL};
 	};
-	info_out->map = malloc(sizeof(memmap_chunk) * info_out->map_chunks);
-	memcpy(info_out->map, memory_map, sizeof(memmap_chunk) * info_out->map_chunks);
+	sms->header.info.map = malloc(sizeof(memmap_chunk) * sms->header.info.map_chunks);
+	memcpy(sms->header.info.map, memory_map, sizeof(memmap_chunk) * sms->header.info.map_chunks);
 	z80_options *zopts = malloc(sizeof(z80_options));
-	init_z80_opts(zopts, info_out->map, info_out->map_chunks, io_map, 4, 15, 0xFF);
+	init_z80_opts(zopts, sms->header.info.map, sms->header.info.map_chunks, io_map, 4, 15, 0xFF);
 	sms->z80 = init_z80_context(zopts);
 	sms->z80->system = sms;
 	sms->z80->options->gen.debug_cmd_handler = debug_commands;
 	
 	sms->rom = media->buffer;
 	sms->rom_size = rom_size;
-	if (info_out->map_chunks > 2) {
+	if (sms->header.info.map_chunks > 2) {
 		sms->z80->mem_pointers[0] = sms->rom;
 		sms->z80->mem_pointers[1] = sms->rom + 0x4000;
 		sms->z80->mem_pointers[2] = sms->rom + 0x8000;
@@ -513,23 +563,20 @@
 		sms->bank_regs[3] = 0x8000 >> 14;
 	}
 	
-	char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval;
-	uint32_t lowpass_cutoff = lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : 3390;
-	
 	//TODO: Detect region and pick master clock based off of that
 	sms->normal_clock = sms->master_clock = 53693175;
 	
 	sms->psg = malloc(sizeof(psg_context));
-	psg_init(sms->psg, render_sample_rate(), sms->master_clock, 15*16, render_audio_buffer(), lowpass_cutoff);
+	psg_init(sms->psg, sms->master_clock, 15*16);
 	
-	sms->vdp = malloc(sizeof(vdp_context));
-	init_vdp_context(sms->vdp, 0);
+	sms->vdp = init_vdp_context(0);
 	sms->vdp->system = &sms->header;
 	
-	info_out->save_type = SAVE_NONE;
-	info_out->name = strdup(media->name);
+	sms->header.info.save_type = SAVE_NONE;
+	sms->header.info.name = strdup(media->name);
 	
-	setup_io_devices(config, info_out, &sms->io);
+	setup_io_devices(config, &sms->header.info, &sms->io);
+	sms->header.has_keyboard = io_has_keyboard(&sms->io);
 	
 	sms->header.set_speed_percent = set_speed_percent;
 	sms->header.start_context = start_sms;
@@ -542,7 +589,15 @@
 	sms->header.request_exit = request_exit;
 	sms->header.soft_reset = soft_reset;
 	sms->header.inc_debug_mode = inc_debug_mode;
-	sms->header.inc_debug_pal = inc_debug_pal;
+	sms->header.gamepad_down = gamepad_down;
+	sms->header.gamepad_up = gamepad_up;
+	sms->header.mouse_down = mouse_down;
+	sms->header.mouse_up = mouse_up;
+	sms->header.mouse_motion_absolute = mouse_motion_absolute;
+	sms->header.mouse_motion_relative = mouse_motion_relative;
+	sms->header.keyboard_down = keyboard_down;
+	sms->header.keyboard_up = keyboard_up;
+	sms->header.config_updated = config_updated;
 	sms->header.type = SYSTEM_SMS;
 	
 	return sms;
--- a/sms.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/sms.h	Sat Jan 05 00:58:08 2019 -0800
@@ -26,6 +26,6 @@
 	uint8_t       cart_ram[SMS_CART_RAM_SIZE];
 } sms_context;
 
-sms_context *alloc_configure_sms(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info_out);
+sms_context *alloc_configure_sms(system_media *media, uint32_t opts, uint8_t force_region);
 
 #endif //SMS_H_
--- a/stateview.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/stateview.c	Sat Jan 05 00:58:08 2019 -0800
@@ -79,6 +79,10 @@
 {
 }
 
+void controller_add_mappings()
+{
+}
+
 tern_node * config;
 int headless = 0;
 
@@ -112,17 +116,16 @@
 	width = width < 320 ? def_width : width;
 	height = height < 240 ? (width/320) * 240 : height;
 
-	vdp_context context;
 	render_init(width, height, "GST State Viewer", 0);
-	init_vdp_context(&context, 0);
-	vdp_load_gst(&context, state_file);
-	vdp_run_to_vblank(&context);
-	vdp_print_sprite_table(&context);
-	printf("Display %s\n", (context.regs[REG_MODE_2] & DISPLAY_ENABLE) ? "enabled" : "disabled");
-	if (!(context.regs[REG_MODE_2] & DISPLAY_ENABLE)) {
+	vdp_context *context = init_vdp_context(0);
+	vdp_load_gst(context, state_file);
+	vdp_run_to_vblank(context);
+	vdp_print_sprite_table(context);
+	printf("Display %s\n", (context->regs[REG_MODE_2] & DISPLAY_ENABLE) ? "enabled" : "disabled");
+	if (!(context->regs[REG_MODE_2] & DISPLAY_ENABLE)) {
 		puts("Forcing display on");
-		vdp_control_port_write(&context, 0x8000 | REG_MODE_2 << 8 | context.regs[REG_MODE_2] | DISPLAY_ENABLE);
+		vdp_control_port_write(context, 0x8000 | REG_MODE_2 << 8 | context->regs[REG_MODE_2] | DISPLAY_ENABLE);
 	}
-    render_wait_quit(&context);
+    render_wait_quit(context);
     return 0;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svp.cpu	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,642 @@
+info
+	prefix svp_
+	opcode_size 16
+	body svp_run_op
+	header svp.h
+	include svp_util.c
+	
+regs
+	internal 16 scratch2 x y pad0 st pad1 pc
+	a 32
+	scratch1 32
+	rom ptr16
+	stack 16 stack0 stack1 stack2 stack3 stack4 stack5
+	stackidx 8
+	p 32
+	external 16 pm0 pm1 pm2 xst pm4 ext5 pmc
+	pointers0 8 r0 r1 r2 r3 
+	pointers1 8 r4 r5 r6 r7
+	pm_address 32 5
+	pm_mode 8 5
+	
+	iram 16 1024
+	ram0 16 256
+	ram1 16 256
+	zflag 8
+	nflag 8
+	rpl 16
+	
+flags
+	register st
+	Z 13 zero zflag
+	N 15 sign nflag
+	R 0-2 none rpl
+	
+svp_pop
+	mov stack.stackidx dst
+	add 1 stackidx stackidx
+	switch stackidx
+	case 6
+	mov 0 stackidx
+	end
+	
+svp_push
+	arg src 16
+	sub 1 stackidx stackidx
+	switch stackidx
+	case 0xFF
+	mov 5 stackidx
+	end
+	mov src stack.stackidx
+	
+svp_ram_read
+	arg mode 16
+	arg banki 16
+	arg regi 16
+	local idx 16
+	
+	switch banki
+	case 0
+	meta bank ram0
+	meta reg pointers0.regi
+	
+	default
+	meta bank ram1
+	meta reg pointers1.regi
+	end
+	
+	mov reg idx
+	switch mode
+	case 0
+	meta modestr ""
+	
+	case 1
+	meta modestr +!
+	add 1 reg reg
+	
+	case 2
+	#loop decremenet
+	meta modestr -
+	
+	if rpl
+		local tmp 16
+		mov reg tmp
+		lsl 1 rpl rpl
+		sub 1 rpl rpl
+		local mask 16
+		not rpl mask
+		and reg mask reg
+		sub 1 tmp tmp
+		and rpl tmp tmp
+		or tmp reg reg
+	else
+		sub 1 reg reg
+	end
+	
+	case 3
+	#loop increment
+	meta modestr +
+	
+	and 7 st rpl
+	if rpl
+		local tmp 16
+		mov reg tmp
+		lsl 1 rpl rpl
+		sub 1 rpl rpl
+		local mask 16
+		not rpl mask
+		and reg mask reg
+		add 1 tmp tmp
+		and rpl tmp tmp
+		or tmp reg reg
+	else
+		sub 1 reg reg
+	end
+	end
+	
+	and 255 idx idx
+	meta val bank.idx
+	
+svp_read_ext
+	arg regidxr 16
+	switch regidxr
+	case 7
+	meta val a
+	
+	default
+	#TODO: PMAR stuff
+	meta val external.regidxr
+	end
+	
+svp_write_ext
+	arg regidxw 16
+	switch regidxw
+	case 7
+	and 0xFFFF0000 a a
+	or src a a
+	
+	default
+	#TODO: PMAR stuff
+	mov src external.regidxw
+	end
+	
+svp_alu_op
+	arg P 16
+	arg param 32
+	
+	switch P
+	case 1
+	dis "sub %s" name
+	sub param a a
+	
+	case 3
+	dis "cmp %s" name
+	cmp param a
+	
+	case 4
+	dis "add %s" name
+	add param a a
+	
+	case 5
+	dis "and %s" name
+	and param a a
+	
+	case 6
+	dis "or %s" name
+	or param a a
+	
+	case 7
+	dis "eor %s" name
+	xor param a a
+	end
+	update_flags ZN
+	
+svp_check_cond
+	arg fval 16
+	arg cond 16
+	local invert 8
+	switch cond
+	case 0
+	meta flag 1
+	
+	case 5
+	meta flag zflag
+	
+	case 7
+	meta flag nflag
+	
+	default
+	meta flag 0
+	end
+	
+	if fval
+	meta istrue flag
+	
+	else
+	lnot flag invert
+	meta istrue invert
+	
+	end
+	
+PPP0000000000000 alu_n1
+	invalid P 0
+	invalid P 2
+	meta name "-"
+	svp_alu_op P 0xFFFF0000
+	
+PPP0000000000RRR alu_r
+	invalid P 0
+	invalid P 2
+	local tmp 32 
+	lsl internal.R 16 tmp
+	meta name internal.R
+	svp_alu_op P tmp
+	
+PPP0000000000011 alu_a
+	invalid P 0
+	invalid P 2
+	svp_alu_op P a
+	
+PPP0000000000101 alu_stack
+	invalid P 0
+	invalid P 2
+	local tmp 32
+	meta dst tmp
+	svp_pop
+	meta name "stack"
+	svp_alu_op P tmp
+	
+PPP0000000000111 alu_p
+	invalid P 0
+	invalid P 2
+	meta name p
+	svp_alu_op P p
+	
+PPP0000000001RRR alu_ext
+	invalid P 0
+	invalid P 2
+	local tmp 32
+	svp_read_ext R
+	lsl val 16 tmp
+	meta name val
+	svp_alu_op P tmp
+	
+PPP0001B0000MMRR alu_ram
+	invalid P 0
+	invalid P 2
+	svp_ram_read M B R
+	local tmp 32
+	lsl val 16 tmp
+	
+	switch P
+	case 1
+	dis "sub (%s%s)" reg modestr
+	sub tmp a a
+	
+	case 3
+	dis "cmp (%s%s)" reg modestr
+	cmp tmp a
+	
+	case 4
+	dis "add (%s%s)" reg modestr
+	add tmp a a
+	
+	case 5
+	dis "and (%s%s)" reg modestr
+	and tmp a a
+	
+	case 6
+	dis "or (%s%s)" reg modestr
+	or tmp a a
+	
+	case 7
+	dis "eor (%s%s)" reg modestr
+	xor tmp a a
+	end
+	
+	update_flags ZN
+	
+PPP0101B0000MMRR alu_ram_indirect
+	invalid P 0
+	invalid P 2
+	svp_ram_read M B R
+	svp_prog_ram_read val
+	local tmp 32
+	lsl scratch1 16 tmp
+	
+	switch P
+	case 1
+	dis "sub ((%s%s))" reg modestr
+	sub tmp a a
+	
+	case 3
+	dis "cmp ((%s%s))" reg modestr
+	cmp tmp a
+	
+	case 4
+	dis "add ((%s%s))" reg modestr
+	add tmp a a
+	
+	case 5
+	dis "and ((%s%s))" reg modestr
+	and tmp a a
+	
+	case 6
+	dis "or ((%s%s))" reg modestr
+	or tmp a a
+	
+	case 7
+	dis "eor ((%s%s))" reg modestr
+	xor tmp a a
+	end
+	
+	update_flags ZN
+	
+PPP0000000001111 alu_al
+	invalid P 0
+	invalid P 2
+	local tmp 32
+	lsl a 16 tmp
+	
+	meta name al
+	svp_alu_op P tmp
+	
+PPP0011JAAAAAAAA alu_ram_direct
+	invalid P 0
+	invalid P 2
+	if J
+	meta src ram1.A
+	else
+	meta src ram0.A
+	end
+	svp_alu_op P src
+	
+PPP0010000000000 alu_immed
+	invalid P 0
+	invalid P 2
+	svp_op_fetch
+	svp_alu_op P scratch1
+	
+1001000FCCCC0OOO cond_mod
+	svp_check_cond F C
+	if istrue
+
+	switch O
+	case 2
+	asr a 1 a
+	update_flags ZN
+	
+	case 3
+	lsl a 1 a
+	update_flags ZN
+	
+	case 6
+	neg a a
+	update_flags ZN
+	
+	case 7
+	abs a a
+	update_flags N
+	end
+	end
+
+000000000DDD0SSS ld_int_int
+	dis "ld %s, %s" internal.D internal.S
+	mov internal.S internal.D
+	
+000000000DDD0101 ld_int_stack
+	dis "ld %s, stack" internal.D 
+	meta dst internal.D
+	svp_pop
+	
+0000000000110101 ld_a_stack
+	dis "ld a, stack"
+	local tmp 32
+	meta dst tmp
+	svp_pop
+	lsl tmp 16 tmp
+	and 0xFFFF a a
+	or tmp a a
+	
+0000000001110101 ld_p_stack
+	dis "ld p, stack"
+	local tmp 32
+	meta dst tmp
+	svp_pop
+	lsl tmp 16 tmp
+	and 0xFFFF p p
+	or tmp p p
+	
+0000000001010SSS ld_stack_int
+	dis "ld stack, %s" internal.S
+	svp_push internal.S
+	
+0000000001010011 ld_stack_a
+	dis "ld stack, a"
+	local tmp 32
+	lsr a 16 tmp
+	svp_push tmp
+	
+0000000001010111 ld_stack_p
+	dis "ld stack, p"
+	local tmp 32
+	lsr p 16 tmp
+	svp_push tmp
+	
+0000000000000000 ld_n1_n1
+	#nop?
+	dis "ld -, -"
+	
+0000000000000SSS ld_n1_int
+	#nop?
+	dis "nop??"
+	
+0000000000110111 ld_a_p
+	dis "ld a, p"
+	mov p a
+	
+0000000001110011 ld_p_a
+	dis "ld p, a"
+	mov a p
+	
+0000000000110011 ld_a_a
+	dis "ld a, a"
+	mov a a
+	
+0000000001110111 ld_p_p
+	dis "ld p, p"
+	mov p p
+
+000000000DDD0111 ld_int_p
+	local tmp 32
+	lsr p 16 tmp
+	mov tmp internal.D
+	dis "ld %s, p" internal.D
+	
+000000000DDD0111 ld_int_a
+	local tmp 32
+	lsr a 16 tmp
+	mov tmp internal.D
+	dis "ld %s, a" internal.D
+	
+0000000001110SSS ld_p_int
+	local tmp 32
+	lsl internal.S 16 tmp
+	mov tmp p
+	dis "ld p, %s" internal.S
+	
+0000000000110SSS ld_a_int
+	local tmp 32
+	lsl internal.S 16 tmp
+	mov tmp a
+	dis "ld a, %s" internal.S
+	
+000000000DDD0000 ld_int_n1
+	dis "ld %s, -" internal.D
+	mov 0xFFFF internal.D
+	
+0000000000110000 ld_a_n1
+	dis "ld a, -"
+	and 0xFFFF a a
+	or 0xFFFF0000 a a
+	
+0000000001110000 ld_p_n1
+	dis "ld p, -"
+	and 0xFFFF p p
+	or 0xFFFF0000 p p
+
+000000000DDD1SSS ld_int_ext
+	svp_read_ext S
+	dis "ld %s, %s" internal.D val
+	mov val internal.D
+	
+0000000000111SSS ld_a_ext
+	svp_read_ext S
+	dis "ld a, %s" val
+	local tmp 32
+	lsl val 16 tmp
+	and 0xFFFF a a
+	or tmp a a
+	
+0000000001111SSS ld_p_ext
+	svp_read_ext S
+	dis "ld p, %s" val
+	local tmp 32
+	lsl val 16 tmp
+	and 0xFFFF p p
+	or tmp p p
+	
+000000001DDD0SSS ld_ext_int
+	meta src internal.S
+	svp_write_ext D
+	switch D
+	case 7
+	dis "ld al, %s" src
+	
+	default
+	dis "ld %s, %s" external.D src
+	end
+	
+000000001DDD0011 ld_ext_a
+	local tmp 32
+	lsr a 16 tmp
+	meta src tmp
+	svp_write_ext D
+	switch D
+	case 7
+	dis "ld al, a"
+	
+	default
+	dis "ld %s, a" external.D
+	end
+	
+000000001DDD0111 ld_ext_p
+	local tmp 32
+	lsr p 16 tmp
+	meta src tmp
+	svp_write_ext D
+	switch D
+	case 7
+	dis "ld al, p"
+	
+	default
+	dis "ld %s, p" external.D
+	end
+	
+	
+000000001DDD1SSS ld_ext_ext
+	svp_read_ext S
+	meta src val
+	svp_write_ext D
+	switch D
+	case 7
+	dis "ld al, %s" src
+	default
+	dis "ld %s, %s" external.D src
+	end
+	
+0000001B0DDDMMPP ld_int_ram
+	svp_ram_read M B P
+	dis "ld %s, (%s%s)" internal.D reg modestr
+	mov val internal.D
+	
+0000001B0011MMPP ld_a_ram
+	svp_ram_read M B P
+	dis "ld a, (%s%s)" reg modestr
+	local tmp 32
+	lsl val 16 tmp
+	and 0xFFFF a a
+	or tmp a a
+	
+0000001B0111MMPP ld_p_ram
+	svp_ram_read M B P
+	dis "ld p, (%s%s)" reg modestr
+	local tmp 32
+	lsl val 16 tmp
+	and 0xFFFF p p
+	or tmp p p
+	
+0000001B0101MMPP ld_stack_ram
+	svp_ram_read M B P
+	dis "ld stack, (%s%s)" reg modestr
+	svp_push val
+	
+000010000DDD0000 ld_int_immed
+	svp_op_fetch
+	dis "ld %s, %X" internal.D scratch1
+	mov scratch1 internal.D
+	
+0000100000000000 ld_n1_immed
+	svp_op_fetch
+	dis "ld -, %X" scratch1
+
+0000100000110000 ld_a_immed
+	local tmp 32
+	svp_op_fetch
+	dis "ld a, %X" scratch1
+	lsl 16 scratch1 tmp
+	and 0xFFFF a a
+	or tmp a a
+	
+0000100001010000 ld_stack_immed
+	svp_op_fetch
+	dis "ld stack, %X" scratch1
+	svp_push scratch1
+
+0000100001110000 ld_p_immed
+	local tmp 32
+	svp_op_fetch
+	dis "ld p, %X" scratch1
+	lsl 16 scratch1 tmp
+	and 0xFFFF p p
+	or tmp p p
+	
+000010001DDD0000 ld_ext_immed
+	svp_op_fetch
+	dis "ld %s, %X", external.D, scratch1
+	meta src scratch1
+	svp_write_ext D
+	switch D
+	case 7
+	dis "ld al, %X" scratch1
+	
+	default
+	dis "ld %s, %X" external.D scratch1
+	end
+	
+0100100FCCCC0000 call_cond
+	svp_check_cond F C
+	svp_op_fetch
+	
+	if istrue
+	svp_push pc
+	mov scratch1 pc
+	end
+	
+0100110FCCCC0000 bra_cond
+	svp_check_cond F C
+	svp_op_fetch
+	if istrue
+	mov scratch1 pc
+	end
+	
+svp_prog_ram_read
+	arg src 16
+	cycles 1
+	cmp 1024 src
+	
+	if >=U
+	add src src scratch1
+	ocall prog_read_16
+	
+	else
+	mov iram.src scratch1
+	end
+
+svp_op_fetch
+	svp_prog_ram_read pc
+	add 1 pc pc
+	
+svp_run_op
+	svp_op_fetch
+	dispatch scratch1
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svp_util.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,6 @@
+
+void svp_prog_read_16(svp_context *context)
+{
+	uint16_t address = context->scratch1 >> 1;
+	context->scratch1 = context->rom[address];
+}
--- a/system.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/system.c	Sat Jan 05 00:58:08 2019 -0800
@@ -52,7 +52,7 @@
 	return SYSTEM_UNKNOWN;
 }
 
-system_header *alloc_config_system(system_type stype, system_media *media, uint32_t opts, uint8_t force_region, rom_info *info_out)
+system_header *alloc_config_system(system_type stype, system_media *media, uint32_t opts, uint8_t force_region)
 {
 	void *lock_on = NULL;
 	uint32_t lock_on_size = 0;
@@ -63,12 +63,12 @@
 	switch (stype)
 	{
 	case SYSTEM_GENESIS:
-		return &(alloc_config_genesis(media->buffer, media->size, lock_on, lock_on_size, opts, force_region, info_out))->header;
+		return &(alloc_config_genesis(media->buffer, media->size, lock_on, lock_on_size, opts, force_region))->header;
 	case SYSTEM_SEGACD:
-		return &(alloc_config_genesis_cdboot(media, opts, force_region, info_out))->header;
+		return &(alloc_config_genesis_cdboot(media, opts, force_region))->header;
 #ifndef NO_Z80
 	case SYSTEM_SMS:
-		return &(alloc_configure_sms(media, opts, force_region, info_out))->header;
+		return &(alloc_configure_sms(media, opts, force_region))->header;
 #endif
 	default:
 		return NULL;
--- a/system.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/system.h	Sat Jan 05 00:58:08 2019 -0800
@@ -22,8 +22,12 @@
 typedef uint16_t (*system_fun_r16)(system_header *);
 typedef void (*system_str_fun)(system_header *, char *);
 typedef uint8_t (*system_str_fun_r8)(system_header *, char *);
-typedef void (*speed_system_fun)(system_header *, uint32_t);
+typedef void (*system_u32_fun)(system_header *, uint32_t);
+typedef void (*system_u8_fun)(system_header *, uint8_t);
 typedef uint8_t (*system_u8_fun_r8)(system_header *, uint8_t);
+typedef void (*system_u8_u8_fun)(system_header *, uint8_t, uint8_t);
+typedef void (*system_mabs_fun)(system_header *, uint8_t, uint16_t, uint16_t);
+typedef void (*system_mrel_fun)(system_header *, uint8_t, int32_t, int32_t);
 
 #include "arena.h"
 #include "romdb.h"
@@ -39,15 +43,26 @@
 	system_fun        soft_reset;
 	system_fun        free_context;
 	system_fun_r16    get_open_bus_value;
-	speed_system_fun  set_speed_percent;
+	system_u32_fun    set_speed_percent;
 	system_fun        inc_debug_mode;
-	system_fun        inc_debug_pal;
+	system_u8_u8_fun  gamepad_down;
+	system_u8_u8_fun  gamepad_up;
+	system_u8_u8_fun  mouse_down;
+	system_u8_u8_fun  mouse_up;
+	system_mabs_fun   mouse_motion_absolute;
+	system_mrel_fun   mouse_motion_relative;
+	system_u8_fun     keyboard_down;
+	system_u8_fun     keyboard_up;
+	system_fun        config_updated;
+	rom_info          info;
 	arena             *arena;
 	char              *next_rom;
 	char              *save_dir;
 	uint8_t           enter_debugger;
 	uint8_t           should_exit;
 	uint8_t           save_state;
+	uint8_t           delayed_load_slot;
+	uint8_t           has_keyboard;
 	debugger_type     debugger_type;
 	system_type       type;
 };
@@ -70,6 +85,6 @@
 #define OPT_ADDRESS_LOG (1U << 31U)
 
 system_type detect_system_type(system_media *media);
-system_header *alloc_config_system(system_type stype, system_media *media, uint32_t opts, uint8_t force_region, rom_info *info_out);
+system_header *alloc_config_system(system_type stype, system_media *media, uint32_t opts, uint8_t force_region);
 
 #endif //SYSTEM_H_
--- a/tern.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/tern.c	Sat Jan 05 00:58:08 2019 -0800
@@ -45,6 +45,12 @@
 		(*cur)->left = NULL;
 		(*cur)->right = NULL;
 		(*cur)->el = 0;
+		(*cur)->valtype = TVAL_NONE;
+	}
+	if ((*cur)->valtype == TVAL_PTR) {
+		//not freeing tern nodes can also cause leaks, but handling freeing those here is problematic
+		//since updating a sub-tree may involve creating a new root node
+		free((*cur)->straight.value.ptrval);
 	}
 	(*cur)->straight.value = value;
 	(*cur)->valtype = valtype;
@@ -132,6 +138,39 @@
 	return NULL;
 }
 
+uint8_t tern_delete(tern_node **head, char const *key, tern_val *out)
+{
+	tern_node *cur = *head, **last = head;
+	while (cur)
+	{
+		if (cur->el == *key) {
+			if (*key) {
+				last = &cur->straight.next;
+				cur = cur->straight.next;
+				key++;
+			} else {
+				break;
+			}
+		} else if (*key < cur->el) {
+			last = &cur->left;
+			cur = cur->left;
+		} else {
+			last = &cur->right;
+			cur = cur->right;
+		}
+	}
+	if (!cur) {
+		return TVAL_NONE;
+	}
+	*last = cur->right;
+	uint8_t valtype = cur->valtype;
+	if (out) {
+		*out = cur->straight.value;
+	}
+	free(cur);
+	return valtype;
+}
+
 tern_val tern_find_path_default(tern_node *head, char const *key, tern_val def, uint8_t req_valtype)
 {
 	tern_val ret;
@@ -175,6 +214,37 @@
 	return tern_insert(head, key, val, TVAL_NODE);
 }
 
+tern_node *tern_insert_path(tern_node *head, char const *key, tern_val val, uint8_t valtype)
+{
+	const char *next_key = key + strlen(key) + 1;
+	if (*next_key) {
+		tern_node *child = tern_find_node(head, key);
+		child = tern_insert_path(child, next_key, val, valtype);
+		return tern_insert_node(head, key, child);
+	} else {
+		return tern_insert(head, key, val, valtype);
+	}
+}
+
+uint8_t tern_delete_path(tern_node **head, char const *key, tern_val *out)
+{
+	const char *next_key = key + strlen(key) + 1;
+	if (*next_key) {
+		tern_node *child = tern_find_node(*head, key);
+		if (!child) {
+			return TVAL_NONE;
+		}
+		tern_node *tmp = child;
+		uint8_t valtype = tern_delete_path(&tmp, next_key, out);
+		if (tmp != child) {
+			*head = tern_insert_node(*head, key, tmp);
+		}
+		return valtype;
+	} else {
+		return tern_delete(head, key, out);
+	}
+}
+
 uint32_t tern_count(tern_node *head)
 {
 	uint32_t count = 0;
@@ -202,7 +272,7 @@
 	if (head->left) {
 		tern_foreach_int(head->left, fun, data, keybuf, pos);
 	}
-	if (head->el) {
+	if (head->el && head->straight.next) {
 		if (pos == MAX_ITER_KEY) {
 			fatal_error("tern_foreach_int: exceeded maximum key size");
 		}
--- a/tern.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/tern.h	Sat Jan 05 00:58:08 2019 -0800
@@ -43,10 +43,13 @@
 void * tern_find_ptr_default(tern_node * head, char const * key, void * def);
 void * tern_find_ptr(tern_node * head, char const * key);
 tern_node *tern_find_node(tern_node *head, char const *key);
+uint8_t tern_delete(tern_node **head, char const *key, tern_val *out);
 tern_val tern_find_path_default(tern_node *head, char const *key, tern_val def, uint8_t req_valtype);
 tern_val tern_find_path(tern_node *head, char const *key, uint8_t valtype);
+uint8_t tern_delete_path(tern_node **head, char const *key, tern_val *out);
 tern_node * tern_insert_ptr(tern_node * head, char const * key, void * value);
 tern_node * tern_insert_node(tern_node *head, char const *key, tern_node *value);
+tern_node *tern_insert_path(tern_node *head, char const *key, tern_val val, uint8_t valtype);
 uint32_t tern_count(tern_node *head);
 void tern_foreach(tern_node *head, iter_fun fun, void *data);
 char * tern_int_key(uint32_t key, char * buf);
--- a/util.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/util.c	Sat Jan 05 00:58:08 2019 -0800
@@ -204,6 +204,85 @@
 	*(output++) = 0;
 }
 
+char *utf16be_to_utf8(uint8_t *buf, uint32_t max_size)
+{
+	uint8_t *cur = buf;
+	uint32_t converted_size = 0;
+	for (uint32_t i = 0; i < max_size; i++, cur+=2)
+	{
+		uint16_t code = *cur << 16 | cur[1];
+		if (!code) {
+			break;
+		}
+		if (code < 0x80) {
+			converted_size++;
+		} else if (code < 0x800) {
+			converted_size += 2;
+		} else {
+			//TODO: Deal with surrogate pairs
+			converted_size += 3;
+		}
+	}
+	char *out = malloc(converted_size + 1);
+	char *cur_out = out;
+	cur = buf;
+	for (uint32_t i = 0; i < max_size; i++, cur+=2)
+	{
+		uint16_t code = *cur << 16 | cur[1];
+		if (!code) {
+			break;
+		}
+		if (code < 0x80) {
+			*(cur_out++) = code;
+		} else if (code < 0x800) {
+			*(cur_out++) = 0xC0 | code >> 6;
+			*(cur_out++) = 0x80 | (code & 0x3F);
+		} else {
+			//TODO: Deal with surrogate pairs
+			*(cur_out++) = 0xF0 | code >> 12;
+			*(cur_out++) = 0x80 | (code >> 6 & 0x3F);
+			*(cur_out++) = 0x80 | (code & 0x3F);
+		}
+	}
+	*cur_out = 0;
+	return out;
+}
+
+int utf8_codepoint(const char **text)
+{
+	uint8_t initial = **text;
+	(*text)++;
+	if (initial < 0x80) {
+		return initial;
+	}
+	int base = 0;
+	uint8_t extended_bytes = 0;
+	if ((initial & 0xE0) == 0xC0) {
+		base = 0x80;
+		initial &= 0x1F;
+		extended_bytes = 1;
+	} else if ((initial & 0xF0) == 0xE0) {
+		base = 0x800;
+		initial &= 0xF;
+		extended_bytes = 2;
+	} else if ((initial & 0xF8) == 0xF0) {
+		base = 0x10000;
+		initial &= 0x7;
+		extended_bytes = 3;
+	}
+	int value = initial;
+	for (uint8_t i = 0; i < extended_bytes; i++)
+	{
+		if ((**text & 0xC0) != 0x80) {
+			return -1;
+		}
+		value = value << 6;
+		value |= (**text) & 0x3F;
+		(*text)++;
+	}
+	return value + base;
+}
+
 char is_path_sep(char c)
 {
 #ifdef _WIN32
@@ -224,11 +303,11 @@
 	return is_path_sep(path[0]);
 }
 
-char * basename_no_extension(char *path)
+char * basename_no_extension(const char *path)
 {
-	char *lastdot = NULL;
-	char *lastslash = NULL;
-	char *cur;
+	const char *lastdot = NULL;
+	const char *lastslash = NULL;
+	const char *cur;
 	for (cur = path; *cur; cur++)
 	{
 		if (*cur == '.') {
@@ -250,11 +329,11 @@
 	return barename;
 }
 
-char *path_extension(char *path)
+char *path_extension(char const *path)
 {
-	char *lastdot = NULL;
-	char *lastslash = NULL;
-	char *cur;
+	char const *lastdot = NULL;
+	char const *lastslash = NULL;
+	char const *cur;
 	for (cur = path; *cur; cur++)
 	{
 		if (*cur == '.') {
@@ -270,10 +349,28 @@
 	return strdup(lastdot+1);
 }
 
-char * path_dirname(char *path)
+uint8_t path_matches_extensions(char *path, char **ext_list, uint32_t num_exts)
 {
-	char *lastslash = NULL;
-	char *cur;
+	char *ext = path_extension(path);
+	if (!ext) {
+		return 0;
+	}
+	uint32_t extidx;
+	for (extidx = 0; extidx < num_exts; extidx++)
+	{
+		if (!strcasecmp(ext, ext_list[extidx])) {
+			free(ext);
+			return 1;
+		}
+	}
+	free(ext);
+	return 0;
+}
+
+char * path_dirname(const char *path)
+{
+	const char *lastslash = NULL;
+	const char *cur;
 	for (cur = path; *cur; cur++)
 	{
 		if (is_path_sep(*cur)) {
@@ -440,34 +537,70 @@
 
 dir_entry *get_dir_list(char *path, size_t *numret)
 {
-	HANDLE dir;
-	WIN32_FIND_DATA file;
-	char *pattern = alloc_concat(path, "/*.*");
-	dir = FindFirstFile(pattern, &file);
-	free(pattern);
-	if (dir == INVALID_HANDLE_VALUE) {
+	dir_entry *ret;
+	if (path[0] == PATH_SEP[0] && !path[1]) {
+		int drives = GetLogicalDrives();
+		size_t count = 0;
+		for (int i = 0; i < 26; i++)
+		{
+			if (drives & (1 << i)) {
+				count++;
+			}
+		}
+		ret = calloc(count, sizeof(dir_entry));
+		dir_entry *cur = ret;
+		for (int i = 0; i < 26; i++)
+		{
+			if (drives & (1 << i)) {
+				cur->name = malloc(4);
+				cur->name[0] = 'A' + i;
+				cur->name[1] = ':';
+				cur->name[2] = PATH_SEP[0];
+				cur->name[3] = 0;
+				cur->is_dir = 1;
+				cur++;
+			}
+		}
 		if (numret) {
-			*numret = 0;
+			*numret = count;
 		}
-		return NULL;
-	}
-	
-	size_t storage = 64;
-	dir_entry *ret = malloc(sizeof(dir_entry) * storage);
-	size_t pos = 0;
-	
-	do {
-		if (pos == storage) {
-			storage = storage * 2;
-			ret = realloc(ret, sizeof(dir_entry) * storage);
+	} else {
+		HANDLE dir;
+		WIN32_FIND_DATA file;
+		char *pattern = alloc_concat(path, "/*.*");
+		dir = FindFirstFile(pattern, &file);
+		free(pattern);
+		if (dir == INVALID_HANDLE_VALUE) {
+			if (numret) {
+				*numret = 0;
+			}
+			return NULL;
 		}
-		ret[pos].name = strdup(file.cFileName);
-		ret[pos++].is_dir = (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
-	} while (FindNextFile(dir, &file));
-	
-	FindClose(dir);
-	if (numret) {
-		*numret = pos;
+		
+		size_t storage = 64;
+		ret = malloc(sizeof(dir_entry) * storage);
+		size_t pos = 0;
+		
+		if (path[1] == ':' && (!path[2] || (path[2] == PATH_SEP[0] && !path[3]))) {
+			//we are in the root of a drive, add a virtual .. entry
+			//for navigating to the virtual root directory
+			ret[pos].name = strdup("..");
+			ret[pos++].is_dir = 1;
+		}
+		
+		do {
+			if (pos == storage) {
+				storage = storage * 2;
+				ret = realloc(ret, sizeof(dir_entry) * storage);
+			}
+			ret[pos].name = strdup(file.cFileName);
+			ret[pos++].is_dir = (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+		} while (FindNextFile(dir, &file));
+		
+		FindClose(dir);
+		if (numret) {
+			*numret = pos;
+		}
 	}
 	return ret;
 }
@@ -489,7 +622,7 @@
 	return (time_t)wintime;
 }
 
-int ensure_dir_exists(char *path)
+int ensure_dir_exists(const char *path)
 {
 	if (CreateDirectory(path, NULL)) {
 		return 1;
@@ -626,6 +759,7 @@
 	if (numret) {
 		*numret = pos;
 	}
+	closedir(d);
 	return ret;
 }
 
@@ -643,7 +777,7 @@
 #endif
 }
 
-int ensure_dir_exists(char *path)
+int ensure_dir_exists(const char *path)
 {
 	struct stat st;
 	if (stat(path, &st)) {
@@ -680,6 +814,22 @@
 	free(list);
 }
 
+static int sort_dir_alpha(const void *a, const void *b)
+{
+	const dir_entry *da, *db;
+	da = a;
+	db = b;
+	if (da->is_dir != db->is_dir) {
+		return db->is_dir - da->is_dir;
+	}
+	return strcasecmp(((dir_entry *)a)->name, ((dir_entry *)b)->name);
+}
+
+void sort_dir_list(dir_entry *list, size_t num_entries)
+{
+	qsort(list, num_entries, sizeof(dir_entry), sort_dir_alpha);
+}
+
 #ifdef __ANDROID__
 
 #include <SDL.h>
@@ -759,6 +909,7 @@
 	} else {
 		ret = NULL;
 	}
+	fclose(f);
 	return ret;
 }
 
@@ -776,7 +927,14 @@
 
 char const *get_config_dir()
 {
-	return get_userdata_dir();
+	static char* confdir;
+	if (!confdir) {
+		char const *base = get_userdata_dir();
+		if (base) {	
+			confdir = alloc_concat(base,  PATH_SEP "blastem");
+		}
+	}
+	return confdir;
 }
 #define CONFIG_PREFIX ""
 #define SAVE_PREFIX ""
--- a/util.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/util.h	Sat Jan 05 00:58:08 2019 -0800
@@ -34,16 +34,22 @@
 char * split_keyval(char * text);
 //Takes a binary byte buffer and produces a lowercase hex string
 void bin_to_hex(uint8_t *output, uint8_t *input, uint64_t size);
+//Takes an (optionally) null-terminated UTF16-BE string and converts a maximum of max_size code-units to UTF-8
+char *utf16be_to_utf8(uint8_t *buf, uint32_t max_size);
+//Returns the next Unicode codepoint from a utf-8 string
+int utf8_codepoint(const char **text);
 //Determines whether a character is a valid path separator for the current platform
 char is_path_sep(char c);
 //Determines whether a path is considered an absolute path on the current platform
 char is_absolute_path(char *path);
 //Returns the basename of a path with th extension (if any) stripped
-char * basename_no_extension(char *path);
+char * basename_no_extension(const char *path);
 //Returns the extension from a path or NULL if there is no extension
-char *path_extension(char *path);
+char *path_extension(char const *path);
+//Returns true if the given path matches one of the extensions in the list
+uint8_t path_matches_extensions(char *path, char **ext_list, uint32_t num_exts);
 //Returns the directory portion of a path or NULL if there is no directory part
-char *path_dirname(char *path);
+char *path_dirname(const char *path);
 //Gets the smallest power of two that is >= a certain value, won't work for values > 0x80000000
 uint32_t nearest_pow2(uint32_t val);
 //Should be called by main with the value of argv[0] for use by get_exe_dir
@@ -62,10 +68,12 @@
 dir_entry *get_dir_list(char *path, size_t *numret);
 //Frees a dir list returned by get_dir_list
 void free_dir_list(dir_entry *list, size_t numentries);
+//Performs a case-insensitive sort by file name on a dir list
+void sort_dir_list(dir_entry *list, size_t num_entries);
 //Gets the modification time of a file
 time_t get_modification_time(char *path);
 //Recusrively creates a directory if it does not exist
-int ensure_dir_exists(char *path);
+int ensure_dir_exists(const char *path);
 //Returns the contents of a symlink in a newly allocated string
 char * readlink_alloc(char * path);
 //Prints an error message to stderr and to a message box if not in headless mode and then exits
--- a/vdp.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/vdp.c	Sat Jan 05 00:58:08 2019 -0800
@@ -5,7 +5,6 @@
 */
 #include "vdp.h"
 #include "blastem.h"
-#include "genesis.h"
 #include <stdlib.h>
 #include <string.h>
 #include "render.h"
@@ -19,7 +18,6 @@
 #define MAP_BIT_H_FLIP 0x800
 #define MAP_BIT_V_FLIP 0x1000
 
-#define SCROLL_BUFFER_SIZE 32
 #define SCROLL_BUFFER_MASK (SCROLL_BUFFER_SIZE-1)
 #define SCROLL_BUFFER_DRAW (SCROLL_BUFFER_SIZE/2)
 
@@ -138,13 +136,9 @@
 
 static uint8_t color_map_init_done;
 
-void init_vdp_context(vdp_context * context, uint8_t region_pal)
+vdp_context *init_vdp_context(uint8_t region_pal)
 {
-	memset(context, 0, sizeof(*context));
-	context->vdpmem = malloc(VRAM_SIZE);
-	memset(context->vdpmem, 0, VRAM_SIZE);
-	/*
-	*/
+	vdp_context *context = calloc(1, sizeof(vdp_context) + VRAM_SIZE);
 	if (headless) {
 		context->output = malloc(LINEBUF_SIZE * sizeof(uint32_t));
 		context->output_pitch = 0;
@@ -152,10 +146,6 @@
 		context->cur_buffer = FRAMEBUFFER_ODD;
 		context->fb = render_get_framebuffer(FRAMEBUFFER_ODD, &context->output_pitch);
 	}
-	context->linebuf = malloc(LINEBUF_SIZE + SCROLL_BUFFER_SIZE*2);
-	memset(context->linebuf, 0, LINEBUF_SIZE + SCROLL_BUFFER_SIZE*2);
-	context->tmp_buf_a = context->linebuf + LINEBUF_SIZE;
-	context->tmp_buf_b = context->tmp_buf_a + SCROLL_BUFFER_SIZE;
 	context->sprite_draws = MAX_DRAWS;
 	context->fifo_write = 0;
 	context->fifo_read = -1;
@@ -250,12 +240,11 @@
 	if (!headless) {
 		context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * context->border_top);
 	}
+	return context;
 }
 
 void vdp_free(vdp_context *context)
 {
-	free(context->vdpmem);
-	free(context->linebuf);
 	free(context);
 }
 
@@ -528,6 +517,8 @@
 		   context->regs[REG_DMASRC_H],
 		       context->regs[REG_DMASRC_H] << 17 | context->regs[REG_DMASRC_M] << 9 | context->regs[REG_DMASRC_L] << 1,
 			   src_types[context->regs[REG_DMASRC_H] >> 6 & 3]);
+	uint8_t old_flags = context->flags;
+	uint8_t old_flags2 = context->flags2;
 	printf("\n**Internal Group**\n"
 	       "Address: %X\n"
 	       "CD:      %X - %s\n"
@@ -541,8 +532,9 @@
 		   (context->flags & FLAG_PENDING) ? "word" : (context->flags2 & FLAG2_BYTE_PENDING) ? "byte" : "none",
 		   context->vcounter, context->hslot*2, (context->flags2 & FLAG2_VINT_PENDING) ? "true" : "false",
 		   (context->flags2 & FLAG2_HINT_PENDING) ? "true" : "false", vdp_control_port_read(context));
-
-	//TODO: Window Group, DMA Group
+	//restore flags as calling vdp_control_port_read can change them
+	context->flags = old_flags;
+	context->flags2 = old_flags2;
 }
 
 static uint8_t is_active(vdp_context *context)
@@ -778,9 +770,9 @@
 static void update_color_map(vdp_context *context, uint16_t index, uint16_t value)
 {
 	context->colors[index] = color_map[value & CRAM_BITS];
-	context->colors[index + CRAM_SIZE] = color_map[(value & CRAM_BITS) | FBUF_SHADOW];
-	context->colors[index + CRAM_SIZE*2] = color_map[(value & CRAM_BITS) | FBUF_HILIGHT];
-	context->colors[index + CRAM_SIZE*3] = color_map[(value & CRAM_BITS) | FBUF_MODE4];
+	context->colors[index + SHADOW_OFFSET] = color_map[(value & CRAM_BITS) | FBUF_SHADOW];
+	context->colors[index + HIGHLIGHT_OFFSET] = color_map[(value & CRAM_BITS) | FBUF_HILIGHT];
+	context->colors[index + MODE4_OFFSET] = color_map[(value & CRAM_BITS) | FBUF_MODE4];
 }
 
 void write_cram_internal(vdp_context * context, uint16_t addr, uint16_t value)
@@ -806,7 +798,7 @@
 	)) {
 		uint8_t bg_end_slot = BG_START_SLOT + (context->regs[REG_MODE_4] & BIT_H40) ? LINEBUF_SIZE/2 : (256+HORIZ_BORDER)/2;
 		if (context->hslot < bg_end_slot) {
-			uint32_t color = (context->regs[REG_MODE_2] & BIT_MODE_5) ? context->colors[addr] : context->colors[addr + CRAM_SIZE*3];
+			uint32_t color = (context->regs[REG_MODE_2] & BIT_MODE_5) ? context->colors[addr] : context->colors[addr + MODE4_OFFSET];
 			context->output[(context->hslot - BG_START_SLOT)*2 + 1] = color;
 		}
 	}
@@ -875,14 +867,17 @@
 	context->vdpmem[address] = value;
 }
 
+#define DMA_FILL 0x80
+#define DMA_COPY 0xC0
+#define DMA_TYPE_MASK 0xC0
 static void external_slot(vdp_context * context)
 {
-	if ((context->flags & FLAG_DMA_RUN) && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80 && context->fifo_read < 0) {
+	if ((context->flags & FLAG_DMA_RUN) && (context->regs[REG_DMASRC_H] & DMA_TYPE_MASK) == DMA_FILL && context->fifo_read < 0) {
 		context->fifo_read = (context->fifo_write-1) & (FIFO_SIZE-1);
 		fifo_entry * cur = context->fifo + context->fifo_read;
 		cur->cycle = context->cycles;
 		cur->address = context->address;
-		cur->partial = 2;
+		cur->partial = 1;
 		vdp_advance_dma(context);
 	}
 	fifo_entry * start = context->fifo + context->fifo_read;
@@ -893,20 +888,16 @@
 			if ((context->regs[REG_MODE_2] & (BIT_128K_VRAM|BIT_MODE_5)) == (BIT_128K_VRAM|BIT_MODE_5)) {
 				vdp_check_update_sat(context, start->address, start->value);
 				write_vram_word(context, start->address, start->value);
-			} else if (start->partial) {
-				//printf("VRAM Write: %X to %X at %d (line %d, slot %d)\n", start->value, start->address ^ 1, context->cycles, context->cycles/MCLKS_LINE, (context->cycles%MCLKS_LINE)/16);
-				uint8_t byte = start->partial == 2 ? start->value >> 8 : start->value;
-				if (start->partial > 1) {
-					vdp_check_update_sat_byte(context, start->address ^ 1, byte);
-				}
+			} else {
+				uint8_t byte = start->partial == 1 ? start->value >> 8 : start->value;
+				vdp_check_update_sat_byte(context, start->address ^ 1, byte);
 				write_vram_byte(context, start->address ^ 1, byte);
-			} else {
-				//printf("VRAM Write High: %X to %X at %d (line %d, slot %d)\n", start->value >> 8, start->address, context->cycles, context->cycles/MCLKS_LINE, (context->cycles%MCLKS_LINE)/16);
-				vdp_check_update_sat(context, start->address, start->value);
-				write_vram_byte(context, start->address, start->value >> 8);
-				start->partial = 1;
-				//skip auto-increment and removal of entry from fifo
-				return;
+				if (!start->partial) {
+					start->address = start->address ^ 1;
+					start->partial = 1;
+					//skip auto-increment and removal of entry from fifo
+					return;
+				}
 			}
 			break;
 		case CRAM_WRITE: {
@@ -921,7 +912,7 @@
 				}
 				write_cram(context, start->address, val);
 			} else {
-				write_cram(context, start->address, start->partial == 2 ? context->fifo[context->fifo_write].value : start->value);
+				write_cram(context, start->address, start->partial ? context->fifo[context->fifo_write].value : start->value);
 			}
 			break;
 		}
@@ -937,7 +928,7 @@
 						context->vsram[(start->address/2) & 63] |= start->value;
 					}
 				} else {
-					context->vsram[(start->address/2) & 63] = start->partial == 2 ? context->fifo[context->fifo_write].value : start->value;
+					context->vsram[(start->address/2) & 63] = start->partial ? context->fifo[context->fifo_write].value : start->value;
 				}
 			}
 
@@ -945,12 +936,12 @@
 		}
 		context->fifo_read = (context->fifo_read+1) & (FIFO_SIZE-1);
 		if (context->fifo_read == context->fifo_write) {
-			if ((context->cd & 0x20) && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80) {
+			if ((context->cd & 0x20) && (context->regs[REG_DMASRC_H] & DMA_TYPE_MASK) == DMA_FILL) {
 				context->flags |= FLAG_DMA_RUN;
 			}
 			context->fifo_read = -1;
 		}
-	} else if ((context->flags & FLAG_DMA_RUN) && (context->regs[REG_DMASRC_H] & 0xC0) == 0xC0) {
+	} else if ((context->flags & FLAG_DMA_RUN) && (context->regs[REG_DMASRC_H] & DMA_TYPE_MASK) == DMA_COPY) {
 		if (context->flags & FLAG_READ_FETCHED) {
 			write_vram_byte(context, context->address ^ 1, context->prefetch);
 			
@@ -1217,23 +1208,31 @@
 	} else {
 		address += 4 * context->v_offset;
 	}
-	uint16_t pal_priority = (col >> 9) & 0x70;
-	int32_t dir;
+	uint8_t pal_priority = (col >> 9) & 0x70;
+	uint32_t bits = *((uint32_t *)(&context->vdpmem[address]));
 	if (col & MAP_BIT_H_FLIP) {
-		offset += 7;
-		offset &= SCROLL_BUFFER_MASK;
-		dir = -1;
+		uint32_t shift = 28;
+		for (int i = 0; i < 4; i++)
+		{
+			uint8_t right = pal_priority | ((bits >> shift) & 0xF);
+			shift -= 4;
+			tmp_buf[offset++] = pal_priority | ((bits >> shift) & 0xF);
+			shift -= 4;
+			offset &= SCROLL_BUFFER_MASK;
+			tmp_buf[offset++] = right;
+			offset &= SCROLL_BUFFER_MASK;
+		}
 	} else {
-		dir = 1;
-	}
-	for (uint32_t i=0; i < 4; i++, address++)
-	{
-		tmp_buf[offset] = pal_priority | (context->vdpmem[address] >> 4);
-		offset += dir;
-		offset &= SCROLL_BUFFER_MASK;
-		tmp_buf[offset] = pal_priority | (context->vdpmem[address] & 0xF);
-		offset += dir;
-		offset &= SCROLL_BUFFER_MASK;
+		for (int i = 0; i < 4; i++)
+		{
+			uint8_t right = pal_priority | (bits & 0xF);
+			bits >>= 4;
+			tmp_buf[offset++] = pal_priority | (bits & 0xF);
+			offset &= SCROLL_BUFFER_MASK;
+			bits >>= 4;
+			tmp_buf[offset++] = right;
+			offset &= SCROLL_BUFFER_MASK;
+		}
 	}
 }
 
@@ -1272,9 +1271,318 @@
 	context->fetch_tmp[1] = context->vdpmem[address+1];
 }
 
+static uint8_t composite_normal(vdp_context *context, uint8_t *debug_dst, uint8_t sprite, uint8_t plane_a, uint8_t plane_b, uint8_t bg_index)
+{
+	uint8_t pixel = bg_index;
+	uint8_t src = DBG_SRC_BG;
+	if (plane_b & 0xF) {
+		pixel = plane_b;
+		src = DBG_SRC_B;
+	}
+	if (plane_a & 0xF && (plane_a & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) {
+		pixel = plane_a;
+		src = DBG_SRC_A;
+	}
+	if (sprite & 0xF && (sprite & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) {
+		pixel = sprite;
+		src = DBG_SRC_S;
+	}
+	*debug_dst = src;
+	return pixel;
+}
+typedef struct {
+	uint8_t index, intensity;
+} sh_pixel;
+
+static sh_pixel composite_highlight(vdp_context *context, uint8_t *debug_dst, uint8_t sprite, uint8_t plane_a, uint8_t plane_b, uint8_t bg_index)
+{
+	uint8_t pixel = bg_index;
+	uint8_t src = DBG_SRC_BG;
+	uint8_t intensity = 0;
+	if (plane_b & 0xF) {
+		pixel = plane_b;
+		src = DBG_SRC_B;
+	}
+	intensity = plane_b & BUF_BIT_PRIORITY;
+	if (plane_a & 0xF && (plane_a & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) {
+		pixel = plane_a;
+		src = DBG_SRC_A;
+	}
+	intensity |= plane_a & BUF_BIT_PRIORITY;
+	if (sprite & 0xF && (sprite & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) {
+		if ((sprite & 0x3F) == 0x3E) {
+			intensity += BUF_BIT_PRIORITY;
+		} else if ((sprite & 0x3F) == 0x3F) {
+			intensity = 0;
+		} else {
+			pixel = sprite;
+			src = DBG_SRC_S;
+			if ((pixel & 0xF) == 0xE) {
+				intensity = BUF_BIT_PRIORITY;
+			} else {
+				intensity |= pixel & BUF_BIT_PRIORITY;
+			}
+		}
+	}
+	*debug_dst = src;
+	return (sh_pixel){.index = pixel, .intensity = intensity};
+}
+
+static void render_normal(vdp_context *context, int32_t col, uint32_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off)
+{
+	int start = 0;
+	if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) {
+		uint32_t bgcolor = context->colors[context->regs[REG_BG_COLOR] & 0x3F];
+		for (int i = 0; i < 8; ++i)
+		{
+			*(dst++) = bgcolor;
+			*(debug_dst++) = DBG_SRC_BG;
+		}
+		start = 8;
+	}
+	uint8_t *sprite_buf = context->linebuf + col * 8 + start;
+	for (int i = start; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i)
+	{
+		uint8_t sprite, plane_a, plane_b;
+		plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK];
+		plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK];
+		sprite = *sprite_buf;
+		uint8_t pixel = composite_normal(context, debug_dst, sprite, plane_a, plane_b, context->regs[REG_BG_COLOR]);
+		debug_dst++;
+		*(dst++) = context->colors[pixel & 0x3F];
+	}
+}
+
+static void render_highlight(vdp_context *context, int32_t col, uint32_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off)
+{
+	int start = 0;
+	if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) {
+		uint32_t bgcolor = context->colors[SHADOW_OFFSET + (context->regs[REG_BG_COLOR] & 0x3F)];
+		for (int i = 0; i < 8; ++i)
+		{
+			*(dst++) = bgcolor;
+			*(debug_dst++) = DBG_SRC_BG | DBG_SHADOW;
+		}
+		start = 8;
+	}
+	uint8_t *sprite_buf = context->linebuf + col * 8 + start;
+	for (int i = start; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i)
+	{
+		uint8_t sprite, plane_a, plane_b;
+		plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK];
+		plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK];
+		sprite = *sprite_buf;
+		sh_pixel pixel = composite_highlight(context, debug_dst, sprite, plane_a, plane_b, context->regs[REG_BG_COLOR]);
+		uint32_t *colors;
+		if (pixel.intensity == BUF_BIT_PRIORITY << 1) {
+			colors = context->colors + HIGHLIGHT_OFFSET;
+		} else if (pixel.intensity) {
+			colors = context->colors;
+		} else {
+			colors = context->colors + SHADOW_OFFSET;
+		}
+		debug_dst++;
+		*(dst++) = colors[pixel.index & 0x3F];
+	}
+}
+
+static void render_testreg(vdp_context *context, int32_t col, uint32_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off, uint8_t output_disabled, uint8_t test_layer)
+{
+	if (output_disabled) {
+		switch (test_layer)
+		{
+		case 0:
+			for (int i = 0; i < 16; i++)
+			{
+				*(dst++) = 0x3F; //TODO: confirm this on hardware
+				*(debug_dst++) = DBG_SRC_BG;
+			}
+			break;
+		case 1: {
+			uint8_t *sprite_buf = context->linebuf + col * 8;
+			for (int i = 0; i < 16; i++)
+			{
+				*(dst++) = context->colors[*(sprite_buf++) & 0x3F];
+				*(debug_dst++) = DBG_SRC_S;
+			}
+			break;
+		}
+		case 2:
+			for (int i = 0; i < 16; i++)
+			{
+				*(dst++) = context->colors[context->tmp_buf_a[(plane_a_off++) & SCROLL_BUFFER_MASK] & 0x3F];
+				*(debug_dst++) = DBG_SRC_A;
+			}
+			break;
+		case 3:
+			for (int i = 0; i < 16; i++)
+			{
+				*(dst++) = context->colors[context->tmp_buf_b[(plane_b_off++) & SCROLL_BUFFER_MASK] & 0x3F];
+				*(debug_dst++) = DBG_SRC_B;
+			}
+			break;
+		}
+	} else {
+		int start = 0;
+		uint8_t *sprite_buf = context->linebuf + col * 8;
+		if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) {
+			//TODO: Confirm how test register interacts with column 0 blanking
+			uint8_t pixel = context->regs[REG_BG_COLOR] & 0x3F;
+			uint8_t src = DBG_SRC_BG;
+			for (int i = 0; i < 8; ++i)
+			{
+				switch (test_layer)
+				{
+				case 1:
+					pixel &= sprite_buf[i];
+					if (pixel) {
+						src = DBG_SRC_S;
+					}
+					break;
+				case 2:
+					pixel &= context->tmp_buf_a[(plane_a_off + i) & SCROLL_BUFFER_MASK];
+					if (pixel) {
+						src = DBG_SRC_A;
+					}
+					break;
+				case 3:
+					pixel &= context->tmp_buf_b[(plane_b_off + i) & SCROLL_BUFFER_MASK];
+					if (pixel) {
+						src = DBG_SRC_B;
+					}
+					break;
+				}
+				
+				*(dst++) = context->colors[pixel & 0x3F];
+				*(debug_dst++) = src;
+			}
+			plane_a_off += 8;
+			plane_b_off += 8;
+			sprite_buf += 8;
+			start = 8;
+		}
+		for (int i = start; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i)
+		{
+			uint8_t sprite, plane_a, plane_b;
+			plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK];
+			plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK];
+			sprite = *sprite_buf;
+			uint8_t pixel = composite_normal(context, debug_dst, sprite, plane_a, plane_b, 0x3F);
+			switch (test_layer)
+			{
+			case 1:
+				pixel &= sprite;
+				if (pixel) {
+					*debug_dst = DBG_SRC_S;
+				}
+				break;
+			case 2:
+				pixel &= plane_a;
+				if (pixel) {
+					*debug_dst = DBG_SRC_A;
+				}
+				break;
+			case 3:
+				pixel &= plane_b;
+				if (pixel) {
+					*debug_dst = DBG_SRC_B;
+				}
+				break;
+			}
+			debug_dst++;
+			*(dst++) = context->colors[pixel & 0x3F];
+		}
+	}
+}
+
+static void render_testreg_highlight(vdp_context *context, int32_t col, uint32_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off, uint8_t output_disabled, uint8_t test_layer)
+{
+	int start = 0;
+	uint8_t *sprite_buf = context->linebuf + col * 8;
+	if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) {
+		//TODO: Confirm how test register interacts with column 0 blanking
+		uint8_t pixel = context->regs[REG_BG_COLOR] & 0x3F;
+		uint8_t src = DBG_SRC_BG | DBG_SHADOW;
+		for (int i = 0; i < 8; ++i)
+		{
+			switch (test_layer)
+			{
+			case 1:
+				pixel &= sprite_buf[i];
+				if (pixel) {
+					src = DBG_SRC_S | DBG_SHADOW;
+				}
+				break;
+			case 2:
+				pixel &= context->tmp_buf_a[(plane_a_off + i) & SCROLL_BUFFER_MASK];
+				if (pixel) {
+					src = DBG_SRC_A | DBG_SHADOW;
+				}
+				break;
+			case 3:
+				pixel &= context->tmp_buf_b[(plane_b_off + i) & SCROLL_BUFFER_MASK];
+				if (pixel) {
+					src = DBG_SRC_B | DBG_SHADOW;
+				}
+				break;
+			}
+			
+			*(dst++) = context->colors[SHADOW_OFFSET + (pixel & 0x3F)];
+			*(debug_dst++) = src;
+		}
+		plane_a_off += 8;
+		plane_b_off += 8;
+		sprite_buf += 8;
+		start = 8;
+	}
+	for (int i = start; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i)
+	{
+		uint8_t sprite, plane_a, plane_b;
+		plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK];
+		plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK];
+		sprite = *sprite_buf;
+		sh_pixel pixel = composite_highlight(context, debug_dst, sprite, plane_a, plane_b, 0x3F);
+		uint32_t *colors;
+		if (pixel.intensity == BUF_BIT_PRIORITY << 1) {
+			colors = context->colors + HIGHLIGHT_OFFSET;
+		} else if (pixel.intensity) {
+			colors = context->colors;
+		} else {
+			colors = context->colors + SHADOW_OFFSET;
+		}
+		if (output_disabled) {
+			pixel.index = 0x3F;
+		}
+		switch (test_layer)
+		{
+		case 1:
+			pixel.index &= sprite;
+			if (pixel.index) {
+				*debug_dst = DBG_SRC_S;
+			}
+			break;
+		case 2:
+			pixel.index &= plane_a;
+			if (pixel.index) {
+				*debug_dst = DBG_SRC_A;
+			}
+			break;
+		case 3:
+			pixel.index &= plane_b;
+			if (pixel.index) {
+				*debug_dst = DBG_SRC_B;
+			}
+			break;
+		}
+		debug_dst++;
+		*(dst++) = colors[pixel.index & 0x3F];
+	}
+}
+
 static void render_map_output(uint32_t line, int32_t col, vdp_context * context)
 {
 	uint32_t *dst;
+	uint8_t *debug_dst;
 	uint8_t output_disabled = (context->test_port & TEST_BIT_DISABLE) != 0;
 	uint8_t test_layer = context->test_port >> 7 & 3;
 	if (context->state == PREPARING && !test_layer) {
@@ -1301,198 +1609,44 @@
 	}
 	line &= 0xFF;
 	render_map(context->col_2, context->tmp_buf_b, context->buf_b_off+8, context);
-	uint8_t *sprite_buf,  *plane_a, *plane_b;
+	uint8_t *sprite_buf;
+	uint8_t sprite, plane_a, plane_b;
 	int plane_a_off, plane_b_off;
 	if (col)
 	{
 		col-=2;
 		dst = context->output + BORDER_LEFT + col * 8;
-		if (context->debug < 2) {
-			sprite_buf = context->linebuf + col * 8;
-			uint8_t a_src, src;
-			if (context->flags & FLAG_WINDOW) {
-				plane_a_off = context->buf_a_off;
-				a_src = DBG_SRC_W;
-			} else {
-				plane_a_off = context->buf_a_off - (context->hscroll_a & 0xF);
-				a_src = DBG_SRC_A;
-			}
-			plane_b_off = context->buf_b_off - (context->hscroll_b & 0xF);
-			//printf("A | tmp_buf offset: %d\n", 8 - (context->hscroll_a & 0x7));
-
-			if (context->regs[REG_MODE_4] & BIT_HILIGHT) {
-				for (int i = 0; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i) {
-					plane_a = context->tmp_buf_a + (plane_a_off & SCROLL_BUFFER_MASK);
-					plane_b = context->tmp_buf_b + (plane_b_off & SCROLL_BUFFER_MASK);
-					uint8_t pixel = context->regs[REG_BG_COLOR];
-					uint32_t *colors = context->colors;
-					src = DBG_SRC_BG;
-					if (*plane_b & 0xF) {
-						pixel = *plane_b;
-						src = DBG_SRC_B;
-					}
-					uint8_t intensity = *plane_b & BUF_BIT_PRIORITY;
-					if (*plane_a & 0xF && (*plane_a & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) {
-						pixel = *plane_a;
-						src = a_src;
-					}
-					intensity |= *plane_a & BUF_BIT_PRIORITY;
-					if (*sprite_buf & 0xF && (*sprite_buf & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) {
-						if ((*sprite_buf & 0x3F) == 0x3E) {
-							intensity += BUF_BIT_PRIORITY;
-						} else if ((*sprite_buf & 0x3F) == 0x3F) {
-							intensity = 0;
-						} else {
-							pixel = *sprite_buf;
-							src = DBG_SRC_S;
-							if ((pixel & 0xF) == 0xE) {
-								intensity = BUF_BIT_PRIORITY;
-							} else {
-								intensity |= pixel & BUF_BIT_PRIORITY;
-							}
-						}
-					}
-					if (output_disabled) {
-						pixel = 0x3F;
-					}
-					if (!intensity) {
-						src |= DBG_SHADOW;
-						colors += CRAM_SIZE;
-					} else if (intensity ==  BUF_BIT_PRIORITY*2) {
-						src |= DBG_HILIGHT;
-						colors += CRAM_SIZE*2;
-					}
-					//TODO: Verify how test register stuff interacts with shadow/highlight
-					//TODO: Simulate CRAM corruption from bus fight
-					switch (test_layer)
-					{
-					case 1:
-						pixel &= *sprite_buf;
-						if (output_disabled && pixel) {
-							src = DBG_SRC_S;
-						}
-						break;
-					case 2:
-						pixel &= *plane_a;
-						if (output_disabled && pixel) {
-							src = DBG_SRC_A;
-						}
-						break;
-					case 3:
-						pixel &= *plane_b;
-						if (output_disabled && pixel) {
-							src = DBG_SRC_B;
-						}
-						break;
-					}
+		debug_dst = context->layer_debug_buf + BORDER_LEFT + col * 8;
+		
+		
+		uint8_t a_src, src;
+		if (context->flags & FLAG_WINDOW) {
+			plane_a_off = context->buf_a_off;
+			a_src = DBG_SRC_W;
+		} else {
+			plane_a_off = context->buf_a_off - (context->hscroll_a & 0xF);
+			a_src = DBG_SRC_A;
+		}
+		plane_b_off = context->buf_b_off - (context->hscroll_b & 0xF);
+		//printf("A | tmp_buf offset: %d\n", 8 - (context->hscroll_a & 0x7));
 
-					uint32_t outpixel;
-					if (context->debug) {
-						outpixel = context->debugcolors[src];
-					} else {
-						outpixel = colors[pixel & 0x3F];
-					}
-					*(dst++) = outpixel;
-				}
+		if (context->regs[REG_MODE_4] & BIT_HILIGHT) {
+			if (output_disabled || test_layer) {
+				render_testreg_highlight(context, col, dst, debug_dst, plane_a_off, plane_b_off, output_disabled, test_layer);
 			} else {
-				for (int i = 0; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i) {
-					plane_a = context->tmp_buf_a + (plane_a_off & SCROLL_BUFFER_MASK);
-					plane_b = context->tmp_buf_b + (plane_b_off & SCROLL_BUFFER_MASK);
-					uint8_t pixel = context->regs[REG_BG_COLOR];
-					src = DBG_SRC_BG;
-					if (output_disabled) {
-						pixel = 0x3F;
-					} else {
-						if (*plane_b & 0xF) {
-							pixel = *plane_b;
-							src = DBG_SRC_B;
-						}
-						if (*plane_a & 0xF && (*plane_a & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) {
-							pixel = *plane_a;
-							src = a_src;
-						}
-						if (*sprite_buf & 0xF && (*sprite_buf & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) {
-							pixel = *sprite_buf;
-							src = DBG_SRC_S;
-						}
-					}
-					//TODO: Simulate CRAM corruption from bus fight
-					switch (test_layer)
-					{
-					case 1:
-						pixel &= *sprite_buf;
-						if (output_disabled && pixel) {
-							src = DBG_SRC_S;
-						}
-						break;
-					case 2:
-						pixel &= *plane_a;
-						if (output_disabled && pixel) {
-							src = DBG_SRC_A;
-						}
-						break;
-					case 3:
-						pixel &= *plane_b;
-						if (output_disabled && pixel) {
-							src = DBG_SRC_B;
-						}
-						break;
-					}
-					uint32_t outpixel;
-					if (context->debug) {
-						outpixel = context->debugcolors[src];
-					} else {
-						outpixel = context->colors[pixel & 0x3F];
-					}
-					*(dst++) = outpixel;
-				}
-			}
-		} else if (context->debug == 2) {
-			if (col < 32) {
-				*(dst++) = context->colors[col * 2];
-				*(dst++) = context->colors[col * 2];
-				*(dst++) = context->colors[col * 2];
-				*(dst++) = context->colors[col * 2];
-				*(dst++) = context->colors[col * 2 + 1];
-				*(dst++) = context->colors[col * 2 + 1];
-				*(dst++) = context->colors[col * 2 + 1];
-				*(dst++) = context->colors[col * 2 + 1];
-				*(dst++) = context->colors[col * 2 + 2];
-				*(dst++) = context->colors[col * 2 + 2];
-				*(dst++) = context->colors[col * 2 + 2];
-				*(dst++) = context->colors[col * 2 + 2];
-				*(dst++) = context->colors[col * 2 + 3];
-				*(dst++) = context->colors[col * 2 + 3];
-				*(dst++) = context->colors[col * 2 + 3];
-				*(dst++) = context->colors[col * 2 + 3];
-			} else if (col == 32 || line >= 192) {
-				for (int32_t i = 0; i < 16; i ++) {
-					*(dst++) = 0;
-				}
-			} else {
-				for (int32_t i = 0; i < 16; i ++) {
-					*(dst++) = context->colors[line / 3 + (col - 34) * 0x20];
-				}
+				render_highlight(context, col, dst, debug_dst, plane_a_off, plane_b_off);
 			}
 		} else {
-			uint32_t base = (context->debug - 3) * 0x200;
-			uint32_t cell = base + (line / 8) * (context->regs[REG_MODE_4] & BIT_H40 ? 40 : 32) + col;
-			uint32_t address = (cell * 32 + (line % 8) * 4) & 0xFFFF;
-			for (int32_t i = 0; i < 4; i ++) {
-				*(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] >> 4)];
-				*(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] & 0xF)];
-				address++;
-			}
-			cell++;
-			address = (cell * 32 + (line % 8) * 4) & 0xFFFF;
-			for (int32_t i = 0; i < 4; i ++) {
-				*(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] >> 4)];
-				*(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] & 0xF)];
-				address++;
+			if (output_disabled || test_layer) {
+				render_testreg(context, col, dst, debug_dst, plane_a_off, plane_b_off, output_disabled, test_layer);
+			} else {
+				render_normal(context, col, dst, debug_dst, plane_a_off, plane_b_off);
 			}
 		}
+		dst += 16;
 	} else {
 		dst = context->output;
+		debug_dst = context->layer_debug_buf;
 		uint8_t pixel = context->regs[REG_BG_COLOR] & 0x3F;
 		if (output_disabled) {
 			pixel = 0x3F;
@@ -1503,9 +1657,11 @@
 			{
 			case 1:
 				bg_color = context->colors[0];
-				for (int i = 0; i < BORDER_LEFT; i++, dst++)
+				for (int i = 0; i < BORDER_LEFT; i++, dst++, debug_dst++)
 				{
 					*dst = bg_color;
+					*debug_dst = DBG_SRC_BG;
+					
 				}
 				break;
 			case 2: {
@@ -1515,9 +1671,10 @@
 				i = 0;
 				uint8_t buf_off = context->buf_a_off - (context->hscroll_a & 0xF) + (16 - BORDER_LEFT);
 				//uint8_t *src = context->tmp_buf_a + ((context->buf_a_off + (i ? 0 : (16 - BORDER_LEFT) - (context->hscroll_a & 0xF))) & SCROLL_BUFFER_MASK); 
-				for (; i < BORDER_LEFT; buf_off++, i++, dst++)
+				for (; i < BORDER_LEFT; buf_off++, i++, dst++, debug_dst++)
 				{
 					*dst = context->colors[context->tmp_buf_a[buf_off & SCROLL_BUFFER_MASK]];
+					*debug_dst = DBG_SRC_A;
 				}
 				break;
 			}
@@ -1527,17 +1684,19 @@
 				i = 0;
 				uint8_t buf_off = context->buf_b_off - (context->hscroll_b & 0xF) + (16 - BORDER_LEFT);
 				//uint8_t *src = context->tmp_buf_b + ((context->buf_b_off + (i ? 0 : (16 - BORDER_LEFT) - (context->hscroll_b & 0xF))) & SCROLL_BUFFER_MASK); 
-				for (; i < BORDER_LEFT; buf_off++, i++, dst++)
+				for (; i < BORDER_LEFT; buf_off++, i++, dst++, debug_dst++)
 				{
 					*dst = context->colors[context->tmp_buf_b[buf_off & SCROLL_BUFFER_MASK]];
+					*debug_dst = DBG_SRC_B;
 				}
 				break;
 			}
 			}
 		} else {
-			for (int i = 0; i < BORDER_LEFT; i++, dst++)
+			for (int i = 0; i < BORDER_LEFT; i++, dst++, debug_dst++)
 			{
 				*dst = bg_color;
+				*debug_dst = DBG_SRC_BG;
 			}
 		}
 	}
@@ -1586,8 +1745,9 @@
 	}
 	context->buf_a_off = (context->buf_a_off + 8) & 15;
 	
-	uint8_t bgcolor = 0x10 | (context->regs[REG_BG_COLOR] & 0xF) + CRAM_SIZE*3;
+	uint8_t bgcolor = 0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET;
 	uint32_t *dst = context->output + col * 8 + BORDER_LEFT;
+	uint8_t *debug_dst = context->layer_debug_buf + col * 8 + BORDER_LEFT;
 	if (context->state == PREPARING) {
 		for (int i = 0; i < 16; i++)
 		{
@@ -1596,65 +1756,30 @@
 		context->done_output = dst;
 		return;
 	}
-	if (context->debug < 2) {
-		if (col || !(context->regs[REG_MODE_1] & BIT_COL0_MASK)) {
-			uint8_t *sprite_src = context->linebuf + col * 8;
-			if (context->regs[REG_MODE_1] & BIT_SPRITE_8PX) {
-				sprite_src += 8;
-			}
-			for (int i = 0; i < 8; i++, sprite_src++)
-			{
-				uint8_t *bg_src = context->tmp_buf_a + ((8 + i + col * 8 - (context->hscroll_a & 0x7)) & 15);
-				if ((*bg_src & 0x4F) > 0x40 || !*sprite_src) {
-					//background plane has priority and is opaque or sprite layer is transparent
-					if (context->debug) {
-						*(dst++) = context->debugcolors[DBG_SRC_A];
-					} else {
-						*(dst++) = context->colors[(*bg_src & 0x1F) + CRAM_SIZE*3];
-					}
-				} else {
-					//sprite layer is opaque and not covered by high priority BG pixels
-					if (context->debug) {
-						*(dst++) = context->debugcolors[DBG_SRC_S];
-					} else {
-						*(dst++) = context->colors[*sprite_src | 0x10 + CRAM_SIZE*3];
-					}
-				}
-			}
-		} else {
-			for (int i = 0; i < 8; i++)
-			{
-				*(dst++) = context->colors[bgcolor];
+	
+	if (col || !(context->regs[REG_MODE_1] & BIT_COL0_MASK)) {
+		uint8_t *sprite_src = context->linebuf + col * 8;
+		if (context->regs[REG_MODE_1] & BIT_SPRITE_8PX) {
+			sprite_src += 8;
+		}
+		for (int i = 0; i < 8; i++, sprite_src++)
+		{
+			uint8_t *bg_src = context->tmp_buf_a + ((8 + i + col * 8 - (context->hscroll_a & 0x7)) & 15);
+			if ((*bg_src & 0x4F) > 0x40 || !*sprite_src) {
+				//background plane has priority and is opaque or sprite layer is transparent
+				*(dst++) = context->colors[(*bg_src & 0x1F) + MODE4_OFFSET];
+				*(debug_dst++) = DBG_SRC_A;
+			} else {
+				//sprite layer is opaque and not covered by high priority BG pixels
+				*(dst++) = context->colors[*sprite_src | 0x10 + MODE4_OFFSET];
+				*(debug_dst++) = DBG_SRC_S;
 			}
 		}
-	} else if (context->debug == 2) {
+	} else {
 		for (int i = 0; i < 8; i++)
 		{
-			*(dst++) = context->colors[CRAM_SIZE*3 + col];
-		}
-	} else {
-		uint32_t cell = (line / 8) * 32 + col;
-		uint32_t address = cell * 32 + (line % 8) * 4;
-		uint32_t m4_address = mode4_address_map[address & 0x3FFF];
-		uint32_t pixel = planar_to_chunky[context->vdpmem[m4_address]] << 1;
-		pixel |= planar_to_chunky[context->vdpmem[m4_address + 1]];
-		m4_address = mode4_address_map[(address + 2) & 0x3FFF];
-		pixel |= planar_to_chunky[context->vdpmem[m4_address]] << 3;
-		pixel |= planar_to_chunky[context->vdpmem[m4_address + 1]] << 2;
-		if (context->debug_pal < 2) {
-			for (int i = 28; i >= 0; i -= 4)
-			{
-				*(dst++) = context->colors[CRAM_SIZE*3 | (context->debug_pal << 4) | (pixel >> i & 0xF)];
-			}
-		} else {
-			for (int i = 28; i >= 0; i -= 4)
-			{
-				uint8_t value = (pixel >> i & 0xF) * 17;
-				if (context->debug_pal == 3) {
-					value = 255 - value;
-				}
-				*(dst++) = render_map_color(value, value, value);
-			}
+			*(dst++) = context->colors[bgcolor];
+			*(debug_dst++) = DBG_SRC_BG;
 		}
 	}
 	context->done_output = dst;
@@ -1674,31 +1799,69 @@
 	}
 	last_line = context->cycles;
 #endif
-	context->vcounter++;
-	
+	uint16_t jump_start, jump_end;
 	uint8_t is_mode_5 = context->regs[REG_MODE_2] & BIT_MODE_5;
 	if (is_mode_5) {
 		if (context->flags2 & FLAG2_REGION_PAL) {
 			if (context->regs[REG_MODE_2] & BIT_PAL) {
-				if (context->vcounter == 0x10B) {
-					context->vcounter = 0x1D2;
-				}
-			} else if (context->vcounter == 0x103){
-				context->vcounter = 0x1CA;
+				jump_start = 0x10B;
+				jump_end = 0x1D2;
+			} else {
+				jump_start = 0x103;
+				jump_end = 0x1CA;
 			}
+		} else if (context->regs[REG_MODE_2] & BIT_PAL) {
+			jump_start = 0x100;
+			jump_end = 0x1FA;
 		} else {
-			if (context->regs[REG_MODE_2] & BIT_PAL) {
-				if (context->vcounter == 0x100) {
-					context->vcounter = 0x1FA;
+			jump_start = 0xEB;
+			jump_end = 0x1E5;
+		}
+	} else {
+		jump_start = 0xDB;
+		jump_end = 0x1D5;
+	}
+
+	if (context->enabled_debuggers & (1 << VDP_DEBUG_CRAM | 1 << VDP_DEBUG_COMPOSITE)) {
+		uint32_t line = context->vcounter;
+		if (line >= jump_end) {
+			line -= jump_end - jump_start;
+		}
+		uint32_t total_lines = (context->flags2 & FLAG2_REGION_PAL) ? 313 : 262;
+		
+		if (total_lines - line <= context->border_top) {
+			line -= total_lines - context->border_top;
+		} else {
+			line += context->border_top;
+		}
+		if (context->enabled_debuggers & (1 << VDP_DEBUG_CRAM)) {
+			uint32_t *fb = context->debug_fbs[VDP_DEBUG_CRAM] + context->debug_fb_pitch[VDP_DEBUG_CRAM] * line / sizeof(uint32_t);
+			for (int i = 0; i < 64; i++)
+			{
+				for (int x = 0; x < 8; x++)
+				{
+					*(fb++) = context->colors[i];
 				}
-			} else if (context->vcounter == 0xEB) {
-				context->vcounter = 0x1E5;
 			}
 		}
-	} else if (context->vcounter == 0xDB) {
-		context->vcounter = 0x1D5;
+		if (
+			context->enabled_debuggers & (1 << VDP_DEBUG_COMPOSITE)
+			&& line < (context->inactive_start + context->border_bot + context->border_top)
+		) {
+			uint32_t *fb = context->debug_fbs[VDP_DEBUG_COMPOSITE] + context->debug_fb_pitch[VDP_DEBUG_COMPOSITE] * line / sizeof(uint32_t);
+			for (int i = 0; i < LINEBUF_SIZE; i++)
+			{
+				*(fb++) = context->debugcolors[context->layer_debug_buf[i]];
+			}
+		}
 	}
-	context->vcounter &= 0x1FF;
+	
+	context->vcounter++;
+	if (context->vcounter == jump_start) {
+		context->vcounter = jump_end;
+	} else {
+		context->vcounter &= 0x1FF;
+	}
 	if (context->state == PREPARING) {
 		context->state = ACTIVE;
 	}
@@ -1717,6 +1880,190 @@
 	}
 }
 
+static void vdp_update_per_frame_debug(vdp_context *context)
+{
+	if (context->enabled_debuggers & (1 << VDP_DEBUG_PLANE)) {
+		uint32_t pitch;
+		uint32_t *fb = render_get_framebuffer(context->debug_fb_indices[VDP_DEBUG_PLANE], &pitch);
+		uint16_t hscroll_mask;
+		uint16_t v_mul;
+		uint16_t vscroll_mask = 0x1F | (context->regs[REG_SCROLL] & 0x30) << 1;
+		switch(context->regs[REG_SCROLL] & 0x3)
+		{
+		case 0:
+			hscroll_mask = 0x1F;
+			v_mul = 64;
+			break;
+		case 0x1:
+			hscroll_mask = 0x3F;
+			v_mul = 128;
+			break;
+		case 0x2:
+			//TODO: Verify this behavior
+			hscroll_mask = 0x1F;
+			v_mul = 0;
+			break;
+		case 0x3:
+			hscroll_mask = 0x7F;
+			v_mul = 256;
+			break;
+		}
+		uint16_t table_address;
+		switch(context->debug_modes[VDP_DEBUG_PLANE] % 3)
+		{
+		case 0:
+			table_address = context->regs[REG_SCROLL_A] << 10 & 0xE000;
+			break;
+		case 1:
+			table_address = context->regs[REG_SCROLL_B] << 13 & 0xE000;
+			break;
+		case 2:
+			table_address = context->regs[REG_WINDOW] << 10;
+			if (context->regs[REG_MODE_4] & BIT_H40) {
+				table_address &= 0xF000;
+				v_mul = 128;
+				hscroll_mask = 0x3F;
+			} else {
+				table_address &= 0xF800;
+				v_mul = 64;
+				hscroll_mask = 0x1F;
+			}
+			vscroll_mask = 0x1F;
+			break;
+		}
+		uint32_t bg_color = context->colors[context->regs[REG_BG_COLOR & 0x3F]];
+		for (uint16_t row = 0; row < 128; row++)
+		{
+			uint16_t row_address = table_address + (row & vscroll_mask) * v_mul;
+			for (uint16_t col = 0; col < 128; col++)
+			{
+				uint16_t address = row_address + (col & hscroll_mask) * 2;
+				//pccv hnnn nnnn nnnn
+				//
+				uint16_t entry = context->vdpmem[address] << 8 | context->vdpmem[address + 1];
+				uint8_t pal = entry >> 9 & 0x30;
+				
+				uint32_t *dst = fb + (row * pitch * 8 / sizeof(uint32_t)) + col * 8;
+				address = (entry & 0x7FF) * 32;
+				int y_diff = 4;
+				if (entry & 0x1000) {
+					y_diff = -4;
+					address += 7 * 4;
+				}
+				int x_diff = 1;
+				if (entry & 0x800) {
+					x_diff = -1;
+					address += 3;
+				}
+				for (int y = 0; y < 8; y++)
+				{
+					uint16_t trow_address = address;
+					uint32_t *row_dst = dst;
+					for (int x = 0; x < 4; x++)
+					{
+						uint8_t byte = context->vdpmem[trow_address];
+						trow_address += x_diff;
+						uint8_t left, right;
+						if (x_diff > 0) {
+							left = byte >> 4;
+							right = byte & 0xF;
+						} else {
+							left = byte & 0xF;
+							right = byte >> 4;
+						}
+						*(row_dst++) = left ? context->colors[left|pal] : bg_color;
+						*(row_dst++) = right ? context->colors[right|pal] : bg_color;
+					}
+					address += y_diff;
+					dst += pitch / sizeof(uint32_t);
+				}
+			}
+		}
+		render_framebuffer_updated(context->debug_fb_indices[VDP_DEBUG_PLANE], 1024);
+	}
+	
+	if (context->enabled_debuggers & (1 << VDP_DEBUG_VRAM)) {
+		uint32_t pitch;
+		uint32_t *fb = render_get_framebuffer(context->debug_fb_indices[VDP_DEBUG_VRAM], &pitch);
+		
+		uint8_t pal = (context->debug_modes[VDP_DEBUG_VRAM] % 4) << 4;
+		for (int y = 0; y < 512; y++)
+		{
+			uint32_t *line = fb + y * pitch / sizeof(uint32_t);
+			int row = y >> 4;
+			int yoff = y >> 1 & 7;
+			for (int col = 0; col < 64; col++)
+			{
+				uint16_t address = (row * 64 + col) * 32 + yoff * 4;
+				for (int x = 0; x < 4; x++)
+				{
+					uint8_t byte = context->vdpmem[address++];
+					uint8_t left = byte >> 4 | pal;
+					uint8_t right = byte & 0xF | pal;
+					*(line++) = context->colors[left];
+					*(line++) = context->colors[left];
+					*(line++) = context->colors[right];
+					*(line++) = context->colors[right];
+				}
+			}
+		}
+		
+		render_framebuffer_updated(context->debug_fb_indices[VDP_DEBUG_VRAM], 1024);
+	}
+	
+	if (context->enabled_debuggers & (1 << VDP_DEBUG_CRAM)) {
+		uint32_t starting_line = 512 - 32*4;
+		uint32_t *line = context->debug_fbs[VDP_DEBUG_CRAM] 
+			+ context->debug_fb_pitch[VDP_DEBUG_CRAM]  * starting_line / sizeof(uint32_t);
+		for (int pal = 0; pal < 4; pal ++)
+		{
+			uint32_t *cur;
+			for (int y = 0; y < 31; y++)
+			{
+				cur = line;
+				for (int offset = 0; offset < 16; offset++)
+				{
+					for (int x = 0; x < 31; x++)
+					{
+						*(cur++) = context->colors[pal * 16 + offset];
+					}
+					*(cur++) = 0xFF000000;
+				}
+				line += context->debug_fb_pitch[VDP_DEBUG_CRAM] / sizeof(uint32_t);
+			}
+			cur = line;
+			for (int x = 0; x < 512; x++)
+			{
+				*(cur++) = 0xFF000000;
+			}
+			line += context->debug_fb_pitch[VDP_DEBUG_CRAM] / sizeof(uint32_t);
+		}
+		render_framebuffer_updated(context->debug_fb_indices[VDP_DEBUG_CRAM], 512);
+		context->debug_fbs[VDP_DEBUG_CRAM] = render_get_framebuffer(context->debug_fb_indices[VDP_DEBUG_CRAM], &context->debug_fb_pitch[VDP_DEBUG_CRAM]);
+	}
+	if (context->enabled_debuggers & (1 << VDP_DEBUG_COMPOSITE)) {
+		render_framebuffer_updated(context->debug_fb_indices[VDP_DEBUG_COMPOSITE], LINEBUF_SIZE);
+		context->debug_fbs[VDP_DEBUG_COMPOSITE] = render_get_framebuffer(context->debug_fb_indices[VDP_DEBUG_COMPOSITE], &context->debug_fb_pitch[VDP_DEBUG_COMPOSITE]);
+	}		
+}
+
+void vdp_force_update_framebuffer(vdp_context *context)
+{
+	uint16_t lines_max = (context->flags2 & FLAG2_REGION_PAL) 
+			? 240 + BORDER_TOP_V30_PAL + BORDER_BOT_V30_PAL 
+			: 224 + BORDER_TOP_V28 + BORDER_BOT_V28;
+			
+	uint16_t to_fill = lines_max - context->output_lines;
+	memset(
+		((char *)context->fb) + context->output_pitch * context->output_lines,
+		0,
+		to_fill * context->output_pitch
+	);
+	render_framebuffer_updated(context->cur_buffer, context->h40_lines > context->output_lines / 2 ? LINEBUF_SIZE : (256+HORIZ_BORDER));
+	context->fb = render_get_framebuffer(context->cur_buffer, &context->output_pitch);
+	vdp_update_per_frame_debug(context);
+}
+
 static void advance_output_line(vdp_context *context)
 {
 	if (headless) {
@@ -1733,6 +2080,7 @@
 			render_framebuffer_updated(context->cur_buffer, context->h40_lines > (context->inactive_start + context->border_top) / 2 ? LINEBUF_SIZE : (256+HORIZ_BORDER));
 			context->cur_buffer = context->flags2 & FLAG2_EVEN_FIELD ? FRAMEBUFFER_EVEN : FRAMEBUFFER_ODD;
 			context->fb = render_get_framebuffer(context->cur_buffer, &context->output_pitch);
+			vdp_update_per_frame_debug(context);
 			context->h40_lines = 0;
 			context->frame++;
 			context->output_lines = 0;
@@ -2042,7 +2390,7 @@
 	case CALC_SLOT(slot, 3):\
 		if ((slot + 3) == 140) {\
 			uint32_t *dst = context->output + BORDER_LEFT + 256 + 8;\
-			uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + CRAM_SIZE*3];\
+			uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET];\
 			for (int i = 0; i < BORDER_RIGHT-8; i++, dst++)\
 			{\
 				*dst = bgcolor;\
@@ -2562,7 +2910,7 @@
 	case 0: {
 		scan_sprite_table_mode4(context);
 		uint32_t *dst = context->output;;
-		uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + CRAM_SIZE*3];
+		uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET];
 		for (int i = 0; i < BORDER_LEFT-8; i++, dst++)
 		{
 			*dst = bgcolor;
@@ -2584,7 +2932,7 @@
 		context->buf_a_off = 8;
 		memset(context->tmp_buf_a, 0, 8);
 		uint32_t *dst = context->output + BORDER_LEFT - 8;
-		uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + CRAM_SIZE*3];
+		uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET];
 		for (int i = 0; i < 8; i++, dst++)
 		{
 			*dst = bgcolor;
@@ -2640,7 +2988,7 @@
 		context->cur_slot = context->sprite_index = MAX_DRAWS_H32_MODE4-1;
 		context->sprite_draws = MAX_DRAWS_H32_MODE4;
 		uint32_t *dst = context->output + BORDER_LEFT + 256;
-		uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + CRAM_SIZE*3];
+		uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET];
 		for (int i = 0; i < 8; i++, dst++)
 		{
 			*dst = bgcolor;
@@ -2763,18 +3111,26 @@
 			active_line = 0x200;
 		}
 	}
-	uint32_t *dst = (
-		context->vcounter < context->inactive_start + context->border_bot 
-		|| context->vcounter >= 0x200 - context->border_top
-	) && context->hslot >= BG_START_SLOT && context->hslot < bg_end_slot
-		? context->output + 2 * (context->hslot - BG_START_SLOT)
-		: NULL;
+	uint32_t *dst;
+	uint8_t *debug_dst;
+	if (
+		(
+			context->vcounter < context->inactive_start + context->border_bot 
+			|| context->vcounter >= 0x200 - context->border_top
+		) && context->hslot >= BG_START_SLOT && context->hslot < bg_end_slot
+	) {
+		dst = context->output + 2 * (context->hslot - BG_START_SLOT);
+		debug_dst = context->layer_debug_buf + 2 * (context->hslot - BG_START_SLOT);
+	} else {
+		dst = NULL;
+	}
 		
 	if (
 		!dst && context->vcounter == context->inactive_start + context->border_bot
 		&& context->hslot >= line_change  && context->hslot < bg_end_slot
 	) {
 		dst = context->output + 2 * (context->hslot - BG_START_SLOT);
+		debug_dst = context->layer_debug_buf + 2 * (context->hslot - BG_START_SLOT);
 	}
 		
 	uint8_t test_layer = context->test_port >> 7 & 3;
@@ -2790,6 +3146,7 @@
 			|| context->vcounter >= 0x200 - context->border_top
 		)) {
 			dst = context->output + (context->hslot - BG_START_SLOT) * 2;
+			debug_dst = context->layer_debug_buf + 2 * (context->hslot - BG_START_SLOT);
 		} else if (context->hslot == bg_end_slot) {
 			advance_output_line(context);
 			dst = NULL;
@@ -2846,21 +3203,26 @@
 			if (mode_5) {
 				bg_color = context->colors[context->regs[REG_BG_COLOR] & 0x3F];
 			} else if (context->regs[REG_MODE_1] & BIT_MODE_4) {
-				bg_color = context->colors[CRAM_SIZE * 3 + 0x10 + (context->regs[REG_BG_COLOR] & 0xF)];
+				bg_color = context->colors[MODE4_OFFSET + 0x10 + (context->regs[REG_BG_COLOR] & 0xF)];
 			}
 			if (dst >= context->done_output) {
 				*(dst++) = bg_color;
+				*(debug_dst++) = DBG_SRC_BG;
 			} else {
 				dst++;
+				debug_dst++;
 			}
 			if (dst >= context->done_output) {
 				*(dst++) = bg_color;
+				*(debug_dst++) = DBG_SRC_BG;
 				context->done_output = dst;
 			} else {
 				dst++;
+				debug_dst++;
 			}
 			if (context->hslot == (bg_end_slot-1)) {
 				*(dst++) = bg_color;
+				*(debug_dst++) = DBG_SRC_BG;
 				context->done_output = dst;
 			}
 		}
@@ -2949,7 +3311,7 @@
 		}
 		uint32_t min_dma_complete = dmalen * (context->regs[REG_MODE_4] & BIT_H40 ? 16 : 20);
 		if (
-			(context->regs[REG_DMASRC_H] & 0xC0) == 0xC0 
+			(context->regs[REG_DMASRC_H] & DMA_TYPE_MASK) == DMA_COPY 
 			|| (((context->cd & 0xF) == VRAM_WRITE) && !(context->regs[REG_MODE_2] & BIT_128K_VRAM))) {
 			//DMA copies take twice as long to complete since they require a read and a write
 			//DMA Fills and transfers to VRAM also take twice as long as it requires 2 writes for a single word
@@ -3025,7 +3387,7 @@
 		//printf("New Address: %X, New CD: %X\n", context->address, context->cd);
 		if (context->cd & 0x20) {
 			//
-			if((context->regs[REG_DMASRC_H] & 0xC0) != 0x80) {
+			if((context->regs[REG_DMASRC_H] & DMA_TYPE_MASK) != DMA_FILL) {
 				//DMA copy or 68K -> VDP, transfer starts immediately
 				//printf("DMA start (length: %X) at cycle %d, frame: %d, vcounter: %d, hslot: %d\n", (context->regs[REG_DMALEN_H] << 8) | context->regs[REG_DMALEN_L], context->cycles, context->frame, context->vcounter, context->hslot);
 				if (!(context->regs[REG_DMASRC_H] & 0x80)) {
@@ -3106,7 +3468,7 @@
 int vdp_data_port_write(vdp_context * context, uint16_t value)
 {
 	//printf("data port write: %X at %d\n", value, context->cycles);
-	if (context->flags & FLAG_DMA_RUN && (context->regs[REG_DMASRC_H] & 0xC0) != 0x80) {
+	if (context->flags & FLAG_DMA_RUN && (context->regs[REG_DMASRC_H] & DMA_TYPE_MASK) != DMA_FILL) {
 		return -1;
 	}
 	if (context->flags & FLAG_PENDING) {
@@ -3118,7 +3480,7 @@
 	/*if (context->fifo_cur == context->fifo_end) {
 		printf("FIFO full, waiting for space before next write at cycle %X\n", context->cycles);
 	}*/
-	if (context->cd & 0x20 && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80) {
+	if (context->cd & 0x20 && (context->regs[REG_DMASRC_H] & DMA_TYPE_MASK) == DMA_FILL) {
 		context->flags &= ~FLAG_DMA_RUN;
 	}
 	while (context->fifo_write == context->fifo_read) {
@@ -3154,7 +3516,7 @@
 	/*if (context->fifo_cur == context->fifo_end) {
 		printf("FIFO full, waiting for space before next write at cycle %X\n", context->cycles);
 	}*/
-	if (context->cd & 0x20 && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80) {
+	if (context->cd & 0x20 && (context->regs[REG_DMASRC_H] & DMA_TYPE_MASK) == DMA_FILL) {
 		context->flags &= ~FLAG_DMA_RUN;
 	}
 	while (context->fifo_write == context->fifo_read) {
@@ -3721,3 +4083,77 @@
 	context->pending_hint_start = load_int32(buf);
 	update_video_params(context);
 }
+
+static vdp_context *current_vdp;
+static void vdp_debug_window_close(uint8_t which)
+{
+	//TODO: remove need for current_vdp global, and find the VDP via current_system instead
+	for (int i = 0; i < VDP_NUM_DEBUG_TYPES; i++)
+	{
+		if (current_vdp->enabled_debuggers & (1 << i) && which == current_vdp->debug_fb_indices[i]) {
+			vdp_toggle_debug_view(current_vdp, i);
+			break;
+		}
+	}
+}
+
+void vdp_toggle_debug_view(vdp_context *context, uint8_t debug_type)
+{
+	if (context->enabled_debuggers & 1 << debug_type) {
+		render_destroy_window(context->debug_fb_indices[debug_type]);
+		context->enabled_debuggers &= ~(1 << debug_type);
+	} else {
+		uint32_t width,height;
+		uint8_t fetch_immediately = 0;
+		char *caption;
+		switch(debug_type)
+		{
+		case VDP_DEBUG_PLANE:
+			caption = "BlastEm - VDP Plane Debugger";
+			width = height = 1024;
+			break;
+		case VDP_DEBUG_VRAM:
+			caption = "BlastEm - VDP VRAM Debugger";
+			width = 1024;
+			height = 512;
+			break;
+		case VDP_DEBUG_CRAM:
+			caption = "BlastEm - VDP CRAM Debugger";
+			width = 512;
+			height = 512;
+			fetch_immediately = 1;
+			break;
+		case VDP_DEBUG_COMPOSITE:
+			caption = "BlastEm - VDP Plane Composition Debugger";
+			width = LINEBUF_SIZE;
+			height = context->inactive_start + context->border_top + context->border_bot;
+			fetch_immediately = 1;
+			break;
+		default:
+			return;
+		}
+		current_vdp = context;
+		context->debug_fb_indices[debug_type] = render_create_window(caption, width, height, vdp_debug_window_close);
+		if (context->debug_fb_indices[debug_type]) {
+			context->enabled_debuggers |= 1 << debug_type;
+		}
+		if (fetch_immediately) {
+			context->debug_fbs[debug_type] = render_get_framebuffer(context->debug_fb_indices[debug_type], &context->debug_fb_pitch[debug_type]);
+		}
+	}
+}
+
+void vdp_inc_debug_mode(vdp_context *context)
+{
+	uint8_t active = render_get_active_framebuffer();
+	if (active < FRAMEBUFFER_USER_START) {
+		return;
+	}
+	for (int i = 0; i < VDP_NUM_DEBUG_TYPES; i++)
+	{
+		if (context->enabled_debuggers & (1 << i) && context->debug_fb_indices[i] == active) {
+			context->debug_modes[i]++;
+			return;
+		}
+	}
+}
--- a/vdp.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/vdp.h	Sat Jan 05 00:58:08 2019 -0800
@@ -13,12 +13,16 @@
 
 #define VDP_REGS 24
 #define CRAM_SIZE 64
+#define SHADOW_OFFSET CRAM_SIZE
+#define HIGHLIGHT_OFFSET (SHADOW_OFFSET+CRAM_SIZE)
+#define MODE4_OFFSET (HIGHLIGHT_OFFSET+CRAM_SIZE)
 #define VSRAM_SIZE 40
 #define VRAM_SIZE (64*1024)
 #define BORDER_LEFT 13
 #define BORDER_RIGHT 14
 #define HORIZ_BORDER (BORDER_LEFT+BORDER_RIGHT)
 #define LINEBUF_SIZE (320+HORIZ_BORDER) //H40 + full border
+#define SCROLL_BUFFER_SIZE 32
 #define BORDER_BOTTOM 13 //TODO: Replace with actual value
 #define MAX_DRAWS 40
 #define MAX_DRAWS_H32 32
@@ -147,74 +151,89 @@
 	uint8_t  partial;
 } fifo_entry;
 
+enum {
+	VDP_DEBUG_PLANE,
+	VDP_DEBUG_VRAM,
+	VDP_DEBUG_CRAM,
+	VDP_DEBUG_COMPOSITE,
+	VDP_NUM_DEBUG_TYPES
+};
+
 typedef struct {
-	fifo_entry  fifo[FIFO_SIZE];
-	int32_t     fifo_write;
-	int32_t     fifo_read;
-	uint32_t    address;
-	uint32_t    serial_address;
-	uint8_t     cd;
-	uint8_t	    flags;
-	uint8_t     regs[VDP_REGS];
-	//cycle count in MCLKs
-	uint32_t    cycles;
-	uint32_t    pending_vint_start;
-	uint32_t    pending_hint_start;
-	uint8_t     *vdpmem;
-	//stores 2-bit palette + 4-bit palette index + priority for current sprite line
-	uint8_t     *linebuf;
-	//pointer to current line in framebuffer
-	uint32_t    *output;
-	uint32_t    *done_output;
-	uint32_t    *fb;
 	system_header  *system;
-	uint16_t    cram[CRAM_SIZE];
-	uint32_t    colors[CRAM_SIZE*4];
-	uint32_t    debugcolors[1 << (3 + 1 + 1 + 1)];//3 bits for source, 1 bit for priority, 1 bit for shadow, 1 bit for hilight
-	uint16_t    vsram[VSRAM_SIZE];
-	uint16_t    vscroll_latch[2];
-	uint32_t    output_pitch;
-	uint32_t    frame;
-	uint16_t    vcounter;
-	uint16_t    inactive_start;
-	uint16_t    border_top;
-	uint16_t    border_bot;
-	uint16_t    hscroll_a;
-	uint16_t    hscroll_b;
-	uint16_t    h40_lines;
-	uint16_t    output_lines;
-	sprite_draw sprite_draw_list[MAX_DRAWS];
-	sprite_info sprite_info_list[MAX_SPRITES_LINE];
-	uint8_t     sat_cache[SAT_CACHE_SIZE];
-	uint16_t    col_1;
-	uint16_t    col_2;
-	uint16_t    hv_latch;
-	uint16_t    prefetch;
-	uint16_t    test_port;
-	uint8_t     hslot; //hcounter/2
-	uint8_t	    sprite_index;
-	uint8_t     sprite_draws;
-	int8_t      slot_counter;
-	int8_t      cur_slot;
-	uint8_t     max_sprites_frame;
-	uint8_t     max_sprites_line;
-	uint8_t     fetch_tmp[2];
-	uint8_t     v_offset;
-	uint8_t     hint_counter;
-	uint8_t     flags2;
-	uint8_t     double_res;
-	uint8_t     buf_a_off;
-	uint8_t     buf_b_off;
-	uint8_t     debug;
-	uint8_t     debug_pal;
-	uint8_t     pending_byte;
-	uint8_t     state;
-	uint8_t     cur_buffer;
-	uint8_t     *tmp_buf_a;
-	uint8_t     *tmp_buf_b;
+	//pointer to current line in framebuffer
+	uint32_t       *output;
+	uint32_t       *done_output;
+	//pointer to current framebuffer
+	uint32_t       *fb;
+	uint32_t       *debug_fbs[VDP_NUM_DEBUG_TYPES];
+	uint32_t       output_pitch;
+	uint32_t       debug_fb_pitch[VDP_NUM_DEBUG_TYPES];
+	fifo_entry     fifo[FIFO_SIZE];
+	int32_t        fifo_write;
+	int32_t        fifo_read;
+	uint32_t       address;
+	uint32_t       serial_address;
+	uint32_t       colors[CRAM_SIZE*4];
+	uint32_t       debugcolors[1 << (3 + 1 + 1 + 1)];//3 bits for source, 1 bit for priority, 1 bit for shadow, 1 bit for hilight
+	uint16_t       cram[CRAM_SIZE];
+	uint32_t       frame;
+	uint8_t        cd;
+	uint8_t	       flags;
+	uint8_t        regs[VDP_REGS];
+	//cycle count in MCLKs
+	uint32_t       cycles;
+	uint32_t       pending_vint_start;
+	uint32_t       pending_hint_start;
+	uint16_t       vsram[VSRAM_SIZE];
+	uint16_t       vscroll_latch[2];
+	uint16_t       vcounter;
+	uint16_t       inactive_start;
+	uint16_t       border_top;
+	uint16_t       border_bot;
+	uint16_t       hscroll_a;
+	uint16_t       hscroll_b;
+	uint16_t       h40_lines;
+	uint16_t       output_lines;
+	sprite_draw    sprite_draw_list[MAX_DRAWS];
+	sprite_info    sprite_info_list[MAX_SPRITES_LINE];
+	uint8_t        sat_cache[SAT_CACHE_SIZE];
+	uint16_t       col_1;
+	uint16_t       col_2;
+	uint16_t       hv_latch;
+	uint16_t       prefetch;
+	uint16_t       test_port;
+	//stores 2-bit palette + 4-bit palette index + priority for current sprite line
+	uint8_t        linebuf[LINEBUF_SIZE];
+	uint8_t        layer_debug_buf[LINEBUF_SIZE];
+	uint8_t        hslot; //hcounter/2
+	uint8_t	       sprite_index;
+	uint8_t        sprite_draws;
+	int8_t         slot_counter;
+	int8_t         cur_slot;
+	uint8_t        max_sprites_frame;
+	uint8_t        max_sprites_line;
+	uint8_t        fetch_tmp[2];
+	uint8_t        v_offset;
+	uint8_t        hint_counter;
+	uint8_t        flags2;
+	uint8_t        double_res;
+	uint8_t        buf_a_off;
+	uint8_t        buf_b_off;
+	uint8_t        pending_byte;
+	uint8_t        state;
+	uint8_t        cur_buffer;
+	uint8_t        tmp_buf_a[SCROLL_BUFFER_SIZE];
+	uint8_t        tmp_buf_b[SCROLL_BUFFER_SIZE];
+	uint8_t        enabled_debuggers;
+	uint8_t        debug_fb_indices[VDP_NUM_DEBUG_TYPES];
+	uint8_t        debug_modes[VDP_NUM_DEBUG_TYPES];
+	uint8_t        vdpmem[];
 } vdp_context;
 
-void init_vdp_context(vdp_context * context, uint8_t region_pal);
+
+
+vdp_context *init_vdp_context(uint8_t region_pal);
 void vdp_free(vdp_context *context);
 void vdp_run_context_full(vdp_context * context, uint32_t target_cycles);
 void vdp_run_context(vdp_context * context, uint32_t target_cycles);
@@ -252,5 +271,10 @@
 void vdp_reacquire_framebuffer(vdp_context *context);
 void vdp_serialize(vdp_context *context, serialize_buffer *buf);
 void vdp_deserialize(deserialize_buffer *buf, void *vcontext);
+void vdp_force_update_framebuffer(vdp_context *context);
+void vdp_toggle_debug_view(vdp_context *context, uint8_t debug_type);
+void vdp_inc_debug_mode(vdp_context *context);
+//to be implemented by the host system
+uint16_t read_dma_value(uint32_t address);
 
 #endif //VDP_H_
--- a/vgmplay.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/vgmplay.c	Sat Jan 05 00:58:08 2019 -0800
@@ -62,6 +62,10 @@
 {
 }
 
+void controller_add_mappings()
+{
+}
+
 int headless = 0;
 
 #define CYCLE_LIMIT MCLKS_NTSC/60
@@ -108,10 +112,10 @@
 	uint32_t lowpass_cutoff = lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : 3390;
 
 	ym2612_context y_context;
-	ym_init(&y_context, render_sample_rate(), MCLKS_NTSC, MCLKS_PER_YM, render_audio_buffer(), opts, lowpass_cutoff);
+	ym_init(&y_context, MCLKS_NTSC, MCLKS_PER_YM, opts);
 
 	psg_context p_context;
-	psg_init(&p_context, render_sample_rate(), MCLKS_NTSC, MCLKS_PER_PSG, render_audio_buffer(), lowpass_cutoff);
+	psg_init(&p_context, MCLKS_NTSC, MCLKS_PER_PSG);
 
 	FILE * f = fopen(argv[1], "rb");
 	vgm_header header;
--- a/xband.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/xband.c	Sat Jan 05 00:58:08 2019 -0800
@@ -83,6 +83,23 @@
 
 uint8_t xband_detect(uint8_t *rom, uint32_t rom_size)
 {
+	if (rom_size < 0x200) {
+		return 0;
+	} 
+	
+	//product ID is all NULL
+	for (int i = GAME_ID_OFF; i <= (GAME_ID_OFF + GAME_ID_LEN); i++)
+	{
+		if (rom[i]) {
+			return 0;
+		}
+	}
+	
+	if (!memcmp(rom+8, "DAVE", 4)) {
+		//XBAND test roms
+		return 1;
+	}
+	
 	//Internal ROM is 512KB, accept larger ones for overdumps and custom firmware
 	if (rom_size < (512*1024)) {
 		return 0;
@@ -93,13 +110,7 @@
 		return 0;
 	}
 	
-	//product ID is all NULL
-	for (int i = GAME_ID_OFF; i <= (GAME_ID_OFF + GAME_ID_LEN); i++)
-	{
-		if (rom[i]) {
-			return 0;
-		}
-	}
+	
 	return 1;
 }
 
--- a/ym2612.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/ym2612.c	Sat Jan 05 00:58:08 2019 -0800
@@ -116,12 +116,10 @@
 	}
 	log_context = NULL;
 }
-#define BUFFER_INC_RES 0x40000000UL
 
 void ym_adjust_master_clock(ym2612_context * context, uint32_t master_clock)
 {
-	uint64_t old_inc = context->buffer_inc;
-	context->buffer_inc = ((BUFFER_INC_RES * (uint64_t)context->sample_rate) / (uint64_t)master_clock) * (uint64_t)context->clock_inc * NUM_OPERATORS;
+	render_audio_adjust_clock(context->audio, master_clock, context->clock_inc * NUM_OPERATORS);
 }
 
 #ifdef __ANDROID__
@@ -137,6 +135,11 @@
 	memset(context->part1_regs, 0, sizeof(context->part1_regs));
 	memset(context->part2_regs, 0, sizeof(context->part2_regs));
 	memset(context->operators, 0, sizeof(context->operators));
+	FILE* savedlogs[NUM_CHANNELS];
+	for (int i = 0; i < NUM_CHANNELS; i++)
+	{
+		savedlogs[i] = context->channels[i].logfile;
+	}
 	memset(context->channels, 0, sizeof(context->channels));
 	memset(context->ch3_supp, 0, sizeof(context->ch3_supp));
 	context->selected_reg = 0;
@@ -155,6 +158,7 @@
 	//some games seem to expect that the LR flags start out as 1
 	for (int i = 0; i < NUM_CHANNELS; i++) {
 		context->channels[i].lr = 0xC0;
+		context->channels[i].logfile = savedlogs[i];
 	}
 	context->write_cycle = CYCLE_NEVER;
 	for (int i = 0; i < NUM_OPERATORS; i++) {
@@ -163,23 +167,13 @@
 	}
 }
 
-void ym_init(ym2612_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t sample_limit, uint32_t options, uint32_t lowpass_cutoff)
+void ym_init(ym2612_context * context, uint32_t master_clock, uint32_t clock_div, uint32_t options)
 {
 	static uint8_t registered_finalize;
 	dfopen(debug_file, "ym_debug.txt", "w");
 	memset(context, 0, sizeof(*context));
-	context->audio_buffer = malloc(sizeof(*context->audio_buffer) * sample_limit*2);
-	context->back_buffer = malloc(sizeof(*context->audio_buffer) * sample_limit*2);
-	context->sample_rate = sample_rate;
 	context->clock_inc = clock_div * 6;
-	ym_adjust_master_clock(context, master_clock);
-	
-	double rc = (1.0 / (double)lowpass_cutoff) / (2.0 * M_PI);
-	double dt = 1.0 / ((double)master_clock / (double)(context->clock_inc * NUM_OPERATORS));
-	double alpha = dt / (dt + rc);
-	context->lowpass_alpha = (int32_t)(((double)0x10000) * alpha);
-
-	context->sample_limit = sample_limit*2;
+	context->audio = render_audio_source(master_clock, context->clock_inc * NUM_OPERATORS, 2);
 	
 	//some games seem to expect that the LR flags start out as 1
 	for (int i = 0; i < NUM_CHANNELS; i++) {
@@ -191,7 +185,7 @@
 				fprintf(stderr, "Failed to open WAVE log file %s for writing\n", fname);
 				continue;
 			}
-			if (!wave_init(f, sample_rate, 16, 1)) {
+			if (!wave_init(f, master_clock / (context->clock_inc * NUM_OPERATORS), 16, 1)) {
 				fclose(f);
 				context->channels[i].logfile = NULL;
 			}
@@ -266,13 +260,10 @@
 
 void ym_free(ym2612_context *context)
 {
+	render_free_source(context->audio);
 	if (context == log_context) {
 		ym_finalize_log();
 	}
-	free(context->audio_buffer);
-	//TODO: Figure out how to make this 100% safe
-	//audio thread could still be using this
-	free(context->back_buffer);
 	free(context);
 }
 
@@ -475,59 +466,19 @@
 			uint16_t phase = operator->phase_counter >> 10 & 0x3FF;
 			operator->phase_counter += ym_calc_phase_inc(context, operator, context->current_op);
 			int16_t mod = 0;
-			switch (op % 4)
-			{
-			case 0://Operator 1
+			if (op & 3) {
+				if (operator->mod_src[0]) {
+					mod = *operator->mod_src[0];
+					if (operator->mod_src[1]) {
+						mod += *
+						operator->mod_src[1];
+					}
+					mod >>= YM_MOD_SHIFT;
+				}
+			} else {
 				if (chan->feedback) {
 					mod = (chan->op1_old + operator->output) >> (10-chan->feedback);
 				}
-				break;
-			case 1://Operator 3
-				switch(chan->algorithm)
-				{
-				case 0:
-				case 2:
-					//modulate by operator 2
-					mod = context->operators[op+1].output >> YM_MOD_SHIFT;
-					break;
-				case 1:
-					//modulate by operator 1+2
-					mod = (context->operators[op-1].output + context->operators[op+1].output) >> YM_MOD_SHIFT;
-					break;
-				case 5:
-					//modulate by operator 1
-					mod = context->operators[op-1].output >> YM_MOD_SHIFT;
-				}
-				break;
-			case 2://Operator 2
-				if (chan->algorithm != 1 && chan->algorithm != 2 && chan->algorithm != 7) {
-					//modulate by Operator 1
-					mod = context->operators[op-2].output >> YM_MOD_SHIFT;
-				}
-				break;
-			case 3://Operator 4
-				switch(chan->algorithm)
-				{
-				case 0:
-				case 1:
-				case 4:
-					//modulate by operator 3
-					mod = context->operators[op-2].output >> YM_MOD_SHIFT;
-					break;
-				case 2:
-					//modulate by operator 1+3
-					mod = (context->operators[op-3].output + context->operators[op-2].output) >> YM_MOD_SHIFT;
-					break;
-				case 3:
-					//modulate by operator 2+3
-					mod = (context->operators[op-1].output + context->operators[op-2].output) >> YM_MOD_SHIFT;
-					break;
-				case 5:
-					//modulate by operator 1
-					mod = context->operators[op-3].output >> YM_MOD_SHIFT;
-					break;
-				}
-				break;
 			}
 			uint16_t env = operator->envelope;
 			if (operator->ssg) {
@@ -607,7 +558,6 @@
 		if (context->current_op == NUM_OPERATORS) {
 			context->current_op = 0;
 			
-			context->buffer_fraction += context->buffer_inc;
 			int16_t left = 0, right = 0;
 			for (int i = 0; i < NUM_CHANNELS; i++) {
 				int16_t value = context->channels[i].output;
@@ -621,7 +571,7 @@
 						value |= 0xC000;
 					}
 				}
-				if (context->channels[i].logfile && context->buffer_fraction > BUFFER_INC_RES) {
+				if (context->channels[i].logfile) {
 					fwrite(&value, sizeof(value), 1, context->channels[i].logfile);
 				}
 				if (context->channels[i].lr & 0x80) {
@@ -631,30 +581,7 @@
 					right += (value * YM_VOLUME_MULTIPLIER) / YM_VOLUME_DIVIDER;
 				}
 			}
-			int32_t tmp = left * context->lowpass_alpha + context->last_left * (0x10000 - context->lowpass_alpha);
-			left = tmp >> 16;
-			tmp = right * context->lowpass_alpha + context->last_right * (0x10000 - context->lowpass_alpha);
-			right = tmp >> 16;
-			while (context->buffer_fraction > BUFFER_INC_RES) {
-				context->buffer_fraction -= BUFFER_INC_RES;
-
-				int64_t tmp = context->last_left * ((context->buffer_fraction << 16) / context->buffer_inc);
-				tmp += left * (0x10000 - ((context->buffer_fraction << 16) / context->buffer_inc));
-				context->audio_buffer[context->buffer_pos] = tmp >> 16;
-				
-				tmp = context->last_right * ((context->buffer_fraction << 16) / context->buffer_inc);
-				tmp += right * (0x10000 - ((context->buffer_fraction << 16) / context->buffer_inc));
-				context->audio_buffer[context->buffer_pos+1] = tmp >> 16;
-				
-				context->buffer_pos += 2;
-				if (context->buffer_pos == context->sample_limit) {
-					if (!headless) {
-						render_wait_ym(context);
-					}
-				}
-			}
-			context->last_left = left;
-			context->last_right = right;
+			render_put_stereo_sample(context->audio, left, right);
 		}
 		
 	}
@@ -969,6 +896,103 @@
 				break;
 			case REG_ALG_FEEDBACK:
 				context->channels[channel].algorithm = value & 0x7;
+				switch (context->channels[channel].algorithm)
+				{
+				case 0:
+					//operator 3 modulated by operator 2
+					context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+2].output;
+					context->operators[channel*4+1].mod_src[1] = NULL;
+					
+					//operator 2 modulated by operator 1
+					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
+					
+					//operator 4 modulated by operator 3
+					context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+1].output;
+					context->operators[channel*4+3].mod_src[1] = NULL;
+					break;
+				case 1:
+					//operator 3 modulated by operator 1+2
+					context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+0].output;
+					context->operators[channel*4+1].mod_src[1] = &context->operators[channel*4+2].output;
+					
+					//operator 2 unmodulated
+					context->operators[channel*4+2].mod_src[0] = NULL;
+					
+					//operator 4 modulated by operator 3
+					context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+1].output;
+					context->operators[channel*4+3].mod_src[1] = NULL;
+					break;
+				case 2:
+					//operator 3 modulated by operator 2
+					context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+2].output;
+					context->operators[channel*4+1].mod_src[1] = NULL;
+					
+					//operator 2 unmodulated
+					context->operators[channel*4+2].mod_src[0] = NULL;
+					
+					//operator 4 modulated by operator 1+3
+					context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+0].output;
+					context->operators[channel*4+3].mod_src[1] = &context->operators[channel*4+1].output;
+					break;
+				case 3:
+					//operator 3 unmodulated
+					context->operators[channel*4+1].mod_src[0] = NULL;
+					context->operators[channel*4+1].mod_src[1] = NULL;
+					
+					//operator 2 modulated by operator 1
+					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
+					
+					//operator 4 modulated by operator 2+3
+					context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+2].output;
+					context->operators[channel*4+3].mod_src[1] = &context->operators[channel*4+1].output;
+					break;
+				case 4:
+					//operator 3 unmodulated
+					context->operators[channel*4+1].mod_src[0] = NULL;
+					context->operators[channel*4+1].mod_src[1] = NULL;
+					
+					//operator 2 modulated by operator 1
+					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
+					
+					//operator 4 modulated by operator 3
+					context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+1].output;
+					context->operators[channel*4+3].mod_src[1] = NULL;
+					break;
+				case 5:
+					//operator 3 modulated by operator 1
+					context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+0].output;
+					context->operators[channel*4+1].mod_src[1] = NULL;
+					
+					//operator 2 modulated by operator 1
+					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
+					
+					//operator 4 modulated by operator 1
+					context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+0].output;
+					context->operators[channel*4+3].mod_src[1] = NULL;
+					break;
+				case 6:
+					//operator 3 unmodulated
+					context->operators[channel*4+1].mod_src[0] = NULL;
+					context->operators[channel*4+1].mod_src[1] = NULL;
+					
+					//operator 2 modulated by operator 1
+					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
+					
+					//operator 4 unmodulated
+					context->operators[channel*4+3].mod_src[0] = NULL;
+					context->operators[channel*4+3].mod_src[1] = NULL;
+					break;
+				case 7:
+					//everything is an output so no modulation (except for op 1 feedback)
+					context->operators[channel*4+1].mod_src[0] = NULL;
+					context->operators[channel*4+1].mod_src[1] = NULL;
+					
+					context->operators[channel*4+2].mod_src[0] = NULL;
+					
+					context->operators[channel*4+3].mod_src[0] = NULL;
+					context->operators[channel*4+3].mod_src[1] = NULL;
+					break;
+				}
 				context->channels[channel].feedback = value >> 3 & 0x7;
 				//printf("Algorithm %d, feedback %d for channel %d\n", value & 0x7, value >> 3 & 0x7, channel);
 				break;
--- a/ym2612.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/ym2612.h	Sat Jan 05 00:58:08 2019 -0800
@@ -9,6 +9,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include "serialize.h"
+#include "render.h"
 
 #define NUM_PART_REGS (0xB7-0x30)
 #define NUM_CHANNELS 6
@@ -17,6 +18,7 @@
 #define YM_OPT_WAVE_LOG 1
 
 typedef struct {
+	int16_t  *mod_src[2];
 	uint32_t phase_counter;
 	uint16_t envelope;
 	int16_t  output;
@@ -62,14 +64,8 @@
 #define YM_PART2_REGS (YM_REG_END-YM_PART2_START)
 
 typedef struct {
-    int16_t     *audio_buffer;
-    int16_t     *back_buffer;
-    uint64_t    buffer_fraction;
-    uint64_t    buffer_inc;
+	audio_source *audio;
     uint32_t    clock_inc;
-    uint32_t    buffer_pos;
-	uint32_t    sample_rate;
-    uint32_t    sample_limit;
 	uint32_t    current_cycle;
 	//TODO: Condense the next two fields into one
 	uint32_t    write_cycle;
@@ -81,8 +77,6 @@
 	uint16_t    timer_a_load;
 	uint16_t    env_counter;
 	ym_supp     ch3_supp[3];
-	int16_t     last_left;
-	int16_t     last_right;
 	uint8_t     timer_b;
 	uint8_t     sub_timer_b;
 	uint8_t     timer_b_load;
@@ -131,7 +125,7 @@
 	REG_LR_AMS_PMS   = 0xB4
 };
 
-void ym_init(ym2612_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t sample_limit, uint32_t options, uint32_t lowpass_cutoff);
+void ym_init(ym2612_context * context, uint32_t master_clock, uint32_t clock_div, uint32_t options);
 void ym_reset(ym2612_context *context);
 void ym_free(ym2612_context *context);
 void ym_adjust_master_clock(ym2612_context * context, uint32_t master_clock);
--- a/z80_to_x86.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/z80_to_x86.c	Sat Jan 05 00:58:08 2019 -0800
@@ -2075,7 +2075,7 @@
 		break;
 	}
 	case Z80_JPCC: {
-		cycles(&opts->gen, num_cycles + 3);//T States: 4,3
+		cycles(&opts->gen, num_cycles + 6);//T States: 4,3,3
 		uint8_t cond = CC_Z;
 		switch (inst->reg)
 		{
@@ -2102,7 +2102,6 @@
 		}
 		uint8_t *no_jump_off = code->cur+1;
 		jcc(code, cond, code->cur+2);
-		cycles(&opts->gen, 5);//T States: 5
 		uint16_t dest_addr = inst->immed;
 		code_ptr call_dst = z80_get_native_address(context, dest_addr);
 			if (!call_dst) {
@@ -3454,15 +3453,43 @@
 	cmp_irdisp(code, 0, options->gen.context_reg, offsetof(z80_context, int_is_nmi), SZ_B);
 	is_nmi = code->cur + 1;
 	jcc(code, CC_NZ, is_nmi);
-	//TODO: Support interrupt mode 0 and 2
+	cycles(&options->gen, 6); //interupt ack cycle
+	//TODO: Support interrupt mode 0, not needed for Genesis sit it seems to read $FF during intack
+	//which is conveniently rst $38, i.e. the same thing that im 1 does
+	//check interrupt mode
+	cmp_irdisp(code, 2, options->gen.context_reg, offsetof(z80_context, im), SZ_B);
+	code_ptr im2 = code->cur + 1;
+	jcc(code, CC_Z, im2);
 	mov_ir(code, 0x38, options->gen.scratch1, SZ_W);
 	code_ptr after_int_dest = code->cur + 1;
 	jmp(code, after_int_dest);
+	*im2 = code->cur - (im2 + 1);
+	//read vector address from I << 8 | vector
+	mov_rdispr(code, options->gen.context_reg, offsetof(z80_context, regs) + Z80_I, options->gen.scratch1, SZ_B);
+	shl_ir(code, 8, options->gen.scratch1, SZ_W);
+	movzx_rdispr(code, options->gen.context_reg, offsetof(z80_context, im2_vector), options->gen.scratch2, SZ_B, SZ_W);
+	or_rr(code, options->gen.scratch2, options->gen.scratch1, SZ_W);
+	push_r(code, options->gen.scratch1);
+	cycles(&options->gen, 3);
+	call(code, options->read_8_noinc);
+	pop_r(code, options->gen.scratch2);
+	push_r(code, options->gen.scratch1);
+	mov_rr(code, options->gen.scratch2, options->gen.scratch1, SZ_W);
+	add_ir(code, 1, options->gen.scratch1, SZ_W);
+	cycles(&options->gen, 3);
+	call(code, options->read_8_noinc);
+	pop_r(code, options->gen.scratch2);
+	shl_ir(code, 8, options->gen.scratch1, SZ_W);
+	movzx_rr(code, options->gen.scratch2, options->gen.scratch2, SZ_B, SZ_W);
+	or_rr(code, options->gen.scratch2, options->gen.scratch1, SZ_W);
+	code_ptr after_int_dest2 = code->cur + 1;
+	jmp(code, after_int_dest2);
 	*is_nmi = code->cur - (is_nmi + 1);
 	mov_irdisp(code, 0, options->gen.context_reg, offsetof(z80_context, int_is_nmi), SZ_B);
 	mov_irdisp(code, CYCLE_NEVER, options->gen.context_reg, offsetof(z80_context, nmi_start), SZ_D);
 	mov_ir(code, 0x66, options->gen.scratch1, SZ_W);
 	*after_int_dest = code->cur - (after_int_dest + 1);
+	*after_int_dest2 = code->cur - (after_int_dest2 + 1);
 	call(code, options->native_addr);
 	mov_rrind(code, options->gen.scratch1, options->gen.context_reg, SZ_PTR);
 	tmp_stack_off = code->stack_off;
@@ -3644,7 +3671,19 @@
 
 void z80_options_free(z80_options *opts)
 {
+	for (uint32_t address = 0; address < opts->gen.address_mask; address += NATIVE_CHUNK_SIZE)
+	{
+		uint32_t chunk = address / NATIVE_CHUNK_SIZE;
+		if (opts->gen.native_code_map[chunk].base) {
+			free(opts->gen.native_code_map[chunk].offsets);
+		}
+	}
 	free(opts->gen.native_code_map);
+	uint32_t ram_inst_slots = ram_size(&opts->gen) / 1024;
+	for (uint32_t i = 0; i < ram_inst_slots; i++)
+	{
+		free(opts->gen.ram_inst_sizes[i]);
+	}
 	free(opts->gen.ram_inst_sizes);
 	free(opts);
 }
--- a/z80_to_x86.h	Tue Dec 19 00:49:13 2017 -0800
+++ b/z80_to_x86.h	Sat Jan 05 00:58:08 2019 -0800
@@ -87,6 +87,7 @@
 	uint8_t           busreq;
 	uint8_t           busack;
 	uint8_t           int_is_nmi;
+	uint8_t           im2_vector;
 	uint8_t           ram_code_flags[];
 };
 
--- a/zdis.c	Tue Dec 19 00:49:13 2017 -0800
+++ b/zdis.c	Sat Jan 05 00:58:08 2019 -0800
@@ -112,6 +112,7 @@
 						}
 					}
 				}
+				fclose(address_log);
 				break;
 			case 's':
 				opt++;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zip.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,200 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include "util.h"
+#include "zip.h"
+#ifndef DISABLE_ZLIB
+#include "zlib/zlib.h"
+#endif
+
+static const char cdfd_magic[4] = {'P', 'K', 1, 2};
+static const char eocd_magic[4] = {'P', 'K', 5, 6};
+#define MIN_EOCD_SIZE 22
+#define MIN_CDFD_SIZE 46
+#define ZIP_MAX_EOCD_OFFSET (64*1024+MIN_EOCD_SIZE)
+
+enum {
+	ZIP_STORE = 0,
+	ZIP_DEFLATE = 8
+};
+
+zip_file *zip_open(const char *filename)
+{
+	FILE *f = fopen(filename, "rb");
+	if (!f) {
+		return NULL;
+	}
+	long fsize = file_size(f);
+	if (fsize < MIN_EOCD_SIZE) {
+		//too small to be a zip file
+		goto fail;
+	}
+	
+	long max_offset = fsize > ZIP_MAX_EOCD_OFFSET ? ZIP_MAX_EOCD_OFFSET : fsize;
+	fseek(f, -max_offset, SEEK_END);
+	uint8_t *buf = malloc(max_offset);
+	if (max_offset != fread(buf, 1, max_offset, f)) {
+		goto fail;
+	}
+	
+	long current_offset;
+	uint32_t cd_start, cd_size;
+	uint16_t cd_count;
+	for (current_offset = max_offset - MIN_EOCD_SIZE; current_offset >= 0; current_offset--)
+	{
+		if (memcmp(eocd_magic, buf + current_offset, sizeof(eocd_magic))) {
+			continue;
+		}
+		uint16_t comment_size = buf[current_offset + 20] | buf[current_offset + 21] << 8;
+		if (comment_size != (max_offset - current_offset - MIN_EOCD_SIZE)) {
+			continue;
+		}
+		cd_start = buf[current_offset + 16] | buf[current_offset + 17] << 8
+			| buf[current_offset + 18] << 16 | buf[current_offset + 19] << 24;
+		if (cd_start > (fsize - (max_offset - current_offset))) {
+			continue;
+		}
+		cd_size = buf[current_offset + 12] | buf[current_offset + 13] << 8
+			| buf[current_offset + 14] << 16 | buf[current_offset + 15] << 24;
+		if ((cd_start + cd_size) > (fsize - (max_offset - current_offset))) {
+			continue;
+		}
+		cd_count = buf[current_offset + 10] | buf[current_offset + 11] << 8;
+		break;
+	}
+	free(buf);
+	if (current_offset < 0) {
+		//failed to find EOCD
+		goto fail;
+	}
+	buf = malloc(cd_size);
+	fseek(f, cd_start, SEEK_SET);
+	if (cd_size != fread(buf, 1, cd_size, f)) {
+		goto fail_free;
+	}
+	zip_entry *entries = calloc(cd_count, sizeof(zip_entry));
+	uint32_t cd_max_last = cd_size - MIN_CDFD_SIZE;
+	zip_entry *cur_entry = entries;
+	for (uint32_t off = 0; cd_count && off <= cd_max_last; cur_entry++, cd_count--)
+	{
+		if (memcmp(buf + off, cdfd_magic, sizeof(cdfd_magic))) {
+			goto fail_entries;
+		}
+		uint32_t name_length = buf[off + 28] | buf[off + 29] << 8;
+		uint32_t extra_length = buf[off + 30] | buf[off + 31] << 8;
+		//TODO: verify name length doesn't go past end of CD
+		
+		cur_entry->name = malloc(name_length + 1);
+		memcpy(cur_entry->name, buf + off + MIN_CDFD_SIZE, name_length);
+		cur_entry->name[name_length] = 0;
+		
+		cur_entry->compressed_size = buf[off + 20] | buf[off + 21] << 8 
+			| buf[off + 22] << 16 | buf[off + 23] << 24;
+		cur_entry->size = buf[off + 24] | buf[off + 25] << 8 
+			| buf[off + 26] << 16 | buf[off + 27] << 24;
+			
+		cur_entry->local_header_off = buf[off + 42] | buf[off + 43] << 8 
+			| buf[off + 44] << 16 | buf[off + 45] << 24;
+			
+		cur_entry->compression_method = buf[off + 10] | buf[off + 11] << 8;
+		
+		off += name_length + extra_length + MIN_CDFD_SIZE;
+	}
+	
+	zip_file *z = malloc(sizeof(zip_file));
+	z->entries = entries;
+	z->file = f;
+	z->num_entries = cur_entry - entries;
+	return z;
+	
+fail_entries:
+	for (cur_entry--; cur_entry >= entries; cur_entry--)
+	{
+		free(cur_entry->name);
+	}
+	free(entries);
+fail_free:
+	free(buf);
+fail:
+	fclose(f);
+	return NULL;
+}
+
+uint8_t *zip_read(zip_file *f, uint32_t index, size_t *out_size)
+{
+	
+	fseek(f->file, f->entries[index].local_header_off + 26, SEEK_SET);
+	uint8_t tmp[4];
+	if (sizeof(tmp) != fread(tmp, 1, sizeof(tmp), f->file)) {
+		return NULL;
+	}
+	uint32_t local_variable = (tmp[0] | tmp[1] << 8) + (tmp[2] | tmp[3] << 8);
+	fseek(f->file, f->entries[index].local_header_off + local_variable + 30, SEEK_SET);
+	
+	size_t int_size;
+	if (!out_size) {
+		out_size = &int_size;
+		int_size = f->entries[index].size;
+	}
+	
+	uint8_t *buf = malloc(*out_size);
+	if (*out_size > f->entries[index].size) {
+		*out_size = f->entries[index].size;
+	}
+	switch(f->entries[index].compression_method)
+	{
+	case ZIP_STORE:
+		if (*out_size != fread(buf, 1, *out_size, f->file)) {
+			free(buf);
+			return NULL;
+		}
+		break;
+#ifndef DISABLE_ZLIB
+	case ZIP_DEFLATE: {
+		//note in unzip.c in zlib/contrib suggests a dummy byte is needed, so we allocate an extra byte here
+		uint8_t *src_buf = malloc(f->entries[index].compressed_size + 1);
+		if (f->entries[index].compressed_size != fread(src_buf, 1, f->entries[index].compressed_size, f->file)) {
+			free(src_buf);
+			return NULL;
+		}
+		uLongf destLen = *out_size;
+		z_stream stream;
+		memset(&stream, 0, sizeof(stream));
+		stream.avail_in = f->entries[index].compressed_size + 1;
+		stream.next_in = src_buf;
+		stream.next_out = buf;
+		stream.avail_out = *out_size;
+		if (Z_OK == inflateInit2(&stream, -15)) {
+			int result = inflate(&stream, Z_FINISH);
+			*out_size = stream.total_out;
+			free(src_buf);
+			inflateEnd(&stream);
+			if (result != Z_OK && result != Z_STREAM_END && result != Z_BUF_ERROR) {
+				free(buf);
+				return NULL;
+			}
+		}
+		break;
+	}
+#endif
+	default:
+		free(buf);
+		return NULL;
+	}
+	
+	return buf;
+}
+
+void zip_close(zip_file *f)
+{
+	fclose(f->file);
+	for (uint32_t i = 0; i < f->num_entries; i++)
+	{
+		free(f->entries[i].name);
+	}
+	free(f->entries);
+	free(f);
+}
+
+ 
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zip.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,25 @@
+#ifndef ZIP_H_
+#define ZIP_H_
+
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct {
+	uint64_t compressed_size;
+	uint64_t size;
+	uint64_t local_header_off;
+	char     *name;
+	uint16_t compression_method;
+} zip_entry;
+
+typedef struct {
+	zip_entry *entries;
+	FILE      *file;
+	uint32_t  num_entries;
+} zip_file;
+
+zip_file *zip_open(const char *filename);
+uint8_t *zip_read(zip_file *f, uint32_t index, size_t *out_size);
+void zip_close(zip_file *f);
+
+#endif //ZIP_H_
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/adler32.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,186 @@
+/* adler32.c -- compute the Adler-32 checksum of a data stream
+ * Copyright (C) 1995-2011, 2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#include "zutil.h"
+
+local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2));
+
+#define BASE 65521U     /* largest prime smaller than 65536 */
+#define NMAX 5552
+/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
+
+#define DO1(buf,i)  {adler += (buf)[i]; sum2 += adler;}
+#define DO2(buf,i)  DO1(buf,i); DO1(buf,i+1);
+#define DO4(buf,i)  DO2(buf,i); DO2(buf,i+2);
+#define DO8(buf,i)  DO4(buf,i); DO4(buf,i+4);
+#define DO16(buf)   DO8(buf,0); DO8(buf,8);
+
+/* use NO_DIVIDE if your processor does not do division in hardware --
+   try it both ways to see which is faster */
+#ifdef NO_DIVIDE
+/* note that this assumes BASE is 65521, where 65536 % 65521 == 15
+   (thank you to John Reiser for pointing this out) */
+#  define CHOP(a) \
+    do { \
+        unsigned long tmp = a >> 16; \
+        a &= 0xffffUL; \
+        a += (tmp << 4) - tmp; \
+    } while (0)
+#  define MOD28(a) \
+    do { \
+        CHOP(a); \
+        if (a >= BASE) a -= BASE; \
+    } while (0)
+#  define MOD(a) \
+    do { \
+        CHOP(a); \
+        MOD28(a); \
+    } while (0)
+#  define MOD63(a) \
+    do { /* this assumes a is not negative */ \
+        z_off64_t tmp = a >> 32; \
+        a &= 0xffffffffL; \
+        a += (tmp << 8) - (tmp << 5) + tmp; \
+        tmp = a >> 16; \
+        a &= 0xffffL; \
+        a += (tmp << 4) - tmp; \
+        tmp = a >> 16; \
+        a &= 0xffffL; \
+        a += (tmp << 4) - tmp; \
+        if (a >= BASE) a -= BASE; \
+    } while (0)
+#else
+#  define MOD(a) a %= BASE
+#  define MOD28(a) a %= BASE
+#  define MOD63(a) a %= BASE
+#endif
+
+/* ========================================================================= */
+uLong ZEXPORT adler32_z(adler, buf, len)
+    uLong adler;
+    const Bytef *buf;
+    z_size_t len;
+{
+    unsigned long sum2;
+    unsigned n;
+
+    /* split Adler-32 into component sums */
+    sum2 = (adler >> 16) & 0xffff;
+    adler &= 0xffff;
+
+    /* in case user likes doing a byte at a time, keep it fast */
+    if (len == 1) {
+        adler += buf[0];
+        if (adler >= BASE)
+            adler -= BASE;
+        sum2 += adler;
+        if (sum2 >= BASE)
+            sum2 -= BASE;
+        return adler | (sum2 << 16);
+    }
+
+    /* initial Adler-32 value (deferred check for len == 1 speed) */
+    if (buf == Z_NULL)
+        return 1L;
+
+    /* in case short lengths are provided, keep it somewhat fast */
+    if (len < 16) {
+        while (len--) {
+            adler += *buf++;
+            sum2 += adler;
+        }
+        if (adler >= BASE)
+            adler -= BASE;
+        MOD28(sum2);            /* only added so many BASE's */
+        return adler | (sum2 << 16);
+    }
+
+    /* do length NMAX blocks -- requires just one modulo operation */
+    while (len >= NMAX) {
+        len -= NMAX;
+        n = NMAX / 16;          /* NMAX is divisible by 16 */
+        do {
+            DO16(buf);          /* 16 sums unrolled */
+            buf += 16;
+        } while (--n);
+        MOD(adler);
+        MOD(sum2);
+    }
+
+    /* do remaining bytes (less than NMAX, still just one modulo) */
+    if (len) {                  /* avoid modulos if none remaining */
+        while (len >= 16) {
+            len -= 16;
+            DO16(buf);
+            buf += 16;
+        }
+        while (len--) {
+            adler += *buf++;
+            sum2 += adler;
+        }
+        MOD(adler);
+        MOD(sum2);
+    }
+
+    /* return recombined sums */
+    return adler | (sum2 << 16);
+}
+
+/* ========================================================================= */
+uLong ZEXPORT adler32(adler, buf, len)
+    uLong adler;
+    const Bytef *buf;
+    uInt len;
+{
+    return adler32_z(adler, buf, len);
+}
+
+/* ========================================================================= */
+local uLong adler32_combine_(adler1, adler2, len2)
+    uLong adler1;
+    uLong adler2;
+    z_off64_t len2;
+{
+    unsigned long sum1;
+    unsigned long sum2;
+    unsigned rem;
+
+    /* for negative len, return invalid adler32 as a clue for debugging */
+    if (len2 < 0)
+        return 0xffffffffUL;
+
+    /* the derivation of this formula is left as an exercise for the reader */
+    MOD63(len2);                /* assumes len2 >= 0 */
+    rem = (unsigned)len2;
+    sum1 = adler1 & 0xffff;
+    sum2 = rem * sum1;
+    MOD(sum2);
+    sum1 += (adler2 & 0xffff) + BASE - 1;
+    sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem;
+    if (sum1 >= BASE) sum1 -= BASE;
+    if (sum1 >= BASE) sum1 -= BASE;
+    if (sum2 >= ((unsigned long)BASE << 1)) sum2 -= ((unsigned long)BASE << 1);
+    if (sum2 >= BASE) sum2 -= BASE;
+    return sum1 | (sum2 << 16);
+}
+
+/* ========================================================================= */
+uLong ZEXPORT adler32_combine(adler1, adler2, len2)
+    uLong adler1;
+    uLong adler2;
+    z_off_t len2;
+{
+    return adler32_combine_(adler1, adler2, len2);
+}
+
+uLong ZEXPORT adler32_combine64(adler1, adler2, len2)
+    uLong adler1;
+    uLong adler2;
+    z_off64_t len2;
+{
+    return adler32_combine_(adler1, adler2, len2);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/compress.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,86 @@
+/* compress.c -- compress a memory buffer
+ * Copyright (C) 1995-2005, 2014, 2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#define ZLIB_INTERNAL
+#include "zlib.h"
+
+/* ===========================================================================
+     Compresses the source buffer into the destination buffer. The level
+   parameter has the same meaning as in deflateInit.  sourceLen is the byte
+   length of the source buffer. Upon entry, destLen is the total size of the
+   destination buffer, which must be at least 0.1% larger than sourceLen plus
+   12 bytes. Upon exit, destLen is the actual size of the compressed buffer.
+
+     compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_BUF_ERROR if there was not enough room in the output buffer,
+   Z_STREAM_ERROR if the level parameter is invalid.
+*/
+int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
+    Bytef *dest;
+    uLongf *destLen;
+    const Bytef *source;
+    uLong sourceLen;
+    int level;
+{
+    z_stream stream;
+    int err;
+    const uInt max = (uInt)-1;
+    uLong left;
+
+    left = *destLen;
+    *destLen = 0;
+
+    stream.zalloc = (alloc_func)0;
+    stream.zfree = (free_func)0;
+    stream.opaque = (voidpf)0;
+
+    err = deflateInit(&stream, level);
+    if (err != Z_OK) return err;
+
+    stream.next_out = dest;
+    stream.avail_out = 0;
+    stream.next_in = (z_const Bytef *)source;
+    stream.avail_in = 0;
+
+    do {
+        if (stream.avail_out == 0) {
+            stream.avail_out = left > (uLong)max ? max : (uInt)left;
+            left -= stream.avail_out;
+        }
+        if (stream.avail_in == 0) {
+            stream.avail_in = sourceLen > (uLong)max ? max : (uInt)sourceLen;
+            sourceLen -= stream.avail_in;
+        }
+        err = deflate(&stream, sourceLen ? Z_NO_FLUSH : Z_FINISH);
+    } while (err == Z_OK);
+
+    *destLen = stream.total_out;
+    deflateEnd(&stream);
+    return err == Z_STREAM_END ? Z_OK : err;
+}
+
+/* ===========================================================================
+ */
+int ZEXPORT compress (dest, destLen, source, sourceLen)
+    Bytef *dest;
+    uLongf *destLen;
+    const Bytef *source;
+    uLong sourceLen;
+{
+    return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION);
+}
+
+/* ===========================================================================
+     If the default memLevel or windowBits for deflateInit() is changed, then
+   this function needs to be updated.
+ */
+uLong ZEXPORT compressBound (sourceLen)
+    uLong sourceLen;
+{
+    return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
+           (sourceLen >> 25) + 13;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/crc32.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,442 @@
+/* crc32.c -- compute the CRC-32 of a data stream
+ * Copyright (C) 1995-2006, 2010, 2011, 2012, 2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ *
+ * Thanks to Rodney Brown <rbrown64@csc.com.au> for his contribution of faster
+ * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing
+ * tables for updating the shift register in one step with three exclusive-ors
+ * instead of four steps with four exclusive-ors.  This results in about a
+ * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3.
+ */
+
+/* @(#) $Id$ */
+
+/*
+  Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore
+  protection on the static variables used to control the first-use generation
+  of the crc tables.  Therefore, if you #define DYNAMIC_CRC_TABLE, you should
+  first call get_crc_table() to initialize the tables before allowing more than
+  one thread to use crc32().
+
+  DYNAMIC_CRC_TABLE and MAKECRCH can be #defined to write out crc32.h.
+ */
+
+#ifdef MAKECRCH
+#  include <stdio.h>
+#  ifndef DYNAMIC_CRC_TABLE
+#    define DYNAMIC_CRC_TABLE
+#  endif /* !DYNAMIC_CRC_TABLE */
+#endif /* MAKECRCH */
+
+#include "zutil.h"      /* for STDC and FAR definitions */
+
+/* Definitions for doing the crc four data bytes at a time. */
+#if !defined(NOBYFOUR) && defined(Z_U4)
+#  define BYFOUR
+#endif
+#ifdef BYFOUR
+   local unsigned long crc32_little OF((unsigned long,
+                        const unsigned char FAR *, z_size_t));
+   local unsigned long crc32_big OF((unsigned long,
+                        const unsigned char FAR *, z_size_t));
+#  define TBLS 8
+#else
+#  define TBLS 1
+#endif /* BYFOUR */
+
+/* Local functions for crc concatenation */
+local unsigned long gf2_matrix_times OF((unsigned long *mat,
+                                         unsigned long vec));
+local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat));
+local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2));
+
+
+#ifdef DYNAMIC_CRC_TABLE
+
+local volatile int crc_table_empty = 1;
+local z_crc_t FAR crc_table[TBLS][256];
+local void make_crc_table OF((void));
+#ifdef MAKECRCH
+   local void write_table OF((FILE *, const z_crc_t FAR *));
+#endif /* MAKECRCH */
+/*
+  Generate tables for a byte-wise 32-bit CRC calculation on the polynomial:
+  x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1.
+
+  Polynomials over GF(2) are represented in binary, one bit per coefficient,
+  with the lowest powers in the most significant bit.  Then adding polynomials
+  is just exclusive-or, and multiplying a polynomial by x is a right shift by
+  one.  If we call the above polynomial p, and represent a byte as the
+  polynomial q, also with the lowest power in the most significant bit (so the
+  byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p,
+  where a mod b means the remainder after dividing a by b.
+
+  This calculation is done using the shift-register method of multiplying and
+  taking the remainder.  The register is initialized to zero, and for each
+  incoming bit, x^32 is added mod p to the register if the bit is a one (where
+  x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by
+  x (which is shifting right by one and adding x^32 mod p if the bit shifted
+  out is a one).  We start with the highest power (least significant bit) of
+  q and repeat for all eight bits of q.
+
+  The first table is simply the CRC of all possible eight bit values.  This is
+  all the information needed to generate CRCs on data a byte at a time for all
+  combinations of CRC register values and incoming bytes.  The remaining tables
+  allow for word-at-a-time CRC calculation for both big-endian and little-
+  endian machines, where a word is four bytes.
+*/
+local void make_crc_table()
+{
+    z_crc_t c;
+    int n, k;
+    z_crc_t poly;                       /* polynomial exclusive-or pattern */
+    /* terms of polynomial defining this crc (except x^32): */
+    static volatile int first = 1;      /* flag to limit concurrent making */
+    static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26};
+
+    /* See if another task is already doing this (not thread-safe, but better
+       than nothing -- significantly reduces duration of vulnerability in
+       case the advice about DYNAMIC_CRC_TABLE is ignored) */
+    if (first) {
+        first = 0;
+
+        /* make exclusive-or pattern from polynomial (0xedb88320UL) */
+        poly = 0;
+        for (n = 0; n < (int)(sizeof(p)/sizeof(unsigned char)); n++)
+            poly |= (z_crc_t)1 << (31 - p[n]);
+
+        /* generate a crc for every 8-bit value */
+        for (n = 0; n < 256; n++) {
+            c = (z_crc_t)n;
+            for (k = 0; k < 8; k++)
+                c = c & 1 ? poly ^ (c >> 1) : c >> 1;
+            crc_table[0][n] = c;
+        }
+
+#ifdef BYFOUR
+        /* generate crc for each value followed by one, two, and three zeros,
+           and then the byte reversal of those as well as the first table */
+        for (n = 0; n < 256; n++) {
+            c = crc_table[0][n];
+            crc_table[4][n] = ZSWAP32(c);
+            for (k = 1; k < 4; k++) {
+                c = crc_table[0][c & 0xff] ^ (c >> 8);
+                crc_table[k][n] = c;
+                crc_table[k + 4][n] = ZSWAP32(c);
+            }
+        }
+#endif /* BYFOUR */
+
+        crc_table_empty = 0;
+    }
+    else {      /* not first */
+        /* wait for the other guy to finish (not efficient, but rare) */
+        while (crc_table_empty)
+            ;
+    }
+
+#ifdef MAKECRCH
+    /* write out CRC tables to crc32.h */
+    {
+        FILE *out;
+
+        out = fopen("crc32.h", "w");
+        if (out == NULL) return;
+        fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n");
+        fprintf(out, " * Generated automatically by crc32.c\n */\n\n");
+        fprintf(out, "local const z_crc_t FAR ");
+        fprintf(out, "crc_table[TBLS][256] =\n{\n  {\n");
+        write_table(out, crc_table[0]);
+#  ifdef BYFOUR
+        fprintf(out, "#ifdef BYFOUR\n");
+        for (k = 1; k < 8; k++) {
+            fprintf(out, "  },\n  {\n");
+            write_table(out, crc_table[k]);
+        }
+        fprintf(out, "#endif\n");
+#  endif /* BYFOUR */
+        fprintf(out, "  }\n};\n");
+        fclose(out);
+    }
+#endif /* MAKECRCH */
+}
+
+#ifdef MAKECRCH
+local void write_table(out, table)
+    FILE *out;
+    const z_crc_t FAR *table;
+{
+    int n;
+
+    for (n = 0; n < 256; n++)
+        fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : "    ",
+                (unsigned long)(table[n]),
+                n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", "));
+}
+#endif /* MAKECRCH */
+
+#else /* !DYNAMIC_CRC_TABLE */
+/* ========================================================================
+ * Tables of CRC-32s of all single-byte values, made by make_crc_table().
+ */
+#include "crc32.h"
+#endif /* DYNAMIC_CRC_TABLE */
+
+/* =========================================================================
+ * This function can be used by asm versions of crc32()
+ */
+const z_crc_t FAR * ZEXPORT get_crc_table()
+{
+#ifdef DYNAMIC_CRC_TABLE
+    if (crc_table_empty)
+        make_crc_table();
+#endif /* DYNAMIC_CRC_TABLE */
+    return (const z_crc_t FAR *)crc_table;
+}
+
+/* ========================================================================= */
+#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8)
+#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1
+
+/* ========================================================================= */
+unsigned long ZEXPORT crc32_z(crc, buf, len)
+    unsigned long crc;
+    const unsigned char FAR *buf;
+    z_size_t len;
+{
+    if (buf == Z_NULL) return 0UL;
+
+#ifdef DYNAMIC_CRC_TABLE
+    if (crc_table_empty)
+        make_crc_table();
+#endif /* DYNAMIC_CRC_TABLE */
+
+#ifdef BYFOUR
+    if (sizeof(void *) == sizeof(ptrdiff_t)) {
+        z_crc_t endian;
+
+        endian = 1;
+        if (*((unsigned char *)(&endian)))
+            return crc32_little(crc, buf, len);
+        else
+            return crc32_big(crc, buf, len);
+    }
+#endif /* BYFOUR */
+    crc = crc ^ 0xffffffffUL;
+    while (len >= 8) {
+        DO8;
+        len -= 8;
+    }
+    if (len) do {
+        DO1;
+    } while (--len);
+    return crc ^ 0xffffffffUL;
+}
+
+/* ========================================================================= */
+unsigned long ZEXPORT crc32(crc, buf, len)
+    unsigned long crc;
+    const unsigned char FAR *buf;
+    uInt len;
+{
+    return crc32_z(crc, buf, len);
+}
+
+#ifdef BYFOUR
+
+/*
+   This BYFOUR code accesses the passed unsigned char * buffer with a 32-bit
+   integer pointer type. This violates the strict aliasing rule, where a
+   compiler can assume, for optimization purposes, that two pointers to
+   fundamentally different types won't ever point to the same memory. This can
+   manifest as a problem only if one of the pointers is written to. This code
+   only reads from those pointers. So long as this code remains isolated in
+   this compilation unit, there won't be a problem. For this reason, this code
+   should not be copied and pasted into a compilation unit in which other code
+   writes to the buffer that is passed to these routines.
+ */
+
+/* ========================================================================= */
+#define DOLIT4 c ^= *buf4++; \
+        c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \
+            crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24]
+#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4
+
+/* ========================================================================= */
+local unsigned long crc32_little(crc, buf, len)
+    unsigned long crc;
+    const unsigned char FAR *buf;
+    z_size_t len;
+{
+    register z_crc_t c;
+    register const z_crc_t FAR *buf4;
+
+    c = (z_crc_t)crc;
+    c = ~c;
+    while (len && ((ptrdiff_t)buf & 3)) {
+        c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8);
+        len--;
+    }
+
+    buf4 = (const z_crc_t FAR *)(const void FAR *)buf;
+    while (len >= 32) {
+        DOLIT32;
+        len -= 32;
+    }
+    while (len >= 4) {
+        DOLIT4;
+        len -= 4;
+    }
+    buf = (const unsigned char FAR *)buf4;
+
+    if (len) do {
+        c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8);
+    } while (--len);
+    c = ~c;
+    return (unsigned long)c;
+}
+
+/* ========================================================================= */
+#define DOBIG4 c ^= *buf4++; \
+        c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \
+            crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24]
+#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4
+
+/* ========================================================================= */
+local unsigned long crc32_big(crc, buf, len)
+    unsigned long crc;
+    const unsigned char FAR *buf;
+    z_size_t len;
+{
+    register z_crc_t c;
+    register const z_crc_t FAR *buf4;
+
+    c = ZSWAP32((z_crc_t)crc);
+    c = ~c;
+    while (len && ((ptrdiff_t)buf & 3)) {
+        c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8);
+        len--;
+    }
+
+    buf4 = (const z_crc_t FAR *)(const void FAR *)buf;
+    while (len >= 32) {
+        DOBIG32;
+        len -= 32;
+    }
+    while (len >= 4) {
+        DOBIG4;
+        len -= 4;
+    }
+    buf = (const unsigned char FAR *)buf4;
+
+    if (len) do {
+        c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8);
+    } while (--len);
+    c = ~c;
+    return (unsigned long)(ZSWAP32(c));
+}
+
+#endif /* BYFOUR */
+
+#define GF2_DIM 32      /* dimension of GF(2) vectors (length of CRC) */
+
+/* ========================================================================= */
+local unsigned long gf2_matrix_times(mat, vec)
+    unsigned long *mat;
+    unsigned long vec;
+{
+    unsigned long sum;
+
+    sum = 0;
+    while (vec) {
+        if (vec & 1)
+            sum ^= *mat;
+        vec >>= 1;
+        mat++;
+    }
+    return sum;
+}
+
+/* ========================================================================= */
+local void gf2_matrix_square(square, mat)
+    unsigned long *square;
+    unsigned long *mat;
+{
+    int n;
+
+    for (n = 0; n < GF2_DIM; n++)
+        square[n] = gf2_matrix_times(mat, mat[n]);
+}
+
+/* ========================================================================= */
+local uLong crc32_combine_(crc1, crc2, len2)
+    uLong crc1;
+    uLong crc2;
+    z_off64_t len2;
+{
+    int n;
+    unsigned long row;
+    unsigned long even[GF2_DIM];    /* even-power-of-two zeros operator */
+    unsigned long odd[GF2_DIM];     /* odd-power-of-two zeros operator */
+
+    /* degenerate case (also disallow negative lengths) */
+    if (len2 <= 0)
+        return crc1;
+
+    /* put operator for one zero bit in odd */
+    odd[0] = 0xedb88320UL;          /* CRC-32 polynomial */
+    row = 1;
+    for (n = 1; n < GF2_DIM; n++) {
+        odd[n] = row;
+        row <<= 1;
+    }
+
+    /* put operator for two zero bits in even */
+    gf2_matrix_square(even, odd);
+
+    /* put operator for four zero bits in odd */
+    gf2_matrix_square(odd, even);
+
+    /* apply len2 zeros to crc1 (first square will put the operator for one
+       zero byte, eight zero bits, in even) */
+    do {
+        /* apply zeros operator for this bit of len2 */
+        gf2_matrix_square(even, odd);
+        if (len2 & 1)
+            crc1 = gf2_matrix_times(even, crc1);
+        len2 >>= 1;
+
+        /* if no more bits set, then done */
+        if (len2 == 0)
+            break;
+
+        /* another iteration of the loop with odd and even swapped */
+        gf2_matrix_square(odd, even);
+        if (len2 & 1)
+            crc1 = gf2_matrix_times(odd, crc1);
+        len2 >>= 1;
+
+        /* if no more bits set, then done */
+    } while (len2 != 0);
+
+    /* return combined crc */
+    crc1 ^= crc2;
+    return crc1;
+}
+
+/* ========================================================================= */
+uLong ZEXPORT crc32_combine(crc1, crc2, len2)
+    uLong crc1;
+    uLong crc2;
+    z_off_t len2;
+{
+    return crc32_combine_(crc1, crc2, len2);
+}
+
+uLong ZEXPORT crc32_combine64(crc1, crc2, len2)
+    uLong crc1;
+    uLong crc2;
+    z_off64_t len2;
+{
+    return crc32_combine_(crc1, crc2, len2);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/crc32.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,441 @@
+/* crc32.h -- tables for rapid CRC calculation
+ * Generated automatically by crc32.c
+ */
+
+local const z_crc_t FAR crc_table[TBLS][256] =
+{
+  {
+    0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL,
+    0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL,
+    0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL,
+    0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL,
+    0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL,
+    0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL,
+    0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL,
+    0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL,
+    0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL,
+    0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL,
+    0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL,
+    0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL,
+    0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL,
+    0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL,
+    0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL,
+    0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL,
+    0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL,
+    0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL,
+    0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL,
+    0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL,
+    0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL,
+    0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL,
+    0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL,
+    0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL,
+    0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL,
+    0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL,
+    0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL,
+    0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL,
+    0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL,
+    0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL,
+    0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL,
+    0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL,
+    0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL,
+    0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL,
+    0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL,
+    0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL,
+    0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL,
+    0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL,
+    0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL,
+    0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL,
+    0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL,
+    0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL,
+    0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL,
+    0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL,
+    0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL,
+    0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL,
+    0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL,
+    0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL,
+    0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL,
+    0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL,
+    0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL,
+    0x2d02ef8dUL
+#ifdef BYFOUR
+  },
+  {
+    0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL,
+    0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL,
+    0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL,
+    0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL,
+    0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL,
+    0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL,
+    0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL,
+    0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL,
+    0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL,
+    0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL,
+    0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL,
+    0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL,
+    0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL,
+    0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL,
+    0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL,
+    0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL,
+    0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL,
+    0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL,
+    0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL,
+    0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL,
+    0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL,
+    0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL,
+    0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL,
+    0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL,
+    0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL,
+    0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL,
+    0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL,
+    0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL,
+    0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL,
+    0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL,
+    0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL,
+    0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL,
+    0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL,
+    0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL,
+    0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL,
+    0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL,
+    0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL,
+    0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL,
+    0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL,
+    0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL,
+    0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL,
+    0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL,
+    0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL,
+    0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL,
+    0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL,
+    0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL,
+    0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL,
+    0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL,
+    0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL,
+    0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL,
+    0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL,
+    0x9324fd72UL
+  },
+  {
+    0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL,
+    0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL,
+    0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL,
+    0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL,
+    0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL,
+    0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL,
+    0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL,
+    0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL,
+    0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL,
+    0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL,
+    0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL,
+    0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL,
+    0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL,
+    0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL,
+    0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL,
+    0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL,
+    0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL,
+    0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL,
+    0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL,
+    0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL,
+    0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL,
+    0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL,
+    0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL,
+    0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL,
+    0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL,
+    0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL,
+    0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL,
+    0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL,
+    0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL,
+    0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL,
+    0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL,
+    0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL,
+    0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL,
+    0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL,
+    0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL,
+    0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL,
+    0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL,
+    0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL,
+    0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL,
+    0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL,
+    0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL,
+    0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL,
+    0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL,
+    0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL,
+    0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL,
+    0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL,
+    0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL,
+    0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL,
+    0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL,
+    0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL,
+    0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL,
+    0xbe9834edUL
+  },
+  {
+    0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL,
+    0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL,
+    0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL,
+    0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL,
+    0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL,
+    0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL,
+    0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL,
+    0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL,
+    0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL,
+    0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL,
+    0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL,
+    0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL,
+    0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL,
+    0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL,
+    0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL,
+    0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL,
+    0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL,
+    0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL,
+    0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL,
+    0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL,
+    0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL,
+    0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL,
+    0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL,
+    0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL,
+    0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL,
+    0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL,
+    0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL,
+    0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL,
+    0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL,
+    0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL,
+    0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL,
+    0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL,
+    0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL,
+    0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL,
+    0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL,
+    0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL,
+    0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL,
+    0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL,
+    0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL,
+    0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL,
+    0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL,
+    0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL,
+    0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL,
+    0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL,
+    0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL,
+    0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL,
+    0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL,
+    0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL,
+    0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL,
+    0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL,
+    0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL,
+    0xde0506f1UL
+  },
+  {
+    0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL,
+    0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL,
+    0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL,
+    0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL,
+    0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL,
+    0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL,
+    0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL,
+    0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL,
+    0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL,
+    0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL,
+    0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL,
+    0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL,
+    0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL,
+    0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL,
+    0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL,
+    0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL,
+    0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL,
+    0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL,
+    0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL,
+    0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL,
+    0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL,
+    0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL,
+    0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL,
+    0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL,
+    0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL,
+    0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL,
+    0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL,
+    0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL,
+    0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL,
+    0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL,
+    0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL,
+    0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL,
+    0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL,
+    0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL,
+    0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL,
+    0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL,
+    0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL,
+    0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL,
+    0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL,
+    0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL,
+    0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL,
+    0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL,
+    0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL,
+    0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL,
+    0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL,
+    0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL,
+    0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL,
+    0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL,
+    0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL,
+    0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL,
+    0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL,
+    0x8def022dUL
+  },
+  {
+    0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL,
+    0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL,
+    0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL,
+    0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL,
+    0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL,
+    0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL,
+    0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL,
+    0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL,
+    0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL,
+    0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL,
+    0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL,
+    0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL,
+    0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL,
+    0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL,
+    0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL,
+    0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL,
+    0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL,
+    0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL,
+    0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL,
+    0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL,
+    0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL,
+    0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL,
+    0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL,
+    0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL,
+    0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL,
+    0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL,
+    0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL,
+    0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL,
+    0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL,
+    0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL,
+    0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL,
+    0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL,
+    0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL,
+    0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL,
+    0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL,
+    0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL,
+    0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL,
+    0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL,
+    0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL,
+    0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL,
+    0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL,
+    0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL,
+    0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL,
+    0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL,
+    0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL,
+    0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL,
+    0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL,
+    0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL,
+    0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL,
+    0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL,
+    0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL,
+    0x72fd2493UL
+  },
+  {
+    0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL,
+    0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL,
+    0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL,
+    0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL,
+    0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL,
+    0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL,
+    0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL,
+    0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL,
+    0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL,
+    0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL,
+    0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL,
+    0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL,
+    0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL,
+    0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL,
+    0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL,
+    0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL,
+    0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL,
+    0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL,
+    0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL,
+    0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL,
+    0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL,
+    0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL,
+    0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL,
+    0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL,
+    0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL,
+    0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL,
+    0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL,
+    0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL,
+    0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL,
+    0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL,
+    0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL,
+    0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL,
+    0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL,
+    0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL,
+    0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL,
+    0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL,
+    0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL,
+    0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL,
+    0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL,
+    0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL,
+    0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL,
+    0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL,
+    0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL,
+    0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL,
+    0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL,
+    0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL,
+    0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL,
+    0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL,
+    0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL,
+    0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL,
+    0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL,
+    0xed3498beUL
+  },
+  {
+    0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL,
+    0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL,
+    0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL,
+    0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL,
+    0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL,
+    0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL,
+    0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL,
+    0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL,
+    0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL,
+    0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL,
+    0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL,
+    0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL,
+    0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL,
+    0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL,
+    0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL,
+    0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL,
+    0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL,
+    0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL,
+    0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL,
+    0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL,
+    0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL,
+    0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL,
+    0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL,
+    0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL,
+    0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL,
+    0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL,
+    0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL,
+    0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL,
+    0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL,
+    0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL,
+    0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL,
+    0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL,
+    0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL,
+    0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL,
+    0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL,
+    0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL,
+    0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL,
+    0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL,
+    0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL,
+    0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL,
+    0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL,
+    0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL,
+    0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL,
+    0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL,
+    0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL,
+    0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL,
+    0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL,
+    0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL,
+    0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL,
+    0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL,
+    0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL,
+    0xf10605deUL
+#endif
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/deflate.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,2163 @@
+/* deflate.c -- compress data using the deflation algorithm
+ * Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ *  ALGORITHM
+ *
+ *      The "deflation" process depends on being able to identify portions
+ *      of the input text which are identical to earlier input (within a
+ *      sliding window trailing behind the input currently being processed).
+ *
+ *      The most straightforward technique turns out to be the fastest for
+ *      most input files: try all possible matches and select the longest.
+ *      The key feature of this algorithm is that insertions into the string
+ *      dictionary are very simple and thus fast, and deletions are avoided
+ *      completely. Insertions are performed at each input character, whereas
+ *      string matches are performed only when the previous match ends. So it
+ *      is preferable to spend more time in matches to allow very fast string
+ *      insertions and avoid deletions. The matching algorithm for small
+ *      strings is inspired from that of Rabin & Karp. A brute force approach
+ *      is used to find longer strings when a small match has been found.
+ *      A similar algorithm is used in comic (by Jan-Mark Wams) and freeze
+ *      (by Leonid Broukhis).
+ *         A previous version of this file used a more sophisticated algorithm
+ *      (by Fiala and Greene) which is guaranteed to run in linear amortized
+ *      time, but has a larger average cost, uses more memory and is patented.
+ *      However the F&G algorithm may be faster for some highly redundant
+ *      files if the parameter max_chain_length (described below) is too large.
+ *
+ *  ACKNOWLEDGEMENTS
+ *
+ *      The idea of lazy evaluation of matches is due to Jan-Mark Wams, and
+ *      I found it in 'freeze' written by Leonid Broukhis.
+ *      Thanks to many people for bug reports and testing.
+ *
+ *  REFERENCES
+ *
+ *      Deutsch, L.P.,"DEFLATE Compressed Data Format Specification".
+ *      Available in http://tools.ietf.org/html/rfc1951
+ *
+ *      A description of the Rabin and Karp algorithm is given in the book
+ *         "Algorithms" by R. Sedgewick, Addison-Wesley, p252.
+ *
+ *      Fiala,E.R., and Greene,D.H.
+ *         Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595
+ *
+ */
+
+/* @(#) $Id$ */
+
+#include "deflate.h"
+
+const char deflate_copyright[] =
+   " deflate 1.2.11 Copyright 1995-2017 Jean-loup Gailly and Mark Adler ";
+/*
+  If you use the zlib library in a product, an acknowledgment is welcome
+  in the documentation of your product. If for some reason you cannot
+  include such an acknowledgment, I would appreciate that you keep this
+  copyright string in the executable of your product.
+ */
+
+/* ===========================================================================
+ *  Function prototypes.
+ */
+typedef enum {
+    need_more,      /* block not completed, need more input or more output */
+    block_done,     /* block flush performed */
+    finish_started, /* finish started, need only more output at next deflate */
+    finish_done     /* finish done, accept no more input or output */
+} block_state;
+
+typedef block_state (*compress_func) OF((deflate_state *s, int flush));
+/* Compression function. Returns the block state after the call. */
+
+local int deflateStateCheck      OF((z_streamp strm));
+local void slide_hash     OF((deflate_state *s));
+local void fill_window    OF((deflate_state *s));
+local block_state deflate_stored OF((deflate_state *s, int flush));
+local block_state deflate_fast   OF((deflate_state *s, int flush));
+#ifndef FASTEST
+local block_state deflate_slow   OF((deflate_state *s, int flush));
+#endif
+local block_state deflate_rle    OF((deflate_state *s, int flush));
+local block_state deflate_huff   OF((deflate_state *s, int flush));
+local void lm_init        OF((deflate_state *s));
+local void putShortMSB    OF((deflate_state *s, uInt b));
+local void flush_pending  OF((z_streamp strm));
+local unsigned read_buf   OF((z_streamp strm, Bytef *buf, unsigned size));
+#ifdef ASMV
+#  pragma message("Assembler code may have bugs -- use at your own risk")
+      void match_init OF((void)); /* asm code initialization */
+      uInt longest_match  OF((deflate_state *s, IPos cur_match));
+#else
+local uInt longest_match  OF((deflate_state *s, IPos cur_match));
+#endif
+
+#ifdef ZLIB_DEBUG
+local  void check_match OF((deflate_state *s, IPos start, IPos match,
+                            int length));
+#endif
+
+/* ===========================================================================
+ * Local data
+ */
+
+#define NIL 0
+/* Tail of hash chains */
+
+#ifndef TOO_FAR
+#  define TOO_FAR 4096
+#endif
+/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+typedef struct config_s {
+   ush good_length; /* reduce lazy search above this match length */
+   ush max_lazy;    /* do not perform lazy search above this match length */
+   ush nice_length; /* quit search above this match length */
+   ush max_chain;
+   compress_func func;
+} config;
+
+#ifdef FASTEST
+local const config configuration_table[2] = {
+/*      good lazy nice chain */
+/* 0 */ {0,    0,  0,    0, deflate_stored},  /* store only */
+/* 1 */ {4,    4,  8,    4, deflate_fast}}; /* max speed, no lazy matches */
+#else
+local const config configuration_table[10] = {
+/*      good lazy nice chain */
+/* 0 */ {0,    0,  0,    0, deflate_stored},  /* store only */
+/* 1 */ {4,    4,  8,    4, deflate_fast}, /* max speed, no lazy matches */
+/* 2 */ {4,    5, 16,    8, deflate_fast},
+/* 3 */ {4,    6, 32,   32, deflate_fast},
+
+/* 4 */ {4,    4, 16,   16, deflate_slow},  /* lazy matches */
+/* 5 */ {8,   16, 32,   32, deflate_slow},
+/* 6 */ {8,   16, 128, 128, deflate_slow},
+/* 7 */ {8,   32, 128, 256, deflate_slow},
+/* 8 */ {32, 128, 258, 1024, deflate_slow},
+/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */
+#endif
+
+/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4
+ * For deflate_fast() (levels <= 3) good is ignored and lazy has a different
+ * meaning.
+ */
+
+/* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */
+#define RANK(f) (((f) * 2) - ((f) > 4 ? 9 : 0))
+
+/* ===========================================================================
+ * Update a hash value with the given input byte
+ * IN  assertion: all calls to UPDATE_HASH are made with consecutive input
+ *    characters, so that a running hash key can be computed from the previous
+ *    key instead of complete recalculation each time.
+ */
+#define UPDATE_HASH(s,h,c) (h = (((h)<<s->hash_shift) ^ (c)) & s->hash_mask)
+
+
+/* ===========================================================================
+ * Insert string str in the dictionary and set match_head to the previous head
+ * of the hash chain (the most recent string with same hash key). Return
+ * the previous length of the hash chain.
+ * If this file is compiled with -DFASTEST, the compression level is forced
+ * to 1, and no hash chains are maintained.
+ * IN  assertion: all calls to INSERT_STRING are made with consecutive input
+ *    characters and the first MIN_MATCH bytes of str are valid (except for
+ *    the last MIN_MATCH-1 bytes of the input file).
+ */
+#ifdef FASTEST
+#define INSERT_STRING(s, str, match_head) \
+   (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \
+    match_head = s->head[s->ins_h], \
+    s->head[s->ins_h] = (Pos)(str))
+#else
+#define INSERT_STRING(s, str, match_head) \
+   (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \
+    match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \
+    s->head[s->ins_h] = (Pos)(str))
+#endif
+
+/* ===========================================================================
+ * Initialize the hash table (avoiding 64K overflow for 16 bit systems).
+ * prev[] will be initialized on the fly.
+ */
+#define CLEAR_HASH(s) \
+    s->head[s->hash_size-1] = NIL; \
+    zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
+
+/* ===========================================================================
+ * Slide the hash table when sliding the window down (could be avoided with 32
+ * bit values at the expense of memory usage). We slide even when level == 0 to
+ * keep the hash table consistent if we switch back to level > 0 later.
+ */
+local void slide_hash(s)
+    deflate_state *s;
+{
+    unsigned n, m;
+    Posf *p;
+    uInt wsize = s->w_size;
+
+    n = s->hash_size;
+    p = &s->head[n];
+    do {
+        m = *--p;
+        *p = (Pos)(m >= wsize ? m - wsize : NIL);
+    } while (--n);
+    n = wsize;
+#ifndef FASTEST
+    p = &s->prev[n];
+    do {
+        m = *--p;
+        *p = (Pos)(m >= wsize ? m - wsize : NIL);
+        /* If n is not on any hash chain, prev[n] is garbage but
+         * its value will never be used.
+         */
+    } while (--n);
+#endif
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateInit_(strm, level, version, stream_size)
+    z_streamp strm;
+    int level;
+    const char *version;
+    int stream_size;
+{
+    return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL,
+                         Z_DEFAULT_STRATEGY, version, stream_size);
+    /* To do: ignore strm->next_in if we use it as window */
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
+                  version, stream_size)
+    z_streamp strm;
+    int  level;
+    int  method;
+    int  windowBits;
+    int  memLevel;
+    int  strategy;
+    const char *version;
+    int stream_size;
+{
+    deflate_state *s;
+    int wrap = 1;
+    static const char my_version[] = ZLIB_VERSION;
+
+    ushf *overlay;
+    /* We overlay pending_buf and d_buf+l_buf. This works since the average
+     * output size for (length,distance) codes is <= 24 bits.
+     */
+
+    if (version == Z_NULL || version[0] != my_version[0] ||
+        stream_size != sizeof(z_stream)) {
+        return Z_VERSION_ERROR;
+    }
+    if (strm == Z_NULL) return Z_STREAM_ERROR;
+
+    strm->msg = Z_NULL;
+    if (strm->zalloc == (alloc_func)0) {
+#ifdef Z_SOLO
+        return Z_STREAM_ERROR;
+#else
+        strm->zalloc = zcalloc;
+        strm->opaque = (voidpf)0;
+#endif
+    }
+    if (strm->zfree == (free_func)0)
+#ifdef Z_SOLO
+        return Z_STREAM_ERROR;
+#else
+        strm->zfree = zcfree;
+#endif
+
+#ifdef FASTEST
+    if (level != 0) level = 1;
+#else
+    if (level == Z_DEFAULT_COMPRESSION) level = 6;
+#endif
+
+    if (windowBits < 0) { /* suppress zlib wrapper */
+        wrap = 0;
+        windowBits = -windowBits;
+    }
+#ifdef GZIP
+    else if (windowBits > 15) {
+        wrap = 2;       /* write gzip wrapper instead */
+        windowBits -= 16;
+    }
+#endif
+    if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED ||
+        windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+        strategy < 0 || strategy > Z_FIXED || (windowBits == 8 && wrap != 1)) {
+        return Z_STREAM_ERROR;
+    }
+    if (windowBits == 8) windowBits = 9;  /* until 256-byte window bug fixed */
+    s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state));
+    if (s == Z_NULL) return Z_MEM_ERROR;
+    strm->state = (struct internal_state FAR *)s;
+    s->strm = strm;
+    s->status = INIT_STATE;     /* to pass state test in deflateReset() */
+
+    s->wrap = wrap;
+    s->gzhead = Z_NULL;
+    s->w_bits = (uInt)windowBits;
+    s->w_size = 1 << s->w_bits;
+    s->w_mask = s->w_size - 1;
+
+    s->hash_bits = (uInt)memLevel + 7;
+    s->hash_size = 1 << s->hash_bits;
+    s->hash_mask = s->hash_size - 1;
+    s->hash_shift =  ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH);
+
+    s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte));
+    s->prev   = (Posf *)  ZALLOC(strm, s->w_size, sizeof(Pos));
+    s->head   = (Posf *)  ZALLOC(strm, s->hash_size, sizeof(Pos));
+
+    s->high_water = 0;      /* nothing written to s->window yet */
+
+    s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
+
+    overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
+    s->pending_buf = (uchf *) overlay;
+    s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L);
+
+    if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL ||
+        s->pending_buf == Z_NULL) {
+        s->status = FINISH_STATE;
+        strm->msg = ERR_MSG(Z_MEM_ERROR);
+        deflateEnd (strm);
+        return Z_MEM_ERROR;
+    }
+    s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+    s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+
+    s->level = level;
+    s->strategy = strategy;
+    s->method = (Byte)method;
+
+    return deflateReset(strm);
+}
+
+/* =========================================================================
+ * Check for a valid deflate stream state. Return 0 if ok, 1 if not.
+ */
+local int deflateStateCheck (strm)
+    z_streamp strm;
+{
+    deflate_state *s;
+    if (strm == Z_NULL ||
+        strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0)
+        return 1;
+    s = strm->state;
+    if (s == Z_NULL || s->strm != strm || (s->status != INIT_STATE &&
+#ifdef GZIP
+                                           s->status != GZIP_STATE &&
+#endif
+                                           s->status != EXTRA_STATE &&
+                                           s->status != NAME_STATE &&
+                                           s->status != COMMENT_STATE &&
+                                           s->status != HCRC_STATE &&
+                                           s->status != BUSY_STATE &&
+                                           s->status != FINISH_STATE))
+        return 1;
+    return 0;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
+    z_streamp strm;
+    const Bytef *dictionary;
+    uInt  dictLength;
+{
+    deflate_state *s;
+    uInt str, n;
+    int wrap;
+    unsigned avail;
+    z_const unsigned char *next;
+
+    if (deflateStateCheck(strm) || dictionary == Z_NULL)
+        return Z_STREAM_ERROR;
+    s = strm->state;
+    wrap = s->wrap;
+    if (wrap == 2 || (wrap == 1 && s->status != INIT_STATE) || s->lookahead)
+        return Z_STREAM_ERROR;
+
+    /* when using zlib wrappers, compute Adler-32 for provided dictionary */
+    if (wrap == 1)
+        strm->adler = adler32(strm->adler, dictionary, dictLength);
+    s->wrap = 0;                    /* avoid computing Adler-32 in read_buf */
+
+    /* if dictionary would fill window, just replace the history */
+    if (dictLength >= s->w_size) {
+        if (wrap == 0) {            /* already empty otherwise */
+            CLEAR_HASH(s);
+            s->strstart = 0;
+            s->block_start = 0L;
+            s->insert = 0;
+        }
+        dictionary += dictLength - s->w_size;  /* use the tail */
+        dictLength = s->w_size;
+    }
+
+    /* insert dictionary into window and hash */
+    avail = strm->avail_in;
+    next = strm->next_in;
+    strm->avail_in = dictLength;
+    strm->next_in = (z_const Bytef *)dictionary;
+    fill_window(s);
+    while (s->lookahead >= MIN_MATCH) {
+        str = s->strstart;
+        n = s->lookahead - (MIN_MATCH-1);
+        do {
+            UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]);
+#ifndef FASTEST
+            s->prev[str & s->w_mask] = s->head[s->ins_h];
+#endif
+            s->head[s->ins_h] = (Pos)str;
+            str++;
+        } while (--n);
+        s->strstart = str;
+        s->lookahead = MIN_MATCH-1;
+        fill_window(s);
+    }
+    s->strstart += s->lookahead;
+    s->block_start = (long)s->strstart;
+    s->insert = s->lookahead;
+    s->lookahead = 0;
+    s->match_length = s->prev_length = MIN_MATCH-1;
+    s->match_available = 0;
+    strm->next_in = next;
+    strm->avail_in = avail;
+    s->wrap = wrap;
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength)
+    z_streamp strm;
+    Bytef *dictionary;
+    uInt  *dictLength;
+{
+    deflate_state *s;
+    uInt len;
+
+    if (deflateStateCheck(strm))
+        return Z_STREAM_ERROR;
+    s = strm->state;
+    len = s->strstart + s->lookahead;
+    if (len > s->w_size)
+        len = s->w_size;
+    if (dictionary != Z_NULL && len)
+        zmemcpy(dictionary, s->window + s->strstart + s->lookahead - len, len);
+    if (dictLength != Z_NULL)
+        *dictLength = len;
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateResetKeep (strm)
+    z_streamp strm;
+{
+    deflate_state *s;
+
+    if (deflateStateCheck(strm)) {
+        return Z_STREAM_ERROR;
+    }
+
+    strm->total_in = strm->total_out = 0;
+    strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */
+    strm->data_type = Z_UNKNOWN;
+
+    s = (deflate_state *)strm->state;
+    s->pending = 0;
+    s->pending_out = s->pending_buf;
+
+    if (s->wrap < 0) {
+        s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */
+    }
+    s->status =
+#ifdef GZIP
+        s->wrap == 2 ? GZIP_STATE :
+#endif
+        s->wrap ? INIT_STATE : BUSY_STATE;
+    strm->adler =
+#ifdef GZIP
+        s->wrap == 2 ? crc32(0L, Z_NULL, 0) :
+#endif
+        adler32(0L, Z_NULL, 0);
+    s->last_flush = Z_NO_FLUSH;
+
+    _tr_init(s);
+
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateReset (strm)
+    z_streamp strm;
+{
+    int ret;
+
+    ret = deflateResetKeep(strm);
+    if (ret == Z_OK)
+        lm_init(strm->state);
+    return ret;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateSetHeader (strm, head)
+    z_streamp strm;
+    gz_headerp head;
+{
+    if (deflateStateCheck(strm) || strm->state->wrap != 2)
+        return Z_STREAM_ERROR;
+    strm->state->gzhead = head;
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflatePending (strm, pending, bits)
+    unsigned *pending;
+    int *bits;
+    z_streamp strm;
+{
+    if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+    if (pending != Z_NULL)
+        *pending = strm->state->pending;
+    if (bits != Z_NULL)
+        *bits = strm->state->bi_valid;
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflatePrime (strm, bits, value)
+    z_streamp strm;
+    int bits;
+    int value;
+{
+    deflate_state *s;
+    int put;
+
+    if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+    s = strm->state;
+    if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3))
+        return Z_BUF_ERROR;
+    do {
+        put = Buf_size - s->bi_valid;
+        if (put > bits)
+            put = bits;
+        s->bi_buf |= (ush)((value & ((1 << put) - 1)) << s->bi_valid);
+        s->bi_valid += put;
+        _tr_flush_bits(s);
+        value >>= put;
+        bits -= put;
+    } while (bits);
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateParams(strm, level, strategy)
+    z_streamp strm;
+    int level;
+    int strategy;
+{
+    deflate_state *s;
+    compress_func func;
+
+    if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+    s = strm->state;
+
+#ifdef FASTEST
+    if (level != 0) level = 1;
+#else
+    if (level == Z_DEFAULT_COMPRESSION) level = 6;
+#endif
+    if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) {
+        return Z_STREAM_ERROR;
+    }
+    func = configuration_table[s->level].func;
+
+    if ((strategy != s->strategy || func != configuration_table[level].func) &&
+        s->high_water) {
+        /* Flush the last buffer: */
+        int err = deflate(strm, Z_BLOCK);
+        if (err == Z_STREAM_ERROR)
+            return err;
+        if (strm->avail_out == 0)
+            return Z_BUF_ERROR;
+    }
+    if (s->level != level) {
+        if (s->level == 0 && s->matches != 0) {
+            if (s->matches == 1)
+                slide_hash(s);
+            else
+                CLEAR_HASH(s);
+            s->matches = 0;
+        }
+        s->level = level;
+        s->max_lazy_match   = configuration_table[level].max_lazy;
+        s->good_match       = configuration_table[level].good_length;
+        s->nice_match       = configuration_table[level].nice_length;
+        s->max_chain_length = configuration_table[level].max_chain;
+    }
+    s->strategy = strategy;
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
+    z_streamp strm;
+    int good_length;
+    int max_lazy;
+    int nice_length;
+    int max_chain;
+{
+    deflate_state *s;
+
+    if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+    s = strm->state;
+    s->good_match = (uInt)good_length;
+    s->max_lazy_match = (uInt)max_lazy;
+    s->nice_match = nice_length;
+    s->max_chain_length = (uInt)max_chain;
+    return Z_OK;
+}
+
+/* =========================================================================
+ * For the default windowBits of 15 and memLevel of 8, this function returns
+ * a close to exact, as well as small, upper bound on the compressed size.
+ * They are coded as constants here for a reason--if the #define's are
+ * changed, then this function needs to be changed as well.  The return
+ * value for 15 and 8 only works for those exact settings.
+ *
+ * For any setting other than those defaults for windowBits and memLevel,
+ * the value returned is a conservative worst case for the maximum expansion
+ * resulting from using fixed blocks instead of stored blocks, which deflate
+ * can emit on compressed data for some combinations of the parameters.
+ *
+ * This function could be more sophisticated to provide closer upper bounds for
+ * every combination of windowBits and memLevel.  But even the conservative
+ * upper bound of about 14% expansion does not seem onerous for output buffer
+ * allocation.
+ */
+uLong ZEXPORT deflateBound(strm, sourceLen)
+    z_streamp strm;
+    uLong sourceLen;
+{
+    deflate_state *s;
+    uLong complen, wraplen;
+
+    /* conservative upper bound for compressed data */
+    complen = sourceLen +
+              ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5;
+
+    /* if can't get parameters, return conservative bound plus zlib wrapper */
+    if (deflateStateCheck(strm))
+        return complen + 6;
+
+    /* compute wrapper length */
+    s = strm->state;
+    switch (s->wrap) {
+    case 0:                                 /* raw deflate */
+        wraplen = 0;
+        break;
+    case 1:                                 /* zlib wrapper */
+        wraplen = 6 + (s->strstart ? 4 : 0);
+        break;
+#ifdef GZIP
+    case 2:                                 /* gzip wrapper */
+        wraplen = 18;
+        if (s->gzhead != Z_NULL) {          /* user-supplied gzip header */
+            Bytef *str;
+            if (s->gzhead->extra != Z_NULL)
+                wraplen += 2 + s->gzhead->extra_len;
+            str = s->gzhead->name;
+            if (str != Z_NULL)
+                do {
+                    wraplen++;
+                } while (*str++);
+            str = s->gzhead->comment;
+            if (str != Z_NULL)
+                do {
+                    wraplen++;
+                } while (*str++);
+            if (s->gzhead->hcrc)
+                wraplen += 2;
+        }
+        break;
+#endif
+    default:                                /* for compiler happiness */
+        wraplen = 6;
+    }
+
+    /* if not default parameters, return conservative bound */
+    if (s->w_bits != 15 || s->hash_bits != 8 + 7)
+        return complen + wraplen;
+
+    /* default settings: return tight bound for that case */
+    return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
+           (sourceLen >> 25) + 13 - 6 + wraplen;
+}
+
+/* =========================================================================
+ * Put a short in the pending buffer. The 16-bit value is put in MSB order.
+ * IN assertion: the stream state is correct and there is enough room in
+ * pending_buf.
+ */
+local void putShortMSB (s, b)
+    deflate_state *s;
+    uInt b;
+{
+    put_byte(s, (Byte)(b >> 8));
+    put_byte(s, (Byte)(b & 0xff));
+}
+
+/* =========================================================================
+ * Flush as much pending output as possible. All deflate() output, except for
+ * some deflate_stored() output, goes through this function so some
+ * applications may wish to modify it to avoid allocating a large
+ * strm->next_out buffer and copying into it. (See also read_buf()).
+ */
+local void flush_pending(strm)
+    z_streamp strm;
+{
+    unsigned len;
+    deflate_state *s = strm->state;
+
+    _tr_flush_bits(s);
+    len = s->pending;
+    if (len > strm->avail_out) len = strm->avail_out;
+    if (len == 0) return;
+
+    zmemcpy(strm->next_out, s->pending_out, len);
+    strm->next_out  += len;
+    s->pending_out  += len;
+    strm->total_out += len;
+    strm->avail_out -= len;
+    s->pending      -= len;
+    if (s->pending == 0) {
+        s->pending_out = s->pending_buf;
+    }
+}
+
+/* ===========================================================================
+ * Update the header CRC with the bytes s->pending_buf[beg..s->pending - 1].
+ */
+#define HCRC_UPDATE(beg) \
+    do { \
+        if (s->gzhead->hcrc && s->pending > (beg)) \
+            strm->adler = crc32(strm->adler, s->pending_buf + (beg), \
+                                s->pending - (beg)); \
+    } while (0)
+
+/* ========================================================================= */
+int ZEXPORT deflate (strm, flush)
+    z_streamp strm;
+    int flush;
+{
+    int old_flush; /* value of flush param for previous deflate call */
+    deflate_state *s;
+
+    if (deflateStateCheck(strm) || flush > Z_BLOCK || flush < 0) {
+        return Z_STREAM_ERROR;
+    }
+    s = strm->state;
+
+    if (strm->next_out == Z_NULL ||
+        (strm->avail_in != 0 && strm->next_in == Z_NULL) ||
+        (s->status == FINISH_STATE && flush != Z_FINISH)) {
+        ERR_RETURN(strm, Z_STREAM_ERROR);
+    }
+    if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR);
+
+    old_flush = s->last_flush;
+    s->last_flush = flush;
+
+    /* Flush as much pending output as possible */
+    if (s->pending != 0) {
+        flush_pending(strm);
+        if (strm->avail_out == 0) {
+            /* Since avail_out is 0, deflate will be called again with
+             * more output space, but possibly with both pending and
+             * avail_in equal to zero. There won't be anything to do,
+             * but this is not an error situation so make sure we
+             * return OK instead of BUF_ERROR at next call of deflate:
+             */
+            s->last_flush = -1;
+            return Z_OK;
+        }
+
+    /* Make sure there is something to do and avoid duplicate consecutive
+     * flushes. For repeated and useless calls with Z_FINISH, we keep
+     * returning Z_STREAM_END instead of Z_BUF_ERROR.
+     */
+    } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) &&
+               flush != Z_FINISH) {
+        ERR_RETURN(strm, Z_BUF_ERROR);
+    }
+
+    /* User must not provide more input after the first FINISH: */
+    if (s->status == FINISH_STATE && strm->avail_in != 0) {
+        ERR_RETURN(strm, Z_BUF_ERROR);
+    }
+
+    /* Write the header */
+    if (s->status == INIT_STATE) {
+        /* zlib header */
+        uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8;
+        uInt level_flags;
+
+        if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2)
+            level_flags = 0;
+        else if (s->level < 6)
+            level_flags = 1;
+        else if (s->level == 6)
+            level_flags = 2;
+        else
+            level_flags = 3;
+        header |= (level_flags << 6);
+        if (s->strstart != 0) header |= PRESET_DICT;
+        header += 31 - (header % 31);
+
+        putShortMSB(s, header);
+
+        /* Save the adler32 of the preset dictionary: */
+        if (s->strstart != 0) {
+            putShortMSB(s, (uInt)(strm->adler >> 16));
+            putShortMSB(s, (uInt)(strm->adler & 0xffff));
+        }
+        strm->adler = adler32(0L, Z_NULL, 0);
+        s->status = BUSY_STATE;
+
+        /* Compression must start with an empty pending buffer */
+        flush_pending(strm);
+        if (s->pending != 0) {
+            s->last_flush = -1;
+            return Z_OK;
+        }
+    }
+#ifdef GZIP
+    if (s->status == GZIP_STATE) {
+        /* gzip header */
+        strm->adler = crc32(0L, Z_NULL, 0);
+        put_byte(s, 31);
+        put_byte(s, 139);
+        put_byte(s, 8);
+        if (s->gzhead == Z_NULL) {
+            put_byte(s, 0);
+            put_byte(s, 0);
+            put_byte(s, 0);
+            put_byte(s, 0);
+            put_byte(s, 0);
+            put_byte(s, s->level == 9 ? 2 :
+                     (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+                      4 : 0));
+            put_byte(s, OS_CODE);
+            s->status = BUSY_STATE;
+
+            /* Compression must start with an empty pending buffer */
+            flush_pending(strm);
+            if (s->pending != 0) {
+                s->last_flush = -1;
+                return Z_OK;
+            }
+        }
+        else {
+            put_byte(s, (s->gzhead->text ? 1 : 0) +
+                     (s->gzhead->hcrc ? 2 : 0) +
+                     (s->gzhead->extra == Z_NULL ? 0 : 4) +
+                     (s->gzhead->name == Z_NULL ? 0 : 8) +
+                     (s->gzhead->comment == Z_NULL ? 0 : 16)
+                     );
+            put_byte(s, (Byte)(s->gzhead->time & 0xff));
+            put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff));
+            put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff));
+            put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff));
+            put_byte(s, s->level == 9 ? 2 :
+                     (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+                      4 : 0));
+            put_byte(s, s->gzhead->os & 0xff);
+            if (s->gzhead->extra != Z_NULL) {
+                put_byte(s, s->gzhead->extra_len & 0xff);
+                put_byte(s, (s->gzhead->extra_len >> 8) & 0xff);
+            }
+            if (s->gzhead->hcrc)
+                strm->adler = crc32(strm->adler, s->pending_buf,
+                                    s->pending);
+            s->gzindex = 0;
+            s->status = EXTRA_STATE;
+        }
+    }
+    if (s->status == EXTRA_STATE) {
+        if (s->gzhead->extra != Z_NULL) {
+            ulg beg = s->pending;   /* start of bytes to update crc */
+            uInt left = (s->gzhead->extra_len & 0xffff) - s->gzindex;
+            while (s->pending + left > s->pending_buf_size) {
+                uInt copy = s->pending_buf_size - s->pending;
+                zmemcpy(s->pending_buf + s->pending,
+                        s->gzhead->extra + s->gzindex, copy);
+                s->pending = s->pending_buf_size;
+                HCRC_UPDATE(beg);
+                s->gzindex += copy;
+                flush_pending(strm);
+                if (s->pending != 0) {
+                    s->last_flush = -1;
+                    return Z_OK;
+                }
+                beg = 0;
+                left -= copy;
+            }
+            zmemcpy(s->pending_buf + s->pending,
+                    s->gzhead->extra + s->gzindex, left);
+            s->pending += left;
+            HCRC_UPDATE(beg);
+            s->gzindex = 0;
+        }
+        s->status = NAME_STATE;
+    }
+    if (s->status == NAME_STATE) {
+        if (s->gzhead->name != Z_NULL) {
+            ulg beg = s->pending;   /* start of bytes to update crc */
+            int val;
+            do {
+                if (s->pending == s->pending_buf_size) {
+                    HCRC_UPDATE(beg);
+                    flush_pending(strm);
+                    if (s->pending != 0) {
+                        s->last_flush = -1;
+                        return Z_OK;
+                    }
+                    beg = 0;
+                }
+                val = s->gzhead->name[s->gzindex++];
+                put_byte(s, val);
+            } while (val != 0);
+            HCRC_UPDATE(beg);
+            s->gzindex = 0;
+        }
+        s->status = COMMENT_STATE;
+    }
+    if (s->status == COMMENT_STATE) {
+        if (s->gzhead->comment != Z_NULL) {
+            ulg beg = s->pending;   /* start of bytes to update crc */
+            int val;
+            do {
+                if (s->pending == s->pending_buf_size) {
+                    HCRC_UPDATE(beg);
+                    flush_pending(strm);
+                    if (s->pending != 0) {
+                        s->last_flush = -1;
+                        return Z_OK;
+                    }
+                    beg = 0;
+                }
+                val = s->gzhead->comment[s->gzindex++];
+                put_byte(s, val);
+            } while (val != 0);
+            HCRC_UPDATE(beg);
+        }
+        s->status = HCRC_STATE;
+    }
+    if (s->status == HCRC_STATE) {
+        if (s->gzhead->hcrc) {
+            if (s->pending + 2 > s->pending_buf_size) {
+                flush_pending(strm);
+                if (s->pending != 0) {
+                    s->last_flush = -1;
+                    return Z_OK;
+                }
+            }
+            put_byte(s, (Byte)(strm->adler & 0xff));
+            put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
+            strm->adler = crc32(0L, Z_NULL, 0);
+        }
+        s->status = BUSY_STATE;
+
+        /* Compression must start with an empty pending buffer */
+        flush_pending(strm);
+        if (s->pending != 0) {
+            s->last_flush = -1;
+            return Z_OK;
+        }
+    }
+#endif
+
+    /* Start a new block or continue the current one.
+     */
+    if (strm->avail_in != 0 || s->lookahead != 0 ||
+        (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) {
+        block_state bstate;
+
+        bstate = s->level == 0 ? deflate_stored(s, flush) :
+                 s->strategy == Z_HUFFMAN_ONLY ? deflate_huff(s, flush) :
+                 s->strategy == Z_RLE ? deflate_rle(s, flush) :
+                 (*(configuration_table[s->level].func))(s, flush);
+
+        if (bstate == finish_started || bstate == finish_done) {
+            s->status = FINISH_STATE;
+        }
+        if (bstate == need_more || bstate == finish_started) {
+            if (strm->avail_out == 0) {
+                s->last_flush = -1; /* avoid BUF_ERROR next call, see above */
+            }
+            return Z_OK;
+            /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
+             * of deflate should use the same flush parameter to make sure
+             * that the flush is complete. So we don't have to output an
+             * empty block here, this will be done at next call. This also
+             * ensures that for a very small output buffer, we emit at most
+             * one empty block.
+             */
+        }
+        if (bstate == block_done) {
+            if (flush == Z_PARTIAL_FLUSH) {
+                _tr_align(s);
+            } else if (flush != Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+                _tr_stored_block(s, (char*)0, 0L, 0);
+                /* For a full flush, this empty block will be recognized
+                 * as a special marker by inflate_sync().
+                 */
+                if (flush == Z_FULL_FLUSH) {
+                    CLEAR_HASH(s);             /* forget history */
+                    if (s->lookahead == 0) {
+                        s->strstart = 0;
+                        s->block_start = 0L;
+                        s->insert = 0;
+                    }
+                }
+            }
+            flush_pending(strm);
+            if (strm->avail_out == 0) {
+              s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */
+              return Z_OK;
+            }
+        }
+    }
+
+    if (flush != Z_FINISH) return Z_OK;
+    if (s->wrap <= 0) return Z_STREAM_END;
+
+    /* Write the trailer */
+#ifdef GZIP
+    if (s->wrap == 2) {
+        put_byte(s, (Byte)(strm->adler & 0xff));
+        put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
+        put_byte(s, (Byte)((strm->adler >> 16) & 0xff));
+        put_byte(s, (Byte)((strm->adler >> 24) & 0xff));
+        put_byte(s, (Byte)(strm->total_in & 0xff));
+        put_byte(s, (Byte)((strm->total_in >> 8) & 0xff));
+        put_byte(s, (Byte)((strm->total_in >> 16) & 0xff));
+        put_byte(s, (Byte)((strm->total_in >> 24) & 0xff));
+    }
+    else
+#endif
+    {
+        putShortMSB(s, (uInt)(strm->adler >> 16));
+        putShortMSB(s, (uInt)(strm->adler & 0xffff));
+    }
+    flush_pending(strm);
+    /* If avail_out is zero, the application will call deflate again
+     * to flush the rest.
+     */
+    if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */
+    return s->pending != 0 ? Z_OK : Z_STREAM_END;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateEnd (strm)
+    z_streamp strm;
+{
+    int status;
+
+    if (deflateStateCheck(strm)) return Z_STREAM_ERROR;
+
+    status = strm->state->status;
+
+    /* Deallocate in reverse order of allocations: */
+    TRY_FREE(strm, strm->state->pending_buf);
+    TRY_FREE(strm, strm->state->head);
+    TRY_FREE(strm, strm->state->prev);
+    TRY_FREE(strm, strm->state->window);
+
+    ZFREE(strm, strm->state);
+    strm->state = Z_NULL;
+
+    return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK;
+}
+
+/* =========================================================================
+ * Copy the source state to the destination state.
+ * To simplify the source, this is not supported for 16-bit MSDOS (which
+ * doesn't have enough memory anyway to duplicate compression states).
+ */
+int ZEXPORT deflateCopy (dest, source)
+    z_streamp dest;
+    z_streamp source;
+{
+#ifdef MAXSEG_64K
+    return Z_STREAM_ERROR;
+#else
+    deflate_state *ds;
+    deflate_state *ss;
+    ushf *overlay;
+
+
+    if (deflateStateCheck(source) || dest == Z_NULL) {
+        return Z_STREAM_ERROR;
+    }
+
+    ss = source->state;
+
+    zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream));
+
+    ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state));
+    if (ds == Z_NULL) return Z_MEM_ERROR;
+    dest->state = (struct internal_state FAR *) ds;
+    zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state));
+    ds->strm = dest;
+
+    ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte));
+    ds->prev   = (Posf *)  ZALLOC(dest, ds->w_size, sizeof(Pos));
+    ds->head   = (Posf *)  ZALLOC(dest, ds->hash_size, sizeof(Pos));
+    overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2);
+    ds->pending_buf = (uchf *) overlay;
+
+    if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL ||
+        ds->pending_buf == Z_NULL) {
+        deflateEnd (dest);
+        return Z_MEM_ERROR;
+    }
+    /* following zmemcpy do not work for 16-bit MSDOS */
+    zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte));
+    zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos));
+    zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos));
+    zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size);
+
+    ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf);
+    ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush);
+    ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize;
+
+    ds->l_desc.dyn_tree = ds->dyn_ltree;
+    ds->d_desc.dyn_tree = ds->dyn_dtree;
+    ds->bl_desc.dyn_tree = ds->bl_tree;
+
+    return Z_OK;
+#endif /* MAXSEG_64K */
+}
+
+/* ===========================================================================
+ * Read a new buffer from the current input stream, update the adler32
+ * and total number of bytes read.  All deflate() input goes through
+ * this function so some applications may wish to modify it to avoid
+ * allocating a large strm->next_in buffer and copying from it.
+ * (See also flush_pending()).
+ */
+local unsigned read_buf(strm, buf, size)
+    z_streamp strm;
+    Bytef *buf;
+    unsigned size;
+{
+    unsigned len = strm->avail_in;
+
+    if (len > size) len = size;
+    if (len == 0) return 0;
+
+    strm->avail_in  -= len;
+
+    zmemcpy(buf, strm->next_in, len);
+    if (strm->state->wrap == 1) {
+        strm->adler = adler32(strm->adler, buf, len);
+    }
+#ifdef GZIP
+    else if (strm->state->wrap == 2) {
+        strm->adler = crc32(strm->adler, buf, len);
+    }
+#endif
+    strm->next_in  += len;
+    strm->total_in += len;
+
+    return len;
+}
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new zlib stream
+ */
+local void lm_init (s)
+    deflate_state *s;
+{
+    s->window_size = (ulg)2L*s->w_size;
+
+    CLEAR_HASH(s);
+
+    /* Set the default configuration parameters:
+     */
+    s->max_lazy_match   = configuration_table[s->level].max_lazy;
+    s->good_match       = configuration_table[s->level].good_length;
+    s->nice_match       = configuration_table[s->level].nice_length;
+    s->max_chain_length = configuration_table[s->level].max_chain;
+
+    s->strstart = 0;
+    s->block_start = 0L;
+    s->lookahead = 0;
+    s->insert = 0;
+    s->match_length = s->prev_length = MIN_MATCH-1;
+    s->match_available = 0;
+    s->ins_h = 0;
+#ifndef FASTEST
+#ifdef ASMV
+    match_init(); /* initialize the asm code */
+#endif
+#endif
+}
+
+#ifndef FASTEST
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ * OUT assertion: the match length is not greater than s->lookahead.
+ */
+#ifndef ASMV
+/* For 80x86 and 680x0, an optimized version will be provided in match.asm or
+ * match.S. The code will be functionally equivalent.
+ */
+local uInt longest_match(s, cur_match)
+    deflate_state *s;
+    IPos cur_match;                             /* current match */
+{
+    unsigned chain_length = s->max_chain_length;/* max hash chain length */
+    register Bytef *scan = s->window + s->strstart; /* current string */
+    register Bytef *match;                      /* matched string */
+    register int len;                           /* length of current match */
+    int best_len = (int)s->prev_length;         /* best match length so far */
+    int nice_match = s->nice_match;             /* stop if match long enough */
+    IPos limit = s->strstart > (IPos)MAX_DIST(s) ?
+        s->strstart - (IPos)MAX_DIST(s) : NIL;
+    /* Stop when cur_match becomes <= limit. To simplify the code,
+     * we prevent matches with the string of window index 0.
+     */
+    Posf *prev = s->prev;
+    uInt wmask = s->w_mask;
+
+#ifdef UNALIGNED_OK
+    /* Compare two bytes at a time. Note: this is not always beneficial.
+     * Try with and without -DUNALIGNED_OK to check.
+     */
+    register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1;
+    register ush scan_start = *(ushf*)scan;
+    register ush scan_end   = *(ushf*)(scan+best_len-1);
+#else
+    register Bytef *strend = s->window + s->strstart + MAX_MATCH;
+    register Byte scan_end1  = scan[best_len-1];
+    register Byte scan_end   = scan[best_len];
+#endif
+
+    /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+     * It is easy to get rid of this optimization if necessary.
+     */
+    Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+    /* Do not waste too much time if we already have a good match: */
+    if (s->prev_length >= s->good_match) {
+        chain_length >>= 2;
+    }
+    /* Do not look for matches beyond the end of the input. This is necessary
+     * to make deflate deterministic.
+     */
+    if ((uInt)nice_match > s->lookahead) nice_match = (int)s->lookahead;
+
+    Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+    do {
+        Assert(cur_match < s->strstart, "no future");
+        match = s->window + cur_match;
+
+        /* Skip to next match if the match length cannot increase
+         * or if the match length is less than 2.  Note that the checks below
+         * for insufficient lookahead only occur occasionally for performance
+         * reasons.  Therefore uninitialized memory will be accessed, and
+         * conditional jumps will be made that depend on those values.
+         * However the length of the match is limited to the lookahead, so
+         * the output of deflate is not affected by the uninitialized values.
+         */
+#if (defined(UNALIGNED_OK) && MAX_MATCH == 258)
+        /* This code assumes sizeof(unsigned short) == 2. Do not use
+         * UNALIGNED_OK if your compiler uses a different size.
+         */
+        if (*(ushf*)(match+best_len-1) != scan_end ||
+            *(ushf*)match != scan_start) continue;
+
+        /* It is not necessary to compare scan[2] and match[2] since they are
+         * always equal when the other bytes match, given that the hash keys
+         * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at
+         * strstart+3, +5, ... up to strstart+257. We check for insufficient
+         * lookahead only every 4th comparison; the 128th check will be made
+         * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is
+         * necessary to put more guard bytes at the end of the window, or
+         * to check more often for insufficient lookahead.
+         */
+        Assert(scan[2] == match[2], "scan[2]?");
+        scan++, match++;
+        do {
+        } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+                 *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+                 *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+                 *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+                 scan < strend);
+        /* The funny "do {}" generates better code on most compilers */
+
+        /* Here, scan <= window+strstart+257 */
+        Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+        if (*scan == *match) scan++;
+
+        len = (MAX_MATCH - 1) - (int)(strend-scan);
+        scan = strend - (MAX_MATCH-1);
+
+#else /* UNALIGNED_OK */
+
+        if (match[best_len]   != scan_end  ||
+            match[best_len-1] != scan_end1 ||
+            *match            != *scan     ||
+            *++match          != scan[1])      continue;
+
+        /* The check at best_len-1 can be removed because it will be made
+         * again later. (This heuristic is not always a win.)
+         * It is not necessary to compare scan[2] and match[2] since they
+         * are always equal when the other bytes match, given that
+         * the hash keys are equal and that HASH_BITS >= 8.
+         */
+        scan += 2, match++;
+        Assert(*scan == *match, "match[2]?");
+
+        /* We check for insufficient lookahead only every 8th comparison;
+         * the 256th check will be made at strstart+258.
+         */
+        do {
+        } while (*++scan == *++match && *++scan == *++match &&
+                 *++scan == *++match && *++scan == *++match &&
+                 *++scan == *++match && *++scan == *++match &&
+                 *++scan == *++match && *++scan == *++match &&
+                 scan < strend);
+
+        Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+        len = MAX_MATCH - (int)(strend - scan);
+        scan = strend - MAX_MATCH;
+
+#endif /* UNALIGNED_OK */
+
+        if (len > best_len) {
+            s->match_start = cur_match;
+            best_len = len;
+            if (len >= nice_match) break;
+#ifdef UNALIGNED_OK
+            scan_end = *(ushf*)(scan+best_len-1);
+#else
+            scan_end1  = scan[best_len-1];
+            scan_end   = scan[best_len];
+#endif
+        }
+    } while ((cur_match = prev[cur_match & wmask]) > limit
+             && --chain_length != 0);
+
+    if ((uInt)best_len <= s->lookahead) return (uInt)best_len;
+    return s->lookahead;
+}
+#endif /* ASMV */
+
+#else /* FASTEST */
+
+/* ---------------------------------------------------------------------------
+ * Optimized version for FASTEST only
+ */
+local uInt longest_match(s, cur_match)
+    deflate_state *s;
+    IPos cur_match;                             /* current match */
+{
+    register Bytef *scan = s->window + s->strstart; /* current string */
+    register Bytef *match;                       /* matched string */
+    register int len;                           /* length of current match */
+    register Bytef *strend = s->window + s->strstart + MAX_MATCH;
+
+    /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+     * It is easy to get rid of this optimization if necessary.
+     */
+    Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+    Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+    Assert(cur_match < s->strstart, "no future");
+
+    match = s->window + cur_match;
+
+    /* Return failure if the match length is less than 2:
+     */
+    if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1;
+
+    /* The check at best_len-1 can be removed because it will be made
+     * again later. (This heuristic is not always a win.)
+     * It is not necessary to compare scan[2] and match[2] since they
+     * are always equal when the other bytes match, given that
+     * the hash keys are equal and that HASH_BITS >= 8.
+     */
+    scan += 2, match += 2;
+    Assert(*scan == *match, "match[2]?");
+
+    /* We check for insufficient lookahead only every 8th comparison;
+     * the 256th check will be made at strstart+258.
+     */
+    do {
+    } while (*++scan == *++match && *++scan == *++match &&
+             *++scan == *++match && *++scan == *++match &&
+             *++scan == *++match && *++scan == *++match &&
+             *++scan == *++match && *++scan == *++match &&
+             scan < strend);
+
+    Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+    len = MAX_MATCH - (int)(strend - scan);
+
+    if (len < MIN_MATCH) return MIN_MATCH - 1;
+
+    s->match_start = cur_match;
+    return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead;
+}
+
+#endif /* FASTEST */
+
+#ifdef ZLIB_DEBUG
+
+#define EQUAL 0
+/* result of memcmp for equal strings */
+
+/* ===========================================================================
+ * Check that the match at match_start is indeed a match.
+ */
+local void check_match(s, start, match, length)
+    deflate_state *s;
+    IPos start, match;
+    int length;
+{
+    /* check that the match is indeed a match */
+    if (zmemcmp(s->window + match,
+                s->window + start, length) != EQUAL) {
+        fprintf(stderr, " start %u, match %u, length %d\n",
+                start, match, length);
+        do {
+            fprintf(stderr, "%c%c", s->window[match++], s->window[start++]);
+        } while (--length != 0);
+        z_error("invalid match");
+    }
+    if (z_verbose > 1) {
+        fprintf(stderr,"\\[%d,%d]", start-match, length);
+        do { putc(s->window[start++], stderr); } while (--length != 0);
+    }
+}
+#else
+#  define check_match(s, start, match, length)
+#endif /* ZLIB_DEBUG */
+
+/* ===========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead.
+ *
+ * IN assertion: lookahead < MIN_LOOKAHEAD
+ * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ *    At least one byte has been read, or avail_in == 0; reads are
+ *    performed for at least two bytes (required for the zip translate_eol
+ *    option -- not supported here).
+ */
+local void fill_window(s)
+    deflate_state *s;
+{
+    unsigned n;
+    unsigned more;    /* Amount of free space at the end of the window. */
+    uInt wsize = s->w_size;
+
+    Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");
+
+    do {
+        more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart);
+
+        /* Deal with !@#$% 64K limit: */
+        if (sizeof(int) <= 2) {
+            if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+                more = wsize;
+
+            } else if (more == (unsigned)(-1)) {
+                /* Very unlikely, but possible on 16 bit machine if
+                 * strstart == 0 && lookahead == 1 (input done a byte at time)
+                 */
+                more--;
+            }
+        }
+
+        /* If the window is almost full and there is insufficient lookahead,
+         * move the upper half to the lower one to make room in the upper half.
+         */
+        if (s->strstart >= wsize+MAX_DIST(s)) {
+
+            zmemcpy(s->window, s->window+wsize, (unsigned)wsize - more);
+            s->match_start -= wsize;
+            s->strstart    -= wsize; /* we now have strstart >= MAX_DIST */
+            s->block_start -= (long) wsize;
+            slide_hash(s);
+            more += wsize;
+        }
+        if (s->strm->avail_in == 0) break;
+
+        /* If there was no sliding:
+         *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+         *    more == window_size - lookahead - strstart
+         * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+         * => more >= window_size - 2*WSIZE + 2
+         * In the BIG_MEM or MMAP case (not yet supported),
+         *   window_size == input_size + MIN_LOOKAHEAD  &&
+         *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+         * Otherwise, window_size == 2*WSIZE so more >= 2.
+         * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+         */
+        Assert(more >= 2, "more < 2");
+
+        n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more);
+        s->lookahead += n;
+
+        /* Initialize the hash value now that we have some input: */
+        if (s->lookahead + s->insert >= MIN_MATCH) {
+            uInt str = s->strstart - s->insert;
+            s->ins_h = s->window[str];
+            UPDATE_HASH(s, s->ins_h, s->window[str + 1]);
+#if MIN_MATCH != 3
+            Call UPDATE_HASH() MIN_MATCH-3 more times
+#endif
+            while (s->insert) {
+                UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]);
+#ifndef FASTEST
+                s->prev[str & s->w_mask] = s->head[s->ins_h];
+#endif
+                s->head[s->ins_h] = (Pos)str;
+                str++;
+                s->insert--;
+                if (s->lookahead + s->insert < MIN_MATCH)
+                    break;
+            }
+        }
+        /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+         * but this is not important since only literal bytes will be emitted.
+         */
+
+    } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0);
+
+    /* If the WIN_INIT bytes after the end of the current data have never been
+     * written, then zero those bytes in order to avoid memory check reports of
+     * the use of uninitialized (or uninitialised as Julian writes) bytes by
+     * the longest match routines.  Update the high water mark for the next
+     * time through here.  WIN_INIT is set to MAX_MATCH since the longest match
+     * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
+     */
+    if (s->high_water < s->window_size) {
+        ulg curr = s->strstart + (ulg)(s->lookahead);
+        ulg init;
+
+        if (s->high_water < curr) {
+            /* Previous high water mark below current data -- zero WIN_INIT
+             * bytes or up to end of window, whichever is less.
+             */
+            init = s->window_size - curr;
+            if (init > WIN_INIT)
+                init = WIN_INIT;
+            zmemzero(s->window + curr, (unsigned)init);
+            s->high_water = curr + init;
+        }
+        else if (s->high_water < (ulg)curr + WIN_INIT) {
+            /* High water mark at or above current data, but below current data
+             * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
+             * to end of window, whichever is less.
+             */
+            init = (ulg)curr + WIN_INIT - s->high_water;
+            if (init > s->window_size - s->high_water)
+                init = s->window_size - s->high_water;
+            zmemzero(s->window + s->high_water, (unsigned)init);
+            s->high_water += init;
+        }
+    }
+
+    Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+           "not enough room for search");
+}
+
+/* ===========================================================================
+ * Flush the current block, with given end-of-file flag.
+ * IN assertion: strstart is set to the end of the current match.
+ */
+#define FLUSH_BLOCK_ONLY(s, last) { \
+   _tr_flush_block(s, (s->block_start >= 0L ? \
+                   (charf *)&s->window[(unsigned)s->block_start] : \
+                   (charf *)Z_NULL), \
+                (ulg)((long)s->strstart - s->block_start), \
+                (last)); \
+   s->block_start = s->strstart; \
+   flush_pending(s->strm); \
+   Tracev((stderr,"[FLUSH]")); \
+}
+
+/* Same but force premature exit if necessary. */
+#define FLUSH_BLOCK(s, last) { \
+   FLUSH_BLOCK_ONLY(s, last); \
+   if (s->strm->avail_out == 0) return (last) ? finish_started : need_more; \
+}
+
+/* Maximum stored block length in deflate format (not including header). */
+#define MAX_STORED 65535
+
+/* Minimum of a and b. */
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+
+/* ===========================================================================
+ * Copy without compression as much as possible from the input stream, return
+ * the current block state.
+ *
+ * In case deflateParams() is used to later switch to a non-zero compression
+ * level, s->matches (otherwise unused when storing) keeps track of the number
+ * of hash table slides to perform. If s->matches is 1, then one hash table
+ * slide will be done when switching. If s->matches is 2, the maximum value
+ * allowed here, then the hash table will be cleared, since two or more slides
+ * is the same as a clear.
+ *
+ * deflate_stored() is written to minimize the number of times an input byte is
+ * copied. It is most efficient with large input and output buffers, which
+ * maximizes the opportunites to have a single copy from next_in to next_out.
+ */
+local block_state deflate_stored(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    /* Smallest worthy block size when not flushing or finishing. By default
+     * this is 32K. This can be as small as 507 bytes for memLevel == 1. For
+     * large input and output buffers, the stored block size will be larger.
+     */
+    unsigned min_block = MIN(s->pending_buf_size - 5, s->w_size);
+
+    /* Copy as many min_block or larger stored blocks directly to next_out as
+     * possible. If flushing, copy the remaining available input to next_out as
+     * stored blocks, if there is enough space.
+     */
+    unsigned len, left, have, last = 0;
+    unsigned used = s->strm->avail_in;
+    do {
+        /* Set len to the maximum size block that we can copy directly with the
+         * available input data and output space. Set left to how much of that
+         * would be copied from what's left in the window.
+         */
+        len = MAX_STORED;       /* maximum deflate stored block length */
+        have = (s->bi_valid + 42) >> 3;         /* number of header bytes */
+        if (s->strm->avail_out < have)          /* need room for header */
+            break;
+            /* maximum stored block length that will fit in avail_out: */
+        have = s->strm->avail_out - have;
+        left = s->strstart - s->block_start;    /* bytes left in window */
+        if (len > (ulg)left + s->strm->avail_in)
+            len = left + s->strm->avail_in;     /* limit len to the input */
+        if (len > have)
+            len = have;                         /* limit len to the output */
+
+        /* If the stored block would be less than min_block in length, or if
+         * unable to copy all of the available input when flushing, then try
+         * copying to the window and the pending buffer instead. Also don't
+         * write an empty block when flushing -- deflate() does that.
+         */
+        if (len < min_block && ((len == 0 && flush != Z_FINISH) ||
+                                flush == Z_NO_FLUSH ||
+                                len != left + s->strm->avail_in))
+            break;
+
+        /* Make a dummy stored block in pending to get the header bytes,
+         * including any pending bits. This also updates the debugging counts.
+         */
+        last = flush == Z_FINISH && len == left + s->strm->avail_in ? 1 : 0;
+        _tr_stored_block(s, (char *)0, 0L, last);
+
+        /* Replace the lengths in the dummy stored block with len. */
+        s->pending_buf[s->pending - 4] = len;
+        s->pending_buf[s->pending - 3] = len >> 8;
+        s->pending_buf[s->pending - 2] = ~len;
+        s->pending_buf[s->pending - 1] = ~len >> 8;
+
+        /* Write the stored block header bytes. */
+        flush_pending(s->strm);
+
+#ifdef ZLIB_DEBUG
+        /* Update debugging counts for the data about to be copied. */
+        s->compressed_len += len << 3;
+        s->bits_sent += len << 3;
+#endif
+
+        /* Copy uncompressed bytes from the window to next_out. */
+        if (left) {
+            if (left > len)
+                left = len;
+            zmemcpy(s->strm->next_out, s->window + s->block_start, left);
+            s->strm->next_out += left;
+            s->strm->avail_out -= left;
+            s->strm->total_out += left;
+            s->block_start += left;
+            len -= left;
+        }
+
+        /* Copy uncompressed bytes directly from next_in to next_out, updating
+         * the check value.
+         */
+        if (len) {
+            read_buf(s->strm, s->strm->next_out, len);
+            s->strm->next_out += len;
+            s->strm->avail_out -= len;
+            s->strm->total_out += len;
+        }
+    } while (last == 0);
+
+    /* Update the sliding window with the last s->w_size bytes of the copied
+     * data, or append all of the copied data to the existing window if less
+     * than s->w_size bytes were copied. Also update the number of bytes to
+     * insert in the hash tables, in the event that deflateParams() switches to
+     * a non-zero compression level.
+     */
+    used -= s->strm->avail_in;      /* number of input bytes directly copied */
+    if (used) {
+        /* If any input was used, then no unused input remains in the window,
+         * therefore s->block_start == s->strstart.
+         */
+        if (used >= s->w_size) {    /* supplant the previous history */
+            s->matches = 2;         /* clear hash */
+            zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size);
+            s->strstart = s->w_size;
+        }
+        else {
+            if (s->window_size - s->strstart <= used) {
+                /* Slide the window down. */
+                s->strstart -= s->w_size;
+                zmemcpy(s->window, s->window + s->w_size, s->strstart);
+                if (s->matches < 2)
+                    s->matches++;   /* add a pending slide_hash() */
+            }
+            zmemcpy(s->window + s->strstart, s->strm->next_in - used, used);
+            s->strstart += used;
+        }
+        s->block_start = s->strstart;
+        s->insert += MIN(used, s->w_size - s->insert);
+    }
+    if (s->high_water < s->strstart)
+        s->high_water = s->strstart;
+
+    /* If the last block was written to next_out, then done. */
+    if (last)
+        return finish_done;
+
+    /* If flushing and all input has been consumed, then done. */
+    if (flush != Z_NO_FLUSH && flush != Z_FINISH &&
+        s->strm->avail_in == 0 && (long)s->strstart == s->block_start)
+        return block_done;
+
+    /* Fill the window with any remaining input. */
+    have = s->window_size - s->strstart - 1;
+    if (s->strm->avail_in > have && s->block_start >= (long)s->w_size) {
+        /* Slide the window down. */
+        s->block_start -= s->w_size;
+        s->strstart -= s->w_size;
+        zmemcpy(s->window, s->window + s->w_size, s->strstart);
+        if (s->matches < 2)
+            s->matches++;           /* add a pending slide_hash() */
+        have += s->w_size;          /* more space now */
+    }
+    if (have > s->strm->avail_in)
+        have = s->strm->avail_in;
+    if (have) {
+        read_buf(s->strm, s->window + s->strstart, have);
+        s->strstart += have;
+    }
+    if (s->high_water < s->strstart)
+        s->high_water = s->strstart;
+
+    /* There was not enough avail_out to write a complete worthy or flushed
+     * stored block to next_out. Write a stored block to pending instead, if we
+     * have enough input for a worthy block, or if flushing and there is enough
+     * room for the remaining input as a stored block in the pending buffer.
+     */
+    have = (s->bi_valid + 42) >> 3;         /* number of header bytes */
+        /* maximum stored block length that will fit in pending: */
+    have = MIN(s->pending_buf_size - have, MAX_STORED);
+    min_block = MIN(have, s->w_size);
+    left = s->strstart - s->block_start;
+    if (left >= min_block ||
+        ((left || flush == Z_FINISH) && flush != Z_NO_FLUSH &&
+         s->strm->avail_in == 0 && left <= have)) {
+        len = MIN(left, have);
+        last = flush == Z_FINISH && s->strm->avail_in == 0 &&
+               len == left ? 1 : 0;
+        _tr_stored_block(s, (charf *)s->window + s->block_start, len, last);
+        s->block_start += len;
+        flush_pending(s->strm);
+    }
+
+    /* We've done all we can with the available input and output. */
+    return last ? finish_started : need_more;
+}
+
+/* ===========================================================================
+ * Compress as much as possible from the input stream, return the current
+ * block state.
+ * This function does not perform lazy evaluation of matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+local block_state deflate_fast(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    IPos hash_head;       /* head of the hash chain */
+    int bflush;           /* set if current block must be flushed */
+
+    for (;;) {
+        /* Make sure that we always have enough lookahead, except
+         * at the end of the input file. We need MAX_MATCH bytes
+         * for the next match, plus MIN_MATCH bytes to insert the
+         * string following the next match.
+         */
+        if (s->lookahead < MIN_LOOKAHEAD) {
+            fill_window(s);
+            if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+                return need_more;
+            }
+            if (s->lookahead == 0) break; /* flush the current block */
+        }
+
+        /* Insert the string window[strstart .. strstart+2] in the
+         * dictionary, and set hash_head to the head of the hash chain:
+         */
+        hash_head = NIL;
+        if (s->lookahead >= MIN_MATCH) {
+            INSERT_STRING(s, s->strstart, hash_head);
+        }
+
+        /* Find the longest match, discarding those <= prev_length.
+         * At this point we have always match_length < MIN_MATCH
+         */
+        if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) {
+            /* To simplify the code, we prevent matches with the string
+             * of window index 0 (in particular we have to avoid a match
+             * of the string with itself at the start of the input file).
+             */
+            s->match_length = longest_match (s, hash_head);
+            /* longest_match() sets match_start */
+        }
+        if (s->match_length >= MIN_MATCH) {
+            check_match(s, s->strstart, s->match_start, s->match_length);
+
+            _tr_tally_dist(s, s->strstart - s->match_start,
+                           s->match_length - MIN_MATCH, bflush);
+
+            s->lookahead -= s->match_length;
+
+            /* Insert new strings in the hash table only if the match length
+             * is not too large. This saves time but degrades compression.
+             */
+#ifndef FASTEST
+            if (s->match_length <= s->max_insert_length &&
+                s->lookahead >= MIN_MATCH) {
+                s->match_length--; /* string at strstart already in table */
+                do {
+                    s->strstart++;
+                    INSERT_STRING(s, s->strstart, hash_head);
+                    /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+                     * always MIN_MATCH bytes ahead.
+                     */
+                } while (--s->match_length != 0);
+                s->strstart++;
+            } else
+#endif
+            {
+                s->strstart += s->match_length;
+                s->match_length = 0;
+                s->ins_h = s->window[s->strstart];
+                UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]);
+#if MIN_MATCH != 3
+                Call UPDATE_HASH() MIN_MATCH-3 more times
+#endif
+                /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+                 * matter since it will be recomputed at next deflate call.
+                 */
+            }
+        } else {
+            /* No match, output a literal byte */
+            Tracevv((stderr,"%c", s->window[s->strstart]));
+            _tr_tally_lit (s, s->window[s->strstart], bflush);
+            s->lookahead--;
+            s->strstart++;
+        }
+        if (bflush) FLUSH_BLOCK(s, 0);
+    }
+    s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1;
+    if (flush == Z_FINISH) {
+        FLUSH_BLOCK(s, 1);
+        return finish_done;
+    }
+    if (s->last_lit)
+        FLUSH_BLOCK(s, 0);
+    return block_done;
+}
+
+#ifndef FASTEST
+/* ===========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+local block_state deflate_slow(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    IPos hash_head;          /* head of hash chain */
+    int bflush;              /* set if current block must be flushed */
+
+    /* Process the input block. */
+    for (;;) {
+        /* Make sure that we always have enough lookahead, except
+         * at the end of the input file. We need MAX_MATCH bytes
+         * for the next match, plus MIN_MATCH bytes to insert the
+         * string following the next match.
+         */
+        if (s->lookahead < MIN_LOOKAHEAD) {
+            fill_window(s);
+            if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+                return need_more;
+            }
+            if (s->lookahead == 0) break; /* flush the current block */
+        }
+
+        /* Insert the string window[strstart .. strstart+2] in the
+         * dictionary, and set hash_head to the head of the hash chain:
+         */
+        hash_head = NIL;
+        if (s->lookahead >= MIN_MATCH) {
+            INSERT_STRING(s, s->strstart, hash_head);
+        }
+
+        /* Find the longest match, discarding those <= prev_length.
+         */
+        s->prev_length = s->match_length, s->prev_match = s->match_start;
+        s->match_length = MIN_MATCH-1;
+
+        if (hash_head != NIL && s->prev_length < s->max_lazy_match &&
+            s->strstart - hash_head <= MAX_DIST(s)) {
+            /* To simplify the code, we prevent matches with the string
+             * of window index 0 (in particular we have to avoid a match
+             * of the string with itself at the start of the input file).
+             */
+            s->match_length = longest_match (s, hash_head);
+            /* longest_match() sets match_start */
+
+            if (s->match_length <= 5 && (s->strategy == Z_FILTERED
+#if TOO_FAR <= 32767
+                || (s->match_length == MIN_MATCH &&
+                    s->strstart - s->match_start > TOO_FAR)
+#endif
+                )) {
+
+                /* If prev_match is also MIN_MATCH, match_start is garbage
+                 * but we will ignore the current match anyway.
+                 */
+                s->match_length = MIN_MATCH-1;
+            }
+        }
+        /* If there was a match at the previous step and the current
+         * match is not better, output the previous match:
+         */
+        if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) {
+            uInt max_insert = s->strstart + s->lookahead - MIN_MATCH;
+            /* Do not insert strings in hash table beyond this. */
+
+            check_match(s, s->strstart-1, s->prev_match, s->prev_length);
+
+            _tr_tally_dist(s, s->strstart -1 - s->prev_match,
+                           s->prev_length - MIN_MATCH, bflush);
+
+            /* Insert in hash table all strings up to the end of the match.
+             * strstart-1 and strstart are already inserted. If there is not
+             * enough lookahead, the last two strings are not inserted in
+             * the hash table.
+             */
+            s->lookahead -= s->prev_length-1;
+            s->prev_length -= 2;
+            do {
+                if (++s->strstart <= max_insert) {
+                    INSERT_STRING(s, s->strstart, hash_head);
+                }
+            } while (--s->prev_length != 0);
+            s->match_available = 0;
+            s->match_length = MIN_MATCH-1;
+            s->strstart++;
+
+            if (bflush) FLUSH_BLOCK(s, 0);
+
+        } else if (s->match_available) {
+            /* If there was no match at the previous position, output a
+             * single literal. If there was a match but the current match
+             * is longer, truncate the previous match to a single literal.
+             */
+            Tracevv((stderr,"%c", s->window[s->strstart-1]));
+            _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+            if (bflush) {
+                FLUSH_BLOCK_ONLY(s, 0);
+            }
+            s->strstart++;
+            s->lookahead--;
+            if (s->strm->avail_out == 0) return need_more;
+        } else {
+            /* There is no previous match to compare with, wait for
+             * the next step to decide.
+             */
+            s->match_available = 1;
+            s->strstart++;
+            s->lookahead--;
+        }
+    }
+    Assert (flush != Z_NO_FLUSH, "no flush?");
+    if (s->match_available) {
+        Tracevv((stderr,"%c", s->window[s->strstart-1]));
+        _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+        s->match_available = 0;
+    }
+    s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1;
+    if (flush == Z_FINISH) {
+        FLUSH_BLOCK(s, 1);
+        return finish_done;
+    }
+    if (s->last_lit)
+        FLUSH_BLOCK(s, 0);
+    return block_done;
+}
+#endif /* FASTEST */
+
+/* ===========================================================================
+ * For Z_RLE, simply look for runs of bytes, generate matches only of distance
+ * one.  Do not maintain a hash table.  (It will be regenerated if this run of
+ * deflate switches away from Z_RLE.)
+ */
+local block_state deflate_rle(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    int bflush;             /* set if current block must be flushed */
+    uInt prev;              /* byte at distance one to match */
+    Bytef *scan, *strend;   /* scan goes up to strend for length of run */
+
+    for (;;) {
+        /* Make sure that we always have enough lookahead, except
+         * at the end of the input file. We need MAX_MATCH bytes
+         * for the longest run, plus one for the unrolled loop.
+         */
+        if (s->lookahead <= MAX_MATCH) {
+            fill_window(s);
+            if (s->lookahead <= MAX_MATCH && flush == Z_NO_FLUSH) {
+                return need_more;
+            }
+            if (s->lookahead == 0) break; /* flush the current block */
+        }
+
+        /* See how many times the previous byte repeats */
+        s->match_length = 0;
+        if (s->lookahead >= MIN_MATCH && s->strstart > 0) {
+            scan = s->window + s->strstart - 1;
+            prev = *scan;
+            if (prev == *++scan && prev == *++scan && prev == *++scan) {
+                strend = s->window + s->strstart + MAX_MATCH;
+                do {
+                } while (prev == *++scan && prev == *++scan &&
+                         prev == *++scan && prev == *++scan &&
+                         prev == *++scan && prev == *++scan &&
+                         prev == *++scan && prev == *++scan &&
+                         scan < strend);
+                s->match_length = MAX_MATCH - (uInt)(strend - scan);
+                if (s->match_length > s->lookahead)
+                    s->match_length = s->lookahead;
+            }
+            Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
+        }
+
+        /* Emit match if have run of MIN_MATCH or longer, else emit literal */
+        if (s->match_length >= MIN_MATCH) {
+            check_match(s, s->strstart, s->strstart - 1, s->match_length);
+
+            _tr_tally_dist(s, 1, s->match_length - MIN_MATCH, bflush);
+
+            s->lookahead -= s->match_length;
+            s->strstart += s->match_length;
+            s->match_length = 0;
+        } else {
+            /* No match, output a literal byte */
+            Tracevv((stderr,"%c", s->window[s->strstart]));
+            _tr_tally_lit (s, s->window[s->strstart], bflush);
+            s->lookahead--;
+            s->strstart++;
+        }
+        if (bflush) FLUSH_BLOCK(s, 0);
+    }
+    s->insert = 0;
+    if (flush == Z_FINISH) {
+        FLUSH_BLOCK(s, 1);
+        return finish_done;
+    }
+    if (s->last_lit)
+        FLUSH_BLOCK(s, 0);
+    return block_done;
+}
+
+/* ===========================================================================
+ * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.
+ * (It will be regenerated if this run of deflate switches away from Huffman.)
+ */
+local block_state deflate_huff(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    int bflush;             /* set if current block must be flushed */
+
+    for (;;) {
+        /* Make sure that we have a literal to write. */
+        if (s->lookahead == 0) {
+            fill_window(s);
+            if (s->lookahead == 0) {
+                if (flush == Z_NO_FLUSH)
+                    return need_more;
+                break;      /* flush the current block */
+            }
+        }
+
+        /* Output a literal byte */
+        s->match_length = 0;
+        Tracevv((stderr,"%c", s->window[s->strstart]));
+        _tr_tally_lit (s, s->window[s->strstart], bflush);
+        s->lookahead--;
+        s->strstart++;
+        if (bflush) FLUSH_BLOCK(s, 0);
+    }
+    s->insert = 0;
+    if (flush == Z_FINISH) {
+        FLUSH_BLOCK(s, 1);
+        return finish_done;
+    }
+    if (s->last_lit)
+        FLUSH_BLOCK(s, 0);
+    return block_done;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/deflate.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,349 @@
+/* deflate.h -- internal compression state
+ * Copyright (C) 1995-2016 Jean-loup Gailly
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+/* @(#) $Id$ */
+
+#ifndef DEFLATE_H
+#define DEFLATE_H
+
+#include "zutil.h"
+
+/* define NO_GZIP when compiling if you want to disable gzip header and
+   trailer creation by deflate().  NO_GZIP would be used to avoid linking in
+   the crc code when it is not needed.  For shared libraries, gzip encoding
+   should be left enabled. */
+#ifndef NO_GZIP
+#  define GZIP
+#endif
+
+/* ===========================================================================
+ * Internal compression state.
+ */
+
+#define LENGTH_CODES 29
+/* number of length codes, not counting the special END_BLOCK code */
+
+#define LITERALS  256
+/* number of literal bytes 0..255 */
+
+#define L_CODES (LITERALS+1+LENGTH_CODES)
+/* number of Literal or Length codes, including the END_BLOCK code */
+
+#define D_CODES   30
+/* number of distance codes */
+
+#define BL_CODES  19
+/* number of codes used to transfer the bit lengths */
+
+#define HEAP_SIZE (2*L_CODES+1)
+/* maximum heap size */
+
+#define MAX_BITS 15
+/* All codes must not exceed MAX_BITS bits */
+
+#define Buf_size 16
+/* size of bit buffer in bi_buf */
+
+#define INIT_STATE    42    /* zlib header -> BUSY_STATE */
+#ifdef GZIP
+#  define GZIP_STATE  57    /* gzip header -> BUSY_STATE | EXTRA_STATE */
+#endif
+#define EXTRA_STATE   69    /* gzip extra block -> NAME_STATE */
+#define NAME_STATE    73    /* gzip file name -> COMMENT_STATE */
+#define COMMENT_STATE 91    /* gzip comment -> HCRC_STATE */
+#define HCRC_STATE   103    /* gzip header CRC -> BUSY_STATE */
+#define BUSY_STATE   113    /* deflate -> FINISH_STATE */
+#define FINISH_STATE 666    /* stream complete */
+/* Stream status */
+
+
+/* Data structure describing a single value and its code string. */
+typedef struct ct_data_s {
+    union {
+        ush  freq;       /* frequency count */
+        ush  code;       /* bit string */
+    } fc;
+    union {
+        ush  dad;        /* father node in Huffman tree */
+        ush  len;        /* length of bit string */
+    } dl;
+} FAR ct_data;
+
+#define Freq fc.freq
+#define Code fc.code
+#define Dad  dl.dad
+#define Len  dl.len
+
+typedef struct static_tree_desc_s  static_tree_desc;
+
+typedef struct tree_desc_s {
+    ct_data *dyn_tree;           /* the dynamic tree */
+    int     max_code;            /* largest code with non zero frequency */
+    const static_tree_desc *stat_desc;  /* the corresponding static tree */
+} FAR tree_desc;
+
+typedef ush Pos;
+typedef Pos FAR Posf;
+typedef unsigned IPos;
+
+/* A Pos is an index in the character window. We use short instead of int to
+ * save space in the various tables. IPos is used only for parameter passing.
+ */
+
+typedef struct internal_state {
+    z_streamp strm;      /* pointer back to this zlib stream */
+    int   status;        /* as the name implies */
+    Bytef *pending_buf;  /* output still pending */
+    ulg   pending_buf_size; /* size of pending_buf */
+    Bytef *pending_out;  /* next pending byte to output to the stream */
+    ulg   pending;       /* nb of bytes in the pending buffer */
+    int   wrap;          /* bit 0 true for zlib, bit 1 true for gzip */
+    gz_headerp  gzhead;  /* gzip header information to write */
+    ulg   gzindex;       /* where in extra, name, or comment */
+    Byte  method;        /* can only be DEFLATED */
+    int   last_flush;    /* value of flush param for previous deflate call */
+
+                /* used by deflate.c: */
+
+    uInt  w_size;        /* LZ77 window size (32K by default) */
+    uInt  w_bits;        /* log2(w_size)  (8..16) */
+    uInt  w_mask;        /* w_size - 1 */
+
+    Bytef *window;
+    /* Sliding window. Input bytes are read into the second half of the window,
+     * and move to the first half later to keep a dictionary of at least wSize
+     * bytes. With this organization, matches are limited to a distance of
+     * wSize-MAX_MATCH bytes, but this ensures that IO is always
+     * performed with a length multiple of the block size. Also, it limits
+     * the window size to 64K, which is quite useful on MSDOS.
+     * To do: use the user input buffer as sliding window.
+     */
+
+    ulg window_size;
+    /* Actual size of window: 2*wSize, except when the user input buffer
+     * is directly used as sliding window.
+     */
+
+    Posf *prev;
+    /* Link to older string with same hash index. To limit the size of this
+     * array to 64K, this link is maintained only for the last 32K strings.
+     * An index in this array is thus a window index modulo 32K.
+     */
+
+    Posf *head; /* Heads of the hash chains or NIL. */
+
+    uInt  ins_h;          /* hash index of string to be inserted */
+    uInt  hash_size;      /* number of elements in hash table */
+    uInt  hash_bits;      /* log2(hash_size) */
+    uInt  hash_mask;      /* hash_size-1 */
+
+    uInt  hash_shift;
+    /* Number of bits by which ins_h must be shifted at each input
+     * step. It must be such that after MIN_MATCH steps, the oldest
+     * byte no longer takes part in the hash key, that is:
+     *   hash_shift * MIN_MATCH >= hash_bits
+     */
+
+    long block_start;
+    /* Window position at the beginning of the current output block. Gets
+     * negative when the window is moved backwards.
+     */
+
+    uInt match_length;           /* length of best match */
+    IPos prev_match;             /* previous match */
+    int match_available;         /* set if previous match exists */
+    uInt strstart;               /* start of string to insert */
+    uInt match_start;            /* start of matching string */
+    uInt lookahead;              /* number of valid bytes ahead in window */
+
+    uInt prev_length;
+    /* Length of the best match at previous step. Matches not greater than this
+     * are discarded. This is used in the lazy match evaluation.
+     */
+
+    uInt max_chain_length;
+    /* To speed up deflation, hash chains are never searched beyond this
+     * length.  A higher limit improves compression ratio but degrades the
+     * speed.
+     */
+
+    uInt max_lazy_match;
+    /* Attempt to find a better match only when the current match is strictly
+     * smaller than this value. This mechanism is used only for compression
+     * levels >= 4.
+     */
+#   define max_insert_length  max_lazy_match
+    /* Insert new strings in the hash table only if the match length is not
+     * greater than this length. This saves time but degrades compression.
+     * max_insert_length is used only for compression levels <= 3.
+     */
+
+    int level;    /* compression level (1..9) */
+    int strategy; /* favor or force Huffman coding*/
+
+    uInt good_match;
+    /* Use a faster search when the previous match is longer than this */
+
+    int nice_match; /* Stop searching when current match exceeds this */
+
+                /* used by trees.c: */
+    /* Didn't use ct_data typedef below to suppress compiler warning */
+    struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */
+    struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
+    struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */
+
+    struct tree_desc_s l_desc;               /* desc. for literal tree */
+    struct tree_desc_s d_desc;               /* desc. for distance tree */
+    struct tree_desc_s bl_desc;              /* desc. for bit length tree */
+
+    ush bl_count[MAX_BITS+1];
+    /* number of codes at each bit length for an optimal tree */
+
+    int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */
+    int heap_len;               /* number of elements in the heap */
+    int heap_max;               /* element of largest frequency */
+    /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+     * The same heap array is used to build all trees.
+     */
+
+    uch depth[2*L_CODES+1];
+    /* Depth of each subtree used as tie breaker for trees of equal frequency
+     */
+
+    uchf *l_buf;          /* buffer for literals or lengths */
+
+    uInt  lit_bufsize;
+    /* Size of match buffer for literals/lengths.  There are 4 reasons for
+     * limiting lit_bufsize to 64K:
+     *   - frequencies can be kept in 16 bit counters
+     *   - if compression is not successful for the first block, all input
+     *     data is still in the window so we can still emit a stored block even
+     *     when input comes from standard input.  (This can also be done for
+     *     all blocks if lit_bufsize is not greater than 32K.)
+     *   - if compression is not successful for a file smaller than 64K, we can
+     *     even emit a stored file instead of a stored block (saving 5 bytes).
+     *     This is applicable only for zip (not gzip or zlib).
+     *   - creating new Huffman trees less frequently may not provide fast
+     *     adaptation to changes in the input data statistics. (Take for
+     *     example a binary file with poorly compressible code followed by
+     *     a highly compressible string table.) Smaller buffer sizes give
+     *     fast adaptation but have of course the overhead of transmitting
+     *     trees more frequently.
+     *   - I can't count above 4
+     */
+
+    uInt last_lit;      /* running index in l_buf */
+
+    ushf *d_buf;
+    /* Buffer for distances. To simplify the code, d_buf and l_buf have
+     * the same number of elements. To use different lengths, an extra flag
+     * array would be necessary.
+     */
+
+    ulg opt_len;        /* bit length of current block with optimal trees */
+    ulg static_len;     /* bit length of current block with static trees */
+    uInt matches;       /* number of string matches in current block */
+    uInt insert;        /* bytes at end of window left to insert */
+
+#ifdef ZLIB_DEBUG
+    ulg compressed_len; /* total bit length of compressed file mod 2^32 */
+    ulg bits_sent;      /* bit length of compressed data sent mod 2^32 */
+#endif
+
+    ush bi_buf;
+    /* Output buffer. bits are inserted starting at the bottom (least
+     * significant bits).
+     */
+    int bi_valid;
+    /* Number of valid bits in bi_buf.  All bits above the last valid bit
+     * are always zero.
+     */
+
+    ulg high_water;
+    /* High water mark offset in window for initialized bytes -- bytes above
+     * this are set to zero in order to avoid memory check warnings when
+     * longest match routines access bytes past the input.  This is then
+     * updated to the new high water mark.
+     */
+
+} FAR deflate_state;
+
+/* Output a byte on the stream.
+ * IN assertion: there is enough room in pending_buf.
+ */
+#define put_byte(s, c) {s->pending_buf[s->pending++] = (Bytef)(c);}
+
+
+#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1)
+/* Minimum amount of lookahead, except at the end of the input file.
+ * See deflate.c for comments about the MIN_MATCH+1.
+ */
+
+#define MAX_DIST(s)  ((s)->w_size-MIN_LOOKAHEAD)
+/* In order to simplify the code, particularly on 16 bit machines, match
+ * distances are limited to MAX_DIST instead of WSIZE.
+ */
+
+#define WIN_INIT MAX_MATCH
+/* Number of bytes after end of data in window to initialize in order to avoid
+   memory checker errors from longest match routines */
+
+        /* in trees.c */
+void ZLIB_INTERNAL _tr_init OF((deflate_state *s));
+int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc));
+void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf,
+                        ulg stored_len, int last));
+void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s));
+void ZLIB_INTERNAL _tr_align OF((deflate_state *s));
+void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
+                        ulg stored_len, int last));
+
+#define d_code(dist) \
+   ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)])
+/* Mapping from a distance to a distance code. dist is the distance - 1 and
+ * must not have side effects. _dist_code[256] and _dist_code[257] are never
+ * used.
+ */
+
+#ifndef ZLIB_DEBUG
+/* Inline versions of _tr_tally for speed: */
+
+#if defined(GEN_TREES_H) || !defined(STDC)
+  extern uch ZLIB_INTERNAL _length_code[];
+  extern uch ZLIB_INTERNAL _dist_code[];
+#else
+  extern const uch ZLIB_INTERNAL _length_code[];
+  extern const uch ZLIB_INTERNAL _dist_code[];
+#endif
+
+# define _tr_tally_lit(s, c, flush) \
+  { uch cc = (c); \
+    s->d_buf[s->last_lit] = 0; \
+    s->l_buf[s->last_lit++] = cc; \
+    s->dyn_ltree[cc].Freq++; \
+    flush = (s->last_lit == s->lit_bufsize-1); \
+   }
+# define _tr_tally_dist(s, distance, length, flush) \
+  { uch len = (uch)(length); \
+    ush dist = (ush)(distance); \
+    s->d_buf[s->last_lit] = dist; \
+    s->l_buf[s->last_lit++] = len; \
+    dist--; \
+    s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \
+    s->dyn_dtree[d_code(dist)].Freq++; \
+    flush = (s->last_lit == s->lit_bufsize-1); \
+  }
+#else
+# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c)
+# define _tr_tally_dist(s, distance, length, flush) \
+              flush = _tr_tally(s, distance, length)
+#endif
+
+#endif /* DEFLATE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/gzclose.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,25 @@
+/* gzclose.c -- zlib gzclose() function
+ * Copyright (C) 2004, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+/* gzclose() is in a separate file so that it is linked in only if it is used.
+   That way the other gzclose functions can be used instead to avoid linking in
+   unneeded compression or decompression routines. */
+int ZEXPORT gzclose(file)
+    gzFile file;
+{
+#ifndef NO_GZCOMPRESS
+    gz_statep state;
+
+    if (file == NULL)
+        return Z_STREAM_ERROR;
+    state = (gz_statep)file;
+
+    return state->mode == GZ_READ ? gzclose_r(file) : gzclose_w(file);
+#else
+    return gzclose_r(file);
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/gzguts.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,218 @@
+/* gzguts.h -- zlib internal header definitions for gz* operations
+ * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#ifdef _LARGEFILE64_SOURCE
+#  ifndef _LARGEFILE_SOURCE
+#    define _LARGEFILE_SOURCE 1
+#  endif
+#  ifdef _FILE_OFFSET_BITS
+#    undef _FILE_OFFSET_BITS
+#  endif
+#endif
+
+#ifdef HAVE_HIDDEN
+#  define ZLIB_INTERNAL __attribute__((visibility ("hidden")))
+#else
+#  define ZLIB_INTERNAL
+#endif
+
+#include <stdio.h>
+#include "zlib.h"
+#ifdef STDC
+#  include <string.h>
+#  include <stdlib.h>
+#  include <limits.h>
+#endif
+
+#ifndef _POSIX_SOURCE
+#  define _POSIX_SOURCE
+#endif
+#include <fcntl.h>
+
+#ifdef _WIN32
+#  include <stddef.h>
+#endif
+
+#if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32)
+#  include <io.h>
+#endif
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+#  define WIDECHAR
+#endif
+
+#ifdef WINAPI_FAMILY
+#  define open _open
+#  define read _read
+#  define write _write
+#  define close _close
+#endif
+
+#ifdef NO_DEFLATE       /* for compatibility with old definition */
+#  define NO_GZCOMPRESS
+#endif
+
+#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550)
+#  ifndef HAVE_VSNPRINTF
+#    define HAVE_VSNPRINTF
+#  endif
+#endif
+
+#if defined(__CYGWIN__)
+#  ifndef HAVE_VSNPRINTF
+#    define HAVE_VSNPRINTF
+#  endif
+#endif
+
+#if defined(MSDOS) && defined(__BORLANDC__) && (BORLANDC > 0x410)
+#  ifndef HAVE_VSNPRINTF
+#    define HAVE_VSNPRINTF
+#  endif
+#endif
+
+#ifndef HAVE_VSNPRINTF
+#  ifdef MSDOS
+/* vsnprintf may exist on some MS-DOS compilers (DJGPP?),
+   but for now we just assume it doesn't. */
+#    define NO_vsnprintf
+#  endif
+#  ifdef __TURBOC__
+#    define NO_vsnprintf
+#  endif
+#  ifdef WIN32
+/* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */
+#    if !defined(vsnprintf) && !defined(NO_vsnprintf)
+#      if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 )
+#         define vsnprintf _vsnprintf
+#      endif
+#    endif
+#  endif
+#  ifdef __SASC
+#    define NO_vsnprintf
+#  endif
+#  ifdef VMS
+#    define NO_vsnprintf
+#  endif
+#  ifdef __OS400__
+#    define NO_vsnprintf
+#  endif
+#  ifdef __MVS__
+#    define NO_vsnprintf
+#  endif
+#endif
+
+/* unlike snprintf (which is required in C99), _snprintf does not guarantee
+   null termination of the result -- however this is only used in gzlib.c where
+   the result is assured to fit in the space provided */
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#  define snprintf _snprintf
+#endif
+
+#ifndef local
+#  define local static
+#endif
+/* since "static" is used to mean two completely different things in C, we
+   define "local" for the non-static meaning of "static", for readability
+   (compile with -Dlocal if your debugger can't find static symbols) */
+
+/* gz* functions always use library allocation functions */
+#ifndef STDC
+  extern voidp  malloc OF((uInt size));
+  extern void   free   OF((voidpf ptr));
+#endif
+
+/* get errno and strerror definition */
+#if defined UNDER_CE
+#  include <windows.h>
+#  define zstrerror() gz_strwinerror((DWORD)GetLastError())
+#else
+#  ifndef NO_STRERROR
+#    include <errno.h>
+#    define zstrerror() strerror(errno)
+#  else
+#    define zstrerror() "stdio error (consult errno)"
+#  endif
+#endif
+
+/* provide prototypes for these when building zlib without LFS */
+#if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0
+    ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+    ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int));
+    ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile));
+    ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile));
+#endif
+
+/* default memLevel */
+#if MAX_MEM_LEVEL >= 8
+#  define DEF_MEM_LEVEL 8
+#else
+#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
+#endif
+
+/* default i/o buffer size -- double this for output when reading (this and
+   twice this must be able to fit in an unsigned type) */
+#define GZBUFSIZE 8192
+
+/* gzip modes, also provide a little integrity check on the passed structure */
+#define GZ_NONE 0
+#define GZ_READ 7247
+#define GZ_WRITE 31153
+#define GZ_APPEND 1     /* mode set to GZ_WRITE after the file is opened */
+
+/* values for gz_state how */
+#define LOOK 0      /* look for a gzip header */
+#define COPY 1      /* copy input directly */
+#define GZIP 2      /* decompress a gzip stream */
+
+/* internal gzip file state data structure */
+typedef struct {
+        /* exposed contents for gzgetc() macro */
+    struct gzFile_s x;      /* "x" for exposed */
+                            /* x.have: number of bytes available at x.next */
+                            /* x.next: next output data to deliver or write */
+                            /* x.pos: current position in uncompressed data */
+        /* used for both reading and writing */
+    int mode;               /* see gzip modes above */
+    int fd;                 /* file descriptor */
+    char *path;             /* path or fd for error messages */
+    unsigned size;          /* buffer size, zero if not allocated yet */
+    unsigned want;          /* requested buffer size, default is GZBUFSIZE */
+    unsigned char *in;      /* input buffer (double-sized when writing) */
+    unsigned char *out;     /* output buffer (double-sized when reading) */
+    int direct;             /* 0 if processing gzip, 1 if transparent */
+        /* just for reading */
+    int how;                /* 0: get header, 1: copy, 2: decompress */
+    z_off64_t start;        /* where the gzip data started, for rewinding */
+    int eof;                /* true if end of input file reached */
+    int past;               /* true if read requested past end */
+        /* just for writing */
+    int level;              /* compression level */
+    int strategy;           /* compression strategy */
+        /* seek request */
+    z_off64_t skip;         /* amount to skip (already rewound if backwards) */
+    int seek;               /* true if seek request pending */
+        /* error information */
+    int err;                /* error code */
+    char *msg;              /* error message */
+        /* zlib inflate or deflate stream */
+    z_stream strm;          /* stream structure in-place (not a pointer) */
+} gz_state;
+typedef gz_state FAR *gz_statep;
+
+/* shared functions */
+void ZLIB_INTERNAL gz_error OF((gz_statep, int, const char *));
+#if defined UNDER_CE
+char ZLIB_INTERNAL *gz_strwinerror OF((DWORD error));
+#endif
+
+/* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t
+   value -- needed when comparing unsigned to z_off64_t, which is signed
+   (possible z_off64_t types off_t, off64_t, and long are all signed) */
+#ifdef INT_MAX
+#  define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX)
+#else
+unsigned ZLIB_INTERNAL gz_intmax OF((void));
+#  define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax())
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/gzlib.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,637 @@
+/* gzlib.c -- zlib functions common to reading and writing gzip files
+ * Copyright (C) 2004-2017 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+#if defined(_WIN32) && !defined(__BORLANDC__) && !defined(__MINGW32__)
+#  define LSEEK _lseeki64
+#else
+#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0
+#  define LSEEK lseek64
+#else
+#  define LSEEK lseek
+#endif
+#endif
+
+/* Local functions */
+local void gz_reset OF((gz_statep));
+local gzFile gz_open OF((const void *, int, const char *));
+
+#if defined UNDER_CE
+
+/* Map the Windows error number in ERROR to a locale-dependent error message
+   string and return a pointer to it.  Typically, the values for ERROR come
+   from GetLastError.
+
+   The string pointed to shall not be modified by the application, but may be
+   overwritten by a subsequent call to gz_strwinerror
+
+   The gz_strwinerror function does not change the current setting of
+   GetLastError. */
+char ZLIB_INTERNAL *gz_strwinerror (error)
+     DWORD error;
+{
+    static char buf[1024];
+
+    wchar_t *msgbuf;
+    DWORD lasterr = GetLastError();
+    DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
+        | FORMAT_MESSAGE_ALLOCATE_BUFFER,
+        NULL,
+        error,
+        0, /* Default language */
+        (LPVOID)&msgbuf,
+        0,
+        NULL);
+    if (chars != 0) {
+        /* If there is an \r\n appended, zap it.  */
+        if (chars >= 2
+            && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') {
+            chars -= 2;
+            msgbuf[chars] = 0;
+        }
+
+        if (chars > sizeof (buf) - 1) {
+            chars = sizeof (buf) - 1;
+            msgbuf[chars] = 0;
+        }
+
+        wcstombs(buf, msgbuf, chars + 1);
+        LocalFree(msgbuf);
+    }
+    else {
+        sprintf(buf, "unknown win32 error (%ld)", error);
+    }
+
+    SetLastError(lasterr);
+    return buf;
+}
+
+#endif /* UNDER_CE */
+
+/* Reset gzip file state */
+local void gz_reset(state)
+    gz_statep state;
+{
+    state->x.have = 0;              /* no output data available */
+    if (state->mode == GZ_READ) {   /* for reading ... */
+        state->eof = 0;             /* not at end of file */
+        state->past = 0;            /* have not read past end yet */
+        state->how = LOOK;          /* look for gzip header */
+    }
+    state->seek = 0;                /* no seek request pending */
+    gz_error(state, Z_OK, NULL);    /* clear error */
+    state->x.pos = 0;               /* no uncompressed data yet */
+    state->strm.avail_in = 0;       /* no input data yet */
+}
+
+/* Open a gzip file either by name or file descriptor. */
+local gzFile gz_open(path, fd, mode)
+    const void *path;
+    int fd;
+    const char *mode;
+{
+    gz_statep state;
+    z_size_t len;
+    int oflag;
+#ifdef O_CLOEXEC
+    int cloexec = 0;
+#endif
+#ifdef O_EXCL
+    int exclusive = 0;
+#endif
+
+    /* check input */
+    if (path == NULL)
+        return NULL;
+
+    /* allocate gzFile structure to return */
+    state = (gz_statep)malloc(sizeof(gz_state));
+    if (state == NULL)
+        return NULL;
+    state->size = 0;            /* no buffers allocated yet */
+    state->want = GZBUFSIZE;    /* requested buffer size */
+    state->msg = NULL;          /* no error message yet */
+
+    /* interpret mode */
+    state->mode = GZ_NONE;
+    state->level = Z_DEFAULT_COMPRESSION;
+    state->strategy = Z_DEFAULT_STRATEGY;
+    state->direct = 0;
+    while (*mode) {
+        if (*mode >= '0' && *mode <= '9')
+            state->level = *mode - '0';
+        else
+            switch (*mode) {
+            case 'r':
+                state->mode = GZ_READ;
+                break;
+#ifndef NO_GZCOMPRESS
+            case 'w':
+                state->mode = GZ_WRITE;
+                break;
+            case 'a':
+                state->mode = GZ_APPEND;
+                break;
+#endif
+            case '+':       /* can't read and write at the same time */
+                free(state);
+                return NULL;
+            case 'b':       /* ignore -- will request binary anyway */
+                break;
+#ifdef O_CLOEXEC
+            case 'e':
+                cloexec = 1;
+                break;
+#endif
+#ifdef O_EXCL
+            case 'x':
+                exclusive = 1;
+                break;
+#endif
+            case 'f':
+                state->strategy = Z_FILTERED;
+                break;
+            case 'h':
+                state->strategy = Z_HUFFMAN_ONLY;
+                break;
+            case 'R':
+                state->strategy = Z_RLE;
+                break;
+            case 'F':
+                state->strategy = Z_FIXED;
+                break;
+            case 'T':
+                state->direct = 1;
+                break;
+            default:        /* could consider as an error, but just ignore */
+                ;
+            }
+        mode++;
+    }
+
+    /* must provide an "r", "w", or "a" */
+    if (state->mode == GZ_NONE) {
+        free(state);
+        return NULL;
+    }
+
+    /* can't force transparent read */
+    if (state->mode == GZ_READ) {
+        if (state->direct) {
+            free(state);
+            return NULL;
+        }
+        state->direct = 1;      /* for empty file */
+    }
+
+    /* save the path name for error messages */
+#ifdef WIDECHAR
+    if (fd == -2) {
+        len = wcstombs(NULL, path, 0);
+        if (len == (z_size_t)-1)
+            len = 0;
+    }
+    else
+#endif
+        len = strlen((const char *)path);
+    state->path = (char *)malloc(len + 1);
+    if (state->path == NULL) {
+        free(state);
+        return NULL;
+    }
+#ifdef WIDECHAR
+    if (fd == -2)
+        if (len)
+            wcstombs(state->path, path, len + 1);
+        else
+            *(state->path) = 0;
+    else
+#endif
+#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
+        (void)snprintf(state->path, len + 1, "%s", (const char *)path);
+#else
+        strcpy(state->path, path);
+#endif
+
+    /* compute the flags for open() */
+    oflag =
+#ifdef O_LARGEFILE
+        O_LARGEFILE |
+#endif
+#ifdef O_BINARY
+        O_BINARY |
+#endif
+#ifdef O_CLOEXEC
+        (cloexec ? O_CLOEXEC : 0) |
+#endif
+        (state->mode == GZ_READ ?
+         O_RDONLY :
+         (O_WRONLY | O_CREAT |
+#ifdef O_EXCL
+          (exclusive ? O_EXCL : 0) |
+#endif
+          (state->mode == GZ_WRITE ?
+           O_TRUNC :
+           O_APPEND)));
+
+    /* open the file with the appropriate flags (or just use fd) */
+    state->fd = fd > -1 ? fd : (
+#ifdef WIDECHAR
+        fd == -2 ? _wopen(path, oflag, 0666) :
+#endif
+        open((const char *)path, oflag, 0666));
+    if (state->fd == -1) {
+        free(state->path);
+        free(state);
+        return NULL;
+    }
+    if (state->mode == GZ_APPEND) {
+        LSEEK(state->fd, 0, SEEK_END);  /* so gzoffset() is correct */
+        state->mode = GZ_WRITE;         /* simplify later checks */
+    }
+
+    /* save the current position for rewinding (only if reading) */
+    if (state->mode == GZ_READ) {
+        state->start = LSEEK(state->fd, 0, SEEK_CUR);
+        if (state->start == -1) state->start = 0;
+    }
+
+    /* initialize stream */
+    gz_reset(state);
+
+    /* return stream */
+    return (gzFile)state;
+}
+
+/* -- see zlib.h -- */
+gzFile ZEXPORT gzopen(path, mode)
+    const char *path;
+    const char *mode;
+{
+    return gz_open(path, -1, mode);
+}
+
+/* -- see zlib.h -- */
+gzFile ZEXPORT gzopen64(path, mode)
+    const char *path;
+    const char *mode;
+{
+    return gz_open(path, -1, mode);
+}
+
+/* -- see zlib.h -- */
+gzFile ZEXPORT gzdopen(fd, mode)
+    int fd;
+    const char *mode;
+{
+    char *path;         /* identifier for error messages */
+    gzFile gz;
+
+    if (fd == -1 || (path = (char *)malloc(7 + 3 * sizeof(int))) == NULL)
+        return NULL;
+#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
+    (void)snprintf(path, 7 + 3 * sizeof(int), "<fd:%d>", fd);
+#else
+    sprintf(path, "<fd:%d>", fd);   /* for debugging */
+#endif
+    gz = gz_open(path, fd, mode);
+    free(path);
+    return gz;
+}
+
+/* -- see zlib.h -- */
+#ifdef WIDECHAR
+gzFile ZEXPORT gzopen_w(path, mode)
+    const wchar_t *path;
+    const char *mode;
+{
+    return gz_open(path, -2, mode);
+}
+#endif
+
+/* -- see zlib.h -- */
+int ZEXPORT gzbuffer(file, size)
+    gzFile file;
+    unsigned size;
+{
+    gz_statep state;
+
+    /* get internal structure and check integrity */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+    if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+        return -1;
+
+    /* make sure we haven't already allocated memory */
+    if (state->size != 0)
+        return -1;
+
+    /* check and set requested size */
+    if ((size << 1) < size)
+        return -1;              /* need to be able to double it */
+    if (size < 2)
+        size = 2;               /* need two bytes to check magic header */
+    state->want = size;
+    return 0;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzrewind(file)
+    gzFile file;
+{
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+
+    /* check that we're reading and that there's no error */
+    if (state->mode != GZ_READ ||
+            (state->err != Z_OK && state->err != Z_BUF_ERROR))
+        return -1;
+
+    /* back up and start over */
+    if (LSEEK(state->fd, state->start, SEEK_SET) == -1)
+        return -1;
+    gz_reset(state);
+    return 0;
+}
+
+/* -- see zlib.h -- */
+z_off64_t ZEXPORT gzseek64(file, offset, whence)
+    gzFile file;
+    z_off64_t offset;
+    int whence;
+{
+    unsigned n;
+    z_off64_t ret;
+    gz_statep state;
+
+    /* get internal structure and check integrity */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+    if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+        return -1;
+
+    /* check that there's no error */
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR)
+        return -1;
+
+    /* can only seek from start or relative to current position */
+    if (whence != SEEK_SET && whence != SEEK_CUR)
+        return -1;
+
+    /* normalize offset to a SEEK_CUR specification */
+    if (whence == SEEK_SET)
+        offset -= state->x.pos;
+    else if (state->seek)
+        offset += state->skip;
+    state->seek = 0;
+
+    /* if within raw area while reading, just go there */
+    if (state->mode == GZ_READ && state->how == COPY &&
+            state->x.pos + offset >= 0) {
+        ret = LSEEK(state->fd, offset - state->x.have, SEEK_CUR);
+        if (ret == -1)
+            return -1;
+        state->x.have = 0;
+        state->eof = 0;
+        state->past = 0;
+        state->seek = 0;
+        gz_error(state, Z_OK, NULL);
+        state->strm.avail_in = 0;
+        state->x.pos += offset;
+        return state->x.pos;
+    }
+
+    /* calculate skip amount, rewinding if needed for back seek when reading */
+    if (offset < 0) {
+        if (state->mode != GZ_READ)         /* writing -- can't go backwards */
+            return -1;
+        offset += state->x.pos;
+        if (offset < 0)                     /* before start of file! */
+            return -1;
+        if (gzrewind(file) == -1)           /* rewind, then skip to offset */
+            return -1;
+    }
+
+    /* if reading, skip what's in output buffer (one less gzgetc() check) */
+    if (state->mode == GZ_READ) {
+        n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > offset ?
+            (unsigned)offset : state->x.have;
+        state->x.have -= n;
+        state->x.next += n;
+        state->x.pos += n;
+        offset -= n;
+    }
+
+    /* request skip (if not zero) */
+    if (offset) {
+        state->seek = 1;
+        state->skip = offset;
+    }
+    return state->x.pos + offset;
+}
+
+/* -- see zlib.h -- */
+z_off_t ZEXPORT gzseek(file, offset, whence)
+    gzFile file;
+    z_off_t offset;
+    int whence;
+{
+    z_off64_t ret;
+
+    ret = gzseek64(file, (z_off64_t)offset, whence);
+    return ret == (z_off_t)ret ? (z_off_t)ret : -1;
+}
+
+/* -- see zlib.h -- */
+z_off64_t ZEXPORT gztell64(file)
+    gzFile file;
+{
+    gz_statep state;
+
+    /* get internal structure and check integrity */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+    if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+        return -1;
+
+    /* return position */
+    return state->x.pos + (state->seek ? state->skip : 0);
+}
+
+/* -- see zlib.h -- */
+z_off_t ZEXPORT gztell(file)
+    gzFile file;
+{
+    z_off64_t ret;
+
+    ret = gztell64(file);
+    return ret == (z_off_t)ret ? (z_off_t)ret : -1;
+}
+
+/* -- see zlib.h -- */
+z_off64_t ZEXPORT gzoffset64(file)
+    gzFile file;
+{
+    z_off64_t offset;
+    gz_statep state;
+
+    /* get internal structure and check integrity */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+    if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+        return -1;
+
+    /* compute and return effective offset in file */
+    offset = LSEEK(state->fd, 0, SEEK_CUR);
+    if (offset == -1)
+        return -1;
+    if (state->mode == GZ_READ)             /* reading */
+        offset -= state->strm.avail_in;     /* don't count buffered input */
+    return offset;
+}
+
+/* -- see zlib.h -- */
+z_off_t ZEXPORT gzoffset(file)
+    gzFile file;
+{
+    z_off64_t ret;
+
+    ret = gzoffset64(file);
+    return ret == (z_off_t)ret ? (z_off_t)ret : -1;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzeof(file)
+    gzFile file;
+{
+    gz_statep state;
+
+    /* get internal structure and check integrity */
+    if (file == NULL)
+        return 0;
+    state = (gz_statep)file;
+    if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+        return 0;
+
+    /* return end-of-file state */
+    return state->mode == GZ_READ ? state->past : 0;
+}
+
+/* -- see zlib.h -- */
+const char * ZEXPORT gzerror(file, errnum)
+    gzFile file;
+    int *errnum;
+{
+    gz_statep state;
+
+    /* get internal structure and check integrity */
+    if (file == NULL)
+        return NULL;
+    state = (gz_statep)file;
+    if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+        return NULL;
+
+    /* return error information */
+    if (errnum != NULL)
+        *errnum = state->err;
+    return state->err == Z_MEM_ERROR ? "out of memory" :
+                                       (state->msg == NULL ? "" : state->msg);
+}
+
+/* -- see zlib.h -- */
+void ZEXPORT gzclearerr(file)
+    gzFile file;
+{
+    gz_statep state;
+
+    /* get internal structure and check integrity */
+    if (file == NULL)
+        return;
+    state = (gz_statep)file;
+    if (state->mode != GZ_READ && state->mode != GZ_WRITE)
+        return;
+
+    /* clear error and end-of-file */
+    if (state->mode == GZ_READ) {
+        state->eof = 0;
+        state->past = 0;
+    }
+    gz_error(state, Z_OK, NULL);
+}
+
+/* Create an error message in allocated memory and set state->err and
+   state->msg accordingly.  Free any previous error message already there.  Do
+   not try to free or allocate space if the error is Z_MEM_ERROR (out of
+   memory).  Simply save the error message as a static string.  If there is an
+   allocation failure constructing the error message, then convert the error to
+   out of memory. */
+void ZLIB_INTERNAL gz_error(state, err, msg)
+    gz_statep state;
+    int err;
+    const char *msg;
+{
+    /* free previously allocated message and clear */
+    if (state->msg != NULL) {
+        if (state->err != Z_MEM_ERROR)
+            free(state->msg);
+        state->msg = NULL;
+    }
+
+    /* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */
+    if (err != Z_OK && err != Z_BUF_ERROR)
+        state->x.have = 0;
+
+    /* set error code, and if no message, then done */
+    state->err = err;
+    if (msg == NULL)
+        return;
+
+    /* for an out of memory error, return literal string when requested */
+    if (err == Z_MEM_ERROR)
+        return;
+
+    /* construct error message with path */
+    if ((state->msg = (char *)malloc(strlen(state->path) + strlen(msg) + 3)) ==
+            NULL) {
+        state->err = Z_MEM_ERROR;
+        return;
+    }
+#if !defined(NO_snprintf) && !defined(NO_vsnprintf)
+    (void)snprintf(state->msg, strlen(state->path) + strlen(msg) + 3,
+                   "%s%s%s", state->path, ": ", msg);
+#else
+    strcpy(state->msg, state->path);
+    strcat(state->msg, ": ");
+    strcat(state->msg, msg);
+#endif
+}
+
+#ifndef INT_MAX
+/* portably return maximum value for an int (when limits.h presumed not
+   available) -- we need to do this to cover cases where 2's complement not
+   used, since C standard permits 1's complement and sign-bit representations,
+   otherwise we could just use ((unsigned)-1) >> 1 */
+unsigned ZLIB_INTERNAL gz_intmax()
+{
+    unsigned p, q;
+
+    p = 1;
+    do {
+        q = p;
+        p <<= 1;
+        p++;
+    } while (p > q);
+    return q >> 1;
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/gzread.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,654 @@
+/* gzread.c -- zlib functions for reading gzip files
+ * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+/* Local functions */
+local int gz_load OF((gz_statep, unsigned char *, unsigned, unsigned *));
+local int gz_avail OF((gz_statep));
+local int gz_look OF((gz_statep));
+local int gz_decomp OF((gz_statep));
+local int gz_fetch OF((gz_statep));
+local int gz_skip OF((gz_statep, z_off64_t));
+local z_size_t gz_read OF((gz_statep, voidp, z_size_t));
+
+/* Use read() to load a buffer -- return -1 on error, otherwise 0.  Read from
+   state->fd, and update state->eof, state->err, and state->msg as appropriate.
+   This function needs to loop on read(), since read() is not guaranteed to
+   read the number of bytes requested, depending on the type of descriptor. */
+local int gz_load(state, buf, len, have)
+    gz_statep state;
+    unsigned char *buf;
+    unsigned len;
+    unsigned *have;
+{
+    int ret;
+    unsigned get, max = ((unsigned)-1 >> 2) + 1;
+
+    *have = 0;
+    do {
+        get = len - *have;
+        if (get > max)
+            get = max;
+        ret = read(state->fd, buf + *have, get);
+        if (ret <= 0)
+            break;
+        *have += (unsigned)ret;
+    } while (*have < len);
+    if (ret < 0) {
+        gz_error(state, Z_ERRNO, zstrerror());
+        return -1;
+    }
+    if (ret == 0)
+        state->eof = 1;
+    return 0;
+}
+
+/* Load up input buffer and set eof flag if last data loaded -- return -1 on
+   error, 0 otherwise.  Note that the eof flag is set when the end of the input
+   file is reached, even though there may be unused data in the buffer.  Once
+   that data has been used, no more attempts will be made to read the file.
+   If strm->avail_in != 0, then the current data is moved to the beginning of
+   the input buffer, and then the remainder of the buffer is loaded with the
+   available data from the input file. */
+local int gz_avail(state)
+    gz_statep state;
+{
+    unsigned got;
+    z_streamp strm = &(state->strm);
+
+    if (state->err != Z_OK && state->err != Z_BUF_ERROR)
+        return -1;
+    if (state->eof == 0) {
+        if (strm->avail_in) {       /* copy what's there to the start */
+            unsigned char *p = state->in;
+            unsigned const char *q = strm->next_in;
+            unsigned n = strm->avail_in;
+            do {
+                *p++ = *q++;
+            } while (--n);
+        }
+        if (gz_load(state, state->in + strm->avail_in,
+                    state->size - strm->avail_in, &got) == -1)
+            return -1;
+        strm->avail_in += got;
+        strm->next_in = state->in;
+    }
+    return 0;
+}
+
+/* Look for gzip header, set up for inflate or copy.  state->x.have must be 0.
+   If this is the first time in, allocate required memory.  state->how will be
+   left unchanged if there is no more input data available, will be set to COPY
+   if there is no gzip header and direct copying will be performed, or it will
+   be set to GZIP for decompression.  If direct copying, then leftover input
+   data from the input buffer will be copied to the output buffer.  In that
+   case, all further file reads will be directly to either the output buffer or
+   a user buffer.  If decompressing, the inflate state will be initialized.
+   gz_look() will return 0 on success or -1 on failure. */
+local int gz_look(state)
+    gz_statep state;
+{
+    z_streamp strm = &(state->strm);
+
+    /* allocate read buffers and inflate memory */
+    if (state->size == 0) {
+        /* allocate buffers */
+        state->in = (unsigned char *)malloc(state->want);
+        state->out = (unsigned char *)malloc(state->want << 1);
+        if (state->in == NULL || state->out == NULL) {
+            free(state->out);
+            free(state->in);
+            gz_error(state, Z_MEM_ERROR, "out of memory");
+            return -1;
+        }
+        state->size = state->want;
+
+        /* allocate inflate memory */
+        state->strm.zalloc = Z_NULL;
+        state->strm.zfree = Z_NULL;
+        state->strm.opaque = Z_NULL;
+        state->strm.avail_in = 0;
+        state->strm.next_in = Z_NULL;
+        if (inflateInit2(&(state->strm), 15 + 16) != Z_OK) {    /* gunzip */
+            free(state->out);
+            free(state->in);
+            state->size = 0;
+            gz_error(state, Z_MEM_ERROR, "out of memory");
+            return -1;
+        }
+    }
+
+    /* get at least the magic bytes in the input buffer */
+    if (strm->avail_in < 2) {
+        if (gz_avail(state) == -1)
+            return -1;
+        if (strm->avail_in == 0)
+            return 0;
+    }
+
+    /* look for gzip magic bytes -- if there, do gzip decoding (note: there is
+       a logical dilemma here when considering the case of a partially written
+       gzip file, to wit, if a single 31 byte is written, then we cannot tell
+       whether this is a single-byte file, or just a partially written gzip
+       file -- for here we assume that if a gzip file is being written, then
+       the header will be written in a single operation, so that reading a
+       single byte is sufficient indication that it is not a gzip file) */
+    if (strm->avail_in > 1 &&
+            strm->next_in[0] == 31 && strm->next_in[1] == 139) {
+        inflateReset(strm);
+        state->how = GZIP;
+        state->direct = 0;
+        return 0;
+    }
+
+    /* no gzip header -- if we were decoding gzip before, then this is trailing
+       garbage.  Ignore the trailing garbage and finish. */
+    if (state->direct == 0) {
+        strm->avail_in = 0;
+        state->eof = 1;
+        state->x.have = 0;
+        return 0;
+    }
+
+    /* doing raw i/o, copy any leftover input to output -- this assumes that
+       the output buffer is larger than the input buffer, which also assures
+       space for gzungetc() */
+    state->x.next = state->out;
+    if (strm->avail_in) {
+        memcpy(state->x.next, strm->next_in, strm->avail_in);
+        state->x.have = strm->avail_in;
+        strm->avail_in = 0;
+    }
+    state->how = COPY;
+    state->direct = 1;
+    return 0;
+}
+
+/* Decompress from input to the provided next_out and avail_out in the state.
+   On return, state->x.have and state->x.next point to the just decompressed
+   data.  If the gzip stream completes, state->how is reset to LOOK to look for
+   the next gzip stream or raw data, once state->x.have is depleted.  Returns 0
+   on success, -1 on failure. */
+local int gz_decomp(state)
+    gz_statep state;
+{
+    int ret = Z_OK;
+    unsigned had;
+    z_streamp strm = &(state->strm);
+
+    /* fill output buffer up to end of deflate stream */
+    had = strm->avail_out;
+    do {
+        /* get more input for inflate() */
+        if (strm->avail_in == 0 && gz_avail(state) == -1)
+            return -1;
+        if (strm->avail_in == 0) {
+            gz_error(state, Z_BUF_ERROR, "unexpected end of file");
+            break;
+        }
+
+        /* decompress and handle errors */
+        ret = inflate(strm, Z_NO_FLUSH);
+        if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) {
+            gz_error(state, Z_STREAM_ERROR,
+                     "internal error: inflate stream corrupt");
+            return -1;
+        }
+        if (ret == Z_MEM_ERROR) {
+            gz_error(state, Z_MEM_ERROR, "out of memory");
+            return -1;
+        }
+        if (ret == Z_DATA_ERROR) {              /* deflate stream invalid */
+            gz_error(state, Z_DATA_ERROR,
+                     strm->msg == NULL ? "compressed data error" : strm->msg);
+            return -1;
+        }
+    } while (strm->avail_out && ret != Z_STREAM_END);
+
+    /* update available output */
+    state->x.have = had - strm->avail_out;
+    state->x.next = strm->next_out - state->x.have;
+
+    /* if the gzip stream completed successfully, look for another */
+    if (ret == Z_STREAM_END)
+        state->how = LOOK;
+
+    /* good decompression */
+    return 0;
+}
+
+/* Fetch data and put it in the output buffer.  Assumes state->x.have is 0.
+   Data is either copied from the input file or decompressed from the input
+   file depending on state->how.  If state->how is LOOK, then a gzip header is
+   looked for to determine whether to copy or decompress.  Returns -1 on error,
+   otherwise 0.  gz_fetch() will leave state->how as COPY or GZIP unless the
+   end of the input file has been reached and all data has been processed.  */
+local int gz_fetch(state)
+    gz_statep state;
+{
+    z_streamp strm = &(state->strm);
+
+    do {
+        switch(state->how) {
+        case LOOK:      /* -> LOOK, COPY (only if never GZIP), or GZIP */
+            if (gz_look(state) == -1)
+                return -1;
+            if (state->how == LOOK)
+                return 0;
+            break;
+        case COPY:      /* -> COPY */
+            if (gz_load(state, state->out, state->size << 1, &(state->x.have))
+                    == -1)
+                return -1;
+            state->x.next = state->out;
+            return 0;
+        case GZIP:      /* -> GZIP or LOOK (if end of gzip stream) */
+            strm->avail_out = state->size << 1;
+            strm->next_out = state->out;
+            if (gz_decomp(state) == -1)
+                return -1;
+        }
+    } while (state->x.have == 0 && (!state->eof || strm->avail_in));
+    return 0;
+}
+
+/* Skip len uncompressed bytes of output.  Return -1 on error, 0 on success. */
+local int gz_skip(state, len)
+    gz_statep state;
+    z_off64_t len;
+{
+    unsigned n;
+
+    /* skip over len bytes or reach end-of-file, whichever comes first */
+    while (len)
+        /* skip over whatever is in output buffer */
+        if (state->x.have) {
+            n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > len ?
+                (unsigned)len : state->x.have;
+            state->x.have -= n;
+            state->x.next += n;
+            state->x.pos += n;
+            len -= n;
+        }
+
+        /* output buffer empty -- return if we're at the end of the input */
+        else if (state->eof && state->strm.avail_in == 0)
+            break;
+
+        /* need more data to skip -- load up output buffer */
+        else {
+            /* get more output, looking for header if required */
+            if (gz_fetch(state) == -1)
+                return -1;
+        }
+    return 0;
+}
+
+/* Read len bytes into buf from file, or less than len up to the end of the
+   input.  Return the number of bytes read.  If zero is returned, either the
+   end of file was reached, or there was an error.  state->err must be
+   consulted in that case to determine which. */
+local z_size_t gz_read(state, buf, len)
+    gz_statep state;
+    voidp buf;
+    z_size_t len;
+{
+    z_size_t got;
+    unsigned n;
+
+    /* if len is zero, avoid unnecessary operations */
+    if (len == 0)
+        return 0;
+
+    /* process a skip request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_skip(state, state->skip) == -1)
+            return 0;
+    }
+
+    /* get len bytes to buf, or less than len if at the end */
+    got = 0;
+    do {
+        /* set n to the maximum amount of len that fits in an unsigned int */
+        n = -1;
+        if (n > len)
+            n = len;
+
+        /* first just try copying data from the output buffer */
+        if (state->x.have) {
+            if (state->x.have < n)
+                n = state->x.have;
+            memcpy(buf, state->x.next, n);
+            state->x.next += n;
+            state->x.have -= n;
+        }
+
+        /* output buffer empty -- return if we're at the end of the input */
+        else if (state->eof && state->strm.avail_in == 0) {
+            state->past = 1;        /* tried to read past end */
+            break;
+        }
+
+        /* need output data -- for small len or new stream load up our output
+           buffer */
+        else if (state->how == LOOK || n < (state->size << 1)) {
+            /* get more output, looking for header if required */
+            if (gz_fetch(state) == -1)
+                return 0;
+            continue;       /* no progress yet -- go back to copy above */
+            /* the copy above assures that we will leave with space in the
+               output buffer, allowing at least one gzungetc() to succeed */
+        }
+
+        /* large len -- read directly into user buffer */
+        else if (state->how == COPY) {      /* read directly */
+            if (gz_load(state, (unsigned char *)buf, n, &n) == -1)
+                return 0;
+        }
+
+        /* large len -- decompress directly into user buffer */
+        else {  /* state->how == GZIP */
+            state->strm.avail_out = n;
+            state->strm.next_out = (unsigned char *)buf;
+            if (gz_decomp(state) == -1)
+                return 0;
+            n = state->x.have;
+            state->x.have = 0;
+        }
+
+        /* update progress */
+        len -= n;
+        buf = (char *)buf + n;
+        got += n;
+        state->x.pos += n;
+    } while (len);
+
+    /* return number of bytes read into user buffer */
+    return got;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzread(file, buf, len)
+    gzFile file;
+    voidp buf;
+    unsigned len;
+{
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+
+    /* check that we're reading and that there's no (serious) error */
+    if (state->mode != GZ_READ ||
+            (state->err != Z_OK && state->err != Z_BUF_ERROR))
+        return -1;
+
+    /* since an int is returned, make sure len fits in one, otherwise return
+       with an error (this avoids a flaw in the interface) */
+    if ((int)len < 0) {
+        gz_error(state, Z_STREAM_ERROR, "request does not fit in an int");
+        return -1;
+    }
+
+    /* read len or fewer bytes to buf */
+    len = gz_read(state, buf, len);
+
+    /* check for an error */
+    if (len == 0 && state->err != Z_OK && state->err != Z_BUF_ERROR)
+        return -1;
+
+    /* return the number of bytes read (this is assured to fit in an int) */
+    return (int)len;
+}
+
+/* -- see zlib.h -- */
+z_size_t ZEXPORT gzfread(buf, size, nitems, file)
+    voidp buf;
+    z_size_t size;
+    z_size_t nitems;
+    gzFile file;
+{
+    z_size_t len;
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return 0;
+    state = (gz_statep)file;
+
+    /* check that we're reading and that there's no (serious) error */
+    if (state->mode != GZ_READ ||
+            (state->err != Z_OK && state->err != Z_BUF_ERROR))
+        return 0;
+
+    /* compute bytes to read -- error on overflow */
+    len = nitems * size;
+    if (size && len / size != nitems) {
+        gz_error(state, Z_STREAM_ERROR, "request does not fit in a size_t");
+        return 0;
+    }
+
+    /* read len or fewer bytes to buf, return the number of full items read */
+    return len ? gz_read(state, buf, len) / size : 0;
+}
+
+/* -- see zlib.h -- */
+#ifdef Z_PREFIX_SET
+#  undef z_gzgetc
+#else
+#  undef gzgetc
+#endif
+int ZEXPORT gzgetc(file)
+    gzFile file;
+{
+    int ret;
+    unsigned char buf[1];
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+
+    /* check that we're reading and that there's no (serious) error */
+    if (state->mode != GZ_READ ||
+        (state->err != Z_OK && state->err != Z_BUF_ERROR))
+        return -1;
+
+    /* try output buffer (no need to check for skip request) */
+    if (state->x.have) {
+        state->x.have--;
+        state->x.pos++;
+        return *(state->x.next)++;
+    }
+
+    /* nothing there -- try gz_read() */
+    ret = gz_read(state, buf, 1);
+    return ret < 1 ? -1 : buf[0];
+}
+
+int ZEXPORT gzgetc_(file)
+gzFile file;
+{
+    return gzgetc(file);
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzungetc(c, file)
+    int c;
+    gzFile file;
+{
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+
+    /* check that we're reading and that there's no (serious) error */
+    if (state->mode != GZ_READ ||
+        (state->err != Z_OK && state->err != Z_BUF_ERROR))
+        return -1;
+
+    /* process a skip request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_skip(state, state->skip) == -1)
+            return -1;
+    }
+
+    /* can't push EOF */
+    if (c < 0)
+        return -1;
+
+    /* if output buffer empty, put byte at end (allows more pushing) */
+    if (state->x.have == 0) {
+        state->x.have = 1;
+        state->x.next = state->out + (state->size << 1) - 1;
+        state->x.next[0] = (unsigned char)c;
+        state->x.pos--;
+        state->past = 0;
+        return c;
+    }
+
+    /* if no room, give up (must have already done a gzungetc()) */
+    if (state->x.have == (state->size << 1)) {
+        gz_error(state, Z_DATA_ERROR, "out of room to push characters");
+        return -1;
+    }
+
+    /* slide output data if needed and insert byte before existing data */
+    if (state->x.next == state->out) {
+        unsigned char *src = state->out + state->x.have;
+        unsigned char *dest = state->out + (state->size << 1);
+        while (src > state->out)
+            *--dest = *--src;
+        state->x.next = dest;
+    }
+    state->x.have++;
+    state->x.next--;
+    state->x.next[0] = (unsigned char)c;
+    state->x.pos--;
+    state->past = 0;
+    return c;
+}
+
+/* -- see zlib.h -- */
+char * ZEXPORT gzgets(file, buf, len)
+    gzFile file;
+    char *buf;
+    int len;
+{
+    unsigned left, n;
+    char *str;
+    unsigned char *eol;
+    gz_statep state;
+
+    /* check parameters and get internal structure */
+    if (file == NULL || buf == NULL || len < 1)
+        return NULL;
+    state = (gz_statep)file;
+
+    /* check that we're reading and that there's no (serious) error */
+    if (state->mode != GZ_READ ||
+        (state->err != Z_OK && state->err != Z_BUF_ERROR))
+        return NULL;
+
+    /* process a skip request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_skip(state, state->skip) == -1)
+            return NULL;
+    }
+
+    /* copy output bytes up to new line or len - 1, whichever comes first --
+       append a terminating zero to the string (we don't check for a zero in
+       the contents, let the user worry about that) */
+    str = buf;
+    left = (unsigned)len - 1;
+    if (left) do {
+        /* assure that something is in the output buffer */
+        if (state->x.have == 0 && gz_fetch(state) == -1)
+            return NULL;                /* error */
+        if (state->x.have == 0) {       /* end of file */
+            state->past = 1;            /* read past end */
+            break;                      /* return what we have */
+        }
+
+        /* look for end-of-line in current output buffer */
+        n = state->x.have > left ? left : state->x.have;
+        eol = (unsigned char *)memchr(state->x.next, '\n', n);
+        if (eol != NULL)
+            n = (unsigned)(eol - state->x.next) + 1;
+
+        /* copy through end-of-line, or remainder if not found */
+        memcpy(buf, state->x.next, n);
+        state->x.have -= n;
+        state->x.next += n;
+        state->x.pos += n;
+        left -= n;
+        buf += n;
+    } while (left && eol == NULL);
+
+    /* return terminated string, or if nothing, end of file */
+    if (buf == str)
+        return NULL;
+    buf[0] = 0;
+    return str;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzdirect(file)
+    gzFile file;
+{
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return 0;
+    state = (gz_statep)file;
+
+    /* if the state is not known, but we can find out, then do so (this is
+       mainly for right after a gzopen() or gzdopen()) */
+    if (state->mode == GZ_READ && state->how == LOOK && state->x.have == 0)
+        (void)gz_look(state);
+
+    /* return 1 if transparent, 0 if processing a gzip stream */
+    return state->direct;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzclose_r(file)
+    gzFile file;
+{
+    int ret, err;
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return Z_STREAM_ERROR;
+    state = (gz_statep)file;
+
+    /* check that we're reading */
+    if (state->mode != GZ_READ)
+        return Z_STREAM_ERROR;
+
+    /* free memory and close file */
+    if (state->size) {
+        inflateEnd(&(state->strm));
+        free(state->out);
+        free(state->in);
+    }
+    err = state->err == Z_BUF_ERROR ? Z_BUF_ERROR : Z_OK;
+    gz_error(state, Z_OK, NULL);
+    free(state->path);
+    ret = close(state->fd);
+    free(state);
+    return ret ? Z_ERRNO : err;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/gzwrite.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,665 @@
+/* gzwrite.c -- zlib functions for writing gzip files
+ * Copyright (C) 2004-2017 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "gzguts.h"
+
+/* Local functions */
+local int gz_init OF((gz_statep));
+local int gz_comp OF((gz_statep, int));
+local int gz_zero OF((gz_statep, z_off64_t));
+local z_size_t gz_write OF((gz_statep, voidpc, z_size_t));
+
+/* Initialize state for writing a gzip file.  Mark initialization by setting
+   state->size to non-zero.  Return -1 on a memory allocation failure, or 0 on
+   success. */
+local int gz_init(state)
+    gz_statep state;
+{
+    int ret;
+    z_streamp strm = &(state->strm);
+
+    /* allocate input buffer (double size for gzprintf) */
+    state->in = (unsigned char *)malloc(state->want << 1);
+    if (state->in == NULL) {
+        gz_error(state, Z_MEM_ERROR, "out of memory");
+        return -1;
+    }
+
+    /* only need output buffer and deflate state if compressing */
+    if (!state->direct) {
+        /* allocate output buffer */
+        state->out = (unsigned char *)malloc(state->want);
+        if (state->out == NULL) {
+            free(state->in);
+            gz_error(state, Z_MEM_ERROR, "out of memory");
+            return -1;
+        }
+
+        /* allocate deflate memory, set up for gzip compression */
+        strm->zalloc = Z_NULL;
+        strm->zfree = Z_NULL;
+        strm->opaque = Z_NULL;
+        ret = deflateInit2(strm, state->level, Z_DEFLATED,
+                           MAX_WBITS + 16, DEF_MEM_LEVEL, state->strategy);
+        if (ret != Z_OK) {
+            free(state->out);
+            free(state->in);
+            gz_error(state, Z_MEM_ERROR, "out of memory");
+            return -1;
+        }
+        strm->next_in = NULL;
+    }
+
+    /* mark state as initialized */
+    state->size = state->want;
+
+    /* initialize write buffer if compressing */
+    if (!state->direct) {
+        strm->avail_out = state->size;
+        strm->next_out = state->out;
+        state->x.next = strm->next_out;
+    }
+    return 0;
+}
+
+/* Compress whatever is at avail_in and next_in and write to the output file.
+   Return -1 if there is an error writing to the output file or if gz_init()
+   fails to allocate memory, otherwise 0.  flush is assumed to be a valid
+   deflate() flush value.  If flush is Z_FINISH, then the deflate() state is
+   reset to start a new gzip stream.  If gz->direct is true, then simply write
+   to the output file without compressing, and ignore flush. */
+local int gz_comp(state, flush)
+    gz_statep state;
+    int flush;
+{
+    int ret, writ;
+    unsigned have, put, max = ((unsigned)-1 >> 2) + 1;
+    z_streamp strm = &(state->strm);
+
+    /* allocate memory if this is the first time through */
+    if (state->size == 0 && gz_init(state) == -1)
+        return -1;
+
+    /* write directly if requested */
+    if (state->direct) {
+        while (strm->avail_in) {
+            put = strm->avail_in > max ? max : strm->avail_in;
+            writ = write(state->fd, strm->next_in, put);
+            if (writ < 0) {
+                gz_error(state, Z_ERRNO, zstrerror());
+                return -1;
+            }
+            strm->avail_in -= (unsigned)writ;
+            strm->next_in += writ;
+        }
+        return 0;
+    }
+
+    /* run deflate() on provided input until it produces no more output */
+    ret = Z_OK;
+    do {
+        /* write out current buffer contents if full, or if flushing, but if
+           doing Z_FINISH then don't write until we get to Z_STREAM_END */
+        if (strm->avail_out == 0 || (flush != Z_NO_FLUSH &&
+            (flush != Z_FINISH || ret == Z_STREAM_END))) {
+            while (strm->next_out > state->x.next) {
+                put = strm->next_out - state->x.next > (int)max ? max :
+                      (unsigned)(strm->next_out - state->x.next);
+                writ = write(state->fd, state->x.next, put);
+                if (writ < 0) {
+                    gz_error(state, Z_ERRNO, zstrerror());
+                    return -1;
+                }
+                state->x.next += writ;
+            }
+            if (strm->avail_out == 0) {
+                strm->avail_out = state->size;
+                strm->next_out = state->out;
+                state->x.next = state->out;
+            }
+        }
+
+        /* compress */
+        have = strm->avail_out;
+        ret = deflate(strm, flush);
+        if (ret == Z_STREAM_ERROR) {
+            gz_error(state, Z_STREAM_ERROR,
+                      "internal error: deflate stream corrupt");
+            return -1;
+        }
+        have -= strm->avail_out;
+    } while (have);
+
+    /* if that completed a deflate stream, allow another to start */
+    if (flush == Z_FINISH)
+        deflateReset(strm);
+
+    /* all done, no errors */
+    return 0;
+}
+
+/* Compress len zeros to output.  Return -1 on a write error or memory
+   allocation failure by gz_comp(), or 0 on success. */
+local int gz_zero(state, len)
+    gz_statep state;
+    z_off64_t len;
+{
+    int first;
+    unsigned n;
+    z_streamp strm = &(state->strm);
+
+    /* consume whatever's left in the input buffer */
+    if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
+        return -1;
+
+    /* compress len zeros (len guaranteed > 0) */
+    first = 1;
+    while (len) {
+        n = GT_OFF(state->size) || (z_off64_t)state->size > len ?
+            (unsigned)len : state->size;
+        if (first) {
+            memset(state->in, 0, n);
+            first = 0;
+        }
+        strm->avail_in = n;
+        strm->next_in = state->in;
+        state->x.pos += n;
+        if (gz_comp(state, Z_NO_FLUSH) == -1)
+            return -1;
+        len -= n;
+    }
+    return 0;
+}
+
+/* Write len bytes from buf to file.  Return the number of bytes written.  If
+   the returned value is less than len, then there was an error. */
+local z_size_t gz_write(state, buf, len)
+    gz_statep state;
+    voidpc buf;
+    z_size_t len;
+{
+    z_size_t put = len;
+
+    /* if len is zero, avoid unnecessary operations */
+    if (len == 0)
+        return 0;
+
+    /* allocate memory if this is the first time through */
+    if (state->size == 0 && gz_init(state) == -1)
+        return 0;
+
+    /* check for seek request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_zero(state, state->skip) == -1)
+            return 0;
+    }
+
+    /* for small len, copy to input buffer, otherwise compress directly */
+    if (len < state->size) {
+        /* copy to input buffer, compress when full */
+        do {
+            unsigned have, copy;
+
+            if (state->strm.avail_in == 0)
+                state->strm.next_in = state->in;
+            have = (unsigned)((state->strm.next_in + state->strm.avail_in) -
+                              state->in);
+            copy = state->size - have;
+            if (copy > len)
+                copy = len;
+            memcpy(state->in + have, buf, copy);
+            state->strm.avail_in += copy;
+            state->x.pos += copy;
+            buf = (const char *)buf + copy;
+            len -= copy;
+            if (len && gz_comp(state, Z_NO_FLUSH) == -1)
+                return 0;
+        } while (len);
+    }
+    else {
+        /* consume whatever's left in the input buffer */
+        if (state->strm.avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
+            return 0;
+
+        /* directly compress user buffer to file */
+        state->strm.next_in = (z_const Bytef *)buf;
+        do {
+            unsigned n = (unsigned)-1;
+            if (n > len)
+                n = len;
+            state->strm.avail_in = n;
+            state->x.pos += n;
+            if (gz_comp(state, Z_NO_FLUSH) == -1)
+                return 0;
+            len -= n;
+        } while (len);
+    }
+
+    /* input was all buffered or compressed */
+    return put;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzwrite(file, buf, len)
+    gzFile file;
+    voidpc buf;
+    unsigned len;
+{
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return 0;
+    state = (gz_statep)file;
+
+    /* check that we're writing and that there's no error */
+    if (state->mode != GZ_WRITE || state->err != Z_OK)
+        return 0;
+
+    /* since an int is returned, make sure len fits in one, otherwise return
+       with an error (this avoids a flaw in the interface) */
+    if ((int)len < 0) {
+        gz_error(state, Z_DATA_ERROR, "requested length does not fit in int");
+        return 0;
+    }
+
+    /* write len bytes from buf (the return value will fit in an int) */
+    return (int)gz_write(state, buf, len);
+}
+
+/* -- see zlib.h -- */
+z_size_t ZEXPORT gzfwrite(buf, size, nitems, file)
+    voidpc buf;
+    z_size_t size;
+    z_size_t nitems;
+    gzFile file;
+{
+    z_size_t len;
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return 0;
+    state = (gz_statep)file;
+
+    /* check that we're writing and that there's no error */
+    if (state->mode != GZ_WRITE || state->err != Z_OK)
+        return 0;
+
+    /* compute bytes to read -- error on overflow */
+    len = nitems * size;
+    if (size && len / size != nitems) {
+        gz_error(state, Z_STREAM_ERROR, "request does not fit in a size_t");
+        return 0;
+    }
+
+    /* write len bytes to buf, return the number of full items written */
+    return len ? gz_write(state, buf, len) / size : 0;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzputc(file, c)
+    gzFile file;
+    int c;
+{
+    unsigned have;
+    unsigned char buf[1];
+    gz_statep state;
+    z_streamp strm;
+
+    /* get internal structure */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+    strm = &(state->strm);
+
+    /* check that we're writing and that there's no error */
+    if (state->mode != GZ_WRITE || state->err != Z_OK)
+        return -1;
+
+    /* check for seek request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_zero(state, state->skip) == -1)
+            return -1;
+    }
+
+    /* try writing to input buffer for speed (state->size == 0 if buffer not
+       initialized) */
+    if (state->size) {
+        if (strm->avail_in == 0)
+            strm->next_in = state->in;
+        have = (unsigned)((strm->next_in + strm->avail_in) - state->in);
+        if (have < state->size) {
+            state->in[have] = (unsigned char)c;
+            strm->avail_in++;
+            state->x.pos++;
+            return c & 0xff;
+        }
+    }
+
+    /* no room in buffer or not initialized, use gz_write() */
+    buf[0] = (unsigned char)c;
+    if (gz_write(state, buf, 1) != 1)
+        return -1;
+    return c & 0xff;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzputs(file, str)
+    gzFile file;
+    const char *str;
+{
+    int ret;
+    z_size_t len;
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return -1;
+    state = (gz_statep)file;
+
+    /* check that we're writing and that there's no error */
+    if (state->mode != GZ_WRITE || state->err != Z_OK)
+        return -1;
+
+    /* write string */
+    len = strlen(str);
+    ret = gz_write(state, str, len);
+    return ret == 0 && len != 0 ? -1 : ret;
+}
+
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+#include <stdarg.h>
+
+/* -- see zlib.h -- */
+int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va)
+{
+    int len;
+    unsigned left;
+    char *next;
+    gz_statep state;
+    z_streamp strm;
+
+    /* get internal structure */
+    if (file == NULL)
+        return Z_STREAM_ERROR;
+    state = (gz_statep)file;
+    strm = &(state->strm);
+
+    /* check that we're writing and that there's no error */
+    if (state->mode != GZ_WRITE || state->err != Z_OK)
+        return Z_STREAM_ERROR;
+
+    /* make sure we have some buffer space */
+    if (state->size == 0 && gz_init(state) == -1)
+        return state->err;
+
+    /* check for seek request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_zero(state, state->skip) == -1)
+            return state->err;
+    }
+
+    /* do the printf() into the input buffer, put length in len -- the input
+       buffer is double-sized just for this function, so there is guaranteed to
+       be state->size bytes available after the current contents */
+    if (strm->avail_in == 0)
+        strm->next_in = state->in;
+    next = (char *)(state->in + (strm->next_in - state->in) + strm->avail_in);
+    next[state->size - 1] = 0;
+#ifdef NO_vsnprintf
+#  ifdef HAS_vsprintf_void
+    (void)vsprintf(next, format, va);
+    for (len = 0; len < state->size; len++)
+        if (next[len] == 0) break;
+#  else
+    len = vsprintf(next, format, va);
+#  endif
+#else
+#  ifdef HAS_vsnprintf_void
+    (void)vsnprintf(next, state->size, format, va);
+    len = strlen(next);
+#  else
+    len = vsnprintf(next, state->size, format, va);
+#  endif
+#endif
+
+    /* check that printf() results fit in buffer */
+    if (len == 0 || (unsigned)len >= state->size || next[state->size - 1] != 0)
+        return 0;
+
+    /* update buffer and position, compress first half if past that */
+    strm->avail_in += (unsigned)len;
+    state->x.pos += len;
+    if (strm->avail_in >= state->size) {
+        left = strm->avail_in - state->size;
+        strm->avail_in = state->size;
+        if (gz_comp(state, Z_NO_FLUSH) == -1)
+            return state->err;
+        memcpy(state->in, state->in + state->size, left);
+        strm->next_in = state->in;
+        strm->avail_in = left;
+    }
+    return len;
+}
+
+int ZEXPORTVA gzprintf(gzFile file, const char *format, ...)
+{
+    va_list va;
+    int ret;
+
+    va_start(va, format);
+    ret = gzvprintf(file, format, va);
+    va_end(va);
+    return ret;
+}
+
+#else /* !STDC && !Z_HAVE_STDARG_H */
+
+/* -- see zlib.h -- */
+int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+                       a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+    gzFile file;
+    const char *format;
+    int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+        a11, a12, a13, a14, a15, a16, a17, a18, a19, a20;
+{
+    unsigned len, left;
+    char *next;
+    gz_statep state;
+    z_streamp strm;
+
+    /* get internal structure */
+    if (file == NULL)
+        return Z_STREAM_ERROR;
+    state = (gz_statep)file;
+    strm = &(state->strm);
+
+    /* check that can really pass pointer in ints */
+    if (sizeof(int) != sizeof(void *))
+        return Z_STREAM_ERROR;
+
+    /* check that we're writing and that there's no error */
+    if (state->mode != GZ_WRITE || state->err != Z_OK)
+        return Z_STREAM_ERROR;
+
+    /* make sure we have some buffer space */
+    if (state->size == 0 && gz_init(state) == -1)
+        return state->error;
+
+    /* check for seek request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_zero(state, state->skip) == -1)
+            return state->error;
+    }
+
+    /* do the printf() into the input buffer, put length in len -- the input
+       buffer is double-sized just for this function, so there is guaranteed to
+       be state->size bytes available after the current contents */
+    if (strm->avail_in == 0)
+        strm->next_in = state->in;
+    next = (char *)(strm->next_in + strm->avail_in);
+    next[state->size - 1] = 0;
+#ifdef NO_snprintf
+#  ifdef HAS_sprintf_void
+    sprintf(next, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
+            a13, a14, a15, a16, a17, a18, a19, a20);
+    for (len = 0; len < size; len++)
+        if (next[len] == 0)
+            break;
+#  else
+    len = sprintf(next, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11,
+                  a12, a13, a14, a15, a16, a17, a18, a19, a20);
+#  endif
+#else
+#  ifdef HAS_snprintf_void
+    snprintf(next, state->size, format, a1, a2, a3, a4, a5, a6, a7, a8, a9,
+             a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
+    len = strlen(next);
+#  else
+    len = snprintf(next, state->size, format, a1, a2, a3, a4, a5, a6, a7, a8,
+                   a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
+#  endif
+#endif
+
+    /* check that printf() results fit in buffer */
+    if (len == 0 || len >= state->size || next[state->size - 1] != 0)
+        return 0;
+
+    /* update buffer and position, compress first half if past that */
+    strm->avail_in += len;
+    state->x.pos += len;
+    if (strm->avail_in >= state->size) {
+        left = strm->avail_in - state->size;
+        strm->avail_in = state->size;
+        if (gz_comp(state, Z_NO_FLUSH) == -1)
+            return state->err;
+        memcpy(state->in, state->in + state->size, left);
+        strm->next_in = state->in;
+        strm->avail_in = left;
+    }
+    return (int)len;
+}
+
+#endif
+
+/* -- see zlib.h -- */
+int ZEXPORT gzflush(file, flush)
+    gzFile file;
+    int flush;
+{
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return Z_STREAM_ERROR;
+    state = (gz_statep)file;
+
+    /* check that we're writing and that there's no error */
+    if (state->mode != GZ_WRITE || state->err != Z_OK)
+        return Z_STREAM_ERROR;
+
+    /* check flush parameter */
+    if (flush < 0 || flush > Z_FINISH)
+        return Z_STREAM_ERROR;
+
+    /* check for seek request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_zero(state, state->skip) == -1)
+            return state->err;
+    }
+
+    /* compress remaining data with requested flush */
+    (void)gz_comp(state, flush);
+    return state->err;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzsetparams(file, level, strategy)
+    gzFile file;
+    int level;
+    int strategy;
+{
+    gz_statep state;
+    z_streamp strm;
+
+    /* get internal structure */
+    if (file == NULL)
+        return Z_STREAM_ERROR;
+    state = (gz_statep)file;
+    strm = &(state->strm);
+
+    /* check that we're writing and that there's no error */
+    if (state->mode != GZ_WRITE || state->err != Z_OK)
+        return Z_STREAM_ERROR;
+
+    /* if no change is requested, then do nothing */
+    if (level == state->level && strategy == state->strategy)
+        return Z_OK;
+
+    /* check for seek request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_zero(state, state->skip) == -1)
+            return state->err;
+    }
+
+    /* change compression parameters for subsequent input */
+    if (state->size) {
+        /* flush previous input with previous parameters before changing */
+        if (strm->avail_in && gz_comp(state, Z_BLOCK) == -1)
+            return state->err;
+        deflateParams(strm, level, strategy);
+    }
+    state->level = level;
+    state->strategy = strategy;
+    return Z_OK;
+}
+
+/* -- see zlib.h -- */
+int ZEXPORT gzclose_w(file)
+    gzFile file;
+{
+    int ret = Z_OK;
+    gz_statep state;
+
+    /* get internal structure */
+    if (file == NULL)
+        return Z_STREAM_ERROR;
+    state = (gz_statep)file;
+
+    /* check that we're writing */
+    if (state->mode != GZ_WRITE)
+        return Z_STREAM_ERROR;
+
+    /* check for seek request */
+    if (state->seek) {
+        state->seek = 0;
+        if (gz_zero(state, state->skip) == -1)
+            ret = state->err;
+    }
+
+    /* flush, free memory, and close file */
+    if (gz_comp(state, Z_FINISH) == -1)
+        ret = state->err;
+    if (state->size) {
+        if (!state->direct) {
+            (void)deflateEnd(&(state->strm));
+            free(state->out);
+        }
+        free(state->in);
+    }
+    gz_error(state, Z_OK, NULL);
+    free(state->path);
+    if (close(state->fd) == -1)
+        ret = Z_ERRNO;
+    free(state);
+    return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/infback.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,640 @@
+/* infback.c -- inflate using a call-back interface
+ * Copyright (C) 1995-2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+   This code is largely copied from inflate.c.  Normally either infback.o or
+   inflate.o would be linked into an application--not both.  The interface
+   with inffast.c is retained so that optimized assembler-coded versions of
+   inflate_fast() can be used with either inflate.c or infback.c.
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+/* function prototypes */
+local void fixedtables OF((struct inflate_state FAR *state));
+
+/*
+   strm provides memory allocation functions in zalloc and zfree, or
+   Z_NULL to use the library memory allocation functions.
+
+   windowBits is in the range 8..15, and window is a user-supplied
+   window and output buffer that is 2**windowBits bytes.
+ */
+int ZEXPORT inflateBackInit_(strm, windowBits, window, version, stream_size)
+z_streamp strm;
+int windowBits;
+unsigned char FAR *window;
+const char *version;
+int stream_size;
+{
+    struct inflate_state FAR *state;
+
+    if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
+        stream_size != (int)(sizeof(z_stream)))
+        return Z_VERSION_ERROR;
+    if (strm == Z_NULL || window == Z_NULL ||
+        windowBits < 8 || windowBits > 15)
+        return Z_STREAM_ERROR;
+    strm->msg = Z_NULL;                 /* in case we return an error */
+    if (strm->zalloc == (alloc_func)0) {
+#ifdef Z_SOLO
+        return Z_STREAM_ERROR;
+#else
+        strm->zalloc = zcalloc;
+        strm->opaque = (voidpf)0;
+#endif
+    }
+    if (strm->zfree == (free_func)0)
+#ifdef Z_SOLO
+        return Z_STREAM_ERROR;
+#else
+    strm->zfree = zcfree;
+#endif
+    state = (struct inflate_state FAR *)ZALLOC(strm, 1,
+                                               sizeof(struct inflate_state));
+    if (state == Z_NULL) return Z_MEM_ERROR;
+    Tracev((stderr, "inflate: allocated\n"));
+    strm->state = (struct internal_state FAR *)state;
+    state->dmax = 32768U;
+    state->wbits = (uInt)windowBits;
+    state->wsize = 1U << windowBits;
+    state->window = window;
+    state->wnext = 0;
+    state->whave = 0;
+    return Z_OK;
+}
+
+/*
+   Return state with length and distance decoding tables and index sizes set to
+   fixed code decoding.  Normally this returns fixed tables from inffixed.h.
+   If BUILDFIXED is defined, then instead this routine builds the tables the
+   first time it's called, and returns those tables the first time and
+   thereafter.  This reduces the size of the code by about 2K bytes, in
+   exchange for a little execution time.  However, BUILDFIXED should not be
+   used for threaded applications, since the rewriting of the tables and virgin
+   may not be thread-safe.
+ */
+local void fixedtables(state)
+struct inflate_state FAR *state;
+{
+#ifdef BUILDFIXED
+    static int virgin = 1;
+    static code *lenfix, *distfix;
+    static code fixed[544];
+
+    /* build fixed huffman tables if first call (may not be thread safe) */
+    if (virgin) {
+        unsigned sym, bits;
+        static code *next;
+
+        /* literal/length table */
+        sym = 0;
+        while (sym < 144) state->lens[sym++] = 8;
+        while (sym < 256) state->lens[sym++] = 9;
+        while (sym < 280) state->lens[sym++] = 7;
+        while (sym < 288) state->lens[sym++] = 8;
+        next = fixed;
+        lenfix = next;
+        bits = 9;
+        inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work);
+
+        /* distance table */
+        sym = 0;
+        while (sym < 32) state->lens[sym++] = 5;
+        distfix = next;
+        bits = 5;
+        inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work);
+
+        /* do this just once */
+        virgin = 0;
+    }
+#else /* !BUILDFIXED */
+#   include "inffixed.h"
+#endif /* BUILDFIXED */
+    state->lencode = lenfix;
+    state->lenbits = 9;
+    state->distcode = distfix;
+    state->distbits = 5;
+}
+
+/* Macros for inflateBack(): */
+
+/* Load returned state from inflate_fast() */
+#define LOAD() \
+    do { \
+        put = strm->next_out; \
+        left = strm->avail_out; \
+        next = strm->next_in; \
+        have = strm->avail_in; \
+        hold = state->hold; \
+        bits = state->bits; \
+    } while (0)
+
+/* Set state from registers for inflate_fast() */
+#define RESTORE() \
+    do { \
+        strm->next_out = put; \
+        strm->avail_out = left; \
+        strm->next_in = next; \
+        strm->avail_in = have; \
+        state->hold = hold; \
+        state->bits = bits; \
+    } while (0)
+
+/* Clear the input bit accumulator */
+#define INITBITS() \
+    do { \
+        hold = 0; \
+        bits = 0; \
+    } while (0)
+
+/* Assure that some input is available.  If input is requested, but denied,
+   then return a Z_BUF_ERROR from inflateBack(). */
+#define PULL() \
+    do { \
+        if (have == 0) { \
+            have = in(in_desc, &next); \
+            if (have == 0) { \
+                next = Z_NULL; \
+                ret = Z_BUF_ERROR; \
+                goto inf_leave; \
+            } \
+        } \
+    } while (0)
+
+/* Get a byte of input into the bit accumulator, or return from inflateBack()
+   with an error if there is no input available. */
+#define PULLBYTE() \
+    do { \
+        PULL(); \
+        have--; \
+        hold += (unsigned long)(*next++) << bits; \
+        bits += 8; \
+    } while (0)
+
+/* Assure that there are at least n bits in the bit accumulator.  If there is
+   not enough available input to do that, then return from inflateBack() with
+   an error. */
+#define NEEDBITS(n) \
+    do { \
+        while (bits < (unsigned)(n)) \
+            PULLBYTE(); \
+    } while (0)
+
+/* Return the low n bits of the bit accumulator (n < 16) */
+#define BITS(n) \
+    ((unsigned)hold & ((1U << (n)) - 1))
+
+/* Remove n bits from the bit accumulator */
+#define DROPBITS(n) \
+    do { \
+        hold >>= (n); \
+        bits -= (unsigned)(n); \
+    } while (0)
+
+/* Remove zero to seven bits as needed to go to a byte boundary */
+#define BYTEBITS() \
+    do { \
+        hold >>= bits & 7; \
+        bits -= bits & 7; \
+    } while (0)
+
+/* Assure that some output space is available, by writing out the window
+   if it's full.  If the write fails, return from inflateBack() with a
+   Z_BUF_ERROR. */
+#define ROOM() \
+    do { \
+        if (left == 0) { \
+            put = state->window; \
+            left = state->wsize; \
+            state->whave = left; \
+            if (out(out_desc, put, left)) { \
+                ret = Z_BUF_ERROR; \
+                goto inf_leave; \
+            } \
+        } \
+    } while (0)
+
+/*
+   strm provides the memory allocation functions and window buffer on input,
+   and provides information on the unused input on return.  For Z_DATA_ERROR
+   returns, strm will also provide an error message.
+
+   in() and out() are the call-back input and output functions.  When
+   inflateBack() needs more input, it calls in().  When inflateBack() has
+   filled the window with output, or when it completes with data in the
+   window, it calls out() to write out the data.  The application must not
+   change the provided input until in() is called again or inflateBack()
+   returns.  The application must not change the window/output buffer until
+   inflateBack() returns.
+
+   in() and out() are called with a descriptor parameter provided in the
+   inflateBack() call.  This parameter can be a structure that provides the
+   information required to do the read or write, as well as accumulated
+   information on the input and output such as totals and check values.
+
+   in() should return zero on failure.  out() should return non-zero on
+   failure.  If either in() or out() fails, than inflateBack() returns a
+   Z_BUF_ERROR.  strm->next_in can be checked for Z_NULL to see whether it
+   was in() or out() that caused in the error.  Otherwise,  inflateBack()
+   returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format
+   error, or Z_MEM_ERROR if it could not allocate memory for the state.
+   inflateBack() can also return Z_STREAM_ERROR if the input parameters
+   are not correct, i.e. strm is Z_NULL or the state was not initialized.
+ */
+int ZEXPORT inflateBack(strm, in, in_desc, out, out_desc)
+z_streamp strm;
+in_func in;
+void FAR *in_desc;
+out_func out;
+void FAR *out_desc;
+{
+    struct inflate_state FAR *state;
+    z_const unsigned char FAR *next;    /* next input */
+    unsigned char FAR *put;     /* next output */
+    unsigned have, left;        /* available input and output */
+    unsigned long hold;         /* bit buffer */
+    unsigned bits;              /* bits in bit buffer */
+    unsigned copy;              /* number of stored or match bytes to copy */
+    unsigned char FAR *from;    /* where to copy match bytes from */
+    code here;                  /* current decoding table entry */
+    code last;                  /* parent table entry */
+    unsigned len;               /* length to copy for repeats, bits to drop */
+    int ret;                    /* return code */
+    static const unsigned short order[19] = /* permutation of code lengths */
+        {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+    /* Check that the strm exists and that the state was initialized */
+    if (strm == Z_NULL || strm->state == Z_NULL)
+        return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+
+    /* Reset the state */
+    strm->msg = Z_NULL;
+    state->mode = TYPE;
+    state->last = 0;
+    state->whave = 0;
+    next = strm->next_in;
+    have = next != Z_NULL ? strm->avail_in : 0;
+    hold = 0;
+    bits = 0;
+    put = state->window;
+    left = state->wsize;
+
+    /* Inflate until end of block marked as last */
+    for (;;)
+        switch (state->mode) {
+        case TYPE:
+            /* determine and dispatch block type */
+            if (state->last) {
+                BYTEBITS();
+                state->mode = DONE;
+                break;
+            }
+            NEEDBITS(3);
+            state->last = BITS(1);
+            DROPBITS(1);
+            switch (BITS(2)) {
+            case 0:                             /* stored block */
+                Tracev((stderr, "inflate:     stored block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = STORED;
+                break;
+            case 1:                             /* fixed block */
+                fixedtables(state);
+                Tracev((stderr, "inflate:     fixed codes block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = LEN;              /* decode codes */
+                break;
+            case 2:                             /* dynamic block */
+                Tracev((stderr, "inflate:     dynamic codes block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = TABLE;
+                break;
+            case 3:
+                strm->msg = (char *)"invalid block type";
+                state->mode = BAD;
+            }
+            DROPBITS(2);
+            break;
+
+        case STORED:
+            /* get and verify stored block length */
+            BYTEBITS();                         /* go to byte boundary */
+            NEEDBITS(32);
+            if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) {
+                strm->msg = (char *)"invalid stored block lengths";
+                state->mode = BAD;
+                break;
+            }
+            state->length = (unsigned)hold & 0xffff;
+            Tracev((stderr, "inflate:       stored length %u\n",
+                    state->length));
+            INITBITS();
+
+            /* copy stored block from input to output */
+            while (state->length != 0) {
+                copy = state->length;
+                PULL();
+                ROOM();
+                if (copy > have) copy = have;
+                if (copy > left) copy = left;
+                zmemcpy(put, next, copy);
+                have -= copy;
+                next += copy;
+                left -= copy;
+                put += copy;
+                state->length -= copy;
+            }
+            Tracev((stderr, "inflate:       stored end\n"));
+            state->mode = TYPE;
+            break;
+
+        case TABLE:
+            /* get dynamic table entries descriptor */
+            NEEDBITS(14);
+            state->nlen = BITS(5) + 257;
+            DROPBITS(5);
+            state->ndist = BITS(5) + 1;
+            DROPBITS(5);
+            state->ncode = BITS(4) + 4;
+            DROPBITS(4);
+#ifndef PKZIP_BUG_WORKAROUND
+            if (state->nlen > 286 || state->ndist > 30) {
+                strm->msg = (char *)"too many length or distance symbols";
+                state->mode = BAD;
+                break;
+            }
+#endif
+            Tracev((stderr, "inflate:       table sizes ok\n"));
+
+            /* get code length code lengths (not a typo) */
+            state->have = 0;
+            while (state->have < state->ncode) {
+                NEEDBITS(3);
+                state->lens[order[state->have++]] = (unsigned short)BITS(3);
+                DROPBITS(3);
+            }
+            while (state->have < 19)
+                state->lens[order[state->have++]] = 0;
+            state->next = state->codes;
+            state->lencode = (code const FAR *)(state->next);
+            state->lenbits = 7;
+            ret = inflate_table(CODES, state->lens, 19, &(state->next),
+                                &(state->lenbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid code lengths set";
+                state->mode = BAD;
+                break;
+            }
+            Tracev((stderr, "inflate:       code lengths ok\n"));
+
+            /* get length and distance code code lengths */
+            state->have = 0;
+            while (state->have < state->nlen + state->ndist) {
+                for (;;) {
+                    here = state->lencode[BITS(state->lenbits)];
+                    if ((unsigned)(here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                if (here.val < 16) {
+                    DROPBITS(here.bits);
+                    state->lens[state->have++] = here.val;
+                }
+                else {
+                    if (here.val == 16) {
+                        NEEDBITS(here.bits + 2);
+                        DROPBITS(here.bits);
+                        if (state->have == 0) {
+                            strm->msg = (char *)"invalid bit length repeat";
+                            state->mode = BAD;
+                            break;
+                        }
+                        len = (unsigned)(state->lens[state->have - 1]);
+                        copy = 3 + BITS(2);
+                        DROPBITS(2);
+                    }
+                    else if (here.val == 17) {
+                        NEEDBITS(here.bits + 3);
+                        DROPBITS(here.bits);
+                        len = 0;
+                        copy = 3 + BITS(3);
+                        DROPBITS(3);
+                    }
+                    else {
+                        NEEDBITS(here.bits + 7);
+                        DROPBITS(here.bits);
+                        len = 0;
+                        copy = 11 + BITS(7);
+                        DROPBITS(7);
+                    }
+                    if (state->have + copy > state->nlen + state->ndist) {
+                        strm->msg = (char *)"invalid bit length repeat";
+                        state->mode = BAD;
+                        break;
+                    }
+                    while (copy--)
+                        state->lens[state->have++] = (unsigned short)len;
+                }
+            }
+
+            /* handle error breaks in while */
+            if (state->mode == BAD) break;
+
+            /* check for end-of-block code (better have one) */
+            if (state->lens[256] == 0) {
+                strm->msg = (char *)"invalid code -- missing end-of-block";
+                state->mode = BAD;
+                break;
+            }
+
+            /* build code tables -- note: do not change the lenbits or distbits
+               values here (9 and 6) without reading the comments in inftrees.h
+               concerning the ENOUGH constants, which depend on those values */
+            state->next = state->codes;
+            state->lencode = (code const FAR *)(state->next);
+            state->lenbits = 9;
+            ret = inflate_table(LENS, state->lens, state->nlen, &(state->next),
+                                &(state->lenbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid literal/lengths set";
+                state->mode = BAD;
+                break;
+            }
+            state->distcode = (code const FAR *)(state->next);
+            state->distbits = 6;
+            ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist,
+                            &(state->next), &(state->distbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid distances set";
+                state->mode = BAD;
+                break;
+            }
+            Tracev((stderr, "inflate:       codes ok\n"));
+            state->mode = LEN;
+
+        case LEN:
+            /* use inflate_fast() if we have enough input and output */
+            if (have >= 6 && left >= 258) {
+                RESTORE();
+                if (state->whave < state->wsize)
+                    state->whave = state->wsize - left;
+                inflate_fast(strm, state->wsize);
+                LOAD();
+                break;
+            }
+
+            /* get a literal, length, or end-of-block code */
+            for (;;) {
+                here = state->lencode[BITS(state->lenbits)];
+                if ((unsigned)(here.bits) <= bits) break;
+                PULLBYTE();
+            }
+            if (here.op && (here.op & 0xf0) == 0) {
+                last = here;
+                for (;;) {
+                    here = state->lencode[last.val +
+                            (BITS(last.bits + last.op) >> last.bits)];
+                    if ((unsigned)(last.bits + here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                DROPBITS(last.bits);
+            }
+            DROPBITS(here.bits);
+            state->length = (unsigned)here.val;
+
+            /* process literal */
+            if (here.op == 0) {
+                Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+                        "inflate:         literal '%c'\n" :
+                        "inflate:         literal 0x%02x\n", here.val));
+                ROOM();
+                *put++ = (unsigned char)(state->length);
+                left--;
+                state->mode = LEN;
+                break;
+            }
+
+            /* process end of block */
+            if (here.op & 32) {
+                Tracevv((stderr, "inflate:         end of block\n"));
+                state->mode = TYPE;
+                break;
+            }
+
+            /* invalid code */
+            if (here.op & 64) {
+                strm->msg = (char *)"invalid literal/length code";
+                state->mode = BAD;
+                break;
+            }
+
+            /* length code -- get extra bits, if any */
+            state->extra = (unsigned)(here.op) & 15;
+            if (state->extra != 0) {
+                NEEDBITS(state->extra);
+                state->length += BITS(state->extra);
+                DROPBITS(state->extra);
+            }
+            Tracevv((stderr, "inflate:         length %u\n", state->length));
+
+            /* get distance code */
+            for (;;) {
+                here = state->distcode[BITS(state->distbits)];
+                if ((unsigned)(here.bits) <= bits) break;
+                PULLBYTE();
+            }
+            if ((here.op & 0xf0) == 0) {
+                last = here;
+                for (;;) {
+                    here = state->distcode[last.val +
+                            (BITS(last.bits + last.op) >> last.bits)];
+                    if ((unsigned)(last.bits + here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                DROPBITS(last.bits);
+            }
+            DROPBITS(here.bits);
+            if (here.op & 64) {
+                strm->msg = (char *)"invalid distance code";
+                state->mode = BAD;
+                break;
+            }
+            state->offset = (unsigned)here.val;
+
+            /* get distance extra bits, if any */
+            state->extra = (unsigned)(here.op) & 15;
+            if (state->extra != 0) {
+                NEEDBITS(state->extra);
+                state->offset += BITS(state->extra);
+                DROPBITS(state->extra);
+            }
+            if (state->offset > state->wsize - (state->whave < state->wsize ?
+                                                left : 0)) {
+                strm->msg = (char *)"invalid distance too far back";
+                state->mode = BAD;
+                break;
+            }
+            Tracevv((stderr, "inflate:         distance %u\n", state->offset));
+
+            /* copy match from window to output */
+            do {
+                ROOM();
+                copy = state->wsize - state->offset;
+                if (copy < left) {
+                    from = put + copy;
+                    copy = left - copy;
+                }
+                else {
+                    from = put - state->offset;
+                    copy = left;
+                }
+                if (copy > state->length) copy = state->length;
+                state->length -= copy;
+                left -= copy;
+                do {
+                    *put++ = *from++;
+                } while (--copy);
+            } while (state->length != 0);
+            break;
+
+        case DONE:
+            /* inflate stream terminated properly -- write leftover output */
+            ret = Z_STREAM_END;
+            if (left < state->wsize) {
+                if (out(out_desc, state->window, state->wsize - left))
+                    ret = Z_BUF_ERROR;
+            }
+            goto inf_leave;
+
+        case BAD:
+            ret = Z_DATA_ERROR;
+            goto inf_leave;
+
+        default:                /* can't happen, but makes compilers happy */
+            ret = Z_STREAM_ERROR;
+            goto inf_leave;
+        }
+
+    /* Return unused input */
+  inf_leave:
+    strm->next_in = next;
+    strm->avail_in = have;
+    return ret;
+}
+
+int ZEXPORT inflateBackEnd(strm)
+z_streamp strm;
+{
+    if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
+        return Z_STREAM_ERROR;
+    ZFREE(strm, strm->state);
+    strm->state = Z_NULL;
+    Tracev((stderr, "inflate: end\n"));
+    return Z_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/inffast.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,323 @@
+/* inffast.c -- fast decoding
+ * Copyright (C) 1995-2017 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+#ifdef ASMINF
+#  pragma message("Assembler code may have bugs -- use at your own risk")
+#else
+
+/*
+   Decode literal, length, and distance codes and write out the resulting
+   literal and match bytes until either not enough input or output is
+   available, an end-of-block is encountered, or a data error is encountered.
+   When large enough input and output buffers are supplied to inflate(), for
+   example, a 16K input buffer and a 64K output buffer, more than 95% of the
+   inflate execution time is spent in this routine.
+
+   Entry assumptions:
+
+        state->mode == LEN
+        strm->avail_in >= 6
+        strm->avail_out >= 258
+        start >= strm->avail_out
+        state->bits < 8
+
+   On return, state->mode is one of:
+
+        LEN -- ran out of enough output space or enough available input
+        TYPE -- reached end of block code, inflate() to interpret next block
+        BAD -- error in block data
+
+   Notes:
+
+    - The maximum input bits used by a length/distance pair is 15 bits for the
+      length code, 5 bits for the length extra, 15 bits for the distance code,
+      and 13 bits for the distance extra.  This totals 48 bits, or six bytes.
+      Therefore if strm->avail_in >= 6, then there is enough input to avoid
+      checking for available input while decoding.
+
+    - The maximum bytes that a single length/distance pair can output is 258
+      bytes, which is the maximum length that can be coded.  inflate_fast()
+      requires strm->avail_out >= 258 for each loop to avoid checking for
+      output space.
+ */
+void ZLIB_INTERNAL inflate_fast(strm, start)
+z_streamp strm;
+unsigned start;         /* inflate()'s starting value for strm->avail_out */
+{
+    struct inflate_state FAR *state;
+    z_const unsigned char FAR *in;      /* local strm->next_in */
+    z_const unsigned char FAR *last;    /* have enough input while in < last */
+    unsigned char FAR *out;     /* local strm->next_out */
+    unsigned char FAR *beg;     /* inflate()'s initial strm->next_out */
+    unsigned char FAR *end;     /* while out < end, enough space available */
+#ifdef INFLATE_STRICT
+    unsigned dmax;              /* maximum distance from zlib header */
+#endif
+    unsigned wsize;             /* window size or zero if not using window */
+    unsigned whave;             /* valid bytes in the window */
+    unsigned wnext;             /* window write index */
+    unsigned char FAR *window;  /* allocated sliding window, if wsize != 0 */
+    unsigned long hold;         /* local strm->hold */
+    unsigned bits;              /* local strm->bits */
+    code const FAR *lcode;      /* local strm->lencode */
+    code const FAR *dcode;      /* local strm->distcode */
+    unsigned lmask;             /* mask for first level of length codes */
+    unsigned dmask;             /* mask for first level of distance codes */
+    code here;                  /* retrieved table entry */
+    unsigned op;                /* code bits, operation, extra bits, or */
+                                /*  window position, window bytes to copy */
+    unsigned len;               /* match length, unused bytes */
+    unsigned dist;              /* match distance */
+    unsigned char FAR *from;    /* where to copy match from */
+
+    /* copy state to local variables */
+    state = (struct inflate_state FAR *)strm->state;
+    in = strm->next_in;
+    last = in + (strm->avail_in - 5);
+    out = strm->next_out;
+    beg = out - (start - strm->avail_out);
+    end = out + (strm->avail_out - 257);
+#ifdef INFLATE_STRICT
+    dmax = state->dmax;
+#endif
+    wsize = state->wsize;
+    whave = state->whave;
+    wnext = state->wnext;
+    window = state->window;
+    hold = state->hold;
+    bits = state->bits;
+    lcode = state->lencode;
+    dcode = state->distcode;
+    lmask = (1U << state->lenbits) - 1;
+    dmask = (1U << state->distbits) - 1;
+
+    /* decode literals and length/distances until end-of-block or not enough
+       input data or output space */
+    do {
+        if (bits < 15) {
+            hold += (unsigned long)(*in++) << bits;
+            bits += 8;
+            hold += (unsigned long)(*in++) << bits;
+            bits += 8;
+        }
+        here = lcode[hold & lmask];
+      dolen:
+        op = (unsigned)(here.bits);
+        hold >>= op;
+        bits -= op;
+        op = (unsigned)(here.op);
+        if (op == 0) {                          /* literal */
+            Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+                    "inflate:         literal '%c'\n" :
+                    "inflate:         literal 0x%02x\n", here.val));
+            *out++ = (unsigned char)(here.val);
+        }
+        else if (op & 16) {                     /* length base */
+            len = (unsigned)(here.val);
+            op &= 15;                           /* number of extra bits */
+            if (op) {
+                if (bits < op) {
+                    hold += (unsigned long)(*in++) << bits;
+                    bits += 8;
+                }
+                len += (unsigned)hold & ((1U << op) - 1);
+                hold >>= op;
+                bits -= op;
+            }
+            Tracevv((stderr, "inflate:         length %u\n", len));
+            if (bits < 15) {
+                hold += (unsigned long)(*in++) << bits;
+                bits += 8;
+                hold += (unsigned long)(*in++) << bits;
+                bits += 8;
+            }
+            here = dcode[hold & dmask];
+          dodist:
+            op = (unsigned)(here.bits);
+            hold >>= op;
+            bits -= op;
+            op = (unsigned)(here.op);
+            if (op & 16) {                      /* distance base */
+                dist = (unsigned)(here.val);
+                op &= 15;                       /* number of extra bits */
+                if (bits < op) {
+                    hold += (unsigned long)(*in++) << bits;
+                    bits += 8;
+                    if (bits < op) {
+                        hold += (unsigned long)(*in++) << bits;
+                        bits += 8;
+                    }
+                }
+                dist += (unsigned)hold & ((1U << op) - 1);
+#ifdef INFLATE_STRICT
+                if (dist > dmax) {
+                    strm->msg = (char *)"invalid distance too far back";
+                    state->mode = BAD;
+                    break;
+                }
+#endif
+                hold >>= op;
+                bits -= op;
+                Tracevv((stderr, "inflate:         distance %u\n", dist));
+                op = (unsigned)(out - beg);     /* max distance in output */
+                if (dist > op) {                /* see if copy from window */
+                    op = dist - op;             /* distance back in window */
+                    if (op > whave) {
+                        if (state->sane) {
+                            strm->msg =
+                                (char *)"invalid distance too far back";
+                            state->mode = BAD;
+                            break;
+                        }
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+                        if (len <= op - whave) {
+                            do {
+                                *out++ = 0;
+                            } while (--len);
+                            continue;
+                        }
+                        len -= op - whave;
+                        do {
+                            *out++ = 0;
+                        } while (--op > whave);
+                        if (op == 0) {
+                            from = out - dist;
+                            do {
+                                *out++ = *from++;
+                            } while (--len);
+                            continue;
+                        }
+#endif
+                    }
+                    from = window;
+                    if (wnext == 0) {           /* very common case */
+                        from += wsize - op;
+                        if (op < len) {         /* some from window */
+                            len -= op;
+                            do {
+                                *out++ = *from++;
+                            } while (--op);
+                            from = out - dist;  /* rest from output */
+                        }
+                    }
+                    else if (wnext < op) {      /* wrap around window */
+                        from += wsize + wnext - op;
+                        op -= wnext;
+                        if (op < len) {         /* some from end of window */
+                            len -= op;
+                            do {
+                                *out++ = *from++;
+                            } while (--op);
+                            from = window;
+                            if (wnext < len) {  /* some from start of window */
+                                op = wnext;
+                                len -= op;
+                                do {
+                                    *out++ = *from++;
+                                } while (--op);
+                                from = out - dist;      /* rest from output */
+                            }
+                        }
+                    }
+                    else {                      /* contiguous in window */
+                        from += wnext - op;
+                        if (op < len) {         /* some from window */
+                            len -= op;
+                            do {
+                                *out++ = *from++;
+                            } while (--op);
+                            from = out - dist;  /* rest from output */
+                        }
+                    }
+                    while (len > 2) {
+                        *out++ = *from++;
+                        *out++ = *from++;
+                        *out++ = *from++;
+                        len -= 3;
+                    }
+                    if (len) {
+                        *out++ = *from++;
+                        if (len > 1)
+                            *out++ = *from++;
+                    }
+                }
+                else {
+                    from = out - dist;          /* copy direct from output */
+                    do {                        /* minimum length is three */
+                        *out++ = *from++;
+                        *out++ = *from++;
+                        *out++ = *from++;
+                        len -= 3;
+                    } while (len > 2);
+                    if (len) {
+                        *out++ = *from++;
+                        if (len > 1)
+                            *out++ = *from++;
+                    }
+                }
+            }
+            else if ((op & 64) == 0) {          /* 2nd level distance code */
+                here = dcode[here.val + (hold & ((1U << op) - 1))];
+                goto dodist;
+            }
+            else {
+                strm->msg = (char *)"invalid distance code";
+                state->mode = BAD;
+                break;
+            }
+        }
+        else if ((op & 64) == 0) {              /* 2nd level length code */
+            here = lcode[here.val + (hold & ((1U << op) - 1))];
+            goto dolen;
+        }
+        else if (op & 32) {                     /* end-of-block */
+            Tracevv((stderr, "inflate:         end of block\n"));
+            state->mode = TYPE;
+            break;
+        }
+        else {
+            strm->msg = (char *)"invalid literal/length code";
+            state->mode = BAD;
+            break;
+        }
+    } while (in < last && out < end);
+
+    /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+    len = bits >> 3;
+    in -= len;
+    bits -= len << 3;
+    hold &= (1U << bits) - 1;
+
+    /* update state and return */
+    strm->next_in = in;
+    strm->next_out = out;
+    strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last));
+    strm->avail_out = (unsigned)(out < end ?
+                                 257 + (end - out) : 257 - (out - end));
+    state->hold = hold;
+    state->bits = bits;
+    return;
+}
+
+/*
+   inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe):
+   - Using bit fields for code structure
+   - Different op definition to avoid & for extra bits (do & for table bits)
+   - Three separate decoding do-loops for direct, window, and wnext == 0
+   - Special case for distance > 1 copies to do overlapped load and store copy
+   - Explicit branch predictions (based on measured branch probabilities)
+   - Deferring match copy and interspersed it with decoding subsequent codes
+   - Swapping literal/length else
+   - Swapping window/direct else
+   - Larger unrolled copy loops (three is about right)
+   - Moving len -= 3 statement into middle of loop
+ */
+
+#endif /* !ASMINF */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/inffast.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,11 @@
+/* inffast.h -- header to use inffast.c
+ * Copyright (C) 1995-2003, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/inffixed.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,94 @@
+    /* inffixed.h -- table for decoding fixed codes
+     * Generated automatically by makefixed().
+     */
+
+    /* WARNING: this file should *not* be used by applications.
+       It is part of the implementation of this library and is
+       subject to change. Applications should only use zlib.h.
+     */
+
+    static const code lenfix[512] = {
+        {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48},
+        {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128},
+        {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59},
+        {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176},
+        {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20},
+        {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100},
+        {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8},
+        {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216},
+        {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76},
+        {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114},
+        {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2},
+        {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148},
+        {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42},
+        {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86},
+        {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15},
+        {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236},
+        {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62},
+        {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142},
+        {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31},
+        {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162},
+        {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25},
+        {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105},
+        {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4},
+        {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202},
+        {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69},
+        {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125},
+        {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13},
+        {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195},
+        {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35},
+        {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91},
+        {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19},
+        {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246},
+        {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55},
+        {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135},
+        {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99},
+        {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190},
+        {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16},
+        {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96},
+        {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6},
+        {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209},
+        {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72},
+        {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116},
+        {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4},
+        {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153},
+        {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44},
+        {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82},
+        {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11},
+        {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229},
+        {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58},
+        {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138},
+        {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51},
+        {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173},
+        {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30},
+        {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110},
+        {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0},
+        {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195},
+        {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65},
+        {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121},
+        {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9},
+        {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258},
+        {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37},
+        {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93},
+        {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23},
+        {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251},
+        {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51},
+        {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131},
+        {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67},
+        {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183},
+        {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23},
+        {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103},
+        {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9},
+        {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223},
+        {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79},
+        {0,9,255}
+    };
+
+    static const code distfix[32] = {
+        {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025},
+        {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193},
+        {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385},
+        {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577},
+        {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073},
+        {22,5,193},{64,5,0}
+    };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/inflate.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,1561 @@
+/* inflate.c -- zlib decompression
+ * Copyright (C) 1995-2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ * Change history:
+ *
+ * 1.2.beta0    24 Nov 2002
+ * - First version -- complete rewrite of inflate to simplify code, avoid
+ *   creation of window when not needed, minimize use of window when it is
+ *   needed, make inffast.c even faster, implement gzip decoding, and to
+ *   improve code readability and style over the previous zlib inflate code
+ *
+ * 1.2.beta1    25 Nov 2002
+ * - Use pointers for available input and output checking in inffast.c
+ * - Remove input and output counters in inffast.c
+ * - Change inffast.c entry and loop from avail_in >= 7 to >= 6
+ * - Remove unnecessary second byte pull from length extra in inffast.c
+ * - Unroll direct copy to three copies per loop in inffast.c
+ *
+ * 1.2.beta2    4 Dec 2002
+ * - Change external routine names to reduce potential conflicts
+ * - Correct filename to inffixed.h for fixed tables in inflate.c
+ * - Make hbuf[] unsigned char to match parameter type in inflate.c
+ * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset)
+ *   to avoid negation problem on Alphas (64 bit) in inflate.c
+ *
+ * 1.2.beta3    22 Dec 2002
+ * - Add comments on state->bits assertion in inffast.c
+ * - Add comments on op field in inftrees.h
+ * - Fix bug in reuse of allocated window after inflateReset()
+ * - Remove bit fields--back to byte structure for speed
+ * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths
+ * - Change post-increments to pre-increments in inflate_fast(), PPC biased?
+ * - Add compile time option, POSTINC, to use post-increments instead (Intel?)
+ * - Make MATCH copy in inflate() much faster for when inflate_fast() not used
+ * - Use local copies of stream next and avail values, as well as local bit
+ *   buffer and bit count in inflate()--for speed when inflate_fast() not used
+ *
+ * 1.2.beta4    1 Jan 2003
+ * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings
+ * - Move a comment on output buffer sizes from inffast.c to inflate.c
+ * - Add comments in inffast.c to introduce the inflate_fast() routine
+ * - Rearrange window copies in inflate_fast() for speed and simplification
+ * - Unroll last copy for window match in inflate_fast()
+ * - Use local copies of window variables in inflate_fast() for speed
+ * - Pull out common wnext == 0 case for speed in inflate_fast()
+ * - Make op and len in inflate_fast() unsigned for consistency
+ * - Add FAR to lcode and dcode declarations in inflate_fast()
+ * - Simplified bad distance check in inflate_fast()
+ * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new
+ *   source file infback.c to provide a call-back interface to inflate for
+ *   programs like gzip and unzip -- uses window as output buffer to avoid
+ *   window copying
+ *
+ * 1.2.beta5    1 Jan 2003
+ * - Improved inflateBack() interface to allow the caller to provide initial
+ *   input in strm.
+ * - Fixed stored blocks bug in inflateBack()
+ *
+ * 1.2.beta6    4 Jan 2003
+ * - Added comments in inffast.c on effectiveness of POSTINC
+ * - Typecasting all around to reduce compiler warnings
+ * - Changed loops from while (1) or do {} while (1) to for (;;), again to
+ *   make compilers happy
+ * - Changed type of window in inflateBackInit() to unsigned char *
+ *
+ * 1.2.beta7    27 Jan 2003
+ * - Changed many types to unsigned or unsigned short to avoid warnings
+ * - Added inflateCopy() function
+ *
+ * 1.2.0        9 Mar 2003
+ * - Changed inflateBack() interface to provide separate opaque descriptors
+ *   for the in() and out() functions
+ * - Changed inflateBack() argument and in_func typedef to swap the length
+ *   and buffer address return values for the input function
+ * - Check next_in and next_out for Z_NULL on entry to inflate()
+ *
+ * The history for versions after 1.2.0 are in ChangeLog in zlib distribution.
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+#ifdef MAKEFIXED
+#  ifndef BUILDFIXED
+#    define BUILDFIXED
+#  endif
+#endif
+
+/* function prototypes */
+local int inflateStateCheck OF((z_streamp strm));
+local void fixedtables OF((struct inflate_state FAR *state));
+local int updatewindow OF((z_streamp strm, const unsigned char FAR *end,
+                           unsigned copy));
+#ifdef BUILDFIXED
+   void makefixed OF((void));
+#endif
+local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf,
+                              unsigned len));
+
+local int inflateStateCheck(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+    if (strm == Z_NULL ||
+        strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0)
+        return 1;
+    state = (struct inflate_state FAR *)strm->state;
+    if (state == Z_NULL || state->strm != strm ||
+        state->mode < HEAD || state->mode > SYNC)
+        return 1;
+    return 0;
+}
+
+int ZEXPORT inflateResetKeep(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    strm->total_in = strm->total_out = state->total = 0;
+    strm->msg = Z_NULL;
+    if (state->wrap)        /* to support ill-conceived Java test suite */
+        strm->adler = state->wrap & 1;
+    state->mode = HEAD;
+    state->last = 0;
+    state->havedict = 0;
+    state->dmax = 32768U;
+    state->head = Z_NULL;
+    state->hold = 0;
+    state->bits = 0;
+    state->lencode = state->distcode = state->next = state->codes;
+    state->sane = 1;
+    state->back = -1;
+    Tracev((stderr, "inflate: reset\n"));
+    return Z_OK;
+}
+
+int ZEXPORT inflateReset(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    state->wsize = 0;
+    state->whave = 0;
+    state->wnext = 0;
+    return inflateResetKeep(strm);
+}
+
+int ZEXPORT inflateReset2(strm, windowBits)
+z_streamp strm;
+int windowBits;
+{
+    int wrap;
+    struct inflate_state FAR *state;
+
+    /* get the state */
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+
+    /* extract wrap request from windowBits parameter */
+    if (windowBits < 0) {
+        wrap = 0;
+        windowBits = -windowBits;
+    }
+    else {
+        wrap = (windowBits >> 4) + 5;
+#ifdef GUNZIP
+        if (windowBits < 48)
+            windowBits &= 15;
+#endif
+    }
+
+    /* set number of window bits, free window if different */
+    if (windowBits && (windowBits < 8 || windowBits > 15))
+        return Z_STREAM_ERROR;
+    if (state->window != Z_NULL && state->wbits != (unsigned)windowBits) {
+        ZFREE(strm, state->window);
+        state->window = Z_NULL;
+    }
+
+    /* update state and reset the rest of it */
+    state->wrap = wrap;
+    state->wbits = (unsigned)windowBits;
+    return inflateReset(strm);
+}
+
+int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size)
+z_streamp strm;
+int windowBits;
+const char *version;
+int stream_size;
+{
+    int ret;
+    struct inflate_state FAR *state;
+
+    if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
+        stream_size != (int)(sizeof(z_stream)))
+        return Z_VERSION_ERROR;
+    if (strm == Z_NULL) return Z_STREAM_ERROR;
+    strm->msg = Z_NULL;                 /* in case we return an error */
+    if (strm->zalloc == (alloc_func)0) {
+#ifdef Z_SOLO
+        return Z_STREAM_ERROR;
+#else
+        strm->zalloc = zcalloc;
+        strm->opaque = (voidpf)0;
+#endif
+    }
+    if (strm->zfree == (free_func)0)
+#ifdef Z_SOLO
+        return Z_STREAM_ERROR;
+#else
+        strm->zfree = zcfree;
+#endif
+    state = (struct inflate_state FAR *)
+            ZALLOC(strm, 1, sizeof(struct inflate_state));
+    if (state == Z_NULL) return Z_MEM_ERROR;
+    Tracev((stderr, "inflate: allocated\n"));
+    strm->state = (struct internal_state FAR *)state;
+    state->strm = strm;
+    state->window = Z_NULL;
+    state->mode = HEAD;     /* to pass state test in inflateReset2() */
+    ret = inflateReset2(strm, windowBits);
+    if (ret != Z_OK) {
+        ZFREE(strm, state);
+        strm->state = Z_NULL;
+    }
+    return ret;
+}
+
+int ZEXPORT inflateInit_(strm, version, stream_size)
+z_streamp strm;
+const char *version;
+int stream_size;
+{
+    return inflateInit2_(strm, DEF_WBITS, version, stream_size);
+}
+
+int ZEXPORT inflatePrime(strm, bits, value)
+z_streamp strm;
+int bits;
+int value;
+{
+    struct inflate_state FAR *state;
+
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if (bits < 0) {
+        state->hold = 0;
+        state->bits = 0;
+        return Z_OK;
+    }
+    if (bits > 16 || state->bits + (uInt)bits > 32) return Z_STREAM_ERROR;
+    value &= (1L << bits) - 1;
+    state->hold += (unsigned)value << state->bits;
+    state->bits += (uInt)bits;
+    return Z_OK;
+}
+
+/*
+   Return state with length and distance decoding tables and index sizes set to
+   fixed code decoding.  Normally this returns fixed tables from inffixed.h.
+   If BUILDFIXED is defined, then instead this routine builds the tables the
+   first time it's called, and returns those tables the first time and
+   thereafter.  This reduces the size of the code by about 2K bytes, in
+   exchange for a little execution time.  However, BUILDFIXED should not be
+   used for threaded applications, since the rewriting of the tables and virgin
+   may not be thread-safe.
+ */
+local void fixedtables(state)
+struct inflate_state FAR *state;
+{
+#ifdef BUILDFIXED
+    static int virgin = 1;
+    static code *lenfix, *distfix;
+    static code fixed[544];
+
+    /* build fixed huffman tables if first call (may not be thread safe) */
+    if (virgin) {
+        unsigned sym, bits;
+        static code *next;
+
+        /* literal/length table */
+        sym = 0;
+        while (sym < 144) state->lens[sym++] = 8;
+        while (sym < 256) state->lens[sym++] = 9;
+        while (sym < 280) state->lens[sym++] = 7;
+        while (sym < 288) state->lens[sym++] = 8;
+        next = fixed;
+        lenfix = next;
+        bits = 9;
+        inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work);
+
+        /* distance table */
+        sym = 0;
+        while (sym < 32) state->lens[sym++] = 5;
+        distfix = next;
+        bits = 5;
+        inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work);
+
+        /* do this just once */
+        virgin = 0;
+    }
+#else /* !BUILDFIXED */
+#   include "inffixed.h"
+#endif /* BUILDFIXED */
+    state->lencode = lenfix;
+    state->lenbits = 9;
+    state->distcode = distfix;
+    state->distbits = 5;
+}
+
+#ifdef MAKEFIXED
+#include <stdio.h>
+
+/*
+   Write out the inffixed.h that is #include'd above.  Defining MAKEFIXED also
+   defines BUILDFIXED, so the tables are built on the fly.  makefixed() writes
+   those tables to stdout, which would be piped to inffixed.h.  A small program
+   can simply call makefixed to do this:
+
+    void makefixed(void);
+
+    int main(void)
+    {
+        makefixed();
+        return 0;
+    }
+
+   Then that can be linked with zlib built with MAKEFIXED defined and run:
+
+    a.out > inffixed.h
+ */
+void makefixed()
+{
+    unsigned low, size;
+    struct inflate_state state;
+
+    fixedtables(&state);
+    puts("    /* inffixed.h -- table for decoding fixed codes");
+    puts("     * Generated automatically by makefixed().");
+    puts("     */");
+    puts("");
+    puts("    /* WARNING: this file should *not* be used by applications.");
+    puts("       It is part of the implementation of this library and is");
+    puts("       subject to change. Applications should only use zlib.h.");
+    puts("     */");
+    puts("");
+    size = 1U << 9;
+    printf("    static const code lenfix[%u] = {", size);
+    low = 0;
+    for (;;) {
+        if ((low % 7) == 0) printf("\n        ");
+        printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op,
+               state.lencode[low].bits, state.lencode[low].val);
+        if (++low == size) break;
+        putchar(',');
+    }
+    puts("\n    };");
+    size = 1U << 5;
+    printf("\n    static const code distfix[%u] = {", size);
+    low = 0;
+    for (;;) {
+        if ((low % 6) == 0) printf("\n        ");
+        printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits,
+               state.distcode[low].val);
+        if (++low == size) break;
+        putchar(',');
+    }
+    puts("\n    };");
+}
+#endif /* MAKEFIXED */
+
+/*
+   Update the window with the last wsize (normally 32K) bytes written before
+   returning.  If window does not exist yet, create it.  This is only called
+   when a window is already in use, or when output has been written during this
+   inflate call, but the end of the deflate stream has not been reached yet.
+   It is also called to create a window for dictionary data when a dictionary
+   is loaded.
+
+   Providing output buffers larger than 32K to inflate() should provide a speed
+   advantage, since only the last 32K of output is copied to the sliding window
+   upon return from inflate(), and since all distances after the first 32K of
+   output will fall in the output data, making match copies simpler and faster.
+   The advantage may be dependent on the size of the processor's data caches.
+ */
+local int updatewindow(strm, end, copy)
+z_streamp strm;
+const Bytef *end;
+unsigned copy;
+{
+    struct inflate_state FAR *state;
+    unsigned dist;
+
+    state = (struct inflate_state FAR *)strm->state;
+
+    /* if it hasn't been done already, allocate space for the window */
+    if (state->window == Z_NULL) {
+        state->window = (unsigned char FAR *)
+                        ZALLOC(strm, 1U << state->wbits,
+                               sizeof(unsigned char));
+        if (state->window == Z_NULL) return 1;
+    }
+
+    /* if window not in use yet, initialize */
+    if (state->wsize == 0) {
+        state->wsize = 1U << state->wbits;
+        state->wnext = 0;
+        state->whave = 0;
+    }
+
+    /* copy state->wsize or less output bytes into the circular window */
+    if (copy >= state->wsize) {
+        zmemcpy(state->window, end - state->wsize, state->wsize);
+        state->wnext = 0;
+        state->whave = state->wsize;
+    }
+    else {
+        dist = state->wsize - state->wnext;
+        if (dist > copy) dist = copy;
+        zmemcpy(state->window + state->wnext, end - copy, dist);
+        copy -= dist;
+        if (copy) {
+            zmemcpy(state->window, end - copy, copy);
+            state->wnext = copy;
+            state->whave = state->wsize;
+        }
+        else {
+            state->wnext += dist;
+            if (state->wnext == state->wsize) state->wnext = 0;
+            if (state->whave < state->wsize) state->whave += dist;
+        }
+    }
+    return 0;
+}
+
+/* Macros for inflate(): */
+
+/* check function to use adler32() for zlib or crc32() for gzip */
+#ifdef GUNZIP
+#  define UPDATE(check, buf, len) \
+    (state->flags ? crc32(check, buf, len) : adler32(check, buf, len))
+#else
+#  define UPDATE(check, buf, len) adler32(check, buf, len)
+#endif
+
+/* check macros for header crc */
+#ifdef GUNZIP
+#  define CRC2(check, word) \
+    do { \
+        hbuf[0] = (unsigned char)(word); \
+        hbuf[1] = (unsigned char)((word) >> 8); \
+        check = crc32(check, hbuf, 2); \
+    } while (0)
+
+#  define CRC4(check, word) \
+    do { \
+        hbuf[0] = (unsigned char)(word); \
+        hbuf[1] = (unsigned char)((word) >> 8); \
+        hbuf[2] = (unsigned char)((word) >> 16); \
+        hbuf[3] = (unsigned char)((word) >> 24); \
+        check = crc32(check, hbuf, 4); \
+    } while (0)
+#endif
+
+/* Load registers with state in inflate() for speed */
+#define LOAD() \
+    do { \
+        put = strm->next_out; \
+        left = strm->avail_out; \
+        next = strm->next_in; \
+        have = strm->avail_in; \
+        hold = state->hold; \
+        bits = state->bits; \
+    } while (0)
+
+/* Restore state from registers in inflate() */
+#define RESTORE() \
+    do { \
+        strm->next_out = put; \
+        strm->avail_out = left; \
+        strm->next_in = next; \
+        strm->avail_in = have; \
+        state->hold = hold; \
+        state->bits = bits; \
+    } while (0)
+
+/* Clear the input bit accumulator */
+#define INITBITS() \
+    do { \
+        hold = 0; \
+        bits = 0; \
+    } while (0)
+
+/* Get a byte of input into the bit accumulator, or return from inflate()
+   if there is no input available. */
+#define PULLBYTE() \
+    do { \
+        if (have == 0) goto inf_leave; \
+        have--; \
+        hold += (unsigned long)(*next++) << bits; \
+        bits += 8; \
+    } while (0)
+
+/* Assure that there are at least n bits in the bit accumulator.  If there is
+   not enough available input to do that, then return from inflate(). */
+#define NEEDBITS(n) \
+    do { \
+        while (bits < (unsigned)(n)) \
+            PULLBYTE(); \
+    } while (0)
+
+/* Return the low n bits of the bit accumulator (n < 16) */
+#define BITS(n) \
+    ((unsigned)hold & ((1U << (n)) - 1))
+
+/* Remove n bits from the bit accumulator */
+#define DROPBITS(n) \
+    do { \
+        hold >>= (n); \
+        bits -= (unsigned)(n); \
+    } while (0)
+
+/* Remove zero to seven bits as needed to go to a byte boundary */
+#define BYTEBITS() \
+    do { \
+        hold >>= bits & 7; \
+        bits -= bits & 7; \
+    } while (0)
+
+/*
+   inflate() uses a state machine to process as much input data and generate as
+   much output data as possible before returning.  The state machine is
+   structured roughly as follows:
+
+    for (;;) switch (state) {
+    ...
+    case STATEn:
+        if (not enough input data or output space to make progress)
+            return;
+        ... make progress ...
+        state = STATEm;
+        break;
+    ...
+    }
+
+   so when inflate() is called again, the same case is attempted again, and
+   if the appropriate resources are provided, the machine proceeds to the
+   next state.  The NEEDBITS() macro is usually the way the state evaluates
+   whether it can proceed or should return.  NEEDBITS() does the return if
+   the requested bits are not available.  The typical use of the BITS macros
+   is:
+
+        NEEDBITS(n);
+        ... do something with BITS(n) ...
+        DROPBITS(n);
+
+   where NEEDBITS(n) either returns from inflate() if there isn't enough
+   input left to load n bits into the accumulator, or it continues.  BITS(n)
+   gives the low n bits in the accumulator.  When done, DROPBITS(n) drops
+   the low n bits off the accumulator.  INITBITS() clears the accumulator
+   and sets the number of available bits to zero.  BYTEBITS() discards just
+   enough bits to put the accumulator on a byte boundary.  After BYTEBITS()
+   and a NEEDBITS(8), then BITS(8) would return the next byte in the stream.
+
+   NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return
+   if there is no input available.  The decoding of variable length codes uses
+   PULLBYTE() directly in order to pull just enough bytes to decode the next
+   code, and no more.
+
+   Some states loop until they get enough input, making sure that enough
+   state information is maintained to continue the loop where it left off
+   if NEEDBITS() returns in the loop.  For example, want, need, and keep
+   would all have to actually be part of the saved state in case NEEDBITS()
+   returns:
+
+    case STATEw:
+        while (want < need) {
+            NEEDBITS(n);
+            keep[want++] = BITS(n);
+            DROPBITS(n);
+        }
+        state = STATEx;
+    case STATEx:
+
+   As shown above, if the next state is also the next case, then the break
+   is omitted.
+
+   A state may also return if there is not enough output space available to
+   complete that state.  Those states are copying stored data, writing a
+   literal byte, and copying a matching string.
+
+   When returning, a "goto inf_leave" is used to update the total counters,
+   update the check value, and determine whether any progress has been made
+   during that inflate() call in order to return the proper return code.
+   Progress is defined as a change in either strm->avail_in or strm->avail_out.
+   When there is a window, goto inf_leave will update the window with the last
+   output written.  If a goto inf_leave occurs in the middle of decompression
+   and there is no window currently, goto inf_leave will create one and copy
+   output to the window for the next call of inflate().
+
+   In this implementation, the flush parameter of inflate() only affects the
+   return code (per zlib.h).  inflate() always writes as much as possible to
+   strm->next_out, given the space available and the provided input--the effect
+   documented in zlib.h of Z_SYNC_FLUSH.  Furthermore, inflate() always defers
+   the allocation of and copying into a sliding window until necessary, which
+   provides the effect documented in zlib.h for Z_FINISH when the entire input
+   stream available.  So the only thing the flush parameter actually does is:
+   when flush is set to Z_FINISH, inflate() cannot return Z_OK.  Instead it
+   will return Z_BUF_ERROR if it has not reached the end of the stream.
+ */
+
+int ZEXPORT inflate(strm, flush)
+z_streamp strm;
+int flush;
+{
+    struct inflate_state FAR *state;
+    z_const unsigned char FAR *next;    /* next input */
+    unsigned char FAR *put;     /* next output */
+    unsigned have, left;        /* available input and output */
+    unsigned long hold;         /* bit buffer */
+    unsigned bits;              /* bits in bit buffer */
+    unsigned in, out;           /* save starting available input and output */
+    unsigned copy;              /* number of stored or match bytes to copy */
+    unsigned char FAR *from;    /* where to copy match bytes from */
+    code here;                  /* current decoding table entry */
+    code last;                  /* parent table entry */
+    unsigned len;               /* length to copy for repeats, bits to drop */
+    int ret;                    /* return code */
+#ifdef GUNZIP
+    unsigned char hbuf[4];      /* buffer for gzip header crc calculation */
+#endif
+    static const unsigned short order[19] = /* permutation of code lengths */
+        {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+    if (inflateStateCheck(strm) || strm->next_out == Z_NULL ||
+        (strm->next_in == Z_NULL && strm->avail_in != 0))
+        return Z_STREAM_ERROR;
+
+    state = (struct inflate_state FAR *)strm->state;
+    if (state->mode == TYPE) state->mode = TYPEDO;      /* skip check */
+    LOAD();
+    in = have;
+    out = left;
+    ret = Z_OK;
+    for (;;)
+        switch (state->mode) {
+        case HEAD:
+            if (state->wrap == 0) {
+                state->mode = TYPEDO;
+                break;
+            }
+            NEEDBITS(16);
+#ifdef GUNZIP
+            if ((state->wrap & 2) && hold == 0x8b1f) {  /* gzip header */
+                if (state->wbits == 0)
+                    state->wbits = 15;
+                state->check = crc32(0L, Z_NULL, 0);
+                CRC2(state->check, hold);
+                INITBITS();
+                state->mode = FLAGS;
+                break;
+            }
+            state->flags = 0;           /* expect zlib header */
+            if (state->head != Z_NULL)
+                state->head->done = -1;
+            if (!(state->wrap & 1) ||   /* check if zlib header allowed */
+#else
+            if (
+#endif
+                ((BITS(8) << 8) + (hold >> 8)) % 31) {
+                strm->msg = (char *)"incorrect header check";
+                state->mode = BAD;
+                break;
+            }
+            if (BITS(4) != Z_DEFLATED) {
+                strm->msg = (char *)"unknown compression method";
+                state->mode = BAD;
+                break;
+            }
+            DROPBITS(4);
+            len = BITS(4) + 8;
+            if (state->wbits == 0)
+                state->wbits = len;
+            if (len > 15 || len > state->wbits) {
+                strm->msg = (char *)"invalid window size";
+                state->mode = BAD;
+                break;
+            }
+            state->dmax = 1U << len;
+            Tracev((stderr, "inflate:   zlib header ok\n"));
+            strm->adler = state->check = adler32(0L, Z_NULL, 0);
+            state->mode = hold & 0x200 ? DICTID : TYPE;
+            INITBITS();
+            break;
+#ifdef GUNZIP
+        case FLAGS:
+            NEEDBITS(16);
+            state->flags = (int)(hold);
+            if ((state->flags & 0xff) != Z_DEFLATED) {
+                strm->msg = (char *)"unknown compression method";
+                state->mode = BAD;
+                break;
+            }
+            if (state->flags & 0xe000) {
+                strm->msg = (char *)"unknown header flags set";
+                state->mode = BAD;
+                break;
+            }
+            if (state->head != Z_NULL)
+                state->head->text = (int)((hold >> 8) & 1);
+            if ((state->flags & 0x0200) && (state->wrap & 4))
+                CRC2(state->check, hold);
+            INITBITS();
+            state->mode = TIME;
+        case TIME:
+            NEEDBITS(32);
+            if (state->head != Z_NULL)
+                state->head->time = hold;
+            if ((state->flags & 0x0200) && (state->wrap & 4))
+                CRC4(state->check, hold);
+            INITBITS();
+            state->mode = OS;
+        case OS:
+            NEEDBITS(16);
+            if (state->head != Z_NULL) {
+                state->head->xflags = (int)(hold & 0xff);
+                state->head->os = (int)(hold >> 8);
+            }
+            if ((state->flags & 0x0200) && (state->wrap & 4))
+                CRC2(state->check, hold);
+            INITBITS();
+            state->mode = EXLEN;
+        case EXLEN:
+            if (state->flags & 0x0400) {
+                NEEDBITS(16);
+                state->length = (unsigned)(hold);
+                if (state->head != Z_NULL)
+                    state->head->extra_len = (unsigned)hold;
+                if ((state->flags & 0x0200) && (state->wrap & 4))
+                    CRC2(state->check, hold);
+                INITBITS();
+            }
+            else if (state->head != Z_NULL)
+                state->head->extra = Z_NULL;
+            state->mode = EXTRA;
+        case EXTRA:
+            if (state->flags & 0x0400) {
+                copy = state->length;
+                if (copy > have) copy = have;
+                if (copy) {
+                    if (state->head != Z_NULL &&
+                        state->head->extra != Z_NULL) {
+                        len = state->head->extra_len - state->length;
+                        zmemcpy(state->head->extra + len, next,
+                                len + copy > state->head->extra_max ?
+                                state->head->extra_max - len : copy);
+                    }
+                    if ((state->flags & 0x0200) && (state->wrap & 4))
+                        state->check = crc32(state->check, next, copy);
+                    have -= copy;
+                    next += copy;
+                    state->length -= copy;
+                }
+                if (state->length) goto inf_leave;
+            }
+            state->length = 0;
+            state->mode = NAME;
+        case NAME:
+            if (state->flags & 0x0800) {
+                if (have == 0) goto inf_leave;
+                copy = 0;
+                do {
+                    len = (unsigned)(next[copy++]);
+                    if (state->head != Z_NULL &&
+                            state->head->name != Z_NULL &&
+                            state->length < state->head->name_max)
+                        state->head->name[state->length++] = (Bytef)len;
+                } while (len && copy < have);
+                if ((state->flags & 0x0200) && (state->wrap & 4))
+                    state->check = crc32(state->check, next, copy);
+                have -= copy;
+                next += copy;
+                if (len) goto inf_leave;
+            }
+            else if (state->head != Z_NULL)
+                state->head->name = Z_NULL;
+            state->length = 0;
+            state->mode = COMMENT;
+        case COMMENT:
+            if (state->flags & 0x1000) {
+                if (have == 0) goto inf_leave;
+                copy = 0;
+                do {
+                    len = (unsigned)(next[copy++]);
+                    if (state->head != Z_NULL &&
+                            state->head->comment != Z_NULL &&
+                            state->length < state->head->comm_max)
+                        state->head->comment[state->length++] = (Bytef)len;
+                } while (len && copy < have);
+                if ((state->flags & 0x0200) && (state->wrap & 4))
+                    state->check = crc32(state->check, next, copy);
+                have -= copy;
+                next += copy;
+                if (len) goto inf_leave;
+            }
+            else if (state->head != Z_NULL)
+                state->head->comment = Z_NULL;
+            state->mode = HCRC;
+        case HCRC:
+            if (state->flags & 0x0200) {
+                NEEDBITS(16);
+                if ((state->wrap & 4) && hold != (state->check & 0xffff)) {
+                    strm->msg = (char *)"header crc mismatch";
+                    state->mode = BAD;
+                    break;
+                }
+                INITBITS();
+            }
+            if (state->head != Z_NULL) {
+                state->head->hcrc = (int)((state->flags >> 9) & 1);
+                state->head->done = 1;
+            }
+            strm->adler = state->check = crc32(0L, Z_NULL, 0);
+            state->mode = TYPE;
+            break;
+#endif
+        case DICTID:
+            NEEDBITS(32);
+            strm->adler = state->check = ZSWAP32(hold);
+            INITBITS();
+            state->mode = DICT;
+        case DICT:
+            if (state->havedict == 0) {
+                RESTORE();
+                return Z_NEED_DICT;
+            }
+            strm->adler = state->check = adler32(0L, Z_NULL, 0);
+            state->mode = TYPE;
+        case TYPE:
+            if (flush == Z_BLOCK || flush == Z_TREES) goto inf_leave;
+        case TYPEDO:
+            if (state->last) {
+                BYTEBITS();
+                state->mode = CHECK;
+                break;
+            }
+            NEEDBITS(3);
+            state->last = BITS(1);
+            DROPBITS(1);
+            switch (BITS(2)) {
+            case 0:                             /* stored block */
+                Tracev((stderr, "inflate:     stored block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = STORED;
+                break;
+            case 1:                             /* fixed block */
+                fixedtables(state);
+                Tracev((stderr, "inflate:     fixed codes block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = LEN_;             /* decode codes */
+                if (flush == Z_TREES) {
+                    DROPBITS(2);
+                    goto inf_leave;
+                }
+                break;
+            case 2:                             /* dynamic block */
+                Tracev((stderr, "inflate:     dynamic codes block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = TABLE;
+                break;
+            case 3:
+                strm->msg = (char *)"invalid block type";
+                state->mode = BAD;
+            }
+            DROPBITS(2);
+            break;
+        case STORED:
+            BYTEBITS();                         /* go to byte boundary */
+            NEEDBITS(32);
+            if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) {
+                strm->msg = (char *)"invalid stored block lengths";
+                state->mode = BAD;
+                break;
+            }
+            state->length = (unsigned)hold & 0xffff;
+            Tracev((stderr, "inflate:       stored length %u\n",
+                    state->length));
+            INITBITS();
+            state->mode = COPY_;
+            if (flush == Z_TREES) goto inf_leave;
+        case COPY_:
+            state->mode = COPY;
+        case COPY:
+            copy = state->length;
+            if (copy) {
+                if (copy > have) copy = have;
+                if (copy > left) copy = left;
+                if (copy == 0) goto inf_leave;
+                zmemcpy(put, next, copy);
+                have -= copy;
+                next += copy;
+                left -= copy;
+                put += copy;
+                state->length -= copy;
+                break;
+            }
+            Tracev((stderr, "inflate:       stored end\n"));
+            state->mode = TYPE;
+            break;
+        case TABLE:
+            NEEDBITS(14);
+            state->nlen = BITS(5) + 257;
+            DROPBITS(5);
+            state->ndist = BITS(5) + 1;
+            DROPBITS(5);
+            state->ncode = BITS(4) + 4;
+            DROPBITS(4);
+#ifndef PKZIP_BUG_WORKAROUND
+            if (state->nlen > 286 || state->ndist > 30) {
+                strm->msg = (char *)"too many length or distance symbols";
+                state->mode = BAD;
+                break;
+            }
+#endif
+            Tracev((stderr, "inflate:       table sizes ok\n"));
+            state->have = 0;
+            state->mode = LENLENS;
+        case LENLENS:
+            while (state->have < state->ncode) {
+                NEEDBITS(3);
+                state->lens[order[state->have++]] = (unsigned short)BITS(3);
+                DROPBITS(3);
+            }
+            while (state->have < 19)
+                state->lens[order[state->have++]] = 0;
+            state->next = state->codes;
+            state->lencode = (const code FAR *)(state->next);
+            state->lenbits = 7;
+            ret = inflate_table(CODES, state->lens, 19, &(state->next),
+                                &(state->lenbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid code lengths set";
+                state->mode = BAD;
+                break;
+            }
+            Tracev((stderr, "inflate:       code lengths ok\n"));
+            state->have = 0;
+            state->mode = CODELENS;
+        case CODELENS:
+            while (state->have < state->nlen + state->ndist) {
+                for (;;) {
+                    here = state->lencode[BITS(state->lenbits)];
+                    if ((unsigned)(here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                if (here.val < 16) {
+                    DROPBITS(here.bits);
+                    state->lens[state->have++] = here.val;
+                }
+                else {
+                    if (here.val == 16) {
+                        NEEDBITS(here.bits + 2);
+                        DROPBITS(here.bits);
+                        if (state->have == 0) {
+                            strm->msg = (char *)"invalid bit length repeat";
+                            state->mode = BAD;
+                            break;
+                        }
+                        len = state->lens[state->have - 1];
+                        copy = 3 + BITS(2);
+                        DROPBITS(2);
+                    }
+                    else if (here.val == 17) {
+                        NEEDBITS(here.bits + 3);
+                        DROPBITS(here.bits);
+                        len = 0;
+                        copy = 3 + BITS(3);
+                        DROPBITS(3);
+                    }
+                    else {
+                        NEEDBITS(here.bits + 7);
+                        DROPBITS(here.bits);
+                        len = 0;
+                        copy = 11 + BITS(7);
+                        DROPBITS(7);
+                    }
+                    if (state->have + copy > state->nlen + state->ndist) {
+                        strm->msg = (char *)"invalid bit length repeat";
+                        state->mode = BAD;
+                        break;
+                    }
+                    while (copy--)
+                        state->lens[state->have++] = (unsigned short)len;
+                }
+            }
+
+            /* handle error breaks in while */
+            if (state->mode == BAD) break;
+
+            /* check for end-of-block code (better have one) */
+            if (state->lens[256] == 0) {
+                strm->msg = (char *)"invalid code -- missing end-of-block";
+                state->mode = BAD;
+                break;
+            }
+
+            /* build code tables -- note: do not change the lenbits or distbits
+               values here (9 and 6) without reading the comments in inftrees.h
+               concerning the ENOUGH constants, which depend on those values */
+            state->next = state->codes;
+            state->lencode = (const code FAR *)(state->next);
+            state->lenbits = 9;
+            ret = inflate_table(LENS, state->lens, state->nlen, &(state->next),
+                                &(state->lenbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid literal/lengths set";
+                state->mode = BAD;
+                break;
+            }
+            state->distcode = (const code FAR *)(state->next);
+            state->distbits = 6;
+            ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist,
+                            &(state->next), &(state->distbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid distances set";
+                state->mode = BAD;
+                break;
+            }
+            Tracev((stderr, "inflate:       codes ok\n"));
+            state->mode = LEN_;
+            if (flush == Z_TREES) goto inf_leave;
+        case LEN_:
+            state->mode = LEN;
+        case LEN:
+            if (have >= 6 && left >= 258) {
+                RESTORE();
+                inflate_fast(strm, out);
+                LOAD();
+                if (state->mode == TYPE)
+                    state->back = -1;
+                break;
+            }
+            state->back = 0;
+            for (;;) {
+                here = state->lencode[BITS(state->lenbits)];
+                if ((unsigned)(here.bits) <= bits) break;
+                PULLBYTE();
+            }
+            if (here.op && (here.op & 0xf0) == 0) {
+                last = here;
+                for (;;) {
+                    here = state->lencode[last.val +
+                            (BITS(last.bits + last.op) >> last.bits)];
+                    if ((unsigned)(last.bits + here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                DROPBITS(last.bits);
+                state->back += last.bits;
+            }
+            DROPBITS(here.bits);
+            state->back += here.bits;
+            state->length = (unsigned)here.val;
+            if ((int)(here.op) == 0) {
+                Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+                        "inflate:         literal '%c'\n" :
+                        "inflate:         literal 0x%02x\n", here.val));
+                state->mode = LIT;
+                break;
+            }
+            if (here.op & 32) {
+                Tracevv((stderr, "inflate:         end of block\n"));
+                state->back = -1;
+                state->mode = TYPE;
+                break;
+            }
+            if (here.op & 64) {
+                strm->msg = (char *)"invalid literal/length code";
+                state->mode = BAD;
+                break;
+            }
+            state->extra = (unsigned)(here.op) & 15;
+            state->mode = LENEXT;
+        case LENEXT:
+            if (state->extra) {
+                NEEDBITS(state->extra);
+                state->length += BITS(state->extra);
+                DROPBITS(state->extra);
+                state->back += state->extra;
+            }
+            Tracevv((stderr, "inflate:         length %u\n", state->length));
+            state->was = state->length;
+            state->mode = DIST;
+        case DIST:
+            for (;;) {
+                here = state->distcode[BITS(state->distbits)];
+                if ((unsigned)(here.bits) <= bits) break;
+                PULLBYTE();
+            }
+            if ((here.op & 0xf0) == 0) {
+                last = here;
+                for (;;) {
+                    here = state->distcode[last.val +
+                            (BITS(last.bits + last.op) >> last.bits)];
+                    if ((unsigned)(last.bits + here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                DROPBITS(last.bits);
+                state->back += last.bits;
+            }
+            DROPBITS(here.bits);
+            state->back += here.bits;
+            if (here.op & 64) {
+                strm->msg = (char *)"invalid distance code";
+                state->mode = BAD;
+                break;
+            }
+            state->offset = (unsigned)here.val;
+            state->extra = (unsigned)(here.op) & 15;
+            state->mode = DISTEXT;
+        case DISTEXT:
+            if (state->extra) {
+                NEEDBITS(state->extra);
+                state->offset += BITS(state->extra);
+                DROPBITS(state->extra);
+                state->back += state->extra;
+            }
+#ifdef INFLATE_STRICT
+            if (state->offset > state->dmax) {
+                strm->msg = (char *)"invalid distance too far back";
+                state->mode = BAD;
+                break;
+            }
+#endif
+            Tracevv((stderr, "inflate:         distance %u\n", state->offset));
+            state->mode = MATCH;
+        case MATCH:
+            if (left == 0) goto inf_leave;
+            copy = out - left;
+            if (state->offset > copy) {         /* copy from window */
+                copy = state->offset - copy;
+                if (copy > state->whave) {
+                    if (state->sane) {
+                        strm->msg = (char *)"invalid distance too far back";
+                        state->mode = BAD;
+                        break;
+                    }
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+                    Trace((stderr, "inflate.c too far\n"));
+                    copy -= state->whave;
+                    if (copy > state->length) copy = state->length;
+                    if (copy > left) copy = left;
+                    left -= copy;
+                    state->length -= copy;
+                    do {
+                        *put++ = 0;
+                    } while (--copy);
+                    if (state->length == 0) state->mode = LEN;
+                    break;
+#endif
+                }
+                if (copy > state->wnext) {
+                    copy -= state->wnext;
+                    from = state->window + (state->wsize - copy);
+                }
+                else
+                    from = state->window + (state->wnext - copy);
+                if (copy > state->length) copy = state->length;
+            }
+            else {                              /* copy from output */
+                from = put - state->offset;
+                copy = state->length;
+            }
+            if (copy > left) copy = left;
+            left -= copy;
+            state->length -= copy;
+            do {
+                *put++ = *from++;
+            } while (--copy);
+            if (state->length == 0) state->mode = LEN;
+            break;
+        case LIT:
+            if (left == 0) goto inf_leave;
+            *put++ = (unsigned char)(state->length);
+            left--;
+            state->mode = LEN;
+            break;
+        case CHECK:
+            if (state->wrap) {
+                NEEDBITS(32);
+                out -= left;
+                strm->total_out += out;
+                state->total += out;
+                if ((state->wrap & 4) && out)
+                    strm->adler = state->check =
+                        UPDATE(state->check, put - out, out);
+                out = left;
+                if ((state->wrap & 4) && (
+#ifdef GUNZIP
+                     state->flags ? hold :
+#endif
+                     ZSWAP32(hold)) != state->check) {
+                    strm->msg = (char *)"incorrect data check";
+                    state->mode = BAD;
+                    break;
+                }
+                INITBITS();
+                Tracev((stderr, "inflate:   check matches trailer\n"));
+            }
+#ifdef GUNZIP
+            state->mode = LENGTH;
+        case LENGTH:
+            if (state->wrap && state->flags) {
+                NEEDBITS(32);
+                if (hold != (state->total & 0xffffffffUL)) {
+                    strm->msg = (char *)"incorrect length check";
+                    state->mode = BAD;
+                    break;
+                }
+                INITBITS();
+                Tracev((stderr, "inflate:   length matches trailer\n"));
+            }
+#endif
+            state->mode = DONE;
+        case DONE:
+            ret = Z_STREAM_END;
+            goto inf_leave;
+        case BAD:
+            ret = Z_DATA_ERROR;
+            goto inf_leave;
+        case MEM:
+            return Z_MEM_ERROR;
+        case SYNC:
+        default:
+            return Z_STREAM_ERROR;
+        }
+
+    /*
+       Return from inflate(), updating the total counts and the check value.
+       If there was no progress during the inflate() call, return a buffer
+       error.  Call updatewindow() to create and/or update the window state.
+       Note: a memory error from inflate() is non-recoverable.
+     */
+  inf_leave:
+    RESTORE();
+    if (state->wsize || (out != strm->avail_out && state->mode < BAD &&
+            (state->mode < CHECK || flush != Z_FINISH)))
+        if (updatewindow(strm, strm->next_out, out - strm->avail_out)) {
+            state->mode = MEM;
+            return Z_MEM_ERROR;
+        }
+    in -= strm->avail_in;
+    out -= strm->avail_out;
+    strm->total_in += in;
+    strm->total_out += out;
+    state->total += out;
+    if ((state->wrap & 4) && out)
+        strm->adler = state->check =
+            UPDATE(state->check, strm->next_out - out, out);
+    strm->data_type = (int)state->bits + (state->last ? 64 : 0) +
+                      (state->mode == TYPE ? 128 : 0) +
+                      (state->mode == LEN_ || state->mode == COPY_ ? 256 : 0);
+    if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK)
+        ret = Z_BUF_ERROR;
+    return ret;
+}
+
+int ZEXPORT inflateEnd(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+    if (inflateStateCheck(strm))
+        return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if (state->window != Z_NULL) ZFREE(strm, state->window);
+    ZFREE(strm, strm->state);
+    strm->state = Z_NULL;
+    Tracev((stderr, "inflate: end\n"));
+    return Z_OK;
+}
+
+int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength)
+z_streamp strm;
+Bytef *dictionary;
+uInt *dictLength;
+{
+    struct inflate_state FAR *state;
+
+    /* check state */
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+
+    /* copy dictionary */
+    if (state->whave && dictionary != Z_NULL) {
+        zmemcpy(dictionary, state->window + state->wnext,
+                state->whave - state->wnext);
+        zmemcpy(dictionary + state->whave - state->wnext,
+                state->window, state->wnext);
+    }
+    if (dictLength != Z_NULL)
+        *dictLength = state->whave;
+    return Z_OK;
+}
+
+int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength)
+z_streamp strm;
+const Bytef *dictionary;
+uInt dictLength;
+{
+    struct inflate_state FAR *state;
+    unsigned long dictid;
+    int ret;
+
+    /* check state */
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if (state->wrap != 0 && state->mode != DICT)
+        return Z_STREAM_ERROR;
+
+    /* check for correct dictionary identifier */
+    if (state->mode == DICT) {
+        dictid = adler32(0L, Z_NULL, 0);
+        dictid = adler32(dictid, dictionary, dictLength);
+        if (dictid != state->check)
+            return Z_DATA_ERROR;
+    }
+
+    /* copy dictionary to window using updatewindow(), which will amend the
+       existing dictionary if appropriate */
+    ret = updatewindow(strm, dictionary + dictLength, dictLength);
+    if (ret) {
+        state->mode = MEM;
+        return Z_MEM_ERROR;
+    }
+    state->havedict = 1;
+    Tracev((stderr, "inflate:   dictionary set\n"));
+    return Z_OK;
+}
+
+int ZEXPORT inflateGetHeader(strm, head)
+z_streamp strm;
+gz_headerp head;
+{
+    struct inflate_state FAR *state;
+
+    /* check state */
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if ((state->wrap & 2) == 0) return Z_STREAM_ERROR;
+
+    /* save header structure */
+    state->head = head;
+    head->done = 0;
+    return Z_OK;
+}
+
+/*
+   Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff.  Return when found
+   or when out of input.  When called, *have is the number of pattern bytes
+   found in order so far, in 0..3.  On return *have is updated to the new
+   state.  If on return *have equals four, then the pattern was found and the
+   return value is how many bytes were read including the last byte of the
+   pattern.  If *have is less than four, then the pattern has not been found
+   yet and the return value is len.  In the latter case, syncsearch() can be
+   called again with more data and the *have state.  *have is initialized to
+   zero for the first call.
+ */
+local unsigned syncsearch(have, buf, len)
+unsigned FAR *have;
+const unsigned char FAR *buf;
+unsigned len;
+{
+    unsigned got;
+    unsigned next;
+
+    got = *have;
+    next = 0;
+    while (next < len && got < 4) {
+        if ((int)(buf[next]) == (got < 2 ? 0 : 0xff))
+            got++;
+        else if (buf[next])
+            got = 0;
+        else
+            got = 4 - got;
+        next++;
+    }
+    *have = got;
+    return next;
+}
+
+int ZEXPORT inflateSync(strm)
+z_streamp strm;
+{
+    unsigned len;               /* number of bytes to look at or looked at */
+    unsigned long in, out;      /* temporary to save total_in and total_out */
+    unsigned char buf[4];       /* to restore bit buffer to byte string */
+    struct inflate_state FAR *state;
+
+    /* check parameters */
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR;
+
+    /* if first time, start search in bit buffer */
+    if (state->mode != SYNC) {
+        state->mode = SYNC;
+        state->hold <<= state->bits & 7;
+        state->bits -= state->bits & 7;
+        len = 0;
+        while (state->bits >= 8) {
+            buf[len++] = (unsigned char)(state->hold);
+            state->hold >>= 8;
+            state->bits -= 8;
+        }
+        state->have = 0;
+        syncsearch(&(state->have), buf, len);
+    }
+
+    /* search available input */
+    len = syncsearch(&(state->have), strm->next_in, strm->avail_in);
+    strm->avail_in -= len;
+    strm->next_in += len;
+    strm->total_in += len;
+
+    /* return no joy or set up to restart inflate() on a new block */
+    if (state->have != 4) return Z_DATA_ERROR;
+    in = strm->total_in;  out = strm->total_out;
+    inflateReset(strm);
+    strm->total_in = in;  strm->total_out = out;
+    state->mode = TYPE;
+    return Z_OK;
+}
+
+/*
+   Returns true if inflate is currently at the end of a block generated by
+   Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
+   implementation to provide an additional safety check. PPP uses
+   Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored
+   block. When decompressing, PPP checks that at the end of input packet,
+   inflate is waiting for these length bytes.
+ */
+int ZEXPORT inflateSyncPoint(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    return state->mode == STORED && state->bits == 0;
+}
+
+int ZEXPORT inflateCopy(dest, source)
+z_streamp dest;
+z_streamp source;
+{
+    struct inflate_state FAR *state;
+    struct inflate_state FAR *copy;
+    unsigned char FAR *window;
+    unsigned wsize;
+
+    /* check input */
+    if (inflateStateCheck(source) || dest == Z_NULL)
+        return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)source->state;
+
+    /* allocate space */
+    copy = (struct inflate_state FAR *)
+           ZALLOC(source, 1, sizeof(struct inflate_state));
+    if (copy == Z_NULL) return Z_MEM_ERROR;
+    window = Z_NULL;
+    if (state->window != Z_NULL) {
+        window = (unsigned char FAR *)
+                 ZALLOC(source, 1U << state->wbits, sizeof(unsigned char));
+        if (window == Z_NULL) {
+            ZFREE(source, copy);
+            return Z_MEM_ERROR;
+        }
+    }
+
+    /* copy state */
+    zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream));
+    zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state));
+    copy->strm = dest;
+    if (state->lencode >= state->codes &&
+        state->lencode <= state->codes + ENOUGH - 1) {
+        copy->lencode = copy->codes + (state->lencode - state->codes);
+        copy->distcode = copy->codes + (state->distcode - state->codes);
+    }
+    copy->next = copy->codes + (state->next - state->codes);
+    if (window != Z_NULL) {
+        wsize = 1U << state->wbits;
+        zmemcpy(window, state->window, wsize);
+    }
+    copy->window = window;
+    dest->state = (struct internal_state FAR *)copy;
+    return Z_OK;
+}
+
+int ZEXPORT inflateUndermine(strm, subvert)
+z_streamp strm;
+int subvert;
+{
+    struct inflate_state FAR *state;
+
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+    state->sane = !subvert;
+    return Z_OK;
+#else
+    (void)subvert;
+    state->sane = 1;
+    return Z_DATA_ERROR;
+#endif
+}
+
+int ZEXPORT inflateValidate(strm, check)
+z_streamp strm;
+int check;
+{
+    struct inflate_state FAR *state;
+
+    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if (check)
+        state->wrap |= 4;
+    else
+        state->wrap &= ~4;
+    return Z_OK;
+}
+
+long ZEXPORT inflateMark(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+
+    if (inflateStateCheck(strm))
+        return -(1L << 16);
+    state = (struct inflate_state FAR *)strm->state;
+    return (long)(((unsigned long)((long)state->back)) << 16) +
+        (state->mode == COPY ? state->length :
+            (state->mode == MATCH ? state->was - state->length : 0));
+}
+
+unsigned long ZEXPORT inflateCodesUsed(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+    if (inflateStateCheck(strm)) return (unsigned long)-1;
+    state = (struct inflate_state FAR *)strm->state;
+    return (unsigned long)(state->next - state->codes);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/inflate.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,125 @@
+/* inflate.h -- internal inflate state definition
+ * Copyright (C) 1995-2016 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+/* define NO_GZIP when compiling if you want to disable gzip header and
+   trailer decoding by inflate().  NO_GZIP would be used to avoid linking in
+   the crc code when it is not needed.  For shared libraries, gzip decoding
+   should be left enabled. */
+#ifndef NO_GZIP
+#  define GUNZIP
+#endif
+
+/* Possible inflate modes between inflate() calls */
+typedef enum {
+    HEAD = 16180,   /* i: waiting for magic header */
+    FLAGS,      /* i: waiting for method and flags (gzip) */
+    TIME,       /* i: waiting for modification time (gzip) */
+    OS,         /* i: waiting for extra flags and operating system (gzip) */
+    EXLEN,      /* i: waiting for extra length (gzip) */
+    EXTRA,      /* i: waiting for extra bytes (gzip) */
+    NAME,       /* i: waiting for end of file name (gzip) */
+    COMMENT,    /* i: waiting for end of comment (gzip) */
+    HCRC,       /* i: waiting for header crc (gzip) */
+    DICTID,     /* i: waiting for dictionary check value */
+    DICT,       /* waiting for inflateSetDictionary() call */
+        TYPE,       /* i: waiting for type bits, including last-flag bit */
+        TYPEDO,     /* i: same, but skip check to exit inflate on new block */
+        STORED,     /* i: waiting for stored size (length and complement) */
+        COPY_,      /* i/o: same as COPY below, but only first time in */
+        COPY,       /* i/o: waiting for input or output to copy stored block */
+        TABLE,      /* i: waiting for dynamic block table lengths */
+        LENLENS,    /* i: waiting for code length code lengths */
+        CODELENS,   /* i: waiting for length/lit and distance code lengths */
+            LEN_,       /* i: same as LEN below, but only first time in */
+            LEN,        /* i: waiting for length/lit/eob code */
+            LENEXT,     /* i: waiting for length extra bits */
+            DIST,       /* i: waiting for distance code */
+            DISTEXT,    /* i: waiting for distance extra bits */
+            MATCH,      /* o: waiting for output space to copy string */
+            LIT,        /* o: waiting for output space to write literal */
+    CHECK,      /* i: waiting for 32-bit check value */
+    LENGTH,     /* i: waiting for 32-bit length (gzip) */
+    DONE,       /* finished check, done -- remain here until reset */
+    BAD,        /* got a data error -- remain here until reset */
+    MEM,        /* got an inflate() memory error -- remain here until reset */
+    SYNC        /* looking for synchronization bytes to restart inflate() */
+} inflate_mode;
+
+/*
+    State transitions between above modes -
+
+    (most modes can go to BAD or MEM on error -- not shown for clarity)
+
+    Process header:
+        HEAD -> (gzip) or (zlib) or (raw)
+        (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME -> COMMENT ->
+                  HCRC -> TYPE
+        (zlib) -> DICTID or TYPE
+        DICTID -> DICT -> TYPE
+        (raw) -> TYPEDO
+    Read deflate blocks:
+            TYPE -> TYPEDO -> STORED or TABLE or LEN_ or CHECK
+            STORED -> COPY_ -> COPY -> TYPE
+            TABLE -> LENLENS -> CODELENS -> LEN_
+            LEN_ -> LEN
+    Read deflate codes in fixed or dynamic block:
+                LEN -> LENEXT or LIT or TYPE
+                LENEXT -> DIST -> DISTEXT -> MATCH -> LEN
+                LIT -> LEN
+    Process trailer:
+        CHECK -> LENGTH -> DONE
+ */
+
+/* State maintained between inflate() calls -- approximately 7K bytes, not
+   including the allocated sliding window, which is up to 32K bytes. */
+struct inflate_state {
+    z_streamp strm;             /* pointer back to this zlib stream */
+    inflate_mode mode;          /* current inflate mode */
+    int last;                   /* true if processing last block */
+    int wrap;                   /* bit 0 true for zlib, bit 1 true for gzip,
+                                   bit 2 true to validate check value */
+    int havedict;               /* true if dictionary provided */
+    int flags;                  /* gzip header method and flags (0 if zlib) */
+    unsigned dmax;              /* zlib header max distance (INFLATE_STRICT) */
+    unsigned long check;        /* protected copy of check value */
+    unsigned long total;        /* protected copy of output count */
+    gz_headerp head;            /* where to save gzip header information */
+        /* sliding window */
+    unsigned wbits;             /* log base 2 of requested window size */
+    unsigned wsize;             /* window size or zero if not using window */
+    unsigned whave;             /* valid bytes in the window */
+    unsigned wnext;             /* window write index */
+    unsigned char FAR *window;  /* allocated sliding window, if needed */
+        /* bit accumulator */
+    unsigned long hold;         /* input bit accumulator */
+    unsigned bits;              /* number of bits in "in" */
+        /* for string and stored block copying */
+    unsigned length;            /* literal or length of data to copy */
+    unsigned offset;            /* distance back to copy string from */
+        /* for table and code decoding */
+    unsigned extra;             /* extra bits needed */
+        /* fixed and dynamic code tables */
+    code const FAR *lencode;    /* starting table for length/literal codes */
+    code const FAR *distcode;   /* starting table for distance codes */
+    unsigned lenbits;           /* index bits for lencode */
+    unsigned distbits;          /* index bits for distcode */
+        /* dynamic table building */
+    unsigned ncode;             /* number of code length code lengths */
+    unsigned nlen;              /* number of length code lengths */
+    unsigned ndist;             /* number of distance code lengths */
+    unsigned have;              /* number of code lengths in lens[] */
+    code FAR *next;             /* next available space in codes[] */
+    unsigned short lens[320];   /* temporary storage for code lengths */
+    unsigned short work[288];   /* work area for code table building */
+    code codes[ENOUGH];         /* space for code tables */
+    int sane;                   /* if false, allow invalid distance too far */
+    int back;                   /* bits back of last unprocessed length/lit */
+    unsigned was;               /* initial length of match */
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/inftrees.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,304 @@
+/* inftrees.c -- generate Huffman trees for efficient decoding
+ * Copyright (C) 1995-2017 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+
+#define MAXBITS 15
+
+const char inflate_copyright[] =
+   " inflate 1.2.11 Copyright 1995-2017 Mark Adler ";
+/*
+  If you use the zlib library in a product, an acknowledgment is welcome
+  in the documentation of your product. If for some reason you cannot
+  include such an acknowledgment, I would appreciate that you keep this
+  copyright string in the executable of your product.
+ */
+
+/*
+   Build a set of tables to decode the provided canonical Huffman code.
+   The code lengths are lens[0..codes-1].  The result starts at *table,
+   whose indices are 0..2^bits-1.  work is a writable array of at least
+   lens shorts, which is used as a work area.  type is the type of code
+   to be generated, CODES, LENS, or DISTS.  On return, zero is success,
+   -1 is an invalid code, and +1 means that ENOUGH isn't enough.  table
+   on return points to the next available entry's address.  bits is the
+   requested root table index bits, and on return it is the actual root
+   table index bits.  It will differ if the request is greater than the
+   longest code or if it is less than the shortest code.
+ */
+int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work)
+codetype type;
+unsigned short FAR *lens;
+unsigned codes;
+code FAR * FAR *table;
+unsigned FAR *bits;
+unsigned short FAR *work;
+{
+    unsigned len;               /* a code's length in bits */
+    unsigned sym;               /* index of code symbols */
+    unsigned min, max;          /* minimum and maximum code lengths */
+    unsigned root;              /* number of index bits for root table */
+    unsigned curr;              /* number of index bits for current table */
+    unsigned drop;              /* code bits to drop for sub-table */
+    int left;                   /* number of prefix codes available */
+    unsigned used;              /* code entries in table used */
+    unsigned huff;              /* Huffman code */
+    unsigned incr;              /* for incrementing code, index */
+    unsigned fill;              /* index for replicating entries */
+    unsigned low;               /* low bits for current root entry */
+    unsigned mask;              /* mask for low root bits */
+    code here;                  /* table entry for duplication */
+    code FAR *next;             /* next available space in table */
+    const unsigned short FAR *base;     /* base value table to use */
+    const unsigned short FAR *extra;    /* extra bits table to use */
+    unsigned match;             /* use base and extra for symbol >= match */
+    unsigned short count[MAXBITS+1];    /* number of codes of each length */
+    unsigned short offs[MAXBITS+1];     /* offsets in table for each length */
+    static const unsigned short lbase[31] = { /* Length codes 257..285 base */
+        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, 0, 0};
+    static const unsigned short lext[31] = { /* Length codes 257..285 extra */
+        16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+        19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 77, 202};
+    static const unsigned short dbase[32] = { /* Distance codes 0..29 base */
+        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, 0, 0};
+    static const unsigned short dext[32] = { /* Distance codes 0..29 extra */
+        16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+        23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+        28, 28, 29, 29, 64, 64};
+
+    /*
+       Process a set of code lengths to create a canonical Huffman code.  The
+       code lengths are lens[0..codes-1].  Each length corresponds to the
+       symbols 0..codes-1.  The Huffman code is generated by first sorting the
+       symbols by length from short to long, and retaining the symbol order
+       for codes with equal lengths.  Then the code starts with all zero bits
+       for the first code of the shortest length, and the codes are integer
+       increments for the same length, and zeros are appended as the length
+       increases.  For the deflate format, these bits are stored backwards
+       from their more natural integer increment ordering, and so when the
+       decoding tables are built in the large loop below, the integer codes
+       are incremented backwards.
+
+       This routine assumes, but does not check, that all of the entries in
+       lens[] are in the range 0..MAXBITS.  The caller must assure this.
+       1..MAXBITS is interpreted as that code length.  zero means that that
+       symbol does not occur in this code.
+
+       The codes are sorted by computing a count of codes for each length,
+       creating from that a table of starting indices for each length in the
+       sorted table, and then entering the symbols in order in the sorted
+       table.  The sorted table is work[], with that space being provided by
+       the caller.
+
+       The length counts are used for other purposes as well, i.e. finding
+       the minimum and maximum length codes, determining if there are any
+       codes at all, checking for a valid set of lengths, and looking ahead
+       at length counts to determine sub-table sizes when building the
+       decoding tables.
+     */
+
+    /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+    for (len = 0; len <= MAXBITS; len++)
+        count[len] = 0;
+    for (sym = 0; sym < codes; sym++)
+        count[lens[sym]]++;
+
+    /* bound code lengths, force root to be within code lengths */
+    root = *bits;
+    for (max = MAXBITS; max >= 1; max--)
+        if (count[max] != 0) break;
+    if (root > max) root = max;
+    if (max == 0) {                     /* no symbols to code at all */
+        here.op = (unsigned char)64;    /* invalid code marker */
+        here.bits = (unsigned char)1;
+        here.val = (unsigned short)0;
+        *(*table)++ = here;             /* make a table to force an error */
+        *(*table)++ = here;
+        *bits = 1;
+        return 0;     /* no symbols, but wait for decoding to report error */
+    }
+    for (min = 1; min < max; min++)
+        if (count[min] != 0) break;
+    if (root < min) root = min;
+
+    /* check for an over-subscribed or incomplete set of lengths */
+    left = 1;
+    for (len = 1; len <= MAXBITS; len++) {
+        left <<= 1;
+        left -= count[len];
+        if (left < 0) return -1;        /* over-subscribed */
+    }
+    if (left > 0 && (type == CODES || max != 1))
+        return -1;                      /* incomplete set */
+
+    /* generate offsets into symbol table for each length for sorting */
+    offs[1] = 0;
+    for (len = 1; len < MAXBITS; len++)
+        offs[len + 1] = offs[len] + count[len];
+
+    /* sort symbols by length, by symbol order within each length */
+    for (sym = 0; sym < codes; sym++)
+        if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym;
+
+    /*
+       Create and fill in decoding tables.  In this loop, the table being
+       filled is at next and has curr index bits.  The code being used is huff
+       with length len.  That code is converted to an index by dropping drop
+       bits off of the bottom.  For codes where len is less than drop + curr,
+       those top drop + curr - len bits are incremented through all values to
+       fill the table with replicated entries.
+
+       root is the number of index bits for the root table.  When len exceeds
+       root, sub-tables are created pointed to by the root entry with an index
+       of the low root bits of huff.  This is saved in low to check for when a
+       new sub-table should be started.  drop is zero when the root table is
+       being filled, and drop is root when sub-tables are being filled.
+
+       When a new sub-table is needed, it is necessary to look ahead in the
+       code lengths to determine what size sub-table is needed.  The length
+       counts are used for this, and so count[] is decremented as codes are
+       entered in the tables.
+
+       used keeps track of how many table entries have been allocated from the
+       provided *table space.  It is checked for LENS and DIST tables against
+       the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+       the initial root table size constants.  See the comments in inftrees.h
+       for more information.
+
+       sym increments through all symbols, and the loop terminates when
+       all codes of length max, i.e. all codes, have been processed.  This
+       routine permits incomplete codes, so another loop after this one fills
+       in the rest of the decoding tables with invalid code markers.
+     */
+
+    /* set up for code type */
+    switch (type) {
+    case CODES:
+        base = extra = work;    /* dummy value--not used */
+        match = 20;
+        break;
+    case LENS:
+        base = lbase;
+        extra = lext;
+        match = 257;
+        break;
+    default:    /* DISTS */
+        base = dbase;
+        extra = dext;
+        match = 0;
+    }
+
+    /* initialize state for loop */
+    huff = 0;                   /* starting code */
+    sym = 0;                    /* starting code symbol */
+    len = min;                  /* starting code length */
+    next = *table;              /* current table to fill in */
+    curr = root;                /* current table index bits */
+    drop = 0;                   /* current bits to drop from code for index */
+    low = (unsigned)(-1);       /* trigger new sub-table when len > root */
+    used = 1U << root;          /* use root table entries */
+    mask = used - 1;            /* mask for comparing low */
+
+    /* check available table space */
+    if ((type == LENS && used > ENOUGH_LENS) ||
+        (type == DISTS && used > ENOUGH_DISTS))
+        return 1;
+
+    /* process all codes and make table entries */
+    for (;;) {
+        /* create table entry */
+        here.bits = (unsigned char)(len - drop);
+        if (work[sym] + 1U < match) {
+            here.op = (unsigned char)0;
+            here.val = work[sym];
+        }
+        else if (work[sym] >= match) {
+            here.op = (unsigned char)(extra[work[sym] - match]);
+            here.val = base[work[sym] - match];
+        }
+        else {
+            here.op = (unsigned char)(32 + 64);         /* end of block */
+            here.val = 0;
+        }
+
+        /* replicate for those indices with low len bits equal to huff */
+        incr = 1U << (len - drop);
+        fill = 1U << curr;
+        min = fill;                 /* save offset to next table */
+        do {
+            fill -= incr;
+            next[(huff >> drop) + fill] = here;
+        } while (fill != 0);
+
+        /* backwards increment the len-bit code huff */
+        incr = 1U << (len - 1);
+        while (huff & incr)
+            incr >>= 1;
+        if (incr != 0) {
+            huff &= incr - 1;
+            huff += incr;
+        }
+        else
+            huff = 0;
+
+        /* go to next symbol, update count, len */
+        sym++;
+        if (--(count[len]) == 0) {
+            if (len == max) break;
+            len = lens[work[sym]];
+        }
+
+        /* create new sub-table if needed */
+        if (len > root && (huff & mask) != low) {
+            /* if first time, transition to sub-tables */
+            if (drop == 0)
+                drop = root;
+
+            /* increment past last table */
+            next += min;            /* here min is 1 << curr */
+
+            /* determine length of next table */
+            curr = len - drop;
+            left = (int)(1 << curr);
+            while (curr + drop < max) {
+                left -= count[curr + drop];
+                if (left <= 0) break;
+                curr++;
+                left <<= 1;
+            }
+
+            /* check for enough space */
+            used += 1U << curr;
+            if ((type == LENS && used > ENOUGH_LENS) ||
+                (type == DISTS && used > ENOUGH_DISTS))
+                return 1;
+
+            /* point entry in root table to sub-table */
+            low = huff & mask;
+            (*table)[low].op = (unsigned char)curr;
+            (*table)[low].bits = (unsigned char)root;
+            (*table)[low].val = (unsigned short)(next - *table);
+        }
+    }
+
+    /* fill in remaining table entry if code is incomplete (guaranteed to have
+       at most one remaining entry, since if the code is incomplete, the
+       maximum code length that was allowed to get this far is one bit) */
+    if (huff != 0) {
+        here.op = (unsigned char)64;            /* invalid code marker */
+        here.bits = (unsigned char)(len - drop);
+        here.val = (unsigned short)0;
+        next[huff] = here;
+    }
+
+    /* set return parameters */
+    *table += used;
+    *bits = root;
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/inftrees.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,62 @@
+/* inftrees.h -- header to use inftrees.c
+ * Copyright (C) 1995-2005, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+/* Structure for decoding tables.  Each entry provides either the
+   information needed to do the operation requested by the code that
+   indexed that table entry, or it provides a pointer to another
+   table that indexes more bits of the code.  op indicates whether
+   the entry is a pointer to another table, a literal, a length or
+   distance, an end-of-block, or an invalid code.  For a table
+   pointer, the low four bits of op is the number of index bits of
+   that table.  For a length or distance, the low four bits of op
+   is the number of extra bits to get after the code.  bits is
+   the number of bits in this code or part of the code to drop off
+   of the bit buffer.  val is the actual byte to output in the case
+   of a literal, the base length or distance, or the offset from
+   the current table to the next table.  Each entry is four bytes. */
+typedef struct {
+    unsigned char op;           /* operation, extra bits, table bits */
+    unsigned char bits;         /* bits in this part of the code */
+    unsigned short val;         /* offset in table or code value */
+} code;
+
+/* op values as set by inflate_table():
+    00000000 - literal
+    0000tttt - table link, tttt != 0 is the number of table index bits
+    0001eeee - length or distance, eeee is the number of extra bits
+    01100000 - end of block
+    01000000 - invalid code
+ */
+
+/* Maximum size of the dynamic table.  The maximum number of code structures is
+   1444, which is the sum of 852 for literal/length codes and 592 for distance
+   codes.  These values were found by exhaustive searches using the program
+   examples/enough.c found in the zlib distribtution.  The arguments to that
+   program are the number of symbols, the initial root table size, and the
+   maximum bit length of a code.  "enough 286 9 15" for literal/length codes
+   returns returns 852, and "enough 30 6 15" for distance codes returns 592.
+   The initial root table size (9 or 6) is found in the fifth argument of the
+   inflate_table() calls in inflate.c and infback.c.  If the root table size is
+   changed, then these maximum sizes would be need to be recalculated and
+   updated. */
+#define ENOUGH_LENS 852
+#define ENOUGH_DISTS 592
+#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS)
+
+/* Type of code to build for inflate_table() */
+typedef enum {
+    CODES,
+    LENS,
+    DISTS
+} codetype;
+
+int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens,
+                             unsigned codes, code FAR * FAR *table,
+                             unsigned FAR *bits, unsigned short FAR *work));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/trees.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,1203 @@
+/* trees.c -- output deflated data using Huffman coding
+ * Copyright (C) 1995-2017 Jean-loup Gailly
+ * detect_data_type() function provided freely by Cosmin Truta, 2006
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ *  ALGORITHM
+ *
+ *      The "deflation" process uses several Huffman trees. The more
+ *      common source values are represented by shorter bit sequences.
+ *
+ *      Each code tree is stored in a compressed form which is itself
+ * a Huffman encoding of the lengths of all the code strings (in
+ * ascending order by source values).  The actual code strings are
+ * reconstructed from the lengths in the inflate process, as described
+ * in the deflate specification.
+ *
+ *  REFERENCES
+ *
+ *      Deutsch, L.P.,"'Deflate' Compressed Data Format Specification".
+ *      Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc
+ *
+ *      Storer, James A.
+ *          Data Compression:  Methods and Theory, pp. 49-50.
+ *          Computer Science Press, 1988.  ISBN 0-7167-8156-5.
+ *
+ *      Sedgewick, R.
+ *          Algorithms, p290.
+ *          Addison-Wesley, 1983. ISBN 0-201-06672-6.
+ */
+
+/* @(#) $Id$ */
+
+/* #define GEN_TREES_H */
+
+#include "deflate.h"
+
+#ifdef ZLIB_DEBUG
+#  include <ctype.h>
+#endif
+
+/* ===========================================================================
+ * Constants
+ */
+
+#define MAX_BL_BITS 7
+/* Bit length codes must not exceed MAX_BL_BITS bits */
+
+#define END_BLOCK 256
+/* end of block literal code */
+
+#define REP_3_6      16
+/* repeat previous bit length 3-6 times (2 bits of repeat count) */
+
+#define REPZ_3_10    17
+/* repeat a zero length 3-10 times  (3 bits of repeat count) */
+
+#define REPZ_11_138  18
+/* repeat a zero length 11-138 times  (7 bits of repeat count) */
+
+local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */
+   = {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};
+
+local const int extra_dbits[D_CODES] /* extra bits for each distance code */
+   = {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};
+
+local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */
+   = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7};
+
+local const uch bl_order[BL_CODES]
+   = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15};
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+/* ===========================================================================
+ * Local data. These are initialized only once.
+ */
+
+#define DIST_CODE_LEN  512 /* see definition of array dist_code below */
+
+#if defined(GEN_TREES_H) || !defined(STDC)
+/* non ANSI compilers may not accept trees.h */
+
+local ct_data static_ltree[L_CODES+2];
+/* The static literal tree. Since the bit lengths are imposed, there is no
+ * need for the L_CODES extra codes used during heap construction. However
+ * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
+ * below).
+ */
+
+local ct_data static_dtree[D_CODES];
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+uch _dist_code[DIST_CODE_LEN];
+/* Distance codes. The first 256 values correspond to the distances
+ * 3 .. 258, the last 256 values correspond to the top 8 bits of
+ * the 15 bit distances.
+ */
+
+uch _length_code[MAX_MATCH-MIN_MATCH+1];
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+local int base_length[LENGTH_CODES];
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+local int base_dist[D_CODES];
+/* First normalized distance for each code (0 = distance of 1) */
+
+#else
+#  include "trees.h"
+#endif /* GEN_TREES_H */
+
+struct static_tree_desc_s {
+    const ct_data *static_tree;  /* static tree or NULL */
+    const intf *extra_bits;      /* extra bits for each code or NULL */
+    int     extra_base;          /* base index for extra_bits */
+    int     elems;               /* max number of elements in the tree */
+    int     max_length;          /* max bit length for the codes */
+};
+
+local const static_tree_desc  static_l_desc =
+{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS};
+
+local const static_tree_desc  static_d_desc =
+{static_dtree, extra_dbits, 0,          D_CODES, MAX_BITS};
+
+local const static_tree_desc  static_bl_desc =
+{(const ct_data *)0, extra_blbits, 0,   BL_CODES, MAX_BL_BITS};
+
+/* ===========================================================================
+ * Local (static) routines in this file.
+ */
+
+local void tr_static_init OF((void));
+local void init_block     OF((deflate_state *s));
+local void pqdownheap     OF((deflate_state *s, ct_data *tree, int k));
+local void gen_bitlen     OF((deflate_state *s, tree_desc *desc));
+local void gen_codes      OF((ct_data *tree, int max_code, ushf *bl_count));
+local void build_tree     OF((deflate_state *s, tree_desc *desc));
+local void scan_tree      OF((deflate_state *s, ct_data *tree, int max_code));
+local void send_tree      OF((deflate_state *s, ct_data *tree, int max_code));
+local int  build_bl_tree  OF((deflate_state *s));
+local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes,
+                              int blcodes));
+local void compress_block OF((deflate_state *s, const ct_data *ltree,
+                              const ct_data *dtree));
+local int  detect_data_type OF((deflate_state *s));
+local unsigned bi_reverse OF((unsigned value, int length));
+local void bi_windup      OF((deflate_state *s));
+local void bi_flush       OF((deflate_state *s));
+
+#ifdef GEN_TREES_H
+local void gen_trees_header OF((void));
+#endif
+
+#ifndef ZLIB_DEBUG
+#  define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len)
+   /* Send a code of the given tree. c and tree must not have side effects */
+
+#else /* !ZLIB_DEBUG */
+#  define send_code(s, c, tree) \
+     { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \
+       send_bits(s, tree[c].Code, tree[c].Len); }
+#endif
+
+/* ===========================================================================
+ * Output a short LSB first on the stream.
+ * IN assertion: there is enough room in pendingBuf.
+ */
+#define put_short(s, w) { \
+    put_byte(s, (uch)((w) & 0xff)); \
+    put_byte(s, (uch)((ush)(w) >> 8)); \
+}
+
+/* ===========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+#ifdef ZLIB_DEBUG
+local void send_bits      OF((deflate_state *s, int value, int length));
+
+local void send_bits(s, value, length)
+    deflate_state *s;
+    int value;  /* value to send */
+    int length; /* number of bits */
+{
+    Tracevv((stderr," l %2d v %4x ", length, value));
+    Assert(length > 0 && length <= 15, "invalid length");
+    s->bits_sent += (ulg)length;
+
+    /* If not enough room in bi_buf, use (valid) bits from bi_buf and
+     * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid))
+     * unused bits in value.
+     */
+    if (s->bi_valid > (int)Buf_size - length) {
+        s->bi_buf |= (ush)value << s->bi_valid;
+        put_short(s, s->bi_buf);
+        s->bi_buf = (ush)value >> (Buf_size - s->bi_valid);
+        s->bi_valid += length - Buf_size;
+    } else {
+        s->bi_buf |= (ush)value << s->bi_valid;
+        s->bi_valid += length;
+    }
+}
+#else /* !ZLIB_DEBUG */
+
+#define send_bits(s, value, length) \
+{ int len = length;\
+  if (s->bi_valid > (int)Buf_size - len) {\
+    int val = (int)value;\
+    s->bi_buf |= (ush)val << s->bi_valid;\
+    put_short(s, s->bi_buf);\
+    s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\
+    s->bi_valid += len - Buf_size;\
+  } else {\
+    s->bi_buf |= (ush)(value) << s->bi_valid;\
+    s->bi_valid += len;\
+  }\
+}
+#endif /* ZLIB_DEBUG */
+
+
+/* the arguments must not have side effects */
+
+/* ===========================================================================
+ * Initialize the various 'constant' tables.
+ */
+local void tr_static_init()
+{
+#if defined(GEN_TREES_H) || !defined(STDC)
+    static int static_init_done = 0;
+    int n;        /* iterates over tree elements */
+    int bits;     /* bit counter */
+    int length;   /* length value */
+    int code;     /* code value */
+    int dist;     /* distance index */
+    ush bl_count[MAX_BITS+1];
+    /* number of codes at each bit length for an optimal tree */
+
+    if (static_init_done) return;
+
+    /* For some embedded targets, global variables are not initialized: */
+#ifdef NO_INIT_GLOBAL_POINTERS
+    static_l_desc.static_tree = static_ltree;
+    static_l_desc.extra_bits = extra_lbits;
+    static_d_desc.static_tree = static_dtree;
+    static_d_desc.extra_bits = extra_dbits;
+    static_bl_desc.extra_bits = extra_blbits;
+#endif
+
+    /* Initialize the mapping length (0..255) -> length code (0..28) */
+    length = 0;
+    for (code = 0; code < LENGTH_CODES-1; code++) {
+        base_length[code] = length;
+        for (n = 0; n < (1<<extra_lbits[code]); n++) {
+            _length_code[length++] = (uch)code;
+        }
+    }
+    Assert (length == 256, "tr_static_init: length != 256");
+    /* Note that the length 255 (match length 258) can be represented
+     * in two different ways: code 284 + 5 bits or code 285, so we
+     * overwrite length_code[255] to use the best encoding:
+     */
+    _length_code[length-1] = (uch)code;
+
+    /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+    dist = 0;
+    for (code = 0 ; code < 16; code++) {
+        base_dist[code] = dist;
+        for (n = 0; n < (1<<extra_dbits[code]); n++) {
+            _dist_code[dist++] = (uch)code;
+        }
+    }
+    Assert (dist == 256, "tr_static_init: dist != 256");
+    dist >>= 7; /* from now on, all distances are divided by 128 */
+    for ( ; code < D_CODES; code++) {
+        base_dist[code] = dist << 7;
+        for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) {
+            _dist_code[256 + dist++] = (uch)code;
+        }
+    }
+    Assert (dist == 256, "tr_static_init: 256+dist != 512");
+
+    /* Construct the codes of the static literal tree */
+    for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0;
+    n = 0;
+    while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++;
+    while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++;
+    while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++;
+    while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++;
+    /* Codes 286 and 287 do not exist, but we must include them in the
+     * tree construction to get a canonical Huffman tree (longest code
+     * all ones)
+     */
+    gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count);
+
+    /* The static distance tree is trivial: */
+    for (n = 0; n < D_CODES; n++) {
+        static_dtree[n].Len = 5;
+        static_dtree[n].Code = bi_reverse((unsigned)n, 5);
+    }
+    static_init_done = 1;
+
+#  ifdef GEN_TREES_H
+    gen_trees_header();
+#  endif
+#endif /* defined(GEN_TREES_H) || !defined(STDC) */
+}
+
+/* ===========================================================================
+ * Genererate the file trees.h describing the static trees.
+ */
+#ifdef GEN_TREES_H
+#  ifndef ZLIB_DEBUG
+#    include <stdio.h>
+#  endif
+
+#  define SEPARATOR(i, last, width) \
+      ((i) == (last)? "\n};\n\n" :    \
+       ((i) % (width) == (width)-1 ? ",\n" : ", "))
+
+void gen_trees_header()
+{
+    FILE *header = fopen("trees.h", "w");
+    int i;
+
+    Assert (header != NULL, "Can't open trees.h");
+    fprintf(header,
+            "/* header created automatically with -DGEN_TREES_H */\n\n");
+
+    fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n");
+    for (i = 0; i < L_CODES+2; i++) {
+        fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code,
+                static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5));
+    }
+
+    fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n");
+    for (i = 0; i < D_CODES; i++) {
+        fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code,
+                static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5));
+    }
+
+    fprintf(header, "const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {\n");
+    for (i = 0; i < DIST_CODE_LEN; i++) {
+        fprintf(header, "%2u%s", _dist_code[i],
+                SEPARATOR(i, DIST_CODE_LEN-1, 20));
+    }
+
+    fprintf(header,
+        "const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {\n");
+    for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) {
+        fprintf(header, "%2u%s", _length_code[i],
+                SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20));
+    }
+
+    fprintf(header, "local const int base_length[LENGTH_CODES] = {\n");
+    for (i = 0; i < LENGTH_CODES; i++) {
+        fprintf(header, "%1u%s", base_length[i],
+                SEPARATOR(i, LENGTH_CODES-1, 20));
+    }
+
+    fprintf(header, "local const int base_dist[D_CODES] = {\n");
+    for (i = 0; i < D_CODES; i++) {
+        fprintf(header, "%5u%s", base_dist[i],
+                SEPARATOR(i, D_CODES-1, 10));
+    }
+
+    fclose(header);
+}
+#endif /* GEN_TREES_H */
+
+/* ===========================================================================
+ * Initialize the tree data structures for a new zlib stream.
+ */
+void ZLIB_INTERNAL _tr_init(s)
+    deflate_state *s;
+{
+    tr_static_init();
+
+    s->l_desc.dyn_tree = s->dyn_ltree;
+    s->l_desc.stat_desc = &static_l_desc;
+
+    s->d_desc.dyn_tree = s->dyn_dtree;
+    s->d_desc.stat_desc = &static_d_desc;
+
+    s->bl_desc.dyn_tree = s->bl_tree;
+    s->bl_desc.stat_desc = &static_bl_desc;
+
+    s->bi_buf = 0;
+    s->bi_valid = 0;
+#ifdef ZLIB_DEBUG
+    s->compressed_len = 0L;
+    s->bits_sent = 0L;
+#endif
+
+    /* Initialize the first block of the first file: */
+    init_block(s);
+}
+
+/* ===========================================================================
+ * Initialize a new block.
+ */
+local void init_block(s)
+    deflate_state *s;
+{
+    int n; /* iterates over tree elements */
+
+    /* Initialize the trees. */
+    for (n = 0; n < L_CODES;  n++) s->dyn_ltree[n].Freq = 0;
+    for (n = 0; n < D_CODES;  n++) s->dyn_dtree[n].Freq = 0;
+    for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0;
+
+    s->dyn_ltree[END_BLOCK].Freq = 1;
+    s->opt_len = s->static_len = 0L;
+    s->last_lit = s->matches = 0;
+}
+
+#define SMALLEST 1
+/* Index within the heap array of least frequent node in the Huffman tree */
+
+
+/* ===========================================================================
+ * Remove the smallest element from the heap and recreate the heap with
+ * one less element. Updates heap and heap_len.
+ */
+#define pqremove(s, tree, top) \
+{\
+    top = s->heap[SMALLEST]; \
+    s->heap[SMALLEST] = s->heap[s->heap_len--]; \
+    pqdownheap(s, tree, SMALLEST); \
+}
+
+/* ===========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+#define smaller(tree, n, m, depth) \
+   (tree[n].Freq < tree[m].Freq || \
+   (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m]))
+
+/* ===========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+local void pqdownheap(s, tree, k)
+    deflate_state *s;
+    ct_data *tree;  /* the tree to restore */
+    int k;               /* node to move down */
+{
+    int v = s->heap[k];
+    int j = k << 1;  /* left son of k */
+    while (j <= s->heap_len) {
+        /* Set j to the smallest of the two sons: */
+        if (j < s->heap_len &&
+            smaller(tree, s->heap[j+1], s->heap[j], s->depth)) {
+            j++;
+        }
+        /* Exit if v is smaller than both sons */
+        if (smaller(tree, v, s->heap[j], s->depth)) break;
+
+        /* Exchange v with the smallest son */
+        s->heap[k] = s->heap[j];  k = j;
+
+        /* And continue down the tree, setting j to the left son of k */
+        j <<= 1;
+    }
+    s->heap[k] = v;
+}
+
+/* ===========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ *    above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ *     array bl_count contains the frequencies for each bit length.
+ *     The length opt_len is updated; static_len is also updated if stree is
+ *     not null.
+ */
+local void gen_bitlen(s, desc)
+    deflate_state *s;
+    tree_desc *desc;    /* the tree descriptor */
+{
+    ct_data *tree        = desc->dyn_tree;
+    int max_code         = desc->max_code;
+    const ct_data *stree = desc->stat_desc->static_tree;
+    const intf *extra    = desc->stat_desc->extra_bits;
+    int base             = desc->stat_desc->extra_base;
+    int max_length       = desc->stat_desc->max_length;
+    int h;              /* heap index */
+    int n, m;           /* iterate over the tree elements */
+    int bits;           /* bit length */
+    int xbits;          /* extra bits */
+    ush f;              /* frequency */
+    int overflow = 0;   /* number of elements with bit length too large */
+
+    for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0;
+
+    /* In a first pass, compute the optimal bit lengths (which may
+     * overflow in the case of the bit length tree).
+     */
+    tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */
+
+    for (h = s->heap_max+1; h < HEAP_SIZE; h++) {
+        n = s->heap[h];
+        bits = tree[tree[n].Dad].Len + 1;
+        if (bits > max_length) bits = max_length, overflow++;
+        tree[n].Len = (ush)bits;
+        /* We overwrite tree[n].Dad which is no longer needed */
+
+        if (n > max_code) continue; /* not a leaf node */
+
+        s->bl_count[bits]++;
+        xbits = 0;
+        if (n >= base) xbits = extra[n-base];
+        f = tree[n].Freq;
+        s->opt_len += (ulg)f * (unsigned)(bits + xbits);
+        if (stree) s->static_len += (ulg)f * (unsigned)(stree[n].Len + xbits);
+    }
+    if (overflow == 0) return;
+
+    Tracev((stderr,"\nbit length overflow\n"));
+    /* This happens for example on obj2 and pic of the Calgary corpus */
+
+    /* Find the first bit length which could increase: */
+    do {
+        bits = max_length-1;
+        while (s->bl_count[bits] == 0) bits--;
+        s->bl_count[bits]--;      /* move one leaf down the tree */
+        s->bl_count[bits+1] += 2; /* move one overflow item as its brother */
+        s->bl_count[max_length]--;
+        /* The brother of the overflow item also moves one step up,
+         * but this does not affect bl_count[max_length]
+         */
+        overflow -= 2;
+    } while (overflow > 0);
+
+    /* Now recompute all bit lengths, scanning in increasing frequency.
+     * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+     * lengths instead of fixing only the wrong ones. This idea is taken
+     * from 'ar' written by Haruhiko Okumura.)
+     */
+    for (bits = max_length; bits != 0; bits--) {
+        n = s->bl_count[bits];
+        while (n != 0) {
+            m = s->heap[--h];
+            if (m > max_code) continue;
+            if ((unsigned) tree[m].Len != (unsigned) bits) {
+                Tracev((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+                s->opt_len += ((ulg)bits - tree[m].Len) * tree[m].Freq;
+                tree[m].Len = (ush)bits;
+            }
+            n--;
+        }
+    }
+}
+
+/* ===========================================================================
+ * Generate the codes for a given tree and bit counts (which need not be
+ * optimal).
+ * IN assertion: the array bl_count contains the bit length statistics for
+ * the given tree and the field len is set for all tree elements.
+ * OUT assertion: the field code is set for all tree elements of non
+ *     zero code length.
+ */
+local void gen_codes (tree, max_code, bl_count)
+    ct_data *tree;             /* the tree to decorate */
+    int max_code;              /* largest code with non zero frequency */
+    ushf *bl_count;            /* number of codes at each bit length */
+{
+    ush next_code[MAX_BITS+1]; /* next code value for each bit length */
+    unsigned code = 0;         /* running code value */
+    int bits;                  /* bit index */
+    int n;                     /* code index */
+
+    /* The distribution counts are first used to generate the code values
+     * without bit reversal.
+     */
+    for (bits = 1; bits <= MAX_BITS; bits++) {
+        code = (code + bl_count[bits-1]) << 1;
+        next_code[bits] = (ush)code;
+    }
+    /* Check that the bit counts in bl_count are consistent. The last code
+     * must be all ones.
+     */
+    Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+            "inconsistent bit counts");
+    Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+    for (n = 0;  n <= max_code; n++) {
+        int len = tree[n].Len;
+        if (len == 0) continue;
+        /* Now reverse the bits */
+        tree[n].Code = (ush)bi_reverse(next_code[len]++, len);
+
+        Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+             n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+    }
+}
+
+/* ===========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ *     and corresponding code. The length opt_len is updated; static_len is
+ *     also updated if stree is not null. The field max_code is set.
+ */
+local void build_tree(s, desc)
+    deflate_state *s;
+    tree_desc *desc; /* the tree descriptor */
+{
+    ct_data *tree         = desc->dyn_tree;
+    const ct_data *stree  = desc->stat_desc->static_tree;
+    int elems             = desc->stat_desc->elems;
+    int n, m;          /* iterate over heap elements */
+    int max_code = -1; /* largest code with non zero frequency */
+    int node;          /* new node being created */
+
+    /* Construct the initial heap, with least frequent element in
+     * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+     * heap[0] is not used.
+     */
+    s->heap_len = 0, s->heap_max = HEAP_SIZE;
+
+    for (n = 0; n < elems; n++) {
+        if (tree[n].Freq != 0) {
+            s->heap[++(s->heap_len)] = max_code = n;
+            s->depth[n] = 0;
+        } else {
+            tree[n].Len = 0;
+        }
+    }
+
+    /* The pkzip format requires that at least one distance code exists,
+     * and that at least one bit should be sent even if there is only one
+     * possible code. So to avoid special checks later on we force at least
+     * two codes of non zero frequency.
+     */
+    while (s->heap_len < 2) {
+        node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0);
+        tree[node].Freq = 1;
+        s->depth[node] = 0;
+        s->opt_len--; if (stree) s->static_len -= stree[node].Len;
+        /* node is 0 or 1 so it does not have extra bits */
+    }
+    desc->max_code = max_code;
+
+    /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+     * establish sub-heaps of increasing lengths:
+     */
+    for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n);
+
+    /* Construct the Huffman tree by repeatedly combining the least two
+     * frequent nodes.
+     */
+    node = elems;              /* next internal node of the tree */
+    do {
+        pqremove(s, tree, n);  /* n = node of least frequency */
+        m = s->heap[SMALLEST]; /* m = node of next least frequency */
+
+        s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */
+        s->heap[--(s->heap_max)] = m;
+
+        /* Create a new node father of n and m */
+        tree[node].Freq = tree[n].Freq + tree[m].Freq;
+        s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ?
+                                s->depth[n] : s->depth[m]) + 1);
+        tree[n].Dad = tree[m].Dad = (ush)node;
+#ifdef DUMP_BL_TREE
+        if (tree == s->bl_tree) {
+            fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)",
+                    node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq);
+        }
+#endif
+        /* and insert the new node in the heap */
+        s->heap[SMALLEST] = node++;
+        pqdownheap(s, tree, SMALLEST);
+
+    } while (s->heap_len >= 2);
+
+    s->heap[--(s->heap_max)] = s->heap[SMALLEST];
+
+    /* At this point, the fields freq and dad are set. We can now
+     * generate the bit lengths.
+     */
+    gen_bitlen(s, (tree_desc *)desc);
+
+    /* The field len is now set, we can generate the bit codes */
+    gen_codes ((ct_data *)tree, max_code, s->bl_count);
+}
+
+/* ===========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree.
+ */
+local void scan_tree (s, tree, max_code)
+    deflate_state *s;
+    ct_data *tree;   /* the tree to be scanned */
+    int max_code;    /* and its largest code of non zero frequency */
+{
+    int n;                     /* iterates over all tree elements */
+    int prevlen = -1;          /* last emitted length */
+    int curlen;                /* length of current code */
+    int nextlen = tree[0].Len; /* length of next code */
+    int count = 0;             /* repeat count of the current code */
+    int max_count = 7;         /* max repeat count */
+    int min_count = 4;         /* min repeat count */
+
+    if (nextlen == 0) max_count = 138, min_count = 3;
+    tree[max_code+1].Len = (ush)0xffff; /* guard */
+
+    for (n = 0; n <= max_code; n++) {
+        curlen = nextlen; nextlen = tree[n+1].Len;
+        if (++count < max_count && curlen == nextlen) {
+            continue;
+        } else if (count < min_count) {
+            s->bl_tree[curlen].Freq += count;
+        } else if (curlen != 0) {
+            if (curlen != prevlen) s->bl_tree[curlen].Freq++;
+            s->bl_tree[REP_3_6].Freq++;
+        } else if (count <= 10) {
+            s->bl_tree[REPZ_3_10].Freq++;
+        } else {
+            s->bl_tree[REPZ_11_138].Freq++;
+        }
+        count = 0; prevlen = curlen;
+        if (nextlen == 0) {
+            max_count = 138, min_count = 3;
+        } else if (curlen == nextlen) {
+            max_count = 6, min_count = 3;
+        } else {
+            max_count = 7, min_count = 4;
+        }
+    }
+}
+
+/* ===========================================================================
+ * Send a literal or distance tree in compressed form, using the codes in
+ * bl_tree.
+ */
+local void send_tree (s, tree, max_code)
+    deflate_state *s;
+    ct_data *tree; /* the tree to be scanned */
+    int max_code;       /* and its largest code of non zero frequency */
+{
+    int n;                     /* iterates over all tree elements */
+    int prevlen = -1;          /* last emitted length */
+    int curlen;                /* length of current code */
+    int nextlen = tree[0].Len; /* length of next code */
+    int count = 0;             /* repeat count of the current code */
+    int max_count = 7;         /* max repeat count */
+    int min_count = 4;         /* min repeat count */
+
+    /* tree[max_code+1].Len = -1; */  /* guard already set */
+    if (nextlen == 0) max_count = 138, min_count = 3;
+
+    for (n = 0; n <= max_code; n++) {
+        curlen = nextlen; nextlen = tree[n+1].Len;
+        if (++count < max_count && curlen == nextlen) {
+            continue;
+        } else if (count < min_count) {
+            do { send_code(s, curlen, s->bl_tree); } while (--count != 0);
+
+        } else if (curlen != 0) {
+            if (curlen != prevlen) {
+                send_code(s, curlen, s->bl_tree); count--;
+            }
+            Assert(count >= 3 && count <= 6, " 3_6?");
+            send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2);
+
+        } else if (count <= 10) {
+            send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3);
+
+        } else {
+            send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7);
+        }
+        count = 0; prevlen = curlen;
+        if (nextlen == 0) {
+            max_count = 138, min_count = 3;
+        } else if (curlen == nextlen) {
+            max_count = 6, min_count = 3;
+        } else {
+            max_count = 7, min_count = 4;
+        }
+    }
+}
+
+/* ===========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+local int build_bl_tree(s)
+    deflate_state *s;
+{
+    int max_blindex;  /* index of last bit length code of non zero freq */
+
+    /* Determine the bit length frequencies for literal and distance trees */
+    scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code);
+    scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code);
+
+    /* Build the bit length tree: */
+    build_tree(s, (tree_desc *)(&(s->bl_desc)));
+    /* opt_len now includes the length of the tree representations, except
+     * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+     */
+
+    /* Determine the number of bit length codes to send. The pkzip format
+     * requires that at least 4 bit length codes be sent. (appnote.txt says
+     * 3 but the actual value used is 4.)
+     */
+    for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) {
+        if (s->bl_tree[bl_order[max_blindex]].Len != 0) break;
+    }
+    /* Update opt_len to include the bit length tree and counts */
+    s->opt_len += 3*((ulg)max_blindex+1) + 5+5+4;
+    Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+            s->opt_len, s->static_len));
+
+    return max_blindex;
+}
+
+/* ===========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+local void send_all_trees(s, lcodes, dcodes, blcodes)
+    deflate_state *s;
+    int lcodes, dcodes, blcodes; /* number of codes for each tree */
+{
+    int rank;                    /* index in bl_order */
+
+    Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+    Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+            "too many codes");
+    Tracev((stderr, "\nbl counts: "));
+    send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */
+    send_bits(s, dcodes-1,   5);
+    send_bits(s, blcodes-4,  4); /* not -3 as stated in appnote.txt */
+    for (rank = 0; rank < blcodes; rank++) {
+        Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+        send_bits(s, s->bl_tree[bl_order[rank]].Len, 3);
+    }
+    Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
+
+    send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */
+    Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
+
+    send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */
+    Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
+}
+
+/* ===========================================================================
+ * Send a stored block
+ */
+void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
+    deflate_state *s;
+    charf *buf;       /* input block */
+    ulg stored_len;   /* length of input block */
+    int last;         /* one if this is the last block for a file */
+{
+    send_bits(s, (STORED_BLOCK<<1)+last, 3);    /* send block type */
+    bi_windup(s);        /* align on byte boundary */
+    put_short(s, (ush)stored_len);
+    put_short(s, (ush)~stored_len);
+    zmemcpy(s->pending_buf + s->pending, (Bytef *)buf, stored_len);
+    s->pending += stored_len;
+#ifdef ZLIB_DEBUG
+    s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L;
+    s->compressed_len += (stored_len + 4) << 3;
+    s->bits_sent += 2*16;
+    s->bits_sent += stored_len<<3;
+#endif
+}
+
+/* ===========================================================================
+ * Flush the bits in the bit buffer to pending output (leaves at most 7 bits)
+ */
+void ZLIB_INTERNAL _tr_flush_bits(s)
+    deflate_state *s;
+{
+    bi_flush(s);
+}
+
+/* ===========================================================================
+ * Send one empty static block to give enough lookahead for inflate.
+ * This takes 10 bits, of which 7 may remain in the bit buffer.
+ */
+void ZLIB_INTERNAL _tr_align(s)
+    deflate_state *s;
+{
+    send_bits(s, STATIC_TREES<<1, 3);
+    send_code(s, END_BLOCK, static_ltree);
+#ifdef ZLIB_DEBUG
+    s->compressed_len += 10L; /* 3 for block type, 7 for EOB */
+#endif
+    bi_flush(s);
+}
+
+/* ===========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and write out the encoded block.
+ */
+void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
+    deflate_state *s;
+    charf *buf;       /* input block, or NULL if too old */
+    ulg stored_len;   /* length of input block */
+    int last;         /* one if this is the last block for a file */
+{
+    ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */
+    int max_blindex = 0;  /* index of last bit length code of non zero freq */
+
+    /* Build the Huffman trees unless a stored block is forced */
+    if (s->level > 0) {
+
+        /* Check if the file is binary or text */
+        if (s->strm->data_type == Z_UNKNOWN)
+            s->strm->data_type = detect_data_type(s);
+
+        /* Construct the literal and distance trees */
+        build_tree(s, (tree_desc *)(&(s->l_desc)));
+        Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
+                s->static_len));
+
+        build_tree(s, (tree_desc *)(&(s->d_desc)));
+        Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
+                s->static_len));
+        /* At this point, opt_len and static_len are the total bit lengths of
+         * the compressed block data, excluding the tree representations.
+         */
+
+        /* Build the bit length tree for the above two trees, and get the index
+         * in bl_order of the last bit length code to send.
+         */
+        max_blindex = build_bl_tree(s);
+
+        /* Determine the best encoding. Compute the block lengths in bytes. */
+        opt_lenb = (s->opt_len+3+7)>>3;
+        static_lenb = (s->static_len+3+7)>>3;
+
+        Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
+                opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
+                s->last_lit));
+
+        if (static_lenb <= opt_lenb) opt_lenb = static_lenb;
+
+    } else {
+        Assert(buf != (char*)0, "lost buf");
+        opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
+    }
+
+#ifdef FORCE_STORED
+    if (buf != (char*)0) { /* force stored block */
+#else
+    if (stored_len+4 <= opt_lenb && buf != (char*)0) {
+                       /* 4: two words for the lengths */
+#endif
+        /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+         * Otherwise we can't have processed more than WSIZE input bytes since
+         * the last block flush, because compression would have been
+         * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+         * transform a block into a stored block.
+         */
+        _tr_stored_block(s, buf, stored_len, last);
+
+#ifdef FORCE_STATIC
+    } else if (static_lenb >= 0) { /* force static trees */
+#else
+    } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) {
+#endif
+        send_bits(s, (STATIC_TREES<<1)+last, 3);
+        compress_block(s, (const ct_data *)static_ltree,
+                       (const ct_data *)static_dtree);
+#ifdef ZLIB_DEBUG
+        s->compressed_len += 3 + s->static_len;
+#endif
+    } else {
+        send_bits(s, (DYN_TREES<<1)+last, 3);
+        send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1,
+                       max_blindex+1);
+        compress_block(s, (const ct_data *)s->dyn_ltree,
+                       (const ct_data *)s->dyn_dtree);
+#ifdef ZLIB_DEBUG
+        s->compressed_len += 3 + s->opt_len;
+#endif
+    }
+    Assert (s->compressed_len == s->bits_sent, "bad compressed size");
+    /* The above check is made mod 2^32, for files larger than 512 MB
+     * and uLong implemented on 32 bits.
+     */
+    init_block(s);
+
+    if (last) {
+        bi_windup(s);
+#ifdef ZLIB_DEBUG
+        s->compressed_len += 7;  /* align on byte boundary */
+#endif
+    }
+    Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
+           s->compressed_len-7*last));
+}
+
+/* ===========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+int ZLIB_INTERNAL _tr_tally (s, dist, lc)
+    deflate_state *s;
+    unsigned dist;  /* distance of matched string */
+    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
+{
+    s->d_buf[s->last_lit] = (ush)dist;
+    s->l_buf[s->last_lit++] = (uch)lc;
+    if (dist == 0) {
+        /* lc is the unmatched char */
+        s->dyn_ltree[lc].Freq++;
+    } else {
+        s->matches++;
+        /* Here, lc is the match length - MIN_MATCH */
+        dist--;             /* dist = match distance - 1 */
+        Assert((ush)dist < (ush)MAX_DIST(s) &&
+               (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+               (ush)d_code(dist) < (ush)D_CODES,  "_tr_tally: bad match");
+
+        s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++;
+        s->dyn_dtree[d_code(dist)].Freq++;
+    }
+
+#ifdef TRUNCATE_BLOCK
+    /* Try to guess if it is profitable to stop the current block here */
+    if ((s->last_lit & 0x1fff) == 0 && s->level > 2) {
+        /* Compute an upper bound for the compressed length */
+        ulg out_length = (ulg)s->last_lit*8L;
+        ulg in_length = (ulg)((long)s->strstart - s->block_start);
+        int dcode;
+        for (dcode = 0; dcode < D_CODES; dcode++) {
+            out_length += (ulg)s->dyn_dtree[dcode].Freq *
+                (5L+extra_dbits[dcode]);
+        }
+        out_length >>= 3;
+        Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
+               s->last_lit, in_length, out_length,
+               100L - out_length*100L/in_length));
+        if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1;
+    }
+#endif
+    return (s->last_lit == s->lit_bufsize-1);
+    /* We avoid equality with lit_bufsize because of wraparound at 64K
+     * on 16 bit machines and because stored blocks are restricted to
+     * 64K-1 bytes.
+     */
+}
+
+/* ===========================================================================
+ * Send the block data compressed using the given Huffman trees
+ */
+local void compress_block(s, ltree, dtree)
+    deflate_state *s;
+    const ct_data *ltree; /* literal tree */
+    const ct_data *dtree; /* distance tree */
+{
+    unsigned dist;      /* distance of matched string */
+    int lc;             /* match length or unmatched char (if dist == 0) */
+    unsigned lx = 0;    /* running index in l_buf */
+    unsigned code;      /* the code to send */
+    int extra;          /* number of extra bits to send */
+
+    if (s->last_lit != 0) do {
+        dist = s->d_buf[lx];
+        lc = s->l_buf[lx++];
+        if (dist == 0) {
+            send_code(s, lc, ltree); /* send a literal byte */
+            Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+        } else {
+            /* Here, lc is the match length - MIN_MATCH */
+            code = _length_code[lc];
+            send_code(s, code+LITERALS+1, ltree); /* send the length code */
+            extra = extra_lbits[code];
+            if (extra != 0) {
+                lc -= base_length[code];
+                send_bits(s, lc, extra);       /* send the extra length bits */
+            }
+            dist--; /* dist is now the match distance - 1 */
+            code = d_code(dist);
+            Assert (code < D_CODES, "bad d_code");
+
+            send_code(s, code, dtree);       /* send the distance code */
+            extra = extra_dbits[code];
+            if (extra != 0) {
+                dist -= (unsigned)base_dist[code];
+                send_bits(s, dist, extra);   /* send the extra distance bits */
+            }
+        } /* literal or match pair ? */
+
+        /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
+        Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
+               "pendingBuf overflow");
+
+    } while (lx < s->last_lit);
+
+    send_code(s, END_BLOCK, ltree);
+}
+
+/* ===========================================================================
+ * Check if the data type is TEXT or BINARY, using the following algorithm:
+ * - TEXT if the two conditions below are satisfied:
+ *    a) There are no non-portable control characters belonging to the
+ *       "black list" (0..6, 14..25, 28..31).
+ *    b) There is at least one printable character belonging to the
+ *       "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
+ * - BINARY otherwise.
+ * - The following partially-portable control characters form a
+ *   "gray list" that is ignored in this detection algorithm:
+ *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
+ * IN assertion: the fields Freq of dyn_ltree are set.
+ */
+local int detect_data_type(s)
+    deflate_state *s;
+{
+    /* black_mask is the bit mask of black-listed bytes
+     * set bits 0..6, 14..25, and 28..31
+     * 0xf3ffc07f = binary 11110011111111111100000001111111
+     */
+    unsigned long black_mask = 0xf3ffc07fUL;
+    int n;
+
+    /* Check for non-textual ("black-listed") bytes. */
+    for (n = 0; n <= 31; n++, black_mask >>= 1)
+        if ((black_mask & 1) && (s->dyn_ltree[n].Freq != 0))
+            return Z_BINARY;
+
+    /* Check for textual ("white-listed") bytes. */
+    if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0
+            || s->dyn_ltree[13].Freq != 0)
+        return Z_TEXT;
+    for (n = 32; n < LITERALS; n++)
+        if (s->dyn_ltree[n].Freq != 0)
+            return Z_TEXT;
+
+    /* There are no "black-listed" or "white-listed" bytes:
+     * this stream either is empty or has tolerated ("gray-listed") bytes only.
+     */
+    return Z_BINARY;
+}
+
+/* ===========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+local unsigned bi_reverse(code, len)
+    unsigned code; /* the value to invert */
+    int len;       /* its bit length */
+{
+    register unsigned res = 0;
+    do {
+        res |= code & 1;
+        code >>= 1, res <<= 1;
+    } while (--len > 0);
+    return res >> 1;
+}
+
+/* ===========================================================================
+ * Flush the bit buffer, keeping at most 7 bits in it.
+ */
+local void bi_flush(s)
+    deflate_state *s;
+{
+    if (s->bi_valid == 16) {
+        put_short(s, s->bi_buf);
+        s->bi_buf = 0;
+        s->bi_valid = 0;
+    } else if (s->bi_valid >= 8) {
+        put_byte(s, (Byte)s->bi_buf);
+        s->bi_buf >>= 8;
+        s->bi_valid -= 8;
+    }
+}
+
+/* ===========================================================================
+ * Flush the bit buffer and align the output on a byte boundary
+ */
+local void bi_windup(s)
+    deflate_state *s;
+{
+    if (s->bi_valid > 8) {
+        put_short(s, s->bi_buf);
+    } else if (s->bi_valid > 0) {
+        put_byte(s, (Byte)s->bi_buf);
+    }
+    s->bi_buf = 0;
+    s->bi_valid = 0;
+#ifdef ZLIB_DEBUG
+    s->bits_sent = (s->bits_sent+7) & ~7;
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/trees.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,128 @@
+/* header created automatically with -DGEN_TREES_H */
+
+local const ct_data static_ltree[L_CODES+2] = {
+{{ 12},{  8}}, {{140},{  8}}, {{ 76},{  8}}, {{204},{  8}}, {{ 44},{  8}},
+{{172},{  8}}, {{108},{  8}}, {{236},{  8}}, {{ 28},{  8}}, {{156},{  8}},
+{{ 92},{  8}}, {{220},{  8}}, {{ 60},{  8}}, {{188},{  8}}, {{124},{  8}},
+{{252},{  8}}, {{  2},{  8}}, {{130},{  8}}, {{ 66},{  8}}, {{194},{  8}},
+{{ 34},{  8}}, {{162},{  8}}, {{ 98},{  8}}, {{226},{  8}}, {{ 18},{  8}},
+{{146},{  8}}, {{ 82},{  8}}, {{210},{  8}}, {{ 50},{  8}}, {{178},{  8}},
+{{114},{  8}}, {{242},{  8}}, {{ 10},{  8}}, {{138},{  8}}, {{ 74},{  8}},
+{{202},{  8}}, {{ 42},{  8}}, {{170},{  8}}, {{106},{  8}}, {{234},{  8}},
+{{ 26},{  8}}, {{154},{  8}}, {{ 90},{  8}}, {{218},{  8}}, {{ 58},{  8}},
+{{186},{  8}}, {{122},{  8}}, {{250},{  8}}, {{  6},{  8}}, {{134},{  8}},
+{{ 70},{  8}}, {{198},{  8}}, {{ 38},{  8}}, {{166},{  8}}, {{102},{  8}},
+{{230},{  8}}, {{ 22},{  8}}, {{150},{  8}}, {{ 86},{  8}}, {{214},{  8}},
+{{ 54},{  8}}, {{182},{  8}}, {{118},{  8}}, {{246},{  8}}, {{ 14},{  8}},
+{{142},{  8}}, {{ 78},{  8}}, {{206},{  8}}, {{ 46},{  8}}, {{174},{  8}},
+{{110},{  8}}, {{238},{  8}}, {{ 30},{  8}}, {{158},{  8}}, {{ 94},{  8}},
+{{222},{  8}}, {{ 62},{  8}}, {{190},{  8}}, {{126},{  8}}, {{254},{  8}},
+{{  1},{  8}}, {{129},{  8}}, {{ 65},{  8}}, {{193},{  8}}, {{ 33},{  8}},
+{{161},{  8}}, {{ 97},{  8}}, {{225},{  8}}, {{ 17},{  8}}, {{145},{  8}},
+{{ 81},{  8}}, {{209},{  8}}, {{ 49},{  8}}, {{177},{  8}}, {{113},{  8}},
+{{241},{  8}}, {{  9},{  8}}, {{137},{  8}}, {{ 73},{  8}}, {{201},{  8}},
+{{ 41},{  8}}, {{169},{  8}}, {{105},{  8}}, {{233},{  8}}, {{ 25},{  8}},
+{{153},{  8}}, {{ 89},{  8}}, {{217},{  8}}, {{ 57},{  8}}, {{185},{  8}},
+{{121},{  8}}, {{249},{  8}}, {{  5},{  8}}, {{133},{  8}}, {{ 69},{  8}},
+{{197},{  8}}, {{ 37},{  8}}, {{165},{  8}}, {{101},{  8}}, {{229},{  8}},
+{{ 21},{  8}}, {{149},{  8}}, {{ 85},{  8}}, {{213},{  8}}, {{ 53},{  8}},
+{{181},{  8}}, {{117},{  8}}, {{245},{  8}}, {{ 13},{  8}}, {{141},{  8}},
+{{ 77},{  8}}, {{205},{  8}}, {{ 45},{  8}}, {{173},{  8}}, {{109},{  8}},
+{{237},{  8}}, {{ 29},{  8}}, {{157},{  8}}, {{ 93},{  8}}, {{221},{  8}},
+{{ 61},{  8}}, {{189},{  8}}, {{125},{  8}}, {{253},{  8}}, {{ 19},{  9}},
+{{275},{  9}}, {{147},{  9}}, {{403},{  9}}, {{ 83},{  9}}, {{339},{  9}},
+{{211},{  9}}, {{467},{  9}}, {{ 51},{  9}}, {{307},{  9}}, {{179},{  9}},
+{{435},{  9}}, {{115},{  9}}, {{371},{  9}}, {{243},{  9}}, {{499},{  9}},
+{{ 11},{  9}}, {{267},{  9}}, {{139},{  9}}, {{395},{  9}}, {{ 75},{  9}},
+{{331},{  9}}, {{203},{  9}}, {{459},{  9}}, {{ 43},{  9}}, {{299},{  9}},
+{{171},{  9}}, {{427},{  9}}, {{107},{  9}}, {{363},{  9}}, {{235},{  9}},
+{{491},{  9}}, {{ 27},{  9}}, {{283},{  9}}, {{155},{  9}}, {{411},{  9}},
+{{ 91},{  9}}, {{347},{  9}}, {{219},{  9}}, {{475},{  9}}, {{ 59},{  9}},
+{{315},{  9}}, {{187},{  9}}, {{443},{  9}}, {{123},{  9}}, {{379},{  9}},
+{{251},{  9}}, {{507},{  9}}, {{  7},{  9}}, {{263},{  9}}, {{135},{  9}},
+{{391},{  9}}, {{ 71},{  9}}, {{327},{  9}}, {{199},{  9}}, {{455},{  9}},
+{{ 39},{  9}}, {{295},{  9}}, {{167},{  9}}, {{423},{  9}}, {{103},{  9}},
+{{359},{  9}}, {{231},{  9}}, {{487},{  9}}, {{ 23},{  9}}, {{279},{  9}},
+{{151},{  9}}, {{407},{  9}}, {{ 87},{  9}}, {{343},{  9}}, {{215},{  9}},
+{{471},{  9}}, {{ 55},{  9}}, {{311},{  9}}, {{183},{  9}}, {{439},{  9}},
+{{119},{  9}}, {{375},{  9}}, {{247},{  9}}, {{503},{  9}}, {{ 15},{  9}},
+{{271},{  9}}, {{143},{  9}}, {{399},{  9}}, {{ 79},{  9}}, {{335},{  9}},
+{{207},{  9}}, {{463},{  9}}, {{ 47},{  9}}, {{303},{  9}}, {{175},{  9}},
+{{431},{  9}}, {{111},{  9}}, {{367},{  9}}, {{239},{  9}}, {{495},{  9}},
+{{ 31},{  9}}, {{287},{  9}}, {{159},{  9}}, {{415},{  9}}, {{ 95},{  9}},
+{{351},{  9}}, {{223},{  9}}, {{479},{  9}}, {{ 63},{  9}}, {{319},{  9}},
+{{191},{  9}}, {{447},{  9}}, {{127},{  9}}, {{383},{  9}}, {{255},{  9}},
+{{511},{  9}}, {{  0},{  7}}, {{ 64},{  7}}, {{ 32},{  7}}, {{ 96},{  7}},
+{{ 16},{  7}}, {{ 80},{  7}}, {{ 48},{  7}}, {{112},{  7}}, {{  8},{  7}},
+{{ 72},{  7}}, {{ 40},{  7}}, {{104},{  7}}, {{ 24},{  7}}, {{ 88},{  7}},
+{{ 56},{  7}}, {{120},{  7}}, {{  4},{  7}}, {{ 68},{  7}}, {{ 36},{  7}},
+{{100},{  7}}, {{ 20},{  7}}, {{ 84},{  7}}, {{ 52},{  7}}, {{116},{  7}},
+{{  3},{  8}}, {{131},{  8}}, {{ 67},{  8}}, {{195},{  8}}, {{ 35},{  8}},
+{{163},{  8}}, {{ 99},{  8}}, {{227},{  8}}
+};
+
+local const ct_data static_dtree[D_CODES] = {
+{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}},
+{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}},
+{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}},
+{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}},
+{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}},
+{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}}
+};
+
+const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {
+ 0,  1,  2,  3,  4,  4,  5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  8,
+ 8,  8,  8,  8,  9,  9,  9,  9,  9,  9,  9,  9, 10, 10, 10, 10, 10, 10, 10, 10,
+10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
+13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  0,  0, 16, 17,
+18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22,
+23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
+};
+
+const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {
+ 0,  1,  2,  3,  4,  5,  6,  7,  8,  8,  9,  9, 10, 10, 11, 11, 12, 12, 12, 12,
+13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16,
+17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19,
+19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22,
+22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
+23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28
+};
+
+local const int base_length[LENGTH_CODES] = {
+0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
+64, 80, 96, 112, 128, 160, 192, 224, 0
+};
+
+local const int base_dist[D_CODES] = {
+    0,     1,     2,     3,     4,     6,     8,    12,    16,    24,
+   32,    48,    64,    96,   128,   192,   256,   384,   512,   768,
+ 1024,  1536,  2048,  3072,  4096,  6144,  8192, 12288, 16384, 24576
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/uncompr.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,93 @@
+/* uncompr.c -- decompress a memory buffer
+ * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#define ZLIB_INTERNAL
+#include "zlib.h"
+
+/* ===========================================================================
+     Decompresses the source buffer into the destination buffer.  *sourceLen is
+   the byte length of the source buffer. Upon entry, *destLen is the total size
+   of the destination buffer, which must be large enough to hold the entire
+   uncompressed data. (The size of the uncompressed data must have been saved
+   previously by the compressor and transmitted to the decompressor by some
+   mechanism outside the scope of this compression library.) Upon exit,
+   *destLen is the size of the decompressed data and *sourceLen is the number
+   of source bytes consumed. Upon return, source + *sourceLen points to the
+   first unused input byte.
+
+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_BUF_ERROR if there was not enough room in the output buffer, or
+   Z_DATA_ERROR if the input data was corrupted, including if the input data is
+   an incomplete zlib stream.
+*/
+int ZEXPORT uncompress2 (dest, destLen, source, sourceLen)
+    Bytef *dest;
+    uLongf *destLen;
+    const Bytef *source;
+    uLong *sourceLen;
+{
+    z_stream stream;
+    int err;
+    const uInt max = (uInt)-1;
+    uLong len, left;
+    Byte buf[1];    /* for detection of incomplete stream when *destLen == 0 */
+
+    len = *sourceLen;
+    if (*destLen) {
+        left = *destLen;
+        *destLen = 0;
+    }
+    else {
+        left = 1;
+        dest = buf;
+    }
+
+    stream.next_in = (z_const Bytef *)source;
+    stream.avail_in = 0;
+    stream.zalloc = (alloc_func)0;
+    stream.zfree = (free_func)0;
+    stream.opaque = (voidpf)0;
+
+    err = inflateInit(&stream);
+    if (err != Z_OK) return err;
+
+    stream.next_out = dest;
+    stream.avail_out = 0;
+
+    do {
+        if (stream.avail_out == 0) {
+            stream.avail_out = left > (uLong)max ? max : (uInt)left;
+            left -= stream.avail_out;
+        }
+        if (stream.avail_in == 0) {
+            stream.avail_in = len > (uLong)max ? max : (uInt)len;
+            len -= stream.avail_in;
+        }
+        err = inflate(&stream, Z_NO_FLUSH);
+    } while (err == Z_OK);
+
+    *sourceLen -= len + stream.avail_in;
+    if (dest != buf)
+        *destLen = stream.total_out;
+    else if (stream.total_out && err == Z_BUF_ERROR)
+        left = 1;
+
+    inflateEnd(&stream);
+    return err == Z_STREAM_END ? Z_OK :
+           err == Z_NEED_DICT ? Z_DATA_ERROR  :
+           err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
+           err;
+}
+
+int ZEXPORT uncompress (dest, destLen, source, sourceLen)
+    Bytef *dest;
+    uLongf *destLen;
+    const Bytef *source;
+    uLong sourceLen;
+{
+    return uncompress2(dest, destLen, source, &sourceLen);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/zconf.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,534 @@
+/* zconf.h -- configuration of the zlib compression library
+ * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#ifndef ZCONF_H
+#define ZCONF_H
+
+/*
+ * If you *really* need a unique prefix for all types and library functions,
+ * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
+ * Even better than compiling with -DZ_PREFIX would be to use configure to set
+ * this permanently in zconf.h using "./configure --zprefix".
+ */
+#ifdef Z_PREFIX     /* may be set to #if 1 by ./configure */
+#  define Z_PREFIX_SET
+
+/* all linked symbols and init macros */
+#  define _dist_code            z__dist_code
+#  define _length_code          z__length_code
+#  define _tr_align             z__tr_align
+#  define _tr_flush_bits        z__tr_flush_bits
+#  define _tr_flush_block       z__tr_flush_block
+#  define _tr_init              z__tr_init
+#  define _tr_stored_block      z__tr_stored_block
+#  define _tr_tally             z__tr_tally
+#  define adler32               z_adler32
+#  define adler32_combine       z_adler32_combine
+#  define adler32_combine64     z_adler32_combine64
+#  define adler32_z             z_adler32_z
+#  ifndef Z_SOLO
+#    define compress              z_compress
+#    define compress2             z_compress2
+#    define compressBound         z_compressBound
+#  endif
+#  define crc32                 z_crc32
+#  define crc32_combine         z_crc32_combine
+#  define crc32_combine64       z_crc32_combine64
+#  define crc32_z               z_crc32_z
+#  define deflate               z_deflate
+#  define deflateBound          z_deflateBound
+#  define deflateCopy           z_deflateCopy
+#  define deflateEnd            z_deflateEnd
+#  define deflateGetDictionary  z_deflateGetDictionary
+#  define deflateInit           z_deflateInit
+#  define deflateInit2          z_deflateInit2
+#  define deflateInit2_         z_deflateInit2_
+#  define deflateInit_          z_deflateInit_
+#  define deflateParams         z_deflateParams
+#  define deflatePending        z_deflatePending
+#  define deflatePrime          z_deflatePrime
+#  define deflateReset          z_deflateReset
+#  define deflateResetKeep      z_deflateResetKeep
+#  define deflateSetDictionary  z_deflateSetDictionary
+#  define deflateSetHeader      z_deflateSetHeader
+#  define deflateTune           z_deflateTune
+#  define deflate_copyright     z_deflate_copyright
+#  define get_crc_table         z_get_crc_table
+#  ifndef Z_SOLO
+#    define gz_error              z_gz_error
+#    define gz_intmax             z_gz_intmax
+#    define gz_strwinerror        z_gz_strwinerror
+#    define gzbuffer              z_gzbuffer
+#    define gzclearerr            z_gzclearerr
+#    define gzclose               z_gzclose
+#    define gzclose_r             z_gzclose_r
+#    define gzclose_w             z_gzclose_w
+#    define gzdirect              z_gzdirect
+#    define gzdopen               z_gzdopen
+#    define gzeof                 z_gzeof
+#    define gzerror               z_gzerror
+#    define gzflush               z_gzflush
+#    define gzfread               z_gzfread
+#    define gzfwrite              z_gzfwrite
+#    define gzgetc                z_gzgetc
+#    define gzgetc_               z_gzgetc_
+#    define gzgets                z_gzgets
+#    define gzoffset              z_gzoffset
+#    define gzoffset64            z_gzoffset64
+#    define gzopen                z_gzopen
+#    define gzopen64              z_gzopen64
+#    ifdef _WIN32
+#      define gzopen_w              z_gzopen_w
+#    endif
+#    define gzprintf              z_gzprintf
+#    define gzputc                z_gzputc
+#    define gzputs                z_gzputs
+#    define gzread                z_gzread
+#    define gzrewind              z_gzrewind
+#    define gzseek                z_gzseek
+#    define gzseek64              z_gzseek64
+#    define gzsetparams           z_gzsetparams
+#    define gztell                z_gztell
+#    define gztell64              z_gztell64
+#    define gzungetc              z_gzungetc
+#    define gzvprintf             z_gzvprintf
+#    define gzwrite               z_gzwrite
+#  endif
+#  define inflate               z_inflate
+#  define inflateBack           z_inflateBack
+#  define inflateBackEnd        z_inflateBackEnd
+#  define inflateBackInit       z_inflateBackInit
+#  define inflateBackInit_      z_inflateBackInit_
+#  define inflateCodesUsed      z_inflateCodesUsed
+#  define inflateCopy           z_inflateCopy
+#  define inflateEnd            z_inflateEnd
+#  define inflateGetDictionary  z_inflateGetDictionary
+#  define inflateGetHeader      z_inflateGetHeader
+#  define inflateInit           z_inflateInit
+#  define inflateInit2          z_inflateInit2
+#  define inflateInit2_         z_inflateInit2_
+#  define inflateInit_          z_inflateInit_
+#  define inflateMark           z_inflateMark
+#  define inflatePrime          z_inflatePrime
+#  define inflateReset          z_inflateReset
+#  define inflateReset2         z_inflateReset2
+#  define inflateResetKeep      z_inflateResetKeep
+#  define inflateSetDictionary  z_inflateSetDictionary
+#  define inflateSync           z_inflateSync
+#  define inflateSyncPoint      z_inflateSyncPoint
+#  define inflateUndermine      z_inflateUndermine
+#  define inflateValidate       z_inflateValidate
+#  define inflate_copyright     z_inflate_copyright
+#  define inflate_fast          z_inflate_fast
+#  define inflate_table         z_inflate_table
+#  ifndef Z_SOLO
+#    define uncompress            z_uncompress
+#    define uncompress2           z_uncompress2
+#  endif
+#  define zError                z_zError
+#  ifndef Z_SOLO
+#    define zcalloc               z_zcalloc
+#    define zcfree                z_zcfree
+#  endif
+#  define zlibCompileFlags      z_zlibCompileFlags
+#  define zlibVersion           z_zlibVersion
+
+/* all zlib typedefs in zlib.h and zconf.h */
+#  define Byte                  z_Byte
+#  define Bytef                 z_Bytef
+#  define alloc_func            z_alloc_func
+#  define charf                 z_charf
+#  define free_func             z_free_func
+#  ifndef Z_SOLO
+#    define gzFile                z_gzFile
+#  endif
+#  define gz_header             z_gz_header
+#  define gz_headerp            z_gz_headerp
+#  define in_func               z_in_func
+#  define intf                  z_intf
+#  define out_func              z_out_func
+#  define uInt                  z_uInt
+#  define uIntf                 z_uIntf
+#  define uLong                 z_uLong
+#  define uLongf                z_uLongf
+#  define voidp                 z_voidp
+#  define voidpc                z_voidpc
+#  define voidpf                z_voidpf
+
+/* all zlib structs in zlib.h and zconf.h */
+#  define gz_header_s           z_gz_header_s
+#  define internal_state        z_internal_state
+
+#endif
+
+#if defined(__MSDOS__) && !defined(MSDOS)
+#  define MSDOS
+#endif
+#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
+#  define OS2
+#endif
+#if defined(_WINDOWS) && !defined(WINDOWS)
+#  define WINDOWS
+#endif
+#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
+#  ifndef WIN32
+#    define WIN32
+#  endif
+#endif
+#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
+#  if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
+#    ifndef SYS16BIT
+#      define SYS16BIT
+#    endif
+#  endif
+#endif
+
+/*
+ * Compile with -DMAXSEG_64K if the alloc function cannot allocate more
+ * than 64k bytes at a time (needed on systems with 16-bit int).
+ */
+#ifdef SYS16BIT
+#  define MAXSEG_64K
+#endif
+#ifdef MSDOS
+#  define UNALIGNED_OK
+#endif
+
+#ifdef __STDC_VERSION__
+#  ifndef STDC
+#    define STDC
+#  endif
+#  if __STDC_VERSION__ >= 199901L
+#    ifndef STDC99
+#      define STDC99
+#    endif
+#  endif
+#endif
+#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
+#  define STDC
+#endif
+#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
+#  define STDC
+#endif
+#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
+#  define STDC
+#endif
+#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
+#  define STDC
+#endif
+
+#if defined(__OS400__) && !defined(STDC)    /* iSeries (formerly AS/400). */
+#  define STDC
+#endif
+
+#ifndef STDC
+#  ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
+#    define const       /* note: need a more gentle solution here */
+#  endif
+#endif
+
+#if defined(ZLIB_CONST) && !defined(z_const)
+#  define z_const const
+#else
+#  define z_const
+#endif
+
+#ifdef Z_SOLO
+   typedef unsigned long z_size_t;
+#else
+#  define z_longlong long long
+#  if defined(NO_SIZE_T)
+     typedef unsigned NO_SIZE_T z_size_t;
+#  elif defined(STDC)
+#    include <stddef.h>
+     typedef size_t z_size_t;
+#  else
+     typedef unsigned long z_size_t;
+#  endif
+#  undef z_longlong
+#endif
+
+/* Maximum value for memLevel in deflateInit2 */
+#ifndef MAX_MEM_LEVEL
+#  ifdef MAXSEG_64K
+#    define MAX_MEM_LEVEL 8
+#  else
+#    define MAX_MEM_LEVEL 9
+#  endif
+#endif
+
+/* Maximum value for windowBits in deflateInit2 and inflateInit2.
+ * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
+ * created by gzip. (Files created by minigzip can still be extracted by
+ * gzip.)
+ */
+#ifndef MAX_WBITS
+#  define MAX_WBITS   15 /* 32K LZ77 window */
+#endif
+
+/* The memory requirements for deflate are (in bytes):
+            (1 << (windowBits+2)) +  (1 << (memLevel+9))
+ that is: 128K for windowBits=15  +  128K for memLevel = 8  (default values)
+ plus a few kilobytes for small objects. For example, if you want to reduce
+ the default memory requirements from 256K to 128K, compile with
+     make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
+ Of course this will generally degrade compression (there's no free lunch).
+
+   The memory requirements for inflate are (in bytes) 1 << windowBits
+ that is, 32K for windowBits=15 (default value) plus about 7 kilobytes
+ for small objects.
+*/
+
+                        /* Type declarations */
+
+#ifndef OF /* function prototypes */
+#  ifdef STDC
+#    define OF(args)  args
+#  else
+#    define OF(args)  ()
+#  endif
+#endif
+
+#ifndef Z_ARG /* function prototypes for stdarg */
+#  if defined(STDC) || defined(Z_HAVE_STDARG_H)
+#    define Z_ARG(args)  args
+#  else
+#    define Z_ARG(args)  ()
+#  endif
+#endif
+
+/* The following definitions for FAR are needed only for MSDOS mixed
+ * model programming (small or medium model with some far allocations).
+ * This was tested only with MSC; for other MSDOS compilers you may have
+ * to define NO_MEMCPY in zutil.h.  If you don't need the mixed model,
+ * just define FAR to be empty.
+ */
+#ifdef SYS16BIT
+#  if defined(M_I86SM) || defined(M_I86MM)
+     /* MSC small or medium model */
+#    define SMALL_MEDIUM
+#    ifdef _MSC_VER
+#      define FAR _far
+#    else
+#      define FAR far
+#    endif
+#  endif
+#  if (defined(__SMALL__) || defined(__MEDIUM__))
+     /* Turbo C small or medium model */
+#    define SMALL_MEDIUM
+#    ifdef __BORLANDC__
+#      define FAR _far
+#    else
+#      define FAR far
+#    endif
+#  endif
+#endif
+
+#if defined(WINDOWS) || defined(WIN32)
+   /* If building or using zlib as a DLL, define ZLIB_DLL.
+    * This is not mandatory, but it offers a little performance increase.
+    */
+#  ifdef ZLIB_DLL
+#    if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
+#      ifdef ZLIB_INTERNAL
+#        define ZEXTERN extern __declspec(dllexport)
+#      else
+#        define ZEXTERN extern __declspec(dllimport)
+#      endif
+#    endif
+#  endif  /* ZLIB_DLL */
+   /* If building or using zlib with the WINAPI/WINAPIV calling convention,
+    * define ZLIB_WINAPI.
+    * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
+    */
+#  ifdef ZLIB_WINAPI
+#    ifdef FAR
+#      undef FAR
+#    endif
+#    include <windows.h>
+     /* No need for _export, use ZLIB.DEF instead. */
+     /* For complete Windows compatibility, use WINAPI, not __stdcall. */
+#    define ZEXPORT WINAPI
+#    ifdef WIN32
+#      define ZEXPORTVA WINAPIV
+#    else
+#      define ZEXPORTVA FAR CDECL
+#    endif
+#  endif
+#endif
+
+#if defined (__BEOS__)
+#  ifdef ZLIB_DLL
+#    ifdef ZLIB_INTERNAL
+#      define ZEXPORT   __declspec(dllexport)
+#      define ZEXPORTVA __declspec(dllexport)
+#    else
+#      define ZEXPORT   __declspec(dllimport)
+#      define ZEXPORTVA __declspec(dllimport)
+#    endif
+#  endif
+#endif
+
+#ifndef ZEXTERN
+#  define ZEXTERN extern
+#endif
+#ifndef ZEXPORT
+#  define ZEXPORT
+#endif
+#ifndef ZEXPORTVA
+#  define ZEXPORTVA
+#endif
+
+#ifndef FAR
+#  define FAR
+#endif
+
+#if !defined(__MACTYPES__)
+typedef unsigned char  Byte;  /* 8 bits */
+#endif
+typedef unsigned int   uInt;  /* 16 bits or more */
+typedef unsigned long  uLong; /* 32 bits or more */
+
+#ifdef SMALL_MEDIUM
+   /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
+#  define Bytef Byte FAR
+#else
+   typedef Byte  FAR Bytef;
+#endif
+typedef char  FAR charf;
+typedef int   FAR intf;
+typedef uInt  FAR uIntf;
+typedef uLong FAR uLongf;
+
+#ifdef STDC
+   typedef void const *voidpc;
+   typedef void FAR   *voidpf;
+   typedef void       *voidp;
+#else
+   typedef Byte const *voidpc;
+   typedef Byte FAR   *voidpf;
+   typedef Byte       *voidp;
+#endif
+
+#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC)
+#  include <limits.h>
+#  if (UINT_MAX == 0xffffffffUL)
+#    define Z_U4 unsigned
+#  elif (ULONG_MAX == 0xffffffffUL)
+#    define Z_U4 unsigned long
+#  elif (USHRT_MAX == 0xffffffffUL)
+#    define Z_U4 unsigned short
+#  endif
+#endif
+
+#ifdef Z_U4
+   typedef Z_U4 z_crc_t;
+#else
+   typedef unsigned long z_crc_t;
+#endif
+
+#ifdef HAVE_UNISTD_H    /* may be set to #if 1 by ./configure */
+#  define Z_HAVE_UNISTD_H
+#endif
+
+#ifdef HAVE_STDARG_H    /* may be set to #if 1 by ./configure */
+#  define Z_HAVE_STDARG_H
+#endif
+
+#ifdef STDC
+#  ifndef Z_SOLO
+#    include <sys/types.h>      /* for off_t */
+#  endif
+#endif
+
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+#  ifndef Z_SOLO
+#    include <stdarg.h>         /* for va_list */
+#  endif
+#endif
+
+#ifdef _WIN32
+#  ifndef Z_SOLO
+#    include <stddef.h>         /* for wchar_t */
+#  endif
+#endif
+
+/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and
+ * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even
+ * though the former does not conform to the LFS document), but considering
+ * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as
+ * equivalently requesting no 64-bit operations
+ */
+#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1
+#  undef _LARGEFILE64_SOURCE
+#endif
+
+#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H)
+#  define Z_HAVE_UNISTD_H
+#endif
+#ifndef Z_SOLO
+#  if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE)
+#    include <unistd.h>         /* for SEEK_*, off_t, and _LFS64_LARGEFILE */
+#    ifdef VMS
+#      include <unixio.h>       /* for off_t */
+#    endif
+#    ifndef z_off_t
+#      define z_off_t off_t
+#    endif
+#  endif
+#endif
+
+#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0
+#  define Z_LFS64
+#endif
+
+#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64)
+#  define Z_LARGE64
+#endif
+
+#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64)
+#  define Z_WANT64
+#endif
+
+#if !defined(SEEK_SET) && !defined(Z_SOLO)
+#  define SEEK_SET        0       /* Seek from beginning of file.  */
+#  define SEEK_CUR        1       /* Seek from current position.  */
+#  define SEEK_END        2       /* Set file pointer to EOF plus "offset" */
+#endif
+
+#ifndef z_off_t
+#  define z_off_t long
+#endif
+
+#if !defined(_WIN32) && defined(Z_LARGE64)
+#  define z_off64_t off64_t
+#else
+#  if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO)
+#    define z_off64_t __int64
+#  else
+#    define z_off64_t z_off_t
+#  endif
+#endif
+
+/* MVS linker does not support external names larger than 8 bytes */
+#if defined(__MVS__)
+  #pragma map(deflateInit_,"DEIN")
+  #pragma map(deflateInit2_,"DEIN2")
+  #pragma map(deflateEnd,"DEEND")
+  #pragma map(deflateBound,"DEBND")
+  #pragma map(inflateInit_,"ININ")
+  #pragma map(inflateInit2_,"ININ2")
+  #pragma map(inflateEnd,"INEND")
+  #pragma map(inflateSync,"INSY")
+  #pragma map(inflateSetDictionary,"INSEDI")
+  #pragma map(compressBound,"CMBND")
+  #pragma map(inflate_table,"INTABL")
+  #pragma map(inflate_fast,"INFA")
+  #pragma map(inflate_copyright,"INCOPY")
+#endif
+
+#endif /* ZCONF_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/zlib.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,1912 @@
+/* zlib.h -- interface of the 'zlib' general purpose compression library
+  version 1.2.11, January 15th, 2017
+
+  Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  Jean-loup Gailly        Mark Adler
+  jloup@gzip.org          madler@alumni.caltech.edu
+
+
+  The data format used by the zlib library is described by RFCs (Request for
+  Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950
+  (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format).
+*/
+
+#ifndef ZLIB_H
+#define ZLIB_H
+
+#include "zconf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ZLIB_VERSION "1.2.11"
+#define ZLIB_VERNUM 0x12b0
+#define ZLIB_VER_MAJOR 1
+#define ZLIB_VER_MINOR 2
+#define ZLIB_VER_REVISION 11
+#define ZLIB_VER_SUBREVISION 0
+
+/*
+    The 'zlib' compression library provides in-memory compression and
+  decompression functions, including integrity checks of the uncompressed data.
+  This version of the library supports only one compression method (deflation)
+  but other algorithms will be added later and will have the same stream
+  interface.
+
+    Compression can be done in a single step if the buffers are large enough,
+  or can be done by repeated calls of the compression function.  In the latter
+  case, the application must provide more input and/or consume the output
+  (providing more output space) before each call.
+
+    The compressed data format used by default by the in-memory functions is
+  the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped
+  around a deflate stream, which is itself documented in RFC 1951.
+
+    The library also supports reading and writing files in gzip (.gz) format
+  with an interface similar to that of stdio using the functions that start
+  with "gz".  The gzip format is different from the zlib format.  gzip is a
+  gzip wrapper, documented in RFC 1952, wrapped around a deflate stream.
+
+    This library can optionally read and write gzip and raw deflate streams in
+  memory as well.
+
+    The zlib format was designed to be compact and fast for use in memory
+  and on communications channels.  The gzip format was designed for single-
+  file compression on file systems, has a larger header than zlib to maintain
+  directory information, and uses a different, slower check method than zlib.
+
+    The library does not install any signal handler.  The decoder checks
+  the consistency of the compressed data, so the library should never crash
+  even in the case of corrupted input.
+*/
+
+typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size));
+typedef void   (*free_func)  OF((voidpf opaque, voidpf address));
+
+struct internal_state;
+
+typedef struct z_stream_s {
+    z_const Bytef *next_in;     /* next input byte */
+    uInt     avail_in;  /* number of bytes available at next_in */
+    uLong    total_in;  /* total number of input bytes read so far */
+
+    Bytef    *next_out; /* next output byte will go here */
+    uInt     avail_out; /* remaining free space at next_out */
+    uLong    total_out; /* total number of bytes output so far */
+
+    z_const char *msg;  /* last error message, NULL if no error */
+    struct internal_state FAR *state; /* not visible by applications */
+
+    alloc_func zalloc;  /* used to allocate the internal state */
+    free_func  zfree;   /* used to free the internal state */
+    voidpf     opaque;  /* private data object passed to zalloc and zfree */
+
+    int     data_type;  /* best guess about the data type: binary or text
+                           for deflate, or the decoding state for inflate */
+    uLong   adler;      /* Adler-32 or CRC-32 value of the uncompressed data */
+    uLong   reserved;   /* reserved for future use */
+} z_stream;
+
+typedef z_stream FAR *z_streamp;
+
+/*
+     gzip header information passed to and from zlib routines.  See RFC 1952
+  for more details on the meanings of these fields.
+*/
+typedef struct gz_header_s {
+    int     text;       /* true if compressed data believed to be text */
+    uLong   time;       /* modification time */
+    int     xflags;     /* extra flags (not used when writing a gzip file) */
+    int     os;         /* operating system */
+    Bytef   *extra;     /* pointer to extra field or Z_NULL if none */
+    uInt    extra_len;  /* extra field length (valid if extra != Z_NULL) */
+    uInt    extra_max;  /* space at extra (only when reading header) */
+    Bytef   *name;      /* pointer to zero-terminated file name or Z_NULL */
+    uInt    name_max;   /* space at name (only when reading header) */
+    Bytef   *comment;   /* pointer to zero-terminated comment or Z_NULL */
+    uInt    comm_max;   /* space at comment (only when reading header) */
+    int     hcrc;       /* true if there was or will be a header crc */
+    int     done;       /* true when done reading gzip header (not used
+                           when writing a gzip file) */
+} gz_header;
+
+typedef gz_header FAR *gz_headerp;
+
+/*
+     The application must update next_in and avail_in when avail_in has dropped
+   to zero.  It must update next_out and avail_out when avail_out has dropped
+   to zero.  The application must initialize zalloc, zfree and opaque before
+   calling the init function.  All other fields are set by the compression
+   library and must not be updated by the application.
+
+     The opaque value provided by the application will be passed as the first
+   parameter for calls of zalloc and zfree.  This can be useful for custom
+   memory management.  The compression library attaches no meaning to the
+   opaque value.
+
+     zalloc must return Z_NULL if there is not enough memory for the object.
+   If zlib is used in a multi-threaded application, zalloc and zfree must be
+   thread safe.  In that case, zlib is thread-safe.  When zalloc and zfree are
+   Z_NULL on entry to the initialization function, they are set to internal
+   routines that use the standard library functions malloc() and free().
+
+     On 16-bit systems, the functions zalloc and zfree must be able to allocate
+   exactly 65536 bytes, but will not be required to allocate more than this if
+   the symbol MAXSEG_64K is defined (see zconf.h).  WARNING: On MSDOS, pointers
+   returned by zalloc for objects of exactly 65536 bytes *must* have their
+   offset normalized to zero.  The default allocation function provided by this
+   library ensures this (see zutil.c).  To reduce memory requirements and avoid
+   any allocation of 64K objects, at the expense of compression ratio, compile
+   the library with -DMAX_WBITS=14 (see zconf.h).
+
+     The fields total_in and total_out can be used for statistics or progress
+   reports.  After compression, total_in holds the total size of the
+   uncompressed data and may be saved for use by the decompressor (particularly
+   if the decompressor wants to decompress everything in a single step).
+*/
+
+                        /* constants */
+
+#define Z_NO_FLUSH      0
+#define Z_PARTIAL_FLUSH 1
+#define Z_SYNC_FLUSH    2
+#define Z_FULL_FLUSH    3
+#define Z_FINISH        4
+#define Z_BLOCK         5
+#define Z_TREES         6
+/* Allowed flush values; see deflate() and inflate() below for details */
+
+#define Z_OK            0
+#define Z_STREAM_END    1
+#define Z_NEED_DICT     2
+#define Z_ERRNO        (-1)
+#define Z_STREAM_ERROR (-2)
+#define Z_DATA_ERROR   (-3)
+#define Z_MEM_ERROR    (-4)
+#define Z_BUF_ERROR    (-5)
+#define Z_VERSION_ERROR (-6)
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+
+#define Z_NO_COMPRESSION         0
+#define Z_BEST_SPEED             1
+#define Z_BEST_COMPRESSION       9
+#define Z_DEFAULT_COMPRESSION  (-1)
+/* compression levels */
+
+#define Z_FILTERED            1
+#define Z_HUFFMAN_ONLY        2
+#define Z_RLE                 3
+#define Z_FIXED               4
+#define Z_DEFAULT_STRATEGY    0
+/* compression strategy; see deflateInit2() below for details */
+
+#define Z_BINARY   0
+#define Z_TEXT     1
+#define Z_ASCII    Z_TEXT   /* for compatibility with 1.2.2 and earlier */
+#define Z_UNKNOWN  2
+/* Possible values of the data_type field for deflate() */
+
+#define Z_DEFLATED   8
+/* The deflate compression method (the only one supported in this version) */
+
+#define Z_NULL  0  /* for initializing zalloc, zfree, opaque */
+
+#define zlib_version zlibVersion()
+/* for compatibility with versions < 1.0.2 */
+
+
+                        /* basic functions */
+
+ZEXTERN const char * ZEXPORT zlibVersion OF((void));
+/* The application can compare zlibVersion and ZLIB_VERSION for consistency.
+   If the first character differs, the library code actually used is not
+   compatible with the zlib.h header file used by the application.  This check
+   is automatically made by deflateInit and inflateInit.
+ */
+
+/*
+ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level));
+
+     Initializes the internal stream state for compression.  The fields
+   zalloc, zfree and opaque must be initialized before by the caller.  If
+   zalloc and zfree are set to Z_NULL, deflateInit updates them to use default
+   allocation functions.
+
+     The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9:
+   1 gives best speed, 9 gives best compression, 0 gives no compression at all
+   (the input data is simply copied a block at a time).  Z_DEFAULT_COMPRESSION
+   requests a default compromise between speed and compression (currently
+   equivalent to level 6).
+
+     deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_STREAM_ERROR if level is not a valid compression level, or
+   Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible
+   with the version assumed by the caller (ZLIB_VERSION).  msg is set to null
+   if there is no error message.  deflateInit does not perform any compression:
+   this will be done by deflate().
+*/
+
+
+ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
+/*
+    deflate compresses as much data as possible, and stops when the input
+  buffer becomes empty or the output buffer becomes full.  It may introduce
+  some output latency (reading input without producing any output) except when
+  forced to flush.
+
+    The detailed semantics are as follows.  deflate performs one or both of the
+  following actions:
+
+  - Compress more input starting at next_in and update next_in and avail_in
+    accordingly.  If not all input can be processed (because there is not
+    enough room in the output buffer), next_in and avail_in are updated and
+    processing will resume at this point for the next call of deflate().
+
+  - Generate more output starting at next_out and update next_out and avail_out
+    accordingly.  This action is forced if the parameter flush is non zero.
+    Forcing flush frequently degrades the compression ratio, so this parameter
+    should be set only when necessary.  Some output may be provided even if
+    flush is zero.
+
+    Before the call of deflate(), the application should ensure that at least
+  one of the actions is possible, by providing more input and/or consuming more
+  output, and updating avail_in or avail_out accordingly; avail_out should
+  never be zero before the call.  The application can consume the compressed
+  output when it wants, for example when the output buffer is full (avail_out
+  == 0), or after each call of deflate().  If deflate returns Z_OK and with
+  zero avail_out, it must be called again after making room in the output
+  buffer because there might be more output pending. See deflatePending(),
+  which can be used if desired to determine whether or not there is more ouput
+  in that case.
+
+    Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
+  decide how much data to accumulate before producing output, in order to
+  maximize compression.
+
+    If the parameter flush is set to Z_SYNC_FLUSH, all pending output is
+  flushed to the output buffer and the output is aligned on a byte boundary, so
+  that the decompressor can get all input data available so far.  (In
+  particular avail_in is zero after the call if enough output space has been
+  provided before the call.) Flushing may degrade compression for some
+  compression algorithms and so it should be used only when necessary.  This
+  completes the current deflate block and follows it with an empty stored block
+  that is three bits plus filler bits to the next byte, followed by four bytes
+  (00 00 ff ff).
+
+    If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the
+  output buffer, but the output is not aligned to a byte boundary.  All of the
+  input data so far will be available to the decompressor, as for Z_SYNC_FLUSH.
+  This completes the current deflate block and follows it with an empty fixed
+  codes block that is 10 bits long.  This assures that enough bytes are output
+  in order for the decompressor to finish the block before the empty fixed
+  codes block.
+
+    If flush is set to Z_BLOCK, a deflate block is completed and emitted, as
+  for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to
+  seven bits of the current block are held to be written as the next byte after
+  the next deflate block is completed.  In this case, the decompressor may not
+  be provided enough bits at this point in order to complete decompression of
+  the data provided so far to the compressor.  It may need to wait for the next
+  block to be emitted.  This is for advanced applications that need to control
+  the emission of deflate blocks.
+
+    If flush is set to Z_FULL_FLUSH, all output is flushed as with
+  Z_SYNC_FLUSH, and the compression state is reset so that decompression can
+  restart from this point if previous compressed data has been damaged or if
+  random access is desired.  Using Z_FULL_FLUSH too often can seriously degrade
+  compression.
+
+    If deflate returns with avail_out == 0, this function must be called again
+  with the same value of the flush parameter and more output space (updated
+  avail_out), until the flush is complete (deflate returns with non-zero
+  avail_out).  In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that
+  avail_out is greater than six to avoid repeated flush markers due to
+  avail_out == 0 on return.
+
+    If the parameter flush is set to Z_FINISH, pending input is processed,
+  pending output is flushed and deflate returns with Z_STREAM_END if there was
+  enough output space.  If deflate returns with Z_OK or Z_BUF_ERROR, this
+  function must be called again with Z_FINISH and more output space (updated
+  avail_out) but no more input data, until it returns with Z_STREAM_END or an
+  error.  After deflate has returned Z_STREAM_END, the only possible operations
+  on the stream are deflateReset or deflateEnd.
+
+    Z_FINISH can be used in the first deflate call after deflateInit if all the
+  compression is to be done in a single step.  In order to complete in one
+  call, avail_out must be at least the value returned by deflateBound (see
+  below).  Then deflate is guaranteed to return Z_STREAM_END.  If not enough
+  output space is provided, deflate will not return Z_STREAM_END, and it must
+  be called again as described above.
+
+    deflate() sets strm->adler to the Adler-32 checksum of all input read
+  so far (that is, total_in bytes).  If a gzip stream is being generated, then
+  strm->adler will be the CRC-32 checksum of the input read so far.  (See
+  deflateInit2 below.)
+
+    deflate() may update strm->data_type if it can make a good guess about
+  the input data type (Z_BINARY or Z_TEXT).  If in doubt, the data is
+  considered binary.  This field is only for information purposes and does not
+  affect the compression algorithm in any manner.
+
+    deflate() returns Z_OK if some progress has been made (more input
+  processed or more output produced), Z_STREAM_END if all input has been
+  consumed and all output has been produced (only when flush is set to
+  Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
+  if next_in or next_out was Z_NULL or the state was inadvertently written over
+  by the application), or Z_BUF_ERROR if no progress is possible (for example
+  avail_in or avail_out was zero).  Note that Z_BUF_ERROR is not fatal, and
+  deflate() can be called again with more input and more output space to
+  continue compressing.
+*/
+
+
+ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm));
+/*
+     All dynamically allocated data structures for this stream are freed.
+   This function discards any unprocessed input and does not flush any pending
+   output.
+
+     deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the
+   stream state was inconsistent, Z_DATA_ERROR if the stream was freed
+   prematurely (some input or output was discarded).  In the error case, msg
+   may be set but then points to a static string (which must not be
+   deallocated).
+*/
+
+
+/*
+ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm));
+
+     Initializes the internal stream state for decompression.  The fields
+   next_in, avail_in, zalloc, zfree and opaque must be initialized before by
+   the caller.  In the current version of inflate, the provided input is not
+   read or consumed.  The allocation of a sliding window will be deferred to
+   the first call of inflate (if the decompression does not complete on the
+   first call).  If zalloc and zfree are set to Z_NULL, inflateInit updates
+   them to use default allocation functions.
+
+     inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+   version assumed by the caller, or Z_STREAM_ERROR if the parameters are
+   invalid, such as a null pointer to the structure.  msg is set to null if
+   there is no error message.  inflateInit does not perform any decompression.
+   Actual decompression will be done by inflate().  So next_in, and avail_in,
+   next_out, and avail_out are unused and unchanged.  The current
+   implementation of inflateInit() does not process any header information --
+   that is deferred until inflate() is called.
+*/
+
+
+ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
+/*
+    inflate decompresses as much data as possible, and stops when the input
+  buffer becomes empty or the output buffer becomes full.  It may introduce
+  some output latency (reading input without producing any output) except when
+  forced to flush.
+
+  The detailed semantics are as follows.  inflate performs one or both of the
+  following actions:
+
+  - Decompress more input starting at next_in and update next_in and avail_in
+    accordingly.  If not all input can be processed (because there is not
+    enough room in the output buffer), then next_in and avail_in are updated
+    accordingly, and processing will resume at this point for the next call of
+    inflate().
+
+  - Generate more output starting at next_out and update next_out and avail_out
+    accordingly.  inflate() provides as much output as possible, until there is
+    no more input data or no more space in the output buffer (see below about
+    the flush parameter).
+
+    Before the call of inflate(), the application should ensure that at least
+  one of the actions is possible, by providing more input and/or consuming more
+  output, and updating the next_* and avail_* values accordingly.  If the
+  caller of inflate() does not provide both available input and available
+  output space, it is possible that there will be no progress made.  The
+  application can consume the uncompressed output when it wants, for example
+  when the output buffer is full (avail_out == 0), or after each call of
+  inflate().  If inflate returns Z_OK and with zero avail_out, it must be
+  called again after making room in the output buffer because there might be
+  more output pending.
+
+    The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH,
+  Z_BLOCK, or Z_TREES.  Z_SYNC_FLUSH requests that inflate() flush as much
+  output as possible to the output buffer.  Z_BLOCK requests that inflate()
+  stop if and when it gets to the next deflate block boundary.  When decoding
+  the zlib or gzip format, this will cause inflate() to return immediately
+  after the header and before the first block.  When doing a raw inflate,
+  inflate() will go ahead and process the first block, and will return when it
+  gets to the end of that block, or when it runs out of data.
+
+    The Z_BLOCK option assists in appending to or combining deflate streams.
+  To assist in this, on return inflate() always sets strm->data_type to the
+  number of unused bits in the last byte taken from strm->next_in, plus 64 if
+  inflate() is currently decoding the last block in the deflate stream, plus
+  128 if inflate() returned immediately after decoding an end-of-block code or
+  decoding the complete header up to just before the first byte of the deflate
+  stream.  The end-of-block will not be indicated until all of the uncompressed
+  data from that block has been written to strm->next_out.  The number of
+  unused bits may in general be greater than seven, except when bit 7 of
+  data_type is set, in which case the number of unused bits will be less than
+  eight.  data_type is set as noted here every time inflate() returns for all
+  flush options, and so can be used to determine the amount of currently
+  consumed input in bits.
+
+    The Z_TREES option behaves as Z_BLOCK does, but it also returns when the
+  end of each deflate block header is reached, before any actual data in that
+  block is decoded.  This allows the caller to determine the length of the
+  deflate block header for later use in random access within a deflate block.
+  256 is added to the value of strm->data_type when inflate() returns
+  immediately after reaching the end of the deflate block header.
+
+    inflate() should normally be called until it returns Z_STREAM_END or an
+  error.  However if all decompression is to be performed in a single step (a
+  single call of inflate), the parameter flush should be set to Z_FINISH.  In
+  this case all pending input is processed and all pending output is flushed;
+  avail_out must be large enough to hold all of the uncompressed data for the
+  operation to complete.  (The size of the uncompressed data may have been
+  saved by the compressor for this purpose.)  The use of Z_FINISH is not
+  required to perform an inflation in one step.  However it may be used to
+  inform inflate that a faster approach can be used for the single inflate()
+  call.  Z_FINISH also informs inflate to not maintain a sliding window if the
+  stream completes, which reduces inflate's memory footprint.  If the stream
+  does not complete, either because not all of the stream is provided or not
+  enough output space is provided, then a sliding window will be allocated and
+  inflate() can be called again to continue the operation as if Z_NO_FLUSH had
+  been used.
+
+     In this implementation, inflate() always flushes as much output as
+  possible to the output buffer, and always uses the faster approach on the
+  first call.  So the effects of the flush parameter in this implementation are
+  on the return value of inflate() as noted below, when inflate() returns early
+  when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of
+  memory for a sliding window when Z_FINISH is used.
+
+     If a preset dictionary is needed after this call (see inflateSetDictionary
+  below), inflate sets strm->adler to the Adler-32 checksum of the dictionary
+  chosen by the compressor and returns Z_NEED_DICT; otherwise it sets
+  strm->adler to the Adler-32 checksum of all output produced so far (that is,
+  total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described
+  below.  At the end of the stream, inflate() checks that its computed Adler-32
+  checksum is equal to that saved by the compressor and returns Z_STREAM_END
+  only if the checksum is correct.
+
+    inflate() can decompress and check either zlib-wrapped or gzip-wrapped
+  deflate data.  The header type is detected automatically, if requested when
+  initializing with inflateInit2().  Any information contained in the gzip
+  header is not retained unless inflateGetHeader() is used.  When processing
+  gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output
+  produced so far.  The CRC-32 is checked against the gzip trailer, as is the
+  uncompressed length, modulo 2^32.
+
+    inflate() returns Z_OK if some progress has been made (more input processed
+  or more output produced), Z_STREAM_END if the end of the compressed data has
+  been reached and all uncompressed output has been produced, Z_NEED_DICT if a
+  preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
+  corrupted (input stream not conforming to the zlib format or incorrect check
+  value, in which case strm->msg points to a string with a more specific
+  error), Z_STREAM_ERROR if the stream structure was inconsistent (for example
+  next_in or next_out was Z_NULL, or the state was inadvertently written over
+  by the application), Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR
+  if no progress was possible or if there was not enough room in the output
+  buffer when Z_FINISH is used.  Note that Z_BUF_ERROR is not fatal, and
+  inflate() can be called again with more input and more output space to
+  continue decompressing.  If Z_DATA_ERROR is returned, the application may
+  then call inflateSync() to look for a good compression block if a partial
+  recovery of the data is to be attempted.
+*/
+
+
+ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm));
+/*
+     All dynamically allocated data structures for this stream are freed.
+   This function discards any unprocessed input and does not flush any pending
+   output.
+
+     inflateEnd returns Z_OK if success, or Z_STREAM_ERROR if the stream state
+   was inconsistent.
+*/
+
+
+                        /* Advanced functions */
+
+/*
+    The following functions are needed only in some special applications.
+*/
+
+/*
+ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm,
+                                     int  level,
+                                     int  method,
+                                     int  windowBits,
+                                     int  memLevel,
+                                     int  strategy));
+
+     This is another version of deflateInit with more compression options.  The
+   fields next_in, zalloc, zfree and opaque must be initialized before by the
+   caller.
+
+     The method parameter is the compression method.  It must be Z_DEFLATED in
+   this version of the library.
+
+     The windowBits parameter is the base two logarithm of the window size
+   (the size of the history buffer).  It should be in the range 8..15 for this
+   version of the library.  Larger values of this parameter result in better
+   compression at the expense of memory usage.  The default value is 15 if
+   deflateInit is used instead.
+
+     For the current implementation of deflate(), a windowBits value of 8 (a
+   window size of 256 bytes) is not supported.  As a result, a request for 8
+   will result in 9 (a 512-byte window).  In that case, providing 8 to
+   inflateInit2() will result in an error when the zlib header with 9 is
+   checked against the initialization of inflate().  The remedy is to not use 8
+   with deflateInit2() with this initialization, or at least in that case use 9
+   with inflateInit2().
+
+     windowBits can also be -8..-15 for raw deflate.  In this case, -windowBits
+   determines the window size.  deflate() will then generate raw deflate data
+   with no zlib header or trailer, and will not compute a check value.
+
+     windowBits can also be greater than 15 for optional gzip encoding.  Add
+   16 to windowBits to write a simple gzip header and trailer around the
+   compressed data instead of a zlib wrapper.  The gzip header will have no
+   file name, no extra data, no comment, no modification time (set to zero), no
+   header crc, and the operating system will be set to the appropriate value,
+   if the operating system was determined at compile time.  If a gzip stream is
+   being written, strm->adler is a CRC-32 instead of an Adler-32.
+
+     For raw deflate or gzip encoding, a request for a 256-byte window is
+   rejected as invalid, since only the zlib header provides a means of
+   transmitting the window size to the decompressor.
+
+     The memLevel parameter specifies how much memory should be allocated
+   for the internal compression state.  memLevel=1 uses minimum memory but is
+   slow and reduces compression ratio; memLevel=9 uses maximum memory for
+   optimal speed.  The default value is 8.  See zconf.h for total memory usage
+   as a function of windowBits and memLevel.
+
+     The strategy parameter is used to tune the compression algorithm.  Use the
+   value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a
+   filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no
+   string match), or Z_RLE to limit match distances to one (run-length
+   encoding).  Filtered data consists mostly of small values with a somewhat
+   random distribution.  In this case, the compression algorithm is tuned to
+   compress them better.  The effect of Z_FILTERED is to force more Huffman
+   coding and less string matching; it is somewhat intermediate between
+   Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY.  Z_RLE is designed to be almost as
+   fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data.  The
+   strategy parameter only affects the compression ratio but not the
+   correctness of the compressed output even if it is not set appropriately.
+   Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler
+   decoder for special applications.
+
+     deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid
+   method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is
+   incompatible with the version assumed by the caller (ZLIB_VERSION).  msg is
+   set to null if there is no error message.  deflateInit2 does not perform any
+   compression: this will be done by deflate().
+*/
+
+ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm,
+                                             const Bytef *dictionary,
+                                             uInt  dictLength));
+/*
+     Initializes the compression dictionary from the given byte sequence
+   without producing any compressed output.  When using the zlib format, this
+   function must be called immediately after deflateInit, deflateInit2 or
+   deflateReset, and before any call of deflate.  When doing raw deflate, this
+   function must be called either before any call of deflate, or immediately
+   after the completion of a deflate block, i.e. after all input has been
+   consumed and all output has been delivered when using any of the flush
+   options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH.  The
+   compressor and decompressor must use exactly the same dictionary (see
+   inflateSetDictionary).
+
+     The dictionary should consist of strings (byte sequences) that are likely
+   to be encountered later in the data to be compressed, with the most commonly
+   used strings preferably put towards the end of the dictionary.  Using a
+   dictionary is most useful when the data to be compressed is short and can be
+   predicted with good accuracy; the data can then be compressed better than
+   with the default empty dictionary.
+
+     Depending on the size of the compression data structures selected by
+   deflateInit or deflateInit2, a part of the dictionary may in effect be
+   discarded, for example if the dictionary is larger than the window size
+   provided in deflateInit or deflateInit2.  Thus the strings most likely to be
+   useful should be put at the end of the dictionary, not at the front.  In
+   addition, the current implementation of deflate will use at most the window
+   size minus 262 bytes of the provided dictionary.
+
+     Upon return of this function, strm->adler is set to the Adler-32 value
+   of the dictionary; the decompressor may later use this value to determine
+   which dictionary has been used by the compressor.  (The Adler-32 value
+   applies to the whole dictionary even if only a subset of the dictionary is
+   actually used by the compressor.) If a raw deflate was requested, then the
+   Adler-32 value is not computed and strm->adler is not set.
+
+     deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a
+   parameter is invalid (e.g.  dictionary being Z_NULL) or the stream state is
+   inconsistent (for example if deflate has already been called for this stream
+   or if not at a block boundary for raw deflate).  deflateSetDictionary does
+   not perform any compression: this will be done by deflate().
+*/
+
+ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm,
+                                             Bytef *dictionary,
+                                             uInt  *dictLength));
+/*
+     Returns the sliding dictionary being maintained by deflate.  dictLength is
+   set to the number of bytes in the dictionary, and that many bytes are copied
+   to dictionary.  dictionary must have enough space, where 32768 bytes is
+   always enough.  If deflateGetDictionary() is called with dictionary equal to
+   Z_NULL, then only the dictionary length is returned, and nothing is copied.
+   Similary, if dictLength is Z_NULL, then it is not set.
+
+     deflateGetDictionary() may return a length less than the window size, even
+   when more than the window size in input has been provided. It may return up
+   to 258 bytes less in that case, due to how zlib's implementation of deflate
+   manages the sliding window and lookahead for matches, where matches can be
+   up to 258 bytes long. If the application needs the last window-size bytes of
+   input, then that would need to be saved by the application outside of zlib.
+
+     deflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the
+   stream state is inconsistent.
+*/
+
+ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest,
+                                    z_streamp source));
+/*
+     Sets the destination stream as a complete copy of the source stream.
+
+     This function can be useful when several compression strategies will be
+   tried, for example when there are several ways of pre-processing the input
+   data with a filter.  The streams that will be discarded should then be freed
+   by calling deflateEnd.  Note that deflateCopy duplicates the internal
+   compression state which can be quite large, so this strategy is slow and can
+   consume lots of memory.
+
+     deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+   (such as zalloc being Z_NULL).  msg is left unchanged in both source and
+   destination.
+*/
+
+ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm));
+/*
+     This function is equivalent to deflateEnd followed by deflateInit, but
+   does not free and reallocate the internal compression state.  The stream
+   will leave the compression level and any other attributes that may have been
+   set unchanged.
+
+     deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent (such as zalloc or state being Z_NULL).
+*/
+
+ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm,
+                                      int level,
+                                      int strategy));
+/*
+     Dynamically update the compression level and compression strategy.  The
+   interpretation of level and strategy is as in deflateInit2().  This can be
+   used to switch between compression and straight copy of the input data, or
+   to switch to a different kind of input data requiring a different strategy.
+   If the compression approach (which is a function of the level) or the
+   strategy is changed, and if any input has been consumed in a previous
+   deflate() call, then the input available so far is compressed with the old
+   level and strategy using deflate(strm, Z_BLOCK).  There are three approaches
+   for the compression levels 0, 1..3, and 4..9 respectively.  The new level
+   and strategy will take effect at the next call of deflate().
+
+     If a deflate(strm, Z_BLOCK) is performed by deflateParams(), and it does
+   not have enough output space to complete, then the parameter change will not
+   take effect.  In this case, deflateParams() can be called again with the
+   same parameters and more output space to try again.
+
+     In order to assure a change in the parameters on the first try, the
+   deflate stream should be flushed using deflate() with Z_BLOCK or other flush
+   request until strm.avail_out is not zero, before calling deflateParams().
+   Then no more input data should be provided before the deflateParams() call.
+   If this is done, the old level and strategy will be applied to the data
+   compressed before deflateParams(), and the new level and strategy will be
+   applied to the the data compressed after deflateParams().
+
+     deflateParams returns Z_OK on success, Z_STREAM_ERROR if the source stream
+   state was inconsistent or if a parameter was invalid, or Z_BUF_ERROR if
+   there was not enough output space to complete the compression of the
+   available input data before a change in the strategy or approach.  Note that
+   in the case of a Z_BUF_ERROR, the parameters are not changed.  A return
+   value of Z_BUF_ERROR is not fatal, in which case deflateParams() can be
+   retried with more output space.
+*/
+
+ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm,
+                                    int good_length,
+                                    int max_lazy,
+                                    int nice_length,
+                                    int max_chain));
+/*
+     Fine tune deflate's internal compression parameters.  This should only be
+   used by someone who understands the algorithm used by zlib's deflate for
+   searching for the best matching string, and even then only by the most
+   fanatic optimizer trying to squeeze out the last compressed bit for their
+   specific input data.  Read the deflate.c source code for the meaning of the
+   max_lazy, good_length, nice_length, and max_chain parameters.
+
+     deflateTune() can be called after deflateInit() or deflateInit2(), and
+   returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream.
+ */
+
+ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm,
+                                       uLong sourceLen));
+/*
+     deflateBound() returns an upper bound on the compressed size after
+   deflation of sourceLen bytes.  It must be called after deflateInit() or
+   deflateInit2(), and after deflateSetHeader(), if used.  This would be used
+   to allocate an output buffer for deflation in a single pass, and so would be
+   called before deflate().  If that first deflate() call is provided the
+   sourceLen input bytes, an output buffer allocated to the size returned by
+   deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed
+   to return Z_STREAM_END.  Note that it is possible for the compressed size to
+   be larger than the value returned by deflateBound() if flush options other
+   than Z_FINISH or Z_NO_FLUSH are used.
+*/
+
+ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm,
+                                       unsigned *pending,
+                                       int *bits));
+/*
+     deflatePending() returns the number of bytes and bits of output that have
+   been generated, but not yet provided in the available output.  The bytes not
+   provided would be due to the available output space having being consumed.
+   The number of bits of output not provided are between 0 and 7, where they
+   await more bits to join them in order to fill out a full byte.  If pending
+   or bits are Z_NULL, then those values are not set.
+
+     deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+ */
+
+ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm,
+                                     int bits,
+                                     int value));
+/*
+     deflatePrime() inserts bits in the deflate output stream.  The intent
+   is that this function is used to start off the deflate output with the bits
+   leftover from a previous deflate stream when appending to it.  As such, this
+   function can only be used for raw deflate, and must be used before the first
+   deflate() call after a deflateInit2() or deflateReset().  bits must be less
+   than or equal to 16, and that many of the least significant bits of value
+   will be inserted in the output.
+
+     deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough
+   room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the
+   source stream state was inconsistent.
+*/
+
+ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm,
+                                         gz_headerp head));
+/*
+     deflateSetHeader() provides gzip header information for when a gzip
+   stream is requested by deflateInit2().  deflateSetHeader() may be called
+   after deflateInit2() or deflateReset() and before the first call of
+   deflate().  The text, time, os, extra field, name, and comment information
+   in the provided gz_header structure are written to the gzip header (xflag is
+   ignored -- the extra flags are set according to the compression level).  The
+   caller must assure that, if not Z_NULL, name and comment are terminated with
+   a zero byte, and that if extra is not Z_NULL, that extra_len bytes are
+   available there.  If hcrc is true, a gzip header crc is included.  Note that
+   the current versions of the command-line version of gzip (up through version
+   1.3.x) do not support header crc's, and will report that it is a "multi-part
+   gzip file" and give up.
+
+     If deflateSetHeader is not used, the default gzip header has text false,
+   the time set to zero, and os set to 255, with no extra, name, or comment
+   fields.  The gzip header is returned to the default state by deflateReset().
+
+     deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+/*
+ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm,
+                                     int  windowBits));
+
+     This is another version of inflateInit with an extra parameter.  The
+   fields next_in, avail_in, zalloc, zfree and opaque must be initialized
+   before by the caller.
+
+     The windowBits parameter is the base two logarithm of the maximum window
+   size (the size of the history buffer).  It should be in the range 8..15 for
+   this version of the library.  The default value is 15 if inflateInit is used
+   instead.  windowBits must be greater than or equal to the windowBits value
+   provided to deflateInit2() while compressing, or it must be equal to 15 if
+   deflateInit2() was not used.  If a compressed stream with a larger window
+   size is given as input, inflate() will return with the error code
+   Z_DATA_ERROR instead of trying to allocate a larger window.
+
+     windowBits can also be zero to request that inflate use the window size in
+   the zlib header of the compressed stream.
+
+     windowBits can also be -8..-15 for raw inflate.  In this case, -windowBits
+   determines the window size.  inflate() will then process raw deflate data,
+   not looking for a zlib or gzip header, not generating a check value, and not
+   looking for any check values for comparison at the end of the stream.  This
+   is for use with other formats that use the deflate compressed data format
+   such as zip.  Those formats provide their own check values.  If a custom
+   format is developed using the raw deflate format for compressed data, it is
+   recommended that a check value such as an Adler-32 or a CRC-32 be applied to
+   the uncompressed data as is done in the zlib, gzip, and zip formats.  For
+   most applications, the zlib format should be used as is.  Note that comments
+   above on the use in deflateInit2() applies to the magnitude of windowBits.
+
+     windowBits can also be greater than 15 for optional gzip decoding.  Add
+   32 to windowBits to enable zlib and gzip decoding with automatic header
+   detection, or add 16 to decode only the gzip format (the zlib format will
+   return a Z_DATA_ERROR).  If a gzip stream is being decoded, strm->adler is a
+   CRC-32 instead of an Adler-32.  Unlike the gunzip utility and gzread() (see
+   below), inflate() will not automatically decode concatenated gzip streams.
+   inflate() will return Z_STREAM_END at the end of the gzip stream.  The state
+   would need to be reset to continue decoding a subsequent gzip stream.
+
+     inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+   version assumed by the caller, or Z_STREAM_ERROR if the parameters are
+   invalid, such as a null pointer to the structure.  msg is set to null if
+   there is no error message.  inflateInit2 does not perform any decompression
+   apart from possibly reading the zlib header if present: actual decompression
+   will be done by inflate().  (So next_in and avail_in may be modified, but
+   next_out and avail_out are unused and unchanged.) The current implementation
+   of inflateInit2() does not process any header information -- that is
+   deferred until inflate() is called.
+*/
+
+ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm,
+                                             const Bytef *dictionary,
+                                             uInt  dictLength));
+/*
+     Initializes the decompression dictionary from the given uncompressed byte
+   sequence.  This function must be called immediately after a call of inflate,
+   if that call returned Z_NEED_DICT.  The dictionary chosen by the compressor
+   can be determined from the Adler-32 value returned by that call of inflate.
+   The compressor and decompressor must use exactly the same dictionary (see
+   deflateSetDictionary).  For raw inflate, this function can be called at any
+   time to set the dictionary.  If the provided dictionary is smaller than the
+   window and there is already data in the window, then the provided dictionary
+   will amend what's there.  The application must insure that the dictionary
+   that was used for compression is provided.
+
+     inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a
+   parameter is invalid (e.g.  dictionary being Z_NULL) or the stream state is
+   inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the
+   expected one (incorrect Adler-32 value).  inflateSetDictionary does not
+   perform any decompression: this will be done by subsequent calls of
+   inflate().
+*/
+
+ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm,
+                                             Bytef *dictionary,
+                                             uInt  *dictLength));
+/*
+     Returns the sliding dictionary being maintained by inflate.  dictLength is
+   set to the number of bytes in the dictionary, and that many bytes are copied
+   to dictionary.  dictionary must have enough space, where 32768 bytes is
+   always enough.  If inflateGetDictionary() is called with dictionary equal to
+   Z_NULL, then only the dictionary length is returned, and nothing is copied.
+   Similary, if dictLength is Z_NULL, then it is not set.
+
+     inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the
+   stream state is inconsistent.
+*/
+
+ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm));
+/*
+     Skips invalid compressed data until a possible full flush point (see above
+   for the description of deflate with Z_FULL_FLUSH) can be found, or until all
+   available input is skipped.  No output is provided.
+
+     inflateSync searches for a 00 00 FF FF pattern in the compressed data.
+   All full flush points have this pattern, but not all occurrences of this
+   pattern are full flush points.
+
+     inflateSync returns Z_OK if a possible full flush point has been found,
+   Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point
+   has been found, or Z_STREAM_ERROR if the stream structure was inconsistent.
+   In the success case, the application may save the current current value of
+   total_in which indicates where valid compressed data was found.  In the
+   error case, the application may repeatedly call inflateSync, providing more
+   input each time, until success or end of the input data.
+*/
+
+ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest,
+                                    z_streamp source));
+/*
+     Sets the destination stream as a complete copy of the source stream.
+
+     This function can be useful when randomly accessing a large stream.  The
+   first pass through the stream can periodically record the inflate state,
+   allowing restarting inflate at those points when randomly accessing the
+   stream.
+
+     inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+   (such as zalloc being Z_NULL).  msg is left unchanged in both source and
+   destination.
+*/
+
+ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm));
+/*
+     This function is equivalent to inflateEnd followed by inflateInit,
+   but does not free and reallocate the internal decompression state.  The
+   stream will keep attributes that may have been set by inflateInit2.
+
+     inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent (such as zalloc or state being Z_NULL).
+*/
+
+ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm,
+                                      int windowBits));
+/*
+     This function is the same as inflateReset, but it also permits changing
+   the wrap and window size requests.  The windowBits parameter is interpreted
+   the same as it is for inflateInit2.  If the window size is changed, then the
+   memory allocated for the window is freed, and the window will be reallocated
+   by inflate() if needed.
+
+     inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent (such as zalloc or state being Z_NULL), or if
+   the windowBits parameter is invalid.
+*/
+
+ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm,
+                                     int bits,
+                                     int value));
+/*
+     This function inserts bits in the inflate input stream.  The intent is
+   that this function is used to start inflating at a bit position in the
+   middle of a byte.  The provided bits will be used before any bytes are used
+   from next_in.  This function should only be used with raw inflate, and
+   should be used before the first inflate() call after inflateInit2() or
+   inflateReset().  bits must be less than or equal to 16, and that many of the
+   least significant bits of value will be inserted in the input.
+
+     If bits is negative, then the input stream bit buffer is emptied.  Then
+   inflatePrime() can be called again to put bits in the buffer.  This is used
+   to clear out bits leftover after feeding inflate a block description prior
+   to feeding inflate codes.
+
+     inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm));
+/*
+     This function returns two values, one in the lower 16 bits of the return
+   value, and the other in the remaining upper bits, obtained by shifting the
+   return value down 16 bits.  If the upper value is -1 and the lower value is
+   zero, then inflate() is currently decoding information outside of a block.
+   If the upper value is -1 and the lower value is non-zero, then inflate is in
+   the middle of a stored block, with the lower value equaling the number of
+   bytes from the input remaining to copy.  If the upper value is not -1, then
+   it is the number of bits back from the current bit position in the input of
+   the code (literal or length/distance pair) currently being processed.  In
+   that case the lower value is the number of bytes already emitted for that
+   code.
+
+     A code is being processed if inflate is waiting for more input to complete
+   decoding of the code, or if it has completed decoding but is waiting for
+   more output space to write the literal or match data.
+
+     inflateMark() is used to mark locations in the input data for random
+   access, which may be at bit positions, and to note those cases where the
+   output of a code may span boundaries of random access blocks.  The current
+   location in the input stream can be determined from avail_in and data_type
+   as noted in the description for the Z_BLOCK flush parameter for inflate.
+
+     inflateMark returns the value noted above, or -65536 if the provided
+   source stream state was inconsistent.
+*/
+
+ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm,
+                                         gz_headerp head));
+/*
+     inflateGetHeader() requests that gzip header information be stored in the
+   provided gz_header structure.  inflateGetHeader() may be called after
+   inflateInit2() or inflateReset(), and before the first call of inflate().
+   As inflate() processes the gzip stream, head->done is zero until the header
+   is completed, at which time head->done is set to one.  If a zlib stream is
+   being decoded, then head->done is set to -1 to indicate that there will be
+   no gzip header information forthcoming.  Note that Z_BLOCK or Z_TREES can be
+   used to force inflate() to return immediately after header processing is
+   complete and before any actual data is decompressed.
+
+     The text, time, xflags, and os fields are filled in with the gzip header
+   contents.  hcrc is set to true if there is a header CRC.  (The header CRC
+   was valid if done is set to one.) If extra is not Z_NULL, then extra_max
+   contains the maximum number of bytes to write to extra.  Once done is true,
+   extra_len contains the actual extra field length, and extra contains the
+   extra field, or that field truncated if extra_max is less than extra_len.
+   If name is not Z_NULL, then up to name_max characters are written there,
+   terminated with a zero unless the length is greater than name_max.  If
+   comment is not Z_NULL, then up to comm_max characters are written there,
+   terminated with a zero unless the length is greater than comm_max.  When any
+   of extra, name, or comment are not Z_NULL and the respective field is not
+   present in the header, then that field is set to Z_NULL to signal its
+   absence.  This allows the use of deflateSetHeader() with the returned
+   structure to duplicate the header.  However if those fields are set to
+   allocated memory, then the application will need to save those pointers
+   elsewhere so that they can be eventually freed.
+
+     If inflateGetHeader is not used, then the header information is simply
+   discarded.  The header is always checked for validity, including the header
+   CRC if present.  inflateReset() will reset the process to discard the header
+   information.  The application would need to call inflateGetHeader() again to
+   retrieve the header from the next gzip stream.
+
+     inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+/*
+ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits,
+                                        unsigned char FAR *window));
+
+     Initialize the internal stream state for decompression using inflateBack()
+   calls.  The fields zalloc, zfree and opaque in strm must be initialized
+   before the call.  If zalloc and zfree are Z_NULL, then the default library-
+   derived memory allocation routines are used.  windowBits is the base two
+   logarithm of the window size, in the range 8..15.  window is a caller
+   supplied buffer of that size.  Except for special applications where it is
+   assured that deflate was used with small window sizes, windowBits must be 15
+   and a 32K byte window must be supplied to be able to decompress general
+   deflate streams.
+
+     See inflateBack() for the usage of these routines.
+
+     inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of
+   the parameters are invalid, Z_MEM_ERROR if the internal state could not be
+   allocated, or Z_VERSION_ERROR if the version of the library does not match
+   the version of the header file.
+*/
+
+typedef unsigned (*in_func) OF((void FAR *,
+                                z_const unsigned char FAR * FAR *));
+typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned));
+
+ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm,
+                                    in_func in, void FAR *in_desc,
+                                    out_func out, void FAR *out_desc));
+/*
+     inflateBack() does a raw inflate with a single call using a call-back
+   interface for input and output.  This is potentially more efficient than
+   inflate() for file i/o applications, in that it avoids copying between the
+   output and the sliding window by simply making the window itself the output
+   buffer.  inflate() can be faster on modern CPUs when used with large
+   buffers.  inflateBack() trusts the application to not change the output
+   buffer passed by the output function, at least until inflateBack() returns.
+
+     inflateBackInit() must be called first to allocate the internal state
+   and to initialize the state with the user-provided window buffer.
+   inflateBack() may then be used multiple times to inflate a complete, raw
+   deflate stream with each call.  inflateBackEnd() is then called to free the
+   allocated state.
+
+     A raw deflate stream is one with no zlib or gzip header or trailer.
+   This routine would normally be used in a utility that reads zip or gzip
+   files and writes out uncompressed files.  The utility would decode the
+   header and process the trailer on its own, hence this routine expects only
+   the raw deflate stream to decompress.  This is different from the default
+   behavior of inflate(), which expects a zlib header and trailer around the
+   deflate stream.
+
+     inflateBack() uses two subroutines supplied by the caller that are then
+   called by inflateBack() for input and output.  inflateBack() calls those
+   routines until it reads a complete deflate stream and writes out all of the
+   uncompressed data, or until it encounters an error.  The function's
+   parameters and return types are defined above in the in_func and out_func
+   typedefs.  inflateBack() will call in(in_desc, &buf) which should return the
+   number of bytes of provided input, and a pointer to that input in buf.  If
+   there is no input available, in() must return zero -- buf is ignored in that
+   case -- and inflateBack() will return a buffer error.  inflateBack() will
+   call out(out_desc, buf, len) to write the uncompressed data buf[0..len-1].
+   out() should return zero on success, or non-zero on failure.  If out()
+   returns non-zero, inflateBack() will return with an error.  Neither in() nor
+   out() are permitted to change the contents of the window provided to
+   inflateBackInit(), which is also the buffer that out() uses to write from.
+   The length written by out() will be at most the window size.  Any non-zero
+   amount of input may be provided by in().
+
+     For convenience, inflateBack() can be provided input on the first call by
+   setting strm->next_in and strm->avail_in.  If that input is exhausted, then
+   in() will be called.  Therefore strm->next_in must be initialized before
+   calling inflateBack().  If strm->next_in is Z_NULL, then in() will be called
+   immediately for input.  If strm->next_in is not Z_NULL, then strm->avail_in
+   must also be initialized, and then if strm->avail_in is not zero, input will
+   initially be taken from strm->next_in[0 ..  strm->avail_in - 1].
+
+     The in_desc and out_desc parameters of inflateBack() is passed as the
+   first parameter of in() and out() respectively when they are called.  These
+   descriptors can be optionally used to pass any information that the caller-
+   supplied in() and out() functions need to do their job.
+
+     On return, inflateBack() will set strm->next_in and strm->avail_in to
+   pass back any unused input that was provided by the last in() call.  The
+   return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR
+   if in() or out() returned an error, Z_DATA_ERROR if there was a format error
+   in the deflate stream (in which case strm->msg is set to indicate the nature
+   of the error), or Z_STREAM_ERROR if the stream was not properly initialized.
+   In the case of Z_BUF_ERROR, an input or output error can be distinguished
+   using strm->next_in which will be Z_NULL only if in() returned an error.  If
+   strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning
+   non-zero.  (in() will always be called before out(), so strm->next_in is
+   assured to be defined if out() returns non-zero.)  Note that inflateBack()
+   cannot return Z_OK.
+*/
+
+ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm));
+/*
+     All memory allocated by inflateBackInit() is freed.
+
+     inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream
+   state was inconsistent.
+*/
+
+ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void));
+/* Return flags indicating compile-time options.
+
+    Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other:
+     1.0: size of uInt
+     3.2: size of uLong
+     5.4: size of voidpf (pointer)
+     7.6: size of z_off_t
+
+    Compiler, assembler, and debug options:
+     8: ZLIB_DEBUG
+     9: ASMV or ASMINF -- use ASM code
+     10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention
+     11: 0 (reserved)
+
+    One-time table building (smaller code, but not thread-safe if true):
+     12: BUILDFIXED -- build static block decoding tables when needed
+     13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed
+     14,15: 0 (reserved)
+
+    Library content (indicates missing functionality):
+     16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking
+                          deflate code when not needed)
+     17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect
+                    and decode gzip streams (to avoid linking crc code)
+     18-19: 0 (reserved)
+
+    Operation variations (changes in library functionality):
+     20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate
+     21: FASTEST -- deflate algorithm with only one, lowest compression level
+     22,23: 0 (reserved)
+
+    The sprintf variant used by gzprintf (zero is best):
+     24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format
+     25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure!
+     26: 0 = returns value, 1 = void -- 1 means inferred string length returned
+
+    Remainder:
+     27-31: 0 (reserved)
+ */
+
+#ifndef Z_SOLO
+
+                        /* utility functions */
+
+/*
+     The following utility functions are implemented on top of the basic
+   stream-oriented functions.  To simplify the interface, some default options
+   are assumed (compression level and memory usage, standard memory allocation
+   functions).  The source code of these utility functions can be modified if
+   you need special options.
+*/
+
+ZEXTERN int ZEXPORT compress OF((Bytef *dest,   uLongf *destLen,
+                                 const Bytef *source, uLong sourceLen));
+/*
+     Compresses the source buffer into the destination buffer.  sourceLen is
+   the byte length of the source buffer.  Upon entry, destLen is the total size
+   of the destination buffer, which must be at least the value returned by
+   compressBound(sourceLen).  Upon exit, destLen is the actual size of the
+   compressed data.  compress() is equivalent to compress2() with a level
+   parameter of Z_DEFAULT_COMPRESSION.
+
+     compress returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_BUF_ERROR if there was not enough room in the output
+   buffer.
+*/
+
+ZEXTERN int ZEXPORT compress2 OF((Bytef *dest,   uLongf *destLen,
+                                  const Bytef *source, uLong sourceLen,
+                                  int level));
+/*
+     Compresses the source buffer into the destination buffer.  The level
+   parameter has the same meaning as in deflateInit.  sourceLen is the byte
+   length of the source buffer.  Upon entry, destLen is the total size of the
+   destination buffer, which must be at least the value returned by
+   compressBound(sourceLen).  Upon exit, destLen is the actual size of the
+   compressed data.
+
+     compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_BUF_ERROR if there was not enough room in the output buffer,
+   Z_STREAM_ERROR if the level parameter is invalid.
+*/
+
+ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen));
+/*
+     compressBound() returns an upper bound on the compressed size after
+   compress() or compress2() on sourceLen bytes.  It would be used before a
+   compress() or compress2() call to allocate the destination buffer.
+*/
+
+ZEXTERN int ZEXPORT uncompress OF((Bytef *dest,   uLongf *destLen,
+                                   const Bytef *source, uLong sourceLen));
+/*
+     Decompresses the source buffer into the destination buffer.  sourceLen is
+   the byte length of the source buffer.  Upon entry, destLen is the total size
+   of the destination buffer, which must be large enough to hold the entire
+   uncompressed data.  (The size of the uncompressed data must have been saved
+   previously by the compressor and transmitted to the decompressor by some
+   mechanism outside the scope of this compression library.) Upon exit, destLen
+   is the actual size of the uncompressed data.
+
+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_BUF_ERROR if there was not enough room in the output
+   buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete.  In
+   the case where there is not enough room, uncompress() will fill the output
+   buffer with the uncompressed data up to that point.
+*/
+
+ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest,   uLongf *destLen,
+                                    const Bytef *source, uLong *sourceLen));
+/*
+     Same as uncompress, except that sourceLen is a pointer, where the
+   length of the source is *sourceLen.  On return, *sourceLen is the number of
+   source bytes consumed.
+*/
+
+                        /* gzip file access functions */
+
+/*
+     This library supports reading and writing files in gzip (.gz) format with
+   an interface similar to that of stdio, using the functions that start with
+   "gz".  The gzip format is different from the zlib format.  gzip is a gzip
+   wrapper, documented in RFC 1952, wrapped around a deflate stream.
+*/
+
+typedef struct gzFile_s *gzFile;    /* semi-opaque gzip file descriptor */
+
+/*
+ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode));
+
+     Opens a gzip (.gz) file for reading or writing.  The mode parameter is as
+   in fopen ("rb" or "wb") but can also include a compression level ("wb9") or
+   a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only
+   compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F'
+   for fixed code compression as in "wb9F".  (See the description of
+   deflateInit2 for more information about the strategy parameter.)  'T' will
+   request transparent writing or appending with no compression and not using
+   the gzip format.
+
+     "a" can be used instead of "w" to request that the gzip stream that will
+   be written be appended to the file.  "+" will result in an error, since
+   reading and writing to the same gzip file is not supported.  The addition of
+   "x" when writing will create the file exclusively, which fails if the file
+   already exists.  On systems that support it, the addition of "e" when
+   reading or writing will set the flag to close the file on an execve() call.
+
+     These functions, as well as gzip, will read and decode a sequence of gzip
+   streams in a file.  The append function of gzopen() can be used to create
+   such a file.  (Also see gzflush() for another way to do this.)  When
+   appending, gzopen does not test whether the file begins with a gzip stream,
+   nor does it look for the end of the gzip streams to begin appending.  gzopen
+   will simply append a gzip stream to the existing file.
+
+     gzopen can be used to read a file which is not in gzip format; in this
+   case gzread will directly read from the file without decompression.  When
+   reading, this will be detected automatically by looking for the magic two-
+   byte gzip header.
+
+     gzopen returns NULL if the file could not be opened, if there was
+   insufficient memory to allocate the gzFile state, or if an invalid mode was
+   specified (an 'r', 'w', or 'a' was not provided, or '+' was provided).
+   errno can be checked to determine if the reason gzopen failed was that the
+   file could not be opened.
+*/
+
+ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode));
+/*
+     gzdopen associates a gzFile with the file descriptor fd.  File descriptors
+   are obtained from calls like open, dup, creat, pipe or fileno (if the file
+   has been previously opened with fopen).  The mode parameter is as in gzopen.
+
+     The next call of gzclose on the returned gzFile will also close the file
+   descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor
+   fd.  If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd,
+   mode);.  The duplicated descriptor should be saved to avoid a leak, since
+   gzdopen does not close fd if it fails.  If you are using fileno() to get the
+   file descriptor from a FILE *, then you will have to use dup() to avoid
+   double-close()ing the file descriptor.  Both gzclose() and fclose() will
+   close the associated file descriptor, so they need to have different file
+   descriptors.
+
+     gzdopen returns NULL if there was insufficient memory to allocate the
+   gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not
+   provided, or '+' was provided), or if fd is -1.  The file descriptor is not
+   used until the next gz* read, write, seek, or close operation, so gzdopen
+   will not detect if fd is invalid (unless fd is -1).
+*/
+
+ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size));
+/*
+     Set the internal buffer size used by this library's functions.  The
+   default buffer size is 8192 bytes.  This function must be called after
+   gzopen() or gzdopen(), and before any other calls that read or write the
+   file.  The buffer memory allocation is always deferred to the first read or
+   write.  Three times that size in buffer space is allocated.  A larger buffer
+   size of, for example, 64K or 128K bytes will noticeably increase the speed
+   of decompression (reading).
+
+     The new buffer size also affects the maximum length for gzprintf().
+
+     gzbuffer() returns 0 on success, or -1 on failure, such as being called
+   too late.
+*/
+
+ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy));
+/*
+     Dynamically update the compression level or strategy.  See the description
+   of deflateInit2 for the meaning of these parameters.  Previously provided
+   data is flushed before the parameter change.
+
+     gzsetparams returns Z_OK if success, Z_STREAM_ERROR if the file was not
+   opened for writing, Z_ERRNO if there is an error writing the flushed data,
+   or Z_MEM_ERROR if there is a memory allocation error.
+*/
+
+ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len));
+/*
+     Reads the given number of uncompressed bytes from the compressed file.  If
+   the input file is not in gzip format, gzread copies the given number of
+   bytes into the buffer directly from the file.
+
+     After reaching the end of a gzip stream in the input, gzread will continue
+   to read, looking for another gzip stream.  Any number of gzip streams may be
+   concatenated in the input file, and will all be decompressed by gzread().
+   If something other than a gzip stream is encountered after a gzip stream,
+   that remaining trailing garbage is ignored (and no error is returned).
+
+     gzread can be used to read a gzip file that is being concurrently written.
+   Upon reaching the end of the input, gzread will return with the available
+   data.  If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then
+   gzclearerr can be used to clear the end of file indicator in order to permit
+   gzread to be tried again.  Z_OK indicates that a gzip stream was completed
+   on the last gzread.  Z_BUF_ERROR indicates that the input file ended in the
+   middle of a gzip stream.  Note that gzread does not return -1 in the event
+   of an incomplete gzip stream.  This error is deferred until gzclose(), which
+   will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip
+   stream.  Alternatively, gzerror can be used before gzclose to detect this
+   case.
+
+     gzread returns the number of uncompressed bytes actually read, less than
+   len for end of file, or -1 for error.  If len is too large to fit in an int,
+   then nothing is read, -1 is returned, and the error state is set to
+   Z_STREAM_ERROR.
+*/
+
+ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems,
+                                     gzFile file));
+/*
+     Read up to nitems items of size size from file to buf, otherwise operating
+   as gzread() does.  This duplicates the interface of stdio's fread(), with
+   size_t request and return types.  If the library defines size_t, then
+   z_size_t is identical to size_t.  If not, then z_size_t is an unsigned
+   integer type that can contain a pointer.
+
+     gzfread() returns the number of full items read of size size, or zero if
+   the end of the file was reached and a full item could not be read, or if
+   there was an error.  gzerror() must be consulted if zero is returned in
+   order to determine if there was an error.  If the multiplication of size and
+   nitems overflows, i.e. the product does not fit in a z_size_t, then nothing
+   is read, zero is returned, and the error state is set to Z_STREAM_ERROR.
+
+     In the event that the end of file is reached and only a partial item is
+   available at the end, i.e. the remaining uncompressed data length is not a
+   multiple of size, then the final partial item is nevetheless read into buf
+   and the end-of-file flag is set.  The length of the partial item read is not
+   provided, but could be inferred from the result of gztell().  This behavior
+   is the same as the behavior of fread() implementations in common libraries,
+   but it prevents the direct use of gzfread() to read a concurrently written
+   file, reseting and retrying on end-of-file, when size is not 1.
+*/
+
+ZEXTERN int ZEXPORT gzwrite OF((gzFile file,
+                                voidpc buf, unsigned len));
+/*
+     Writes the given number of uncompressed bytes into the compressed file.
+   gzwrite returns the number of uncompressed bytes written or 0 in case of
+   error.
+*/
+
+ZEXTERN z_size_t ZEXPORT gzfwrite OF((voidpc buf, z_size_t size,
+                                      z_size_t nitems, gzFile file));
+/*
+     gzfwrite() writes nitems items of size size from buf to file, duplicating
+   the interface of stdio's fwrite(), with size_t request and return types.  If
+   the library defines size_t, then z_size_t is identical to size_t.  If not,
+   then z_size_t is an unsigned integer type that can contain a pointer.
+
+     gzfwrite() returns the number of full items written of size size, or zero
+   if there was an error.  If the multiplication of size and nitems overflows,
+   i.e. the product does not fit in a z_size_t, then nothing is written, zero
+   is returned, and the error state is set to Z_STREAM_ERROR.
+*/
+
+ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...));
+/*
+     Converts, formats, and writes the arguments to the compressed file under
+   control of the format string, as in fprintf.  gzprintf returns the number of
+   uncompressed bytes actually written, or a negative zlib error code in case
+   of error.  The number of uncompressed bytes written is limited to 8191, or
+   one less than the buffer size given to gzbuffer().  The caller should assure
+   that this limit is not exceeded.  If it is exceeded, then gzprintf() will
+   return an error (0) with nothing written.  In this case, there may also be a
+   buffer overflow with unpredictable consequences, which is possible only if
+   zlib was compiled with the insecure functions sprintf() or vsprintf()
+   because the secure snprintf() or vsnprintf() functions were not available.
+   This can be determined using zlibCompileFlags().
+*/
+
+ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s));
+/*
+     Writes the given null-terminated string to the compressed file, excluding
+   the terminating null character.
+
+     gzputs returns the number of characters written, or -1 in case of error.
+*/
+
+ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len));
+/*
+     Reads bytes from the compressed file until len-1 characters are read, or a
+   newline character is read and transferred to buf, or an end-of-file
+   condition is encountered.  If any characters are read or if len == 1, the
+   string is terminated with a null character.  If no characters are read due
+   to an end-of-file or len < 1, then the buffer is left untouched.
+
+     gzgets returns buf which is a null-terminated string, or it returns NULL
+   for end-of-file or in case of error.  If there was an error, the contents at
+   buf are indeterminate.
+*/
+
+ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c));
+/*
+     Writes c, converted to an unsigned char, into the compressed file.  gzputc
+   returns the value that was written, or -1 in case of error.
+*/
+
+ZEXTERN int ZEXPORT gzgetc OF((gzFile file));
+/*
+     Reads one byte from the compressed file.  gzgetc returns this byte or -1
+   in case of end of file or error.  This is implemented as a macro for speed.
+   As such, it does not do all of the checking the other functions do.  I.e.
+   it does not check to see if file is NULL, nor whether the structure file
+   points to has been clobbered or not.
+*/
+
+ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file));
+/*
+     Push one character back onto the stream to be read as the first character
+   on the next read.  At least one character of push-back is allowed.
+   gzungetc() returns the character pushed, or -1 on failure.  gzungetc() will
+   fail if c is -1, and may fail if a character has been pushed but not read
+   yet.  If gzungetc is used immediately after gzopen or gzdopen, at least the
+   output buffer size of pushed characters is allowed.  (See gzbuffer above.)
+   The pushed character will be discarded if the stream is repositioned with
+   gzseek() or gzrewind().
+*/
+
+ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush));
+/*
+     Flushes all pending output into the compressed file.  The parameter flush
+   is as in the deflate() function.  The return value is the zlib error number
+   (see function gzerror below).  gzflush is only permitted when writing.
+
+     If the flush parameter is Z_FINISH, the remaining data is written and the
+   gzip stream is completed in the output.  If gzwrite() is called again, a new
+   gzip stream will be started in the output.  gzread() is able to read such
+   concatenated gzip streams.
+
+     gzflush should be called only when strictly necessary because it will
+   degrade compression if called too often.
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file,
+                                   z_off_t offset, int whence));
+
+     Sets the starting position for the next gzread or gzwrite on the given
+   compressed file.  The offset represents a number of bytes in the
+   uncompressed data stream.  The whence parameter is defined as in lseek(2);
+   the value SEEK_END is not supported.
+
+     If the file is opened for reading, this function is emulated but can be
+   extremely slow.  If the file is opened for writing, only forward seeks are
+   supported; gzseek then compresses a sequence of zeroes up to the new
+   starting position.
+
+     gzseek returns the resulting offset location as measured in bytes from
+   the beginning of the uncompressed stream, or -1 in case of error, in
+   particular if the file is opened for writing and the new starting position
+   would be before the current position.
+*/
+
+ZEXTERN int ZEXPORT    gzrewind OF((gzFile file));
+/*
+     Rewinds the given file. This function is supported only for reading.
+
+     gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET)
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT    gztell OF((gzFile file));
+
+     Returns the starting position for the next gzread or gzwrite on the given
+   compressed file.  This position represents a number of bytes in the
+   uncompressed data stream, and is zero when starting, even if appending or
+   reading a gzip stream from the middle of a file using gzdopen().
+
+     gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR)
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file));
+
+     Returns the current offset in the file being read or written.  This offset
+   includes the count of bytes that precede the gzip stream, for example when
+   appending or when using gzdopen() for reading.  When reading, the offset
+   does not include as yet unused buffered input.  This information can be used
+   for a progress indicator.  On error, gzoffset() returns -1.
+*/
+
+ZEXTERN int ZEXPORT gzeof OF((gzFile file));
+/*
+     Returns true (1) if the end-of-file indicator has been set while reading,
+   false (0) otherwise.  Note that the end-of-file indicator is set only if the
+   read tried to go past the end of the input, but came up short.  Therefore,
+   just like feof(), gzeof() may return false even if there is no more data to
+   read, in the event that the last read request was for the exact number of
+   bytes remaining in the input file.  This will happen if the input file size
+   is an exact multiple of the buffer size.
+
+     If gzeof() returns true, then the read functions will return no more data,
+   unless the end-of-file indicator is reset by gzclearerr() and the input file
+   has grown since the previous end of file was detected.
+*/
+
+ZEXTERN int ZEXPORT gzdirect OF((gzFile file));
+/*
+     Returns true (1) if file is being copied directly while reading, or false
+   (0) if file is a gzip stream being decompressed.
+
+     If the input file is empty, gzdirect() will return true, since the input
+   does not contain a gzip stream.
+
+     If gzdirect() is used immediately after gzopen() or gzdopen() it will
+   cause buffers to be allocated to allow reading the file to determine if it
+   is a gzip file.  Therefore if gzbuffer() is used, it should be called before
+   gzdirect().
+
+     When writing, gzdirect() returns true (1) if transparent writing was
+   requested ("wT" for the gzopen() mode), or false (0) otherwise.  (Note:
+   gzdirect() is not needed when writing.  Transparent writing must be
+   explicitly requested, so the application already knows the answer.  When
+   linking statically, using gzdirect() will include all of the zlib code for
+   gzip file reading and decompression, which may not be desired.)
+*/
+
+ZEXTERN int ZEXPORT    gzclose OF((gzFile file));
+/*
+     Flushes all pending output if necessary, closes the compressed file and
+   deallocates the (de)compression state.  Note that once file is closed, you
+   cannot call gzerror with file, since its structures have been deallocated.
+   gzclose must not be called more than once on the same file, just as free
+   must not be called more than once on the same allocation.
+
+     gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a
+   file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the
+   last read ended in the middle of a gzip stream, or Z_OK on success.
+*/
+
+ZEXTERN int ZEXPORT gzclose_r OF((gzFile file));
+ZEXTERN int ZEXPORT gzclose_w OF((gzFile file));
+/*
+     Same as gzclose(), but gzclose_r() is only for use when reading, and
+   gzclose_w() is only for use when writing or appending.  The advantage to
+   using these instead of gzclose() is that they avoid linking in zlib
+   compression or decompression code that is not used when only reading or only
+   writing respectively.  If gzclose() is used, then both compression and
+   decompression code will be included the application when linking to a static
+   zlib library.
+*/
+
+ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum));
+/*
+     Returns the error message for the last error which occurred on the given
+   compressed file.  errnum is set to zlib error number.  If an error occurred
+   in the file system and not in the compression library, errnum is set to
+   Z_ERRNO and the application may consult errno to get the exact error code.
+
+     The application must not modify the returned string.  Future calls to
+   this function may invalidate the previously returned string.  If file is
+   closed, then the string previously returned by gzerror will no longer be
+   available.
+
+     gzerror() should be used to distinguish errors from end-of-file for those
+   functions above that do not distinguish those cases in their return values.
+*/
+
+ZEXTERN void ZEXPORT gzclearerr OF((gzFile file));
+/*
+     Clears the error and end-of-file flags for file.  This is analogous to the
+   clearerr() function in stdio.  This is useful for continuing to read a gzip
+   file that is being written concurrently.
+*/
+
+#endif /* !Z_SOLO */
+
+                        /* checksum functions */
+
+/*
+     These functions are not related to compression but are exported
+   anyway because they might be useful in applications using the compression
+   library.
+*/
+
+ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len));
+/*
+     Update a running Adler-32 checksum with the bytes buf[0..len-1] and
+   return the updated checksum.  If buf is Z_NULL, this function returns the
+   required initial value for the checksum.
+
+     An Adler-32 checksum is almost as reliable as a CRC-32 but can be computed
+   much faster.
+
+   Usage example:
+
+     uLong adler = adler32(0L, Z_NULL, 0);
+
+     while (read_buffer(buffer, length) != EOF) {
+       adler = adler32(adler, buffer, length);
+     }
+     if (adler != original_adler) error();
+*/
+
+ZEXTERN uLong ZEXPORT adler32_z OF((uLong adler, const Bytef *buf,
+                                    z_size_t len));
+/*
+     Same as adler32(), but with a size_t length.
+*/
+
+/*
+ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2,
+                                          z_off_t len2));
+
+     Combine two Adler-32 checksums into one.  For two sequences of bytes, seq1
+   and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for
+   each, adler1 and adler2.  adler32_combine() returns the Adler-32 checksum of
+   seq1 and seq2 concatenated, requiring only adler1, adler2, and len2.  Note
+   that the z_off_t type (like off_t) is a signed integer.  If len2 is
+   negative, the result has no meaning or utility.
+*/
+
+ZEXTERN uLong ZEXPORT crc32   OF((uLong crc, const Bytef *buf, uInt len));
+/*
+     Update a running CRC-32 with the bytes buf[0..len-1] and return the
+   updated CRC-32.  If buf is Z_NULL, this function returns the required
+   initial value for the crc.  Pre- and post-conditioning (one's complement) is
+   performed within this function so it shouldn't be done by the application.
+
+   Usage example:
+
+     uLong crc = crc32(0L, Z_NULL, 0);
+
+     while (read_buffer(buffer, length) != EOF) {
+       crc = crc32(crc, buffer, length);
+     }
+     if (crc != original_crc) error();
+*/
+
+ZEXTERN uLong ZEXPORT crc32_z OF((uLong adler, const Bytef *buf,
+                                  z_size_t len));
+/*
+     Same as crc32(), but with a size_t length.
+*/
+
+/*
+ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2));
+
+     Combine two CRC-32 check values into one.  For two sequences of bytes,
+   seq1 and seq2 with lengths len1 and len2, CRC-32 check values were
+   calculated for each, crc1 and crc2.  crc32_combine() returns the CRC-32
+   check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and
+   len2.
+*/
+
+
+                        /* various hacks, don't look :) */
+
+/* deflateInit and inflateInit are macros to allow checking the zlib version
+ * and the compiler's view of z_stream:
+ */
+ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level,
+                                     const char *version, int stream_size));
+ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm,
+                                     const char *version, int stream_size));
+ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int  level, int  method,
+                                      int windowBits, int memLevel,
+                                      int strategy, const char *version,
+                                      int stream_size));
+ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int  windowBits,
+                                      const char *version, int stream_size));
+ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits,
+                                         unsigned char FAR *window,
+                                         const char *version,
+                                         int stream_size));
+#ifdef Z_PREFIX_SET
+#  define z_deflateInit(strm, level) \
+          deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream))
+#  define z_inflateInit(strm) \
+          inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream))
+#  define z_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+          deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
+                        (strategy), ZLIB_VERSION, (int)sizeof(z_stream))
+#  define z_inflateInit2(strm, windowBits) \
+          inflateInit2_((strm), (windowBits), ZLIB_VERSION, \
+                        (int)sizeof(z_stream))
+#  define z_inflateBackInit(strm, windowBits, window) \
+          inflateBackInit_((strm), (windowBits), (window), \
+                           ZLIB_VERSION, (int)sizeof(z_stream))
+#else
+#  define deflateInit(strm, level) \
+          deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream))
+#  define inflateInit(strm) \
+          inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream))
+#  define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+          deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
+                        (strategy), ZLIB_VERSION, (int)sizeof(z_stream))
+#  define inflateInit2(strm, windowBits) \
+          inflateInit2_((strm), (windowBits), ZLIB_VERSION, \
+                        (int)sizeof(z_stream))
+#  define inflateBackInit(strm, windowBits, window) \
+          inflateBackInit_((strm), (windowBits), (window), \
+                           ZLIB_VERSION, (int)sizeof(z_stream))
+#endif
+
+#ifndef Z_SOLO
+
+/* gzgetc() macro and its supporting function and exposed data structure.  Note
+ * that the real internal state is much larger than the exposed structure.
+ * This abbreviated structure exposes just enough for the gzgetc() macro.  The
+ * user should not mess with these exposed elements, since their names or
+ * behavior could change in the future, perhaps even capriciously.  They can
+ * only be used by the gzgetc() macro.  You have been warned.
+ */
+struct gzFile_s {
+    unsigned have;
+    unsigned char *next;
+    z_off64_t pos;
+};
+ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file));  /* backward compatibility */
+#ifdef Z_PREFIX_SET
+#  undef z_gzgetc
+#  define z_gzgetc(g) \
+          ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : (gzgetc)(g))
+#else
+#  define gzgetc(g) \
+          ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : (gzgetc)(g))
+#endif
+
+/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or
+ * change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if
+ * both are true, the application gets the *64 functions, and the regular
+ * functions are changed to 64 bits) -- in case these are set on systems
+ * without large file support, _LFS64_LARGEFILE must also be true
+ */
+#ifdef Z_LARGE64
+   ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+   ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int));
+   ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile));
+   ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile));
+   ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t));
+   ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t));
+#endif
+
+#if !defined(ZLIB_INTERNAL) && defined(Z_WANT64)
+#  ifdef Z_PREFIX_SET
+#    define z_gzopen z_gzopen64
+#    define z_gzseek z_gzseek64
+#    define z_gztell z_gztell64
+#    define z_gzoffset z_gzoffset64
+#    define z_adler32_combine z_adler32_combine64
+#    define z_crc32_combine z_crc32_combine64
+#  else
+#    define gzopen gzopen64
+#    define gzseek gzseek64
+#    define gztell gztell64
+#    define gzoffset gzoffset64
+#    define adler32_combine adler32_combine64
+#    define crc32_combine crc32_combine64
+#  endif
+#  ifndef Z_LARGE64
+     ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+     ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int));
+     ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile));
+     ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile));
+     ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
+     ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
+#  endif
+#else
+   ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *));
+   ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int));
+   ZEXTERN z_off_t ZEXPORT gztell OF((gzFile));
+   ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile));
+   ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t));
+   ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t));
+#endif
+
+#else /* Z_SOLO */
+
+   ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t));
+   ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t));
+
+#endif /* !Z_SOLO */
+
+/* undocumented functions */
+ZEXTERN const char   * ZEXPORT zError           OF((int));
+ZEXTERN int            ZEXPORT inflateSyncPoint OF((z_streamp));
+ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table    OF((void));
+ZEXTERN int            ZEXPORT inflateUndermine OF((z_streamp, int));
+ZEXTERN int            ZEXPORT inflateValidate OF((z_streamp, int));
+ZEXTERN unsigned long  ZEXPORT inflateCodesUsed OF ((z_streamp));
+ZEXTERN int            ZEXPORT inflateResetKeep OF((z_streamp));
+ZEXTERN int            ZEXPORT deflateResetKeep OF((z_streamp));
+#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(Z_SOLO)
+ZEXTERN gzFile         ZEXPORT gzopen_w OF((const wchar_t *path,
+                                            const char *mode));
+#endif
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+#  ifndef Z_SOLO
+ZEXTERN int            ZEXPORTVA gzvprintf Z_ARG((gzFile file,
+                                                  const char *format,
+                                                  va_list va));
+#  endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZLIB_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/zutil.c	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,325 @@
+/* zutil.c -- target dependent utility functions for the compression library
+ * Copyright (C) 1995-2017 Jean-loup Gailly
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* @(#) $Id$ */
+
+#include "zutil.h"
+#ifndef Z_SOLO
+#  include "gzguts.h"
+#endif
+
+z_const char * const z_errmsg[10] = {
+    (z_const char *)"need dictionary",     /* Z_NEED_DICT       2  */
+    (z_const char *)"stream end",          /* Z_STREAM_END      1  */
+    (z_const char *)"",                    /* Z_OK              0  */
+    (z_const char *)"file error",          /* Z_ERRNO         (-1) */
+    (z_const char *)"stream error",        /* Z_STREAM_ERROR  (-2) */
+    (z_const char *)"data error",          /* Z_DATA_ERROR    (-3) */
+    (z_const char *)"insufficient memory", /* Z_MEM_ERROR     (-4) */
+    (z_const char *)"buffer error",        /* Z_BUF_ERROR     (-5) */
+    (z_const char *)"incompatible version",/* Z_VERSION_ERROR (-6) */
+    (z_const char *)""
+};
+
+
+const char * ZEXPORT zlibVersion()
+{
+    return ZLIB_VERSION;
+}
+
+uLong ZEXPORT zlibCompileFlags()
+{
+    uLong flags;
+
+    flags = 0;
+    switch ((int)(sizeof(uInt))) {
+    case 2:     break;
+    case 4:     flags += 1;     break;
+    case 8:     flags += 2;     break;
+    default:    flags += 3;
+    }
+    switch ((int)(sizeof(uLong))) {
+    case 2:     break;
+    case 4:     flags += 1 << 2;        break;
+    case 8:     flags += 2 << 2;        break;
+    default:    flags += 3 << 2;
+    }
+    switch ((int)(sizeof(voidpf))) {
+    case 2:     break;
+    case 4:     flags += 1 << 4;        break;
+    case 8:     flags += 2 << 4;        break;
+    default:    flags += 3 << 4;
+    }
+    switch ((int)(sizeof(z_off_t))) {
+    case 2:     break;
+    case 4:     flags += 1 << 6;        break;
+    case 8:     flags += 2 << 6;        break;
+    default:    flags += 3 << 6;
+    }
+#ifdef ZLIB_DEBUG
+    flags += 1 << 8;
+#endif
+#if defined(ASMV) || defined(ASMINF)
+    flags += 1 << 9;
+#endif
+#ifdef ZLIB_WINAPI
+    flags += 1 << 10;
+#endif
+#ifdef BUILDFIXED
+    flags += 1 << 12;
+#endif
+#ifdef DYNAMIC_CRC_TABLE
+    flags += 1 << 13;
+#endif
+#ifdef NO_GZCOMPRESS
+    flags += 1L << 16;
+#endif
+#ifdef NO_GZIP
+    flags += 1L << 17;
+#endif
+#ifdef PKZIP_BUG_WORKAROUND
+    flags += 1L << 20;
+#endif
+#ifdef FASTEST
+    flags += 1L << 21;
+#endif
+#if defined(STDC) || defined(Z_HAVE_STDARG_H)
+#  ifdef NO_vsnprintf
+    flags += 1L << 25;
+#    ifdef HAS_vsprintf_void
+    flags += 1L << 26;
+#    endif
+#  else
+#    ifdef HAS_vsnprintf_void
+    flags += 1L << 26;
+#    endif
+#  endif
+#else
+    flags += 1L << 24;
+#  ifdef NO_snprintf
+    flags += 1L << 25;
+#    ifdef HAS_sprintf_void
+    flags += 1L << 26;
+#    endif
+#  else
+#    ifdef HAS_snprintf_void
+    flags += 1L << 26;
+#    endif
+#  endif
+#endif
+    return flags;
+}
+
+#ifdef ZLIB_DEBUG
+#include <stdlib.h>
+#  ifndef verbose
+#    define verbose 0
+#  endif
+int ZLIB_INTERNAL z_verbose = verbose;
+
+void ZLIB_INTERNAL z_error (m)
+    char *m;
+{
+    fprintf(stderr, "%s\n", m);
+    exit(1);
+}
+#endif
+
+/* exported to allow conversion of error code to string for compress() and
+ * uncompress()
+ */
+const char * ZEXPORT zError(err)
+    int err;
+{
+    return ERR_MSG(err);
+}
+
+#if defined(_WIN32_WCE)
+    /* The Microsoft C Run-Time Library for Windows CE doesn't have
+     * errno.  We define it as a global variable to simplify porting.
+     * Its value is always 0 and should not be used.
+     */
+    int errno = 0;
+#endif
+
+#ifndef HAVE_MEMCPY
+
+void ZLIB_INTERNAL zmemcpy(dest, source, len)
+    Bytef* dest;
+    const Bytef* source;
+    uInt  len;
+{
+    if (len == 0) return;
+    do {
+        *dest++ = *source++; /* ??? to be unrolled */
+    } while (--len != 0);
+}
+
+int ZLIB_INTERNAL zmemcmp(s1, s2, len)
+    const Bytef* s1;
+    const Bytef* s2;
+    uInt  len;
+{
+    uInt j;
+
+    for (j = 0; j < len; j++) {
+        if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1;
+    }
+    return 0;
+}
+
+void ZLIB_INTERNAL zmemzero(dest, len)
+    Bytef* dest;
+    uInt  len;
+{
+    if (len == 0) return;
+    do {
+        *dest++ = 0;  /* ??? to be unrolled */
+    } while (--len != 0);
+}
+#endif
+
+#ifndef Z_SOLO
+
+#ifdef SYS16BIT
+
+#ifdef __TURBOC__
+/* Turbo C in 16-bit mode */
+
+#  define MY_ZCALLOC
+
+/* Turbo C malloc() does not allow dynamic allocation of 64K bytes
+ * and farmalloc(64K) returns a pointer with an offset of 8, so we
+ * must fix the pointer. Warning: the pointer must be put back to its
+ * original form in order to free it, use zcfree().
+ */
+
+#define MAX_PTR 10
+/* 10*64K = 640K */
+
+local int next_ptr = 0;
+
+typedef struct ptr_table_s {
+    voidpf org_ptr;
+    voidpf new_ptr;
+} ptr_table;
+
+local ptr_table table[MAX_PTR];
+/* This table is used to remember the original form of pointers
+ * to large buffers (64K). Such pointers are normalized with a zero offset.
+ * Since MSDOS is not a preemptive multitasking OS, this table is not
+ * protected from concurrent access. This hack doesn't work anyway on
+ * a protected system like OS/2. Use Microsoft C instead.
+ */
+
+voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
+{
+    voidpf buf;
+    ulg bsize = (ulg)items*size;
+
+    (void)opaque;
+
+    /* If we allocate less than 65520 bytes, we assume that farmalloc
+     * will return a usable pointer which doesn't have to be normalized.
+     */
+    if (bsize < 65520L) {
+        buf = farmalloc(bsize);
+        if (*(ush*)&buf != 0) return buf;
+    } else {
+        buf = farmalloc(bsize + 16L);
+    }
+    if (buf == NULL || next_ptr >= MAX_PTR) return NULL;
+    table[next_ptr].org_ptr = buf;
+
+    /* Normalize the pointer to seg:0 */
+    *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4;
+    *(ush*)&buf = 0;
+    table[next_ptr++].new_ptr = buf;
+    return buf;
+}
+
+void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+{
+    int n;
+
+    (void)opaque;
+
+    if (*(ush*)&ptr != 0) { /* object < 64K */
+        farfree(ptr);
+        return;
+    }
+    /* Find the original pointer */
+    for (n = 0; n < next_ptr; n++) {
+        if (ptr != table[n].new_ptr) continue;
+
+        farfree(table[n].org_ptr);
+        while (++n < next_ptr) {
+            table[n-1] = table[n];
+        }
+        next_ptr--;
+        return;
+    }
+    Assert(0, "zcfree: ptr not found");
+}
+
+#endif /* __TURBOC__ */
+
+
+#ifdef M_I86
+/* Microsoft C in 16-bit mode */
+
+#  define MY_ZCALLOC
+
+#if (!defined(_MSC_VER) || (_MSC_VER <= 600))
+#  define _halloc  halloc
+#  define _hfree   hfree
+#endif
+
+voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size)
+{
+    (void)opaque;
+    return _halloc((long)items, size);
+}
+
+void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+{
+    (void)opaque;
+    _hfree(ptr);
+}
+
+#endif /* M_I86 */
+
+#endif /* SYS16BIT */
+
+
+#ifndef MY_ZCALLOC /* Any system without a special alloc function */
+
+#ifndef STDC
+extern voidp  malloc OF((uInt size));
+extern voidp  calloc OF((uInt items, uInt size));
+extern void   free   OF((voidpf ptr));
+#endif
+
+voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
+    voidpf opaque;
+    unsigned items;
+    unsigned size;
+{
+    (void)opaque;
+    return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) :
+                              (voidpf)calloc(items, size);
+}
+
+void ZLIB_INTERNAL zcfree (opaque, ptr)
+    voidpf opaque;
+    voidpf ptr;
+{
+    (void)opaque;
+    free(ptr);
+}
+
+#endif /* MY_ZCALLOC */
+
+#endif /* !Z_SOLO */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zlib/zutil.h	Sat Jan 05 00:58:08 2019 -0800
@@ -0,0 +1,271 @@
+/* zutil.h -- internal interface and configuration of the compression library
+ * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+/* @(#) $Id$ */
+
+#ifndef ZUTIL_H
+#define ZUTIL_H
+
+#ifdef HAVE_HIDDEN
+#  define ZLIB_INTERNAL __attribute__((visibility ("hidden")))
+#else
+#  define ZLIB_INTERNAL
+#endif
+
+#include "zlib.h"
+
+#if defined(STDC) && !defined(Z_SOLO)
+#  if !(defined(_WIN32_WCE) && defined(_MSC_VER))
+#    include <stddef.h>
+#  endif
+#  include <string.h>
+#  include <stdlib.h>
+#endif
+
+#ifdef Z_SOLO
+   typedef long ptrdiff_t;  /* guess -- will be caught if guess is wrong */
+#endif
+
+#ifndef local
+#  define local static
+#endif
+/* since "static" is used to mean two completely different things in C, we
+   define "local" for the non-static meaning of "static", for readability
+   (compile with -Dlocal if your debugger can't find static symbols) */
+
+typedef unsigned char  uch;
+typedef uch FAR uchf;
+typedef unsigned short ush;
+typedef ush FAR ushf;
+typedef unsigned long  ulg;
+
+extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
+/* (size given to avoid silly warnings with Visual C++) */
+
+#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)]
+
+#define ERR_RETURN(strm,err) \
+  return (strm->msg = ERR_MSG(err), (err))
+/* To be used only when the state is known to be valid */
+
+        /* common constants */
+
+#ifndef DEF_WBITS
+#  define DEF_WBITS MAX_WBITS
+#endif
+/* default windowBits for decompression. MAX_WBITS is for compression only */
+
+#if MAX_MEM_LEVEL >= 8
+#  define DEF_MEM_LEVEL 8
+#else
+#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
+#endif
+/* default memLevel */
+
+#define STORED_BLOCK 0
+#define STATIC_TREES 1
+#define DYN_TREES    2
+/* The three kinds of block type */
+
+#define MIN_MATCH  3
+#define MAX_MATCH  258
+/* The minimum and maximum match lengths */
+
+#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */
+
+        /* target dependencies */
+
+#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32))
+#  define OS_CODE  0x00
+#  ifndef Z_SOLO
+#    if defined(__TURBOC__) || defined(__BORLANDC__)
+#      if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__))
+         /* Allow compilation with ANSI keywords only enabled */
+         void _Cdecl farfree( void *block );
+         void *_Cdecl farmalloc( unsigned long nbytes );
+#      else
+#        include <alloc.h>
+#      endif
+#    else /* MSC or DJGPP */
+#      include <malloc.h>
+#    endif
+#  endif
+#endif
+
+#ifdef AMIGA
+#  define OS_CODE  1
+#endif
+
+#if defined(VAXC) || defined(VMS)
+#  define OS_CODE  2
+#  define F_OPEN(name, mode) \
+     fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512")
+#endif
+
+#ifdef __370__
+#  if __TARGET_LIB__ < 0x20000000
+#    define OS_CODE 4
+#  elif __TARGET_LIB__ < 0x40000000
+#    define OS_CODE 11
+#  else
+#    define OS_CODE 8
+#  endif
+#endif
+
+#if defined(ATARI) || defined(atarist)
+#  define OS_CODE  5
+#endif
+
+#ifdef OS2
+#  define OS_CODE  6
+#  if defined(M_I86) && !defined(Z_SOLO)
+#    include <malloc.h>
+#  endif
+#endif
+
+#if defined(MACOS) || defined(TARGET_OS_MAC)
+#  define OS_CODE  7
+#  ifndef Z_SOLO
+#    if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os
+#      include <unix.h> /* for fdopen */
+#    else
+#      ifndef fdopen
+#        define fdopen(fd,mode) NULL /* No fdopen() */
+#      endif
+#    endif
+#  endif
+#endif
+
+#ifdef __acorn
+#  define OS_CODE 13
+#endif
+
+#if defined(WIN32) && !defined(__CYGWIN__)
+#  define OS_CODE  10
+#endif
+
+#ifdef _BEOS_
+#  define OS_CODE  16
+#endif
+
+#ifdef __TOS_OS400__
+#  define OS_CODE 18
+#endif
+
+#ifdef __APPLE__
+#  define OS_CODE 19
+#endif
+
+#if defined(_BEOS_) || defined(RISCOS)
+#  define fdopen(fd,mode) NULL /* No fdopen() */
+#endif
+
+#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX
+#  if defined(_WIN32_WCE)
+#    define fdopen(fd,mode) NULL /* No fdopen() */
+#    ifndef _PTRDIFF_T_DEFINED
+       typedef int ptrdiff_t;
+#      define _PTRDIFF_T_DEFINED
+#    endif
+#  else
+#    define fdopen(fd,type)  _fdopen(fd,type)
+#  endif
+#endif
+
+#if defined(__BORLANDC__) && !defined(MSDOS)
+  #pragma warn -8004
+  #pragma warn -8008
+  #pragma warn -8066
+#endif
+
+/* provide prototypes for these when building zlib without LFS */
+#if !defined(_WIN32) && \
+    (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0)
+    ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
+    ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
+#endif
+
+        /* common defaults */
+
+#ifndef OS_CODE
+#  define OS_CODE  3     /* assume Unix */
+#endif
+
+#ifndef F_OPEN
+#  define F_OPEN(name, mode) fopen((name), (mode))
+#endif
+
+         /* functions */
+
+#if defined(pyr) || defined(Z_SOLO)
+#  define NO_MEMCPY
+#endif
+#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__)
+ /* Use our own functions for small and medium model with MSC <= 5.0.
+  * You may have to use the same strategy for Borland C (untested).
+  * The __SC__ check is for Symantec.
+  */
+#  define NO_MEMCPY
+#endif
+#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY)
+#  define HAVE_MEMCPY
+#endif
+#ifdef HAVE_MEMCPY
+#  ifdef SMALL_MEDIUM /* MSDOS small or medium model */
+#    define zmemcpy _fmemcpy
+#    define zmemcmp _fmemcmp
+#    define zmemzero(dest, len) _fmemset(dest, 0, len)
+#  else
+#    define zmemcpy memcpy
+#    define zmemcmp memcmp
+#    define zmemzero(dest, len) memset(dest, 0, len)
+#  endif
+#else
+   void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len));
+   int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len));
+   void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len));
+#endif
+
+/* Diagnostic functions */
+#ifdef ZLIB_DEBUG
+#  include <stdio.h>
+   extern int ZLIB_INTERNAL z_verbose;
+   extern void ZLIB_INTERNAL z_error OF((char *m));
+#  define Assert(cond,msg) {if(!(cond)) z_error(msg);}
+#  define Trace(x) {if (z_verbose>=0) fprintf x ;}
+#  define Tracev(x) {if (z_verbose>0) fprintf x ;}
+#  define Tracevv(x) {if (z_verbose>1) fprintf x ;}
+#  define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;}
+#  define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;}
+#else
+#  define Assert(cond,msg)
+#  define Trace(x)
+#  define Tracev(x)
+#  define Tracevv(x)
+#  define Tracec(c,x)
+#  define Tracecv(c,x)
+#endif
+
+#ifndef Z_SOLO
+   voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items,
+                                    unsigned size));
+   void ZLIB_INTERNAL zcfree  OF((voidpf opaque, voidpf ptr));
+#endif
+
+#define ZALLOC(strm, items, size) \
+           (*((strm)->zalloc))((strm)->opaque, (items), (size))
+#define ZFREE(strm, addr)  (*((strm)->zfree))((strm)->opaque, (voidpf)(addr))
+#define TRY_FREE(s, p) {if (p) ZFREE(s, p);}
+
+/* Reverse the bytes in a 32-bit value */
+#define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \
+                    (((q) & 0xff00) << 8) + (((q) & 0xff) << 24))
+
+#endif /* ZUTIL_H */