diff --git a/LICENSE b/LICENSE index 3354f2f..a388783 100644 --- a/LICENSE +++ b/LICENSE @@ -27,6 +27,11 @@ DOS Serial Library https://github.com/kstenerud/DOS-Serial-Library Attribution +ENet +---- +https://github.com/zpl-c/enet +MIT + MemWatch -------- http://www.linkdata.se/sourcecode/memwatch/ @@ -42,11 +47,6 @@ SDL2 https://www.libsdl.org/ BSD 3-Clause -SDL2_Net --------- -https://www.libsdl.org/ -BSD 3-Clause - stb_ds.h -------- https://github.com/nothings/stb @@ -56,3 +56,36 @@ stb_image.h ----------- https://github.com/nothings/stb Public Domain + + +Font Converter +============== + +MemWatch +-------- +http://www.linkdata.se/sourcecode/memwatch/ +GPL2 + +stb_image.h +----------- +https://github.com/nothings/stb +Public Domain + + +Server +====== + +ENet +---- +https://github.com/zpl-c/enet +MIT + +MemWatch +-------- +http://www.linkdata.se/sourcecode/memwatch/ +GPL2 + +stb_ds.h +-------- +https://github.com/nothings/stb +Public Domain diff --git a/client/Makefile.djgpp b/client/Makefile.djgpp index 6e09864..489f53b 100644 --- a/client/Makefile.djgpp +++ b/client/Makefile.djgpp @@ -27,12 +27,13 @@ DEBUG = -g ## CHANGE THIS ## TARGET = client SRCDIR = src +SHARED = ../shared OBJDIR = obj BINDIR = bin ## CHANGE THIS ## # CFLAGS, LDFLAGS, CPPFLAGS, PREFIX can be overriden on CLI -CFLAGS := $(DEBUG) -I$(SRCDIR) -I$(SRCDIR)/system -I$(SRCDIR)/dos -I$(SRCDIR)/gui -I$(SRCDIR)/thirdparty -I$(SRCDIR)/thirdparty/memwatch -I$(SRCDIR)/thirdparty/serial +CFLAGS := $(DEBUG) -I$(SRCDIR) -I$(SRCDIR)/system -I$(SRCDIR)/dos -I$(SRCDIR)/gui -I$(SRCDIR)/thirdparty -I$(SRCDIR)/thirdparty/serial -I$(SHARED) -I$(SHARED)/thirdparty CPPFLAGS := LDFLAGS := PREFIX := /usr/local @@ -52,7 +53,7 @@ ALL_LDLIBS := -lc # Source, Binaries, Dependencies -SRC := $(shell find $(SRCDIR) -type f -name '*.c' | grep -v '/linux/') +SRC := $(shell find $(SRCDIR) -type f -name '*.c' | grep -v '/linux/' && find $(SHARED) -type f -name '*.c') OBJ := $(patsubst $(SRCDIR)/%,$(OBJDIR)/%,$(SRC:.c=.o)) DEP := $(OBJ:.o=.d) BIN := $(BINDIR)/$(TARGET) diff --git a/client/client.pro b/client/client.pro index b34173e..99aabe0 100644 --- a/client/client.pro +++ b/client/client.pro @@ -23,6 +23,7 @@ TEMPLATE = app CONFIG -= qt DESTDIR = $$OUT_PWD/bin +SHARED = $$PWD/../shared DOS_HEADERS = \ src/thirdparty/serial/serial.h @@ -34,15 +35,19 @@ DOS_SOURCES = \ src/dos/vesa.c LINUX_INCLUDES = \ - $$PWD/src/linux + $$PWD/src/linux \ + $$SHARED/thirdparty/enet/include -LINUX_HEADERS = +LINUX_HEADERS = \ + $$SHARED/thirdparty/enet/include/enet.h LINUX_SOURCES = \ src/linux/linux.c INCLUDEPATH += \ $$LINUX_INCLUDES \ + $$SHARED \ + $$SHARED/thirdparty \ $$PWD/src/thirdparty \ $$PWD/src/system \ $$PWD/src/gui \ @@ -50,16 +55,18 @@ INCLUDEPATH += \ HEADERS = \ $$LINUX_HEADERS \ + $$SHARED/stddclmr.h \ + $$SHARED/thirdparty/stb_ds.h \ + $$SHARED/thirdparty/stb_image.h \ + $$SHARED/thirdparty/memwatch/memwatch.h \ + src/system/comport.h \ + src/thirdparty/minicoro/minicoro.h \ src/settings.h \ src/system/color.h \ src/system/memory.h \ src/system/surface.h \ src/system/taglist.h \ src/test.h \ - src/thirdparty/stb_ds.h \ - src/thirdparty/stb_image.h \ - src/thirdparty/memwatch/memwatch.h \ - src/thirdparty/minicoro/minicoro.h \ src/system/keyboard.h \ src/system/task.h \ src/system/timer.h \ @@ -85,13 +92,12 @@ HEADERS = \ src/gui/widget.h \ src/gui/window.h \ src/gui/image.h \ - src/stddclmr.h \ src/welcome.h SOURCES = \ $$LINUX_SOURCES \ + $$SHARED/thirdparty/memwatch/memwatch.c \ src/system/memory.c \ - src/thirdparty/memwatch/memwatch.c \ src/settings.c \ src/system/surface.c \ src/system/taglist.c \ @@ -120,11 +126,12 @@ SOURCES = \ src/welcome.c LIBS = \ - -lSDL2 \ - -lSDL2_net + -lSDL2 OTHER_FILES = \ Makefile.djgpp \ $$DOS_HEADERS \ $$DOS_SOURCES \ - build.sh + build.sh \ + test.sh \ + test.conf diff --git a/client/src/linux/linux.c b/client/src/linux/linux.c index 50bac14..24c624f 100644 --- a/client/src/linux/linux.c +++ b/client/src/linux/linux.c @@ -20,11 +20,15 @@ #include +#define ENET_IMPLEMENTATION +#include "enet.h" + #include "os.h" #include "mouse.h" #include "vesa.h" #include "surface.h" +#include "comport.h" #define SECONDS_IN_DAY 86400 @@ -32,30 +36,46 @@ #define TICKS_PER_DAY (SECONDS_IN_DAY * TICKS_PER_SECOND) #define TIMER_INTERVAL (1000 / TICKS_PER_SECOND) +#define COM_BUFFER_SIZE 2048 +#define MODEM_RESULT_OK 1 +#define MODEM_RESULT_ERROR 2 + static MouseT _mouse; -static SDL_Window *_window = NULL; -static SDL_Renderer *_renderer = NULL; -static SDL_Surface *_surface = NULL; -static SDL_Texture *_texture = NULL; -static uint16_t _width = 0; -static uint16_t _height = 0; -static uint8_t _windowScale = 1; -static uint8_t _alt = 0; -static uint8_t _control = 0; -static uint8_t _shift = 0; -static uint8_t _ASCII = 0; -static uint8_t _scanCode = 0; -static uint8_t _extended = 0; -static uint8_t _ASCIIKeep = 0; -static uint8_t _scanCodeKeep = 0; -static uint8_t _keyPressed = 0; -static uint8_t _debounce = 0; -static long _timerTicks = 0; +static SDL_Window *_window = NULL; +static SDL_Renderer *_renderer = NULL; +static SDL_Surface *_surface = NULL; +static SDL_Texture *_texture = NULL; +static uint16_t _width = 0; +static uint16_t _height = 0; +static uint8_t _windowScale = 1; +static uint8_t _alt = 0; +static uint8_t _control = 0; +static uint8_t _shift = 0; +static uint8_t _ASCII = 0; +static uint8_t _scanCode = 0; +static uint8_t _extended = 0; +static uint8_t _ASCIIKeep = 0; +static uint8_t _scanCodeKeep = 0; +static uint8_t _keyPressed = 0; +static uint8_t _debounce = 0; +static long _timerTicks = 0; static SDL_TimerID _timerID; +static ENetHost *_host = NULL; +static ENetPeer *_peer = NULL; +static ENetAddress _address; +static uint8_t _comPortOpen = 0; +static uint8_t _modemCommandMode = 1; +static char _buffer[COM_BUFFER_SIZE]; +static uint16_t _bufferHead = 0; +static uint16_t _bufferTail = 0; +static uint8_t _connected = 0; +static void comAddToBuffer(char *data, uint16_t len); +static void comModem(uint8_t c); static void processEvent(void); +static void processNetworkEvent(void); long biostime(int cmd, long newtime) { @@ -66,6 +86,188 @@ long biostime(int cmd, long newtime) { } +static void comAddToBuffer(char *data, uint16_t len) { + uint16_t x; + + for (x=0; x= COM_BUFFER_SIZE) _bufferHead = 0; + } +} + + +int comClose(int com) { + // We only pretend to support one COM port. + if (com != 0) return SER_ERR_UNKNOWN; + if (!_comPortOpen) return SER_ERR_NOT_OPEN; + + if (_peer) { + enet_peer_reset(_peer); + _peer = NULL; + } + if (_host) { + enet_host_destroy(_host); + _host = NULL; + } + + _comPortOpen = 0; + + return SER_SUCCESS; +} + + +static void comModem(uint8_t c) { + static char command[COM_BUFFER_SIZE] = { 0 }; + uint16_t x; + uint8_t result = 0; + uint8_t parsing = 1; + char *p = NULL; + + if (c != 13) { + + x = strlen(command); + // Room in buffer? + if (x > COM_BUFFER_SIZE - 1) { + // No. Error. Clear buffer. + snprintf(command, COM_BUFFER_SIZE, "%cERROR%c", 13, 13); + comAddToBuffer(command, strlen(command)); + command[0] = 0; + } else { + // Add to command buffer. + command[x] = toupper(c); + command[x+1] = 0; + } + + } else { + + // AT? + if (command[0] != 'A' || command[1] != 'T') result = MODEM_RESULT_ERROR; + + // Process command string. + x = 2; + while (parsing && !result && command[x]) { + switch (command[x]) { + + // Vendor Commands + case '+': + x++; + // Select Socket Mode + if (strncmp(&command[x], "SOCK", 4) == 0) { + result = MODEM_RESULT_OK; + } else { + result = MODEM_RESULT_ERROR; + } + break; + + + // Dial + case 'D': + x++; + if (command[x] == 'T' || command[x] == 'P') x++; + // Find port, if any. + _address.port = 23; + if ((p = strstr(&command[x], ":"))) { + p = 0; + p++; + _address.port = atoi(p); + } + enet_address_set_host(&_address, &command[x]); + _peer = enet_host_connect(_host, &_address, 1, 0); + if (!_peer) result = MODEM_RESULT_ERROR; + result = MODEM_RESULT_OK; + _modemCommandMode = 0; + break; + + // Dunno. + default: + result = MODEM_RESULT_ERROR; + break; + } + } + + // Send result. + switch (result) { + case MODEM_RESULT_OK: + snprintf(command, COM_BUFFER_SIZE, "OK%c", 13); + comAddToBuffer(command, strlen(command)); + break; + + case MODEM_RESULT_ERROR: + snprintf(command, COM_BUFFER_SIZE, "ERROR%c", 13); + comAddToBuffer(command, strlen(command)); + break; + } + + // Clear buffer. + command[0] = 0; + } +} + + +int comOpen(int com, long bps, int dataBits, char parity, int stopBits, int handshaking) { + (void)bps; + (void)dataBits; + (void)parity; + (void)stopBits; + (void)handshaking; + + // We only pretend to support one COM port. + if (com != 0) return SER_ERR_UNKNOWN; + if (_comPortOpen) return SER_ERR_ALREADY_OPEN; + + _comPortOpen = 1; + _bufferHead = 0; + _bufferTail = 0; + + return SER_SUCCESS; +} + + +int comRead(int com, char *data, int len) { + uint16_t x = 0; + + // We only pretend to support one COM port. + if (com != 0) return SER_ERR_UNKNOWN; + if (!_comPortOpen) return SER_ERR_NOT_OPEN; + + // Is the buffer empty? + if (_bufferHead == _bufferTail) { + return 0; + } + + // Fill buffer. + while (x < len && _bufferHead != _bufferTail) { + data[x++] = _buffer[_bufferTail++]; + if (_bufferTail >= COM_BUFFER_SIZE) _bufferTail = 0; + } + + return x; +} + + +int comWrite(int com, const char *data, int len) { + uint16_t x = 0; + ENetPacket *packet = NULL; + + // We only pretend to support one COM port. + if (com != 0) return SER_ERR_UNKNOWN; + if (!_comPortOpen) return SER_ERR_NOT_OPEN; + + while (_modemCommandMode && x < len) { + comAddToBuffer((char *)&data[x], 1); + comModem(data[x++]); + } + + if (!_modemCommandMode && _connected && len - x > 0) { + packet = enet_packet_create(&data[x], len - x, ENET_PACKET_FLAG_RELIABLE); + if (!packet) return SER_ERR_UNKNOWN; + enet_peer_send(_peer, 0, packet); + } + + return SER_SUCCESS; +} + + uint8_t keyAltGet(void) { return _alt; } @@ -218,6 +420,36 @@ static void processEvent(void) { break; } } + + processNetworkEvent(); +} + + +static void processNetworkEvent(void) { + ENetEvent *event = NULL; + + if (_host) { + while (enet_host_service(_host, event, 1)) { + switch (event->type) { + case ENET_EVENT_TYPE_CONNECT: + _connected = 1; + break; + + case ENET_EVENT_TYPE_RECEIVE: + comAddToBuffer((char *)event->packet->data, event->packet->dataLength); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: + _connected = 0; + _modemCommandMode = 1; + break; + + default: + break; + } + } + } } @@ -264,18 +496,25 @@ ColorT vbeColorMake(uint8_t red, uint8_t green, uint8_t blue) { void vbePresent(void) { - void *pixels; - int pitch; - SurfaceT *s = surfaceOffScreenGet(); + void *pixels; + int temp; + SurfaceT *s = surfaceOffScreenGet(); - SDL_LockTexture(_texture, NULL, &pixels, &pitch); + SDL_LockTexture(_texture, NULL, &pixels, &temp); memcpy(pixels, s->buffer.bits8, s->bytes); SDL_UnlockTexture(_texture); SDL_RenderCopy(_renderer, _texture, NULL, NULL); SDL_RenderPresent(_renderer); - // Throttle this to some sane frame rate. - SDL_Delay(32); + if (_host) { + // Throttle this to some sane frame rate by checking for network events. + for (temp=0; temp<32; temp++) { + processNetworkEvent(); + } + } else { + // No network activity. Just sleep. + SDL_Delay(32); + } } @@ -301,6 +540,9 @@ int16_t vbeShutdown(void) { _window = NULL; } + // Stop network. + enet_deinitialize(); + // Stop timer. SDL_RemoveTimer(_timerID); @@ -332,6 +574,9 @@ uint8_t vbeStartup(uint16_t xRes, uint16_t yRes, uint8_t bpp) { // We do timer startup here, too. _timerID = SDL_AddTimer(TIMER_INTERVAL, timerCallback, NULL); + // And networking/serial startup. + enet_initialize(); + return 0; } diff --git a/client/src/main.c b/client/src/main.c index 12ef389..048d60f 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -65,6 +65,7 @@ static void taskGuiEventLoop(void *data) { ColorT alpha; #ifdef TESTING int8_t debugState = 0; + int8_t key = 0; #endif (void)data; @@ -87,7 +88,7 @@ static void taskGuiEventLoop(void *data) { imageRenderWithAlpha(pointer, mouse->x, mouse->y, alpha); #ifdef TESTING - if (key == '=') guiWidgetTreeDump(guiRootGet(), 0); + if (key == '=') guiDebugWidgetTreeDump(guiRootGet(), 0); if (key == '+') { debugState++; if (debugState > 2) debugState = 0; @@ -150,12 +151,12 @@ int main(int argc, char *argv[]) { taskStartup(); #ifdef TESTING - taskCreate(taskTestTagList, NULL); + taskCreate(comPortScanTest, NULL); #else taskCreate(taskWelcome, NULL); + taskCreate(taskGuiEventLoop, NULL); #endif - taskCreate(taskGuiEventLoop, NULL); taskRun(); taskShutdown(); diff --git a/client/src/settings.c b/client/src/settings.c index 002e320..413be04 100644 --- a/client/src/settings.c +++ b/client/src/settings.c @@ -23,16 +23,38 @@ #include "taglist.h" #include "task.h" +#include "comport.h" +#include "timer.h" #include "window.h" #include "button.h" #include "frame.h" #include "radio.h" +#include "textbox.h" +#include "updown.h" +#include "label.h" -static WindowT *winSettings; -static FrameT *frmComPorts; -static ButtonT *btnOkay; +#define GROUP_COM 1 + +#define PORT_NONE 0 +#define PORT_BAD 1 +#define PORT_GOOD 2 + + +static WindowT *winDetecting; +static LabelT *lblOneMoment; + +static WindowT *winSettings; +static FrameT *frmComPorts; +static FrameT *frmServer; +static ButtonT *btnOkay; +static RadioT *rdoCOM1; +static RadioT *rdoCOM2; +static RadioT *rdoCOM3; +static RadioT *rdoCOM4; +static TextboxT *txtServer; +static UpdownT *updPort; static void btnOkayClick(WidgetT *widget); @@ -47,23 +69,114 @@ static void btnOkayClick(WidgetT *widget) { void taskSettings(void *data) { + int32_t rc; + uint32_t len; + char buffer[1024]; + uint8_t quarterSeconds = 0; + uint8_t ports[4] = { PORT_NONE, PORT_NONE, PORT_NONE, PORT_NONE }; // 0 = No port. 1 = No modem. 2 = Valid. + (void)data; - TagItemT ui[] = { + TagItemT uiDetecting[] = { T_START, - T_WINDOW, O(winSettings), - T_TITLE, P("Settings"), - T_WIDTH, 200, T_HEIGHT, 200, - T_BUTTON, O(btnOkay), - T_TITLE, P("Okay"), - T_CLICK, P(btnOkayClick), - T_BUTTON, T_DONE, + T_WINDOW, O(winDetecting), + T_TITLE, P("Detecting Modems"), + T_WIDTH, 200, T_HEIGHT, 100, + T_LABEL, O(lblOneMoment), + T_X, 25, T_Y, 25, + T_TITLE, P("One Moment Please!"), + T_LABEL, T_DONE, T_WINDOW, T_DONE, T_END }; - tagListRun(ui); + tagListRun(uiDetecting); + taskYield(); // Cause dialog to paint. + + for (int x=0; x<4; x++) { + rc = comOpen(x, 57600L, 8, 'n', 1, SER_HANDSHAKING_RTSCTS); + if (rc == SER_SUCCESS) { + snprintf(buffer, 1024, "%s%c", "AT+SOCK1", 13); + comWrite(x, buffer, strlen(buffer)); + // Wait a second. + while (quarterSeconds < 5) { + taskYield(); + if (timerQuarterSecondTick) quarterSeconds++; + } + len = comRead(x, buffer, 1024); + buffer[len] = 0; + if (strstr(buffer, "OK")) { + ports[x] = PORT_GOOD; + } else { + ports[x] = PORT_BAD; + } + comClose(x); + } + } + + guiDelete(D(winDetecting)); + + TagItemT uiSettings[] = { + T_START, + T_WINDOW, O(winSettings), + T_TITLE, P("Settings"), + T_WIDTH, 300, T_HEIGHT, 295, + + T_FRAME, O(frmComPorts), + T_X, 10, T_Y, 5, T_WIDTH, 266, T_HEIGHT, 100, + T_TITLE, P("COM Ports"), + T_RADIOBUTTON, O(rdoCOM1), + T_X, 5, + T_TITLE, P((ports[0] == PORT_NONE ? "COM1 - Not Present" : (ports[0] == PORT_BAD ? "COM1 - Incompatable Modem" : "COM1 - SoftModem Found!"))), + T_GROUP, GROUP_COM, + T_RADIOBUTTON, T_DONE, + T_RADIOBUTTON, O(rdoCOM2), + T_X, 5, T_Y, 20, + T_TITLE, P((ports[1] == PORT_NONE ? "COM2 - Not Present" : (ports[1] == PORT_BAD ? "COM2 - Incompatable Modem" : "COM2 - SoftModem Found!"))), + T_GROUP, GROUP_COM, + T_RADIOBUTTON, T_DONE, + T_RADIOBUTTON, O(rdoCOM3), + T_X, 5, T_Y, 40, + T_TITLE, P((ports[2] == PORT_NONE ? "COM3 - Not Present" : (ports[2] == PORT_BAD ? "COM3 - Incompatable Modem" : "COM3 - SoftModem Found!"))), + T_GROUP, GROUP_COM, + T_RADIOBUTTON, T_DONE, + T_RADIOBUTTON, O(rdoCOM4), + T_X, 5, T_Y, 60, + T_TITLE, P((ports[3] == PORT_NONE ? "COM4 - Not Present" : (ports[3] == PORT_BAD ? "COM4 - Incompatable Modem" : "COM4 - SoftModem Found!"))), + T_GROUP, GROUP_COM, + T_RADIOBUTTON, T_DONE, + T_FRAME, T_DONE, + + T_FRAME, O(frmServer), + T_X, 10, T_Y, 110, T_WIDTH, 266, T_HEIGHT, 100, + T_TITLE, P("Server"), + T_TEXTBOX, O(txtServer), + T_X, 5, T_WIDTH, 250, + T_TITLE, P("Address:"), + T_VALUE, P("kangaworld.kangaroopunch.com"), + T_TEXTBOX, T_DONE, + T_UPDOWN, O(updPort), + T_X, 5, T_Y, 30, T_WIDTH, 250, + T_TITLE, P(" Port:"), + T_VALUE, 16550, + T_MINIMUM, 1, + T_MAXIMUM, 65535, + T_UPDOWN, T_DONE, + T_FRAME, T_DONE, + + T_BUTTON, O(btnOkay), + T_X, 225, T_Y, 225, + T_TITLE, P("Okay"), + T_CLICK, P(btnOkayClick), + T_BUTTON, T_DONE, + + T_WINDOW, T_DONE, + T_END + }; + + tagListRun(uiSettings); guiDebugAreaShow(W(winSettings)); + guiDebugAreaShow(W(frmComPorts)); guiDebugAreaShow(W(btnOkay)); } diff --git a/font/src/stb.h b/client/src/system/comport.h similarity index 54% rename from font/src/stb.h rename to client/src/system/comport.h index 8a6618a..84d845d 100644 --- a/font/src/stb.h +++ b/client/src/system/comport.h @@ -18,12 +18,38 @@ */ -#ifndef STB_H -#define STB_H +#ifndef COMPORT_H +#define COMPORT_H -#include "stb_image.h" -#include "stb_leakcheck.h" +#include "os.h" -#endif // STB_H +#ifdef __linux__ + +#define SER_SUCCESS 0 +#define SER_ERR_UNKNOWN -1 +#define SER_ERR_NOT_OPEN -2 +#define SER_ERR_ALREADY_OPEN -3 + +#define SER_HANDSHAKING_RTSCTS 2 + + +int comClose(int com); +int comOpen(int com, long bps, int dataBits, char parity, int stopBits, int handshaking); +int comRead(int com, char *data, int len); +int comWrite(int com, const char *data, int len); + +#else + +#include "serial/serial.h" + +#define comClose serial_close +#define comOpen serial_open +#define comRead serial_read +#define comWrite serial_write + +#endif + + +#endif // COMPORT_H diff --git a/client/src/system/timer.c b/client/src/system/timer.c index 8092f9c..8e31211 100644 --- a/client/src/system/timer.c +++ b/client/src/system/timer.c @@ -26,6 +26,10 @@ #define TICKS_PER_DAY (SECONDS_IN_DAY * TICKS_PER_SECOND) +uint8_t timerQuarterSecondTick = 0; +uint8_t timerHalfSecondTick = 0; +uint8_t timerSecondTick = 0; + uint8_t timerQuarterSecondOn = 0; uint8_t timerHalfSecondOn = 0; uint8_t timerSecondOn = 0; @@ -52,6 +56,11 @@ void timerUpdate(void) { now = biostime(0, 0); + // Reset ticks. + timerQuarterSecondTick = 0; + timerHalfSecondTick = 0; + timerSecondTick = 0; + // Ensure we haven't rolled past midnight between calls. if (now >= _timerLast) { delta = now - _timerLast; @@ -66,16 +75,19 @@ void timerUpdate(void) { // Quarter Second timer. timerQuarterSecondOn = !timerQuarterSecondOn; + timerQuarterSecondTick = 1; // Half Second timer. if (--_timerHalfSecond == 0) { _timerHalfSecond = 2; timerHalfSecondOn = !timerHalfSecondOn; + timerHalfSecondTick = 1; // Second timer if (--_timerSecond == 0) { _timerSecond = 2; timerSecondOn = !timerSecondOn; + timerSecondTick = 1; } // Second. } // Half Second. diff --git a/client/src/system/timer.h b/client/src/system/timer.h index ffdd57a..f35e8eb 100644 --- a/client/src/system/timer.h +++ b/client/src/system/timer.h @@ -25,6 +25,10 @@ #include "os.h" +extern uint8_t timerQuarterSecondTick; +extern uint8_t timerHalfSecondTick; +extern uint8_t timerSecondTick; + extern uint8_t timerQuarterSecondOn; extern uint8_t timerHalfSecondOn; extern uint8_t timerSecondOn; diff --git a/client/src/test.c b/client/src/test.c index e8e68e5..f7f3d07 100644 --- a/client/src/test.c +++ b/client/src/test.c @@ -36,6 +36,9 @@ #include "terminal.h" #include "taglist.h" +#include "comport.h" + +#include uint8_t lastKey = 0; @@ -53,6 +56,37 @@ static void buttonClick(WidgetT *widget) { } +void comPortScanTest(void *data) { + int rc; + int len; + char buffer[1024]; + + (void)data; + + for (int x=0; x<4; x++) { + rc = comOpen(x, 57600L, 8, 'n', 1, SER_HANDSHAKING_RTSCTS); + if (rc == SER_SUCCESS) { + logWrite("COM%d exists!\n", x + 1); + snprintf(buffer, 1024, "%s%c", "AT+SOCK1", 13); + comWrite(x, buffer, strlen(buffer)); + sleep(1); + len = comRead(x, buffer, 1024); + buffer[len] = 0; + if (strstr(buffer, "OK")) { + logWrite("ENET SoftModem found!\n"); + } else { + logWrite("Result: [%s]\n", buffer); + } + comClose(x); + } else { + logWrite("No COM%d.\n", x + 1); + } + } + + guiStop(); +} + + /* static void test(void *data) { DesktopT *desktop = (DesktopT *)guiRootGet(); diff --git a/client/src/test.h b/client/src/test.h index 105fc93..fee0b69 100644 --- a/client/src/test.h +++ b/client/src/test.h @@ -29,6 +29,7 @@ extern uint8_t lastKey; +void comPortScanTest(void *data); void taskTestTagList(void *data); void widgetDebugDraw(WidgetT *widget, uint8_t debugToggle); diff --git a/client/src/welcome.c b/client/src/welcome.c index 2ef5209..fad8b55 100644 --- a/client/src/welcome.c +++ b/client/src/welcome.c @@ -65,7 +65,7 @@ void taskWelcome(void *data) { // 450x128 logo - TagItemT ui[] = { + TagItemT uiWelcome[] = { T_START, T_WINDOW, O(winWelcome), T_TITLE, P("Welcome to KangaWorld!"), @@ -93,5 +93,5 @@ void taskWelcome(void *data) { T_END }; - tagListRun(ui); + tagListRun(uiWelcome); } diff --git a/client/test.conf b/client/test.conf new file mode 100644 index 0000000..e372484 --- /dev/null +++ b/client/test.conf @@ -0,0 +1,474 @@ +# This is the configuration file for dosbox-staging (0.78.0). +# Lines starting with a '#' character are comments. + +[sdl] +# fullscreen: Start directly in fullscreen. +# Run INTRO and see Special Keys for window control hotkeys. +# display: Number of display to use; values depend on OS and user settings. +# fullresolution: What resolution to use for fullscreen: 'original', 'desktop' +# or a fixed size (e.g. 1024x768). +# windowresolution: Set window size when running in windowed mode: +# default: Select the best option based on your +# environment and other settings. +# small, medium, or large (or s, m, l): +# Size the window relative to the desktop. +# : Scale the window to the given dimensions in +# WxH format. For example: 1024x768. +# Scaling is not performed for output=surface. +# window_position: Set initial window position when running in windowed mode: +# auto: Let the window manager decide the position. +# : Set window position in X,Y format. For example: 250,100 +# 0,0 is the top-left corner of the screen. +# window_decorations: Controls whether to display window decorations in windowed mode. +# vsync: Synchronize with display refresh rate if supported. This can +# reduce flickering and tearing, but may also impact performance. +# vsync_skip: Number of microseconds to allow rendering to block before skipping the next frame. 0 disables this and will always render. +# max_resolution: Optionally restricts the viewport resolution within the window/screen: +# auto: The viewport fills the window/screen (default). +# : Set max viewport resolution in WxH format. +# For example: 960x720 +# output: What video system to use for output. +# Possible values: surface, texture, texturenb, texturepp, opengl, openglnb, openglpp. +# texture_renderer: Choose a renderer driver when using a texture output mode. +# Use texture_renderer=auto for an automatic choice. +# Possible values: auto, opengl, opengles2, software. +# capture_mouse: Choose a mouse control method: +# onclick: Capture the mouse when clicking any button in the window. +# onstart: Capture the mouse immediately on start. +# seamless: Let the mouse move seamlessly; captures only with middle-click or hotkey. +# nomouse: Hide the mouse and don't send input to the game. +# Choose how middle-clicks are handled (second parameter): +# middlegame: Middle-clicks are sent to the game. +# middlerelease: Middle-click will release the captured mouse, and also capture when seamless. +# Defaults (if not present or incorrect): seamless middlerelease +# Possible values: seamless, onclick, onstart, nomouse. +# sensitivity: Mouse sensitivity. The optional second parameter specifies vertical sensitivity (e.g. 100,-50). +# raw_mouse_input: Enable this setting to bypass your operating system's mouse +# acceleration and sensitivity settings. This works in +# fullscreen or when the mouse is captured in window mode. +# waitonerror: Wait before closing the console if dosbox has an error. +# priority: Priority levels for dosbox. Second entry behind the comma is for when dosbox is not focused/minimized. +# pause is only valid for the second entry. auto disables priority levels and uses OS defaults +# Possible values: auto, lowest, lower, normal, higher, highest, pause. +# mapperfile: File used to load/save the key/event mappings from. +# Resetmapper only works with the default value. +# screensaver: Use 'allow' or 'block' to override the SDL_VIDEO_ALLOW_SCREENSAVER +# environment variable (which usually blocks the OS screensaver +# while the emulator is running). +# Possible values: auto, allow, block. + +fullscreen = false +display = 0 +fullresolution = desktop +windowresolution = default +window_position = auto +window_decorations = true +vsync = false +vsync_skip = 7000 +max_resolution = auto +output = opengl +texture_renderer = auto +capture_mouse = seamless middlerelease +sensitivity = 100 +raw_mouse_input = false +waitonerror = true +priority = auto,auto +mapperfile = mapper-sdl2-0.78.0.map +screensaver = auto + +[dosbox] +# language: Select another language file. +# machine: The type of machine DOSBox tries to emulate. +# Possible values: hercules, cga, cga_mono, tandy, pcjr, ega, vgaonly, svga_s3, svga_et3000, svga_et4000, svga_paradise, vesa_nolfb, vesa_oldvbe. +# captures: Directory where things like wave, midi, screenshot get captured. +# memsize: Amount of memory DOSBox has in megabytes. +# This value is best left at its default to avoid problems with some games, +# though few games might require a higher value. +# There is generally no speed advantage when raising this value. +# vmemsize: Video memory in MiB (1-8) or KiB (256 to 8192). 'auto' uses the default per video adapter. +# Possible values: auto, 1, 2, 4, 8, 256, 512, 1024, 2048, 4096, 8192. +# vesa_modes: Controls the selection of VESA 1.2 and 2.0 modes offered: +# compatible A tailored selection that maximizes game compatibility. +# This is recommended along with 4 or 8 MB of video memory. +# all Offers all modes for a given video memory size, however +# some games may not use them properly (flickering) or may need +# more system memory (mem = ) to use them. +# Possible values: compatible, all. +# autoexec_section: How autoexec sections are handled from multiple config files. +# join : combines them into one big section (legacy behavior). +# overwrite : use the last one encountered, like other conf settings. +# Possible values: join, overwrite. +# startup_verbosity: Controls verbosity prior to displaying the program: +# Verbosity | Splash | Welcome | Early stdout +# high | yes | yes | yes +# medium | no | yes | yes +# low | no | no | yes +# quiet | no | no | no +# splash_only | yes | no | no +# auto | 'low' if exec or dir is passed, otherwise 'high' +# Possible values: auto, high, medium, low, splash_only, quiet. + +language = +machine = svga_s3 +captures = capture +memsize = 16 +vmemsize = auto +vesa_modes = compatible +autoexec_section = join +startup_verbosity = auto + +[render] +# frameskip: How many frames DOSBox skips before drawing one. +# aspect: Scales the vertical resolution to produce a 4:3 display aspect +# ratio, matching that of the original standard-definition monitors +# for which the majority of DOS games were designed. This setting +# only affects video modes that use non-square pixels, such as +# 320x200 or 640x400; where as square-pixel modes, such as 640x480 +# and 800x600, will be displayed as-is. +# monochrome_palette: Select default palette for monochrome display. +# Works only when emulating hercules or cga_mono. +# You can also cycle through available colours using F11. +# Possible values: white, paperwhite, green, amber. +# scaler: Scaler used to enlarge/enhance low resolution modes. +# If 'forced' is appended, then the scaler will be used even if +# the result might not be desired. +# Note that some scalers may use black borders to fit the image +# within your configured display resolution. If this is +# undesirable, try either a different scaler or enabling +# fullresolution output. +# Possible values: none, normal2x, normal3x, advmame2x, advmame3x, advinterp2x, advinterp3x, hq2x, hq3x, 2xsai, super2xsai, supereagle, tv2x, tv3x, rgb2x, rgb3x, scan2x, scan3x. +# glshader: Either 'none' or a GLSL shader name. Works only with +# OpenGL output. Can be either an absolute path, a file +# in the 'glshaders' subdirectory of the DOSBox +# configuration directory, or one of the built-in shaders: +# advinterp2x, advinterp3x, advmame2x, advmame3x, +# crt-easymode-flat, crt-fakelottes-flat, rgb2x, rgb3x, +# scan2x, scan3x, tv2x, tv3x, sharp (default). + +frameskip = 0 +aspect = true +monochrome_palette = white +scaler = none +glshader = default + +[composite] +# composite: Enable composite mode on start. 'auto' lets the program decide. +# Note: Fine-tune the settings below (ie: hue) using the composite hotkeys. +# Then read the new settings from your console and enter them here. +# Possible values: auto, on, off. +# era: Era of composite technology. When 'auto', PCjr uses new and CGA/Tandy use old. +# Possible values: auto, old, new. +# hue: Appearance of RGB palette. For example, adjust until sky is blue. +# saturation: Intensity of colors, from washed out to vivid. +# contrast: Ratio between the dark and light area. +# brightness: Luminosity of the image, from dark to light. +# convergence: Convergence of subpixel elements, from blurry to sharp (CGA and Tandy-only). + +composite = auto +era = auto +hue = 0 +saturation = 100 +contrast = 100 +brightness = 0 +convergence = 0 + +[cpu] +# core: CPU Core used in emulation. auto will switch to dynamic if available and +# appropriate. +# Possible values: auto, dynamic, normal, simple. +# cputype: CPU Type used in emulation. auto is the fastest choice. +# Possible values: auto, 386, 386_slow, 486_slow, pentium_slow, 386_prefetch. +# cycles: Number of instructions DOSBox tries to emulate each millisecond. +# Setting this value too high results in sound dropouts and lags. +# Cycles can be set in 3 ways: +# 'auto' tries to guess what a game needs. +# It usually works, but can fail for certain games. +# 'fixed #number' will set a fixed number of cycles. This is what you usually +# need if 'auto' fails (Example: fixed 4000). +# 'max' will allocate as much cycles as your computer is able to +# handle. +# Possible values: auto, fixed, max. +# cycleup: Number of cycles added or subtracted with speed control hotkeys. +# Run INTRO and see Special Keys for list of hotkeys. +# cycledown: Setting it lower than 100 will be a percentage. + +core = auto +cputype = auto +cycles = auto +cycleup = 10 +cycledown = 20 + +[mixer] +# nosound: Enable silent mode, sound is still emulated though. +# rate: Mixer sample rate, setting any device's rate higher than this will probably lower their sound quality. +# Possible values: 44100, 48000, 32000, 22050, 16000, 11025, 8000, 49716. +# blocksize: Mixer block size, larger blocks might help sound stuttering but sound will also be more lagged. +# Possible values: 1024, 2048, 4096, 8192, 512, 256, 128. +# prebuffer: How many milliseconds of data to keep on top of the blocksize. +# negotiate: Allow system audio driver to negotiate optimal rate and blocksize +# as close to the specified values as possible. + +nosound = false +rate = 48000 +blocksize = 512 +prebuffer = 20 +negotiate = true + +[midi] +# mididevice: Device that will receive the MIDI data (from the emulated MIDI +# interface - MPU-401). Choose one of the following: +# 'fluidsynth', to use the built-in MIDI synthesizer. See the +# [fluidsynth] section for detailed configuration. +# 'mt32', to use the built-in Roland MT-32 synthesizer. +# See the [mt32] section for detailed configuration. +# 'auto', to use the first working external MIDI player. This +# might be a software synthesizer or physical device. +# Possible values: auto, oss, alsa, fluidsynth, mt32, none. +# midiconfig: Configuration options for the selected MIDI interface. +# This is usually the id or name of the MIDI synthesizer you want +# to use (find the id/name with DOS command 'mixer /listmidi'). +# - This option has no effect when using the built-in synthesizers +# (mididevice = fluidsynth or mt32). +# - When using ALSA, use Linux command 'aconnect -l' to list open +# MIDI ports, and select one (for example 'midiconfig=14:0' +# for sequencer client 14, port 0). +# - If you're using a physical Roland MT-32 with revision 0 PCB, +# the hardware may require a delay in order to prevent its +# buffer from overflowing. In that case, add 'delaysysex', +# for example: 'midiconfig=2 delaysysex'. +# See the README/Manual for more details. +# mpu401: Type of MPU-401 to emulate. +# Possible values: intelligent, uart, none. + +mididevice = auto +midiconfig = +mpu401 = intelligent + +[fluidsynth] +# soundfont: Path to a SoundFont file in .sf2 format. You can use an +# absolute or relative path, or the name of an .sf2 inside +# the 'soundfonts' directory within your DOSBox configuration +# directory. +# An optional percentage will scale the SoundFont's volume. +# For example: 'soundfont.sf2 50' will attenuate it by 50 percent. +# The scaling percentage can range from 1 to 500. +# chorus: Chorus effect: 'auto', 'on', 'off', or custom values. +# When using custom values: +# All five must be provided in-order and space-separated. +# They are: voice-count level speed depth modulation-wave, where: +# - voice-count is an integer from 0 to 99. +# - level is a decimal from 0.0 to 10.0 +# - speed is a decimal, measured in Hz, from 0.1 to 5.0 +# - depth is a decimal from 0.0 to 21.0 +# - modulation-wave is either 'sine' or 'triangle' +# For example: chorus = 3 1.2 0.3 8.0 sine +# reverb: Reverb effect: 'auto', 'on', 'off', or custom values. +# When using custom values: +# All four must be provided in-order and space-separated. +# They are: room-size damping width level, where: +# - room-size is a decimal from 0.0 to 1.0 +# - damping is a decimal from 0.0 to 1.0 +# - width is a decimal from 0.0 to 100.0 +# - level is a decimal from 0.0 to 1.0 +# For example: reverb = 0.61 0.23 0.76 0.56 + +soundfont = default.sf2 +chorus = auto +reverb = auto + +[mt32] +# model: Model of synthesizer to use. +# 'auto' picks the first model with available ROMs, in order as listed. +# 'cm32l' and 'mt32' pick the first model of their type, in the order listed. +# 'mt32_old' and 'mt32_new' are aliases for 1.07 and 2.04, respectively. +# Possible values: auto, cm32l, cm32l_102, cm32l_100, mt32, mt32_old, mt32_107, mt32_106, mt32_105, mt32_104, mt32_bluer, mt32_new, mt32_204. +# romdir: The directory containing ROMs for one or more models. +# The directory can be absolute or relative, or leave it blank to +# use the 'mt32-roms' directory in your DOSBox configuration +# directory. Other common system locations will be checked as well. +# ROM files inside this directory may include any of the following: +# - MT32_CONTROL.ROM and MT32_PCM.ROM, for the 'mt32' model. +# - CM32L_CONTROL.ROM and CM32L_PCM.ROM, for the 'cm32l' model. +# - Unzipped MAME MT-32 and CM-32L ROMs, for the versioned models. + +model = auto +romdir = + +[sblaster] +# sbtype: Type of Sound Blaster to emulate. 'gb' is Game Blaster. +# Possible values: sb1, sb2, sbpro1, sbpro2, sb16, gb, none. +# sbbase: The IO address of the Sound Blaster. +# Possible values: 220, 240, 260, 280, 2a0, 2c0, 2e0, 300. +# irq: The IRQ number of the Sound Blaster. +# Possible values: 7, 5, 3, 9, 10, 11, 12. +# dma: The DMA number of the Sound Blaster. +# Possible values: 1, 5, 0, 3, 6, 7. +# hdma: The High DMA number of the Sound Blaster. +# Possible values: 1, 5, 0, 3, 6, 7. +# sbmixer: Allow the Sound Blaster mixer to modify the DOSBox mixer. +# oplmode: Type of OPL emulation. On 'auto' the mode is determined by 'sbtype'. +# All OPL modes are AdLib-compatible, except for 'cms'. +# Possible values: auto, cms, opl2, dualopl2, opl3, opl3gold, none. +# oplemu: Provider for the OPL emulation. 'compat' provides better quality, +# 'nuked' is the default and most accurate (but the most CPU-intensive). +# Possible values: default, compat, fast, mame, nuked. + +sbtype = sb16 +sbbase = 220 +irq = 7 +dma = 1 +hdma = 5 +sbmixer = true +oplmode = auto +oplemu = default + +[gus] +# gus: Enable Gravis UltraSound emulation. +# gusbase: The IO base address of the Gravis UltraSound. +# Possible values: 240, 220, 260, 280, 2a0, 2c0, 2e0, 300. +# gusirq: The IRQ number of the Gravis UltraSound. +# Possible values: 5, 3, 7, 9, 10, 11, 12. +# gusdma: The DMA channel of the Gravis UltraSound. +# Possible values: 3, 0, 1, 5, 6, 7. +# ultradir: Path to UltraSound directory. In this directory +# there should be a MIDI directory that contains +# the patch files for GUS playback. Patch sets used +# with Timidity should work fine. + +gus = false +gusbase = 240 +gusirq = 5 +gusdma = 3 +ultradir = C:\ULTRASND + +[innovation] +# sidmodel: Model of chip to emulate in the Innovation SSI-2001 card: +# - auto: Selects the 6581 chip. +# - 6581: The original chip, known for its bassy and rich character. +# - 8580: A later revision that more closely matched the SID specification. +# It fixed the 6581's DC bias and is less prone to distortion. +# The 8580 is an option on reproduction cards, like the DuoSID. +# - none: Disables the card. +# Possible values: auto, 6581, 8580, none. +# sidclock: The SID chip's clock frequency, which is jumperable on reproduction cards. +# - default: uses 0.895 MHz, per the original SSI-2001 card. +# - c64ntsc: uses 1.023 MHz, per NTSC Commodore PCs and the DuoSID. +# - c64pal: uses 0.985 MHz, per PAL Commodore PCs and the DuoSID. +# - hardsid: uses 1.000 MHz, available on the DuoSID. +# Possible values: default, c64ntsc, c64pal, hardsid. +# sidport: The IO port address of the Innovation SSI-2001. +# Possible values: 240, 260, 280, 2a0, 2c0. +# 6581filter: The SID's analog filtering meant that each chip was physically unique. +# Adjusts the 6581's filtering strength as a percent from 0 to 100. +# 8580filter: Adjusts the 8580's filtering strength as a percent from 0 to 100. + +sidmodel = none +sidclock = default +sidport = 280 +6581filter = 50 +8580filter = 50 + +[speaker] +# pcspeaker: Enable PC-Speaker emulation. +# pcrate: Sample rate of the PC-Speaker sound generation. +# zero_offset: Neutralizes and prevents the PC speaker's DC-offset from harming other sources. +# 'auto' enables this for non-Windows systems and disables it on Windows. +# If your OS performs its own DC-offset correction, then set this to 'false'. +# Possible values: auto, true, false. +# tandy: Enable Tandy Sound System emulation. For 'auto', emulation is present only if machine is set to 'tandy'. +# Possible values: auto, on, off. +# tandyrate: Sample rate of the Tandy 3-Voice generation. +# Possible values: 44100, 48000, 32000, 22050, 16000, 11025, 8000, 49716. +# disney: Enable Disney Sound Source emulation. (Covox Voice Master and Speech Thing compatible). +# ps1audio: Enable IBM PS/1 Audio emulation. + +pcspeaker = true +pcrate = 18939 +zero_offset = auto +tandy = auto +tandyrate = 44100 +disney = true +ps1audio = false + +[joystick] +# joysticktype: Type of joystick to emulate: auto (default), +# auto : Detect and use any joystick(s), if possible., +# 2axis : Support up to two joysticks. +# 4axis : Support the first joystick only. +# 4axis_2 : Support the second joystick only. +# fcs : Support a Thrustmaster-type joystick. +# ch : Support a CH Flightstick-type joystick. +# hidden : Prevent DOS from seeing the joystick(s), but enable them for mapping. +# disabled : Fully disable joysticks: won't be polled, mapped, or visible in DOS. +# (Remember to reset DOSBox's mapperfile if you saved it earlier) +# Possible values: auto, 2axis, 4axis, 4axis_2, fcs, ch, hidden, disabled. +# timed: enable timed intervals for axis. Experiment with this option, if your joystick drifts (away). +# autofire: continuously fires as long as you keep the button pressed. +# swap34: swap the 3rd and the 4th axis. Can be useful for certain joysticks. +# buttonwrap: enable button wrapping at the number of emulated buttons. +# circularinput: enable translation of circular input to square output. +# Try enabling this if your left analog stick can only move in a circle. +# deadzone: the percentage of motion to ignore. 100 turns the stick into a digital one. + +joysticktype = auto +timed = true +autofire = false +swap34 = false +buttonwrap = false +circularinput = false +deadzone = 10 + +[serial] +# serial1: set type of device connected to com port. +# Can be disabled, dummy, modem, nullmodem, directserial. +# Additional parameters must be in the same line in the form of +# parameter:value. Parameter for all types is irq (optional). +# for directserial: realport (required), rxdelay (optional). +# (realport:COM1 realport:ttyS0). +# for modem: listenport (optional). +# for nullmodem: server, rxdelay, txdelay, telnet, usedtr, +# transparent, port, inhsocket (all optional). +# Example: serial1=modem listenport:5000 +# Possible values: dummy, disabled, modem, nullmodem, directserial. +# serial2: see serial1 +# Possible values: dummy, disabled, modem, nullmodem, directserial. +# serial3: see serial1 +# Possible values: dummy, disabled, modem, nullmodem, directserial. +# serial4: see serial1 +# Possible values: dummy, disabled, modem, nullmodem, directserial. +# phonebookfile: File used to map fake phone numbers to addresses. + +serial1 = modem listenport:8192 +serial2 = dummy +serial3 = disabled +serial4 = disabled +phonebookfile = phonebook.txt + +[dos] +# xms: Enable XMS support. +# ems: Enable EMS support. The default (=true) provides the best +# compatibility but certain applications may run better with +# other choices, or require EMS support to be disabled (=false) +# to work at all. +# Possible values: true, emsboard, emm386, false. +# umb: Enable UMB support. +# ver: Set DOS version (5.0 by default). Specify as major.minor format. +# A single number is treated as the major version. +# Common settings are 3.3, 5.0, 6.22, and 7.1. +# keyboardlayout: Language code of the keyboard layout (or none). + +xms = true +ems = true +umb = true +ver = 5.0 +keyboardlayout = auto + +[ipx] +# ipx: Enable ipx over UDP/IP emulation. + +ipx = false + +[autoexec] +mount c /home/scott/dos +c: +autoexec.bat diff --git a/client/test.sh b/client/test.sh new file mode 100755 index 0000000..bf84970 --- /dev/null +++ b/client/test.sh @@ -0,0 +1,2 @@ +#!/bin/bash +${HOME}/code/dosbox-custom/dosbox-staging/build/dosbox -noprimaryconf -nolocalconf -conf test.conf & diff --git a/font/font.pro b/font/font.pro index fcb31c6..557d227 100644 --- a/font/font.pro +++ b/font/font.pro @@ -20,12 +20,17 @@ TEMPLATE = app CONFIG -= qt DESTDIR = $$OUT_PWD/bin +SHARED = $$PWD/../shared + +INCLUDEPATH += \ + $$SHARED \ + $$SHARED/thirdparty HEADERS = \ - src/stb_image.h \ - src/stb_leakcheck.h \ - src/stb.h + $$SHARED/stddclmr.h \ + $$SHARED/thirdparty/memwatch/memwatch.h \ + $$SHARED/thirdparty/stb_image.h SOURCES = \ - src/stb.c \ + $$SHARED/thirdparty/memwatch/memwatch.c \ src/main.c diff --git a/font/src/main.c b/font/src/main.c index e19e19b..826b91e 100644 --- a/font/src/main.c +++ b/font/src/main.c @@ -20,7 +20,14 @@ #include -#include "stb.h" +#define MEMWATCH +#include "memwatch/memwatch.h" + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_PNG +#include "stb_image.h" + +#include "stddclmr.h" void makeFont(char *source, char *target, int pixelsW, int pixelsH, int charsW, int charCount) { @@ -79,8 +86,5 @@ int main(int argc, char *argv[]) { makeFont("/home/scott/code/kpmpgsmkii/font/data/vga8x14.png", "/home/scott/code/kpmpgsmkii/client/bin/vga8x14.dat", 8, 14, 16, 255); makeFont("/home/scott/code/kpmpgsmkii/font/data/vga8x16.png", "/home/scott/code/kpmpgsmkii/client/bin/vga8x16.dat", 8, 16, 16, 255); - // Prove we cleaned up. - stb_leakcheck_dumpmem(); - return 0; } diff --git a/font/src/stb.c b/font/src/stb.c deleted file mode 100644 index 54d9dc2..0000000 --- a/font/src/stb.c +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Kangaroo Punch MultiPlayer Game Server Mark II - * Copyright (C) 2020-2021 Scott Duensing - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - - -#define STB_IMAGE_IMPLEMENTATION -#define STBI_ONLY_PNG -#include "stb_image.h" - -#define STB_LEAKCHECK_IMPLEMENTATION -#include "stb_leakcheck.h" diff --git a/font/src/stb_image.h b/font/src/stb_image.h deleted file mode 100644 index d60371b..0000000 --- a/font/src/stb_image.h +++ /dev/null @@ -1,7897 +0,0 @@ -/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb - no warranty implied; use at your own risk - - Do this: - #define STB_IMAGE_IMPLEMENTATION - before you include this file in *one* C or C++ file to create the implementation. - - // i.e. it should look like this: - #include ... - #include ... - #include ... - #define STB_IMAGE_IMPLEMENTATION - #include "stb_image.h" - - You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. - And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free - - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) - PNG 1/2/4/8/16-bit-per-channel - - TGA (not sure what subset, if a subset) - BMP non-1bpp, non-RLE - PSD (composited view only, no extra channels, 8/16 bit-per-channel) - - GIF (*comp always reports as 4-channel) - HDR (radiance rgbE format) - PIC (Softimage PIC) - PNM (PPM and PGM binary only) - - Animated GIF still needs a proper API, but here's one way to do it: - http://gist.github.com/urraka/685d9a6340b26b830d49 - - - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - - decode from arbitrary I/O callbacks - - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) - - Full documentation under "DOCUMENTATION" below. - - -LICENSE - - See end of file for license information. - -RECENT REVISION HISTORY: - - 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes - 2.26 (2020-07-13) many minor fixes - 2.25 (2020-02-02) fix warnings - 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically - 2.23 (2019-08-11) fix clang static analysis warning - 2.22 (2019-03-04) gif fixes, fix warnings - 2.21 (2019-02-25) fix typo in comment - 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings - 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes - 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 - RGB-format JPEG; remove white matting in PSD; - allocate large structures on the stack; - correct channel count for PNG & BMP - 2.10 (2016-01-22) avoid warning introduced in 2.09 - 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED - - See end of file for full revision history. - - - ============================ Contributors ========================= - - Image formats Extensions, features - Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) - Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) - Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) - Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) - Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) - Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) - Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) - github:urraka (animated gif) Junggon Kim (PNM comments) - Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) - socks-the-fox (16-bit PNG) - Jeremy Sawicki (handle all ImageNet JPGs) - Optimizations & bugfixes Mikhail Morozov (1-bit BMP) - Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) - Arseny Kapoulkine Simon Breuss (16-bit PNM) - John-Mark Allen - Carmelo J Fdez-Aguera - - Bug & warning fixes - Marc LeBlanc David Woo Guillaume George Martins Mozeiko - Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski - Phil Jordan Dave Moore Roy Eltham - Hayaki Saito Nathan Reed Won Chun - Luke Graham Johan Duparc Nick Verigakis the Horde3D community - Thomas Ruf Ronny Chevalier github:rlyeh - Janez Zemva John Bartholomew Michal Cichon github:romigrou - Jonathan Blow Ken Hamada Tero Hanninen github:svdijk - Eugene Golushkov Laurent Gomila Cort Stratton github:snagar - Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex - Cass Everitt Ryamond Barbiero github:grim210 - Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw - Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus - Josh Tobin Matthew Gregan github:poppolopoppo - Julian Raschke Gregory Mullen Christian Floisand github:darealshinji - Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 - Brad Weinberger Matvey Cherevko github:mosra - Luca Sas Alexander Veselov Zack Middleton [reserved] - Ryan C. Gordon [reserved] [reserved] - DO NOT ADD YOUR NAME HERE - - Jacko Dirks - - To add your name to the credits, pick a random blank space in the middle and fill it. - 80% of merge conflicts on stb PRs are due to people adding their name at the end - of the credits. -*/ - -#ifndef STBI_INCLUDE_STB_IMAGE_H -#define STBI_INCLUDE_STB_IMAGE_H - -// DOCUMENTATION -// -// Limitations: -// - no 12-bit-per-channel JPEG -// - no JPEGs with arithmetic coding -// - GIF always returns *comp=4 -// -// Basic usage (see HDR discussion below for HDR usage): -// int x,y,n; -// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... -// // ... x = width, y = height, n = # 8-bit components per pixel ... -// // ... replace '0' with '1'..'4' to force that many components per pixel -// // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) -// -// Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *channels_in_file -- outputs # of image components in image file -// int desired_channels -- if non-zero, # of image components requested in result -// -// The return value from an image loader is an 'unsigned char *' which points -// to the pixel data, or NULL on an allocation failure or if the image is -// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, -// with each pixel consisting of N interleaved 8-bit components; the first -// pixel pointed to is top-left-most in the image. There is no padding between -// image scanlines or between pixels, regardless of format. The number of -// components N is 'desired_channels' if desired_channels is non-zero, or -// *channels_in_file otherwise. If desired_channels is non-zero, -// *channels_in_file has the number of components that _would_ have been -// output otherwise. E.g. if you set desired_channels to 4, you will always -// get RGBA output, but you can check *channels_in_file to see if it's trivially -// opaque because e.g. there were only 3 channels in the source image. -// -// An output image with N components has the following components interleaved -// in this order in each pixel: -// -// N=#comp components -// 1 grey -// 2 grey, alpha -// 3 red, green, blue -// 4 red, green, blue, alpha -// -// If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *channels_in_file will be unchanged. The function -// stbi_failure_reason() can be queried for an extremely brief, end-user -// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS -// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly -// more user-friendly ones. -// -// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. -// -// To query the width, height and component count of an image without having to -// decode the full file, you can use the stbi_info family of functions: -// -// int x,y,n,ok; -// ok = stbi_info(filename, &x, &y, &n); -// // returns ok=1 and sets x, y, n if image is a supported format, -// // 0 otherwise. -// -// Note that stb_image pervasively uses ints in its public API for sizes, -// including sizes of memory buffers. This is now part of the API and thus -// hard to change without causing breakage. As a result, the various image -// loaders all have certain limits on image size; these differ somewhat -// by format but generally boil down to either just under 2GB or just under -// 1GB. When the decoded image would be larger than this, stb_image decoding -// will fail. -// -// Additionally, stb_image will reject image files that have any of their -// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, -// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, -// the only way to have an image with such dimensions load correctly -// is for it to have a rather extreme aspect ratio. Either way, the -// assumption here is that such larger images are likely to be malformed -// or malicious. If you do need to load an image with individual dimensions -// larger than that, and it still fits in the overall size limit, you can -// #define STBI_MAX_DIMENSIONS on your own to be something larger. -// -// =========================================================================== -// -// UNICODE: -// -// If compiling for Windows and you wish to use Unicode filenames, compile -// with -// #define STBI_WINDOWS_UTF8 -// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert -// Windows wchar_t filenames to utf8. -// -// =========================================================================== -// -// Philosophy -// -// stb libraries are designed with the following priorities: -// -// 1. easy to use -// 2. easy to maintain -// 3. good performance -// -// Sometimes I let "good performance" creep up in priority over "easy to maintain", -// and for best performance I may provide less-easy-to-use APIs that give higher -// performance, in addition to the easy-to-use ones. Nevertheless, it's important -// to keep in mind that from the standpoint of you, a client of this library, -// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. -// -// Some secondary priorities arise directly from the first two, some of which -// provide more explicit reasons why performance can't be emphasized. -// -// - Portable ("ease of use") -// - Small source code footprint ("easy to maintain") -// - No dependencies ("ease of use") -// -// =========================================================================== -// -// I/O callbacks -// -// I/O callbacks allow you to read from arbitrary sources, like packaged -// files or some other source. Data read from callbacks are processed -// through a small internal buffer (currently 128 bytes) to try to reduce -// overhead. -// -// The three functions you must define are "read" (reads some bytes of data), -// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). -// -// =========================================================================== -// -// SIMD support -// -// The JPEG decoder will try to automatically use SIMD kernels on x86 when -// supported by the compiler. For ARM Neon support, you must explicitly -// request it. -// -// (The old do-it-yourself SIMD API is no longer supported in the current -// code.) -// -// On x86, SSE2 will automatically be used when available based on a run-time -// test; if not, the generic C versions are used as a fall-back. On ARM targets, -// the typical path is to have separate builds for NEON and non-NEON devices -// (at least this is true for iOS and Android). Therefore, the NEON support is -// toggled by a build flag: define STBI_NEON to get NEON loops. -// -// If for some reason you do not want to use any of SIMD code, or if -// you have issues compiling it, you can disable it entirely by -// defining STBI_NO_SIMD. -// -// =========================================================================== -// -// HDR image support (disable by defining STBI_NO_HDR) -// -// stb_image supports loading HDR images in general, and currently the Radiance -// .HDR file format specifically. You can still load any file through the existing -// interface; if you attempt to load an HDR file, it will be automatically remapped -// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; -// both of these constants can be reconfigured through this interface: -// -// stbi_hdr_to_ldr_gamma(2.2f); -// stbi_hdr_to_ldr_scale(1.0f); -// -// (note, do not use _inverse_ constants; stbi_image will invert them -// appropriately). -// -// Additionally, there is a new, parallel interface for loading files as -// (linear) floats to preserve the full dynamic range: -// -// float *data = stbi_loadf(filename, &x, &y, &n, 0); -// -// If you load LDR images through this interface, those images will -// be promoted to floating point values, run through the inverse of -// constants corresponding to the above: -// -// stbi_ldr_to_hdr_scale(1.0f); -// stbi_ldr_to_hdr_gamma(2.2f); -// -// Finally, given a filename (or an open file or memory block--see header -// file for details) containing image data, you can query for the "most -// appropriate" interface to use (that is, whether the image is HDR or -// not), using: -// -// stbi_is_hdr(char *filename); -// -// =========================================================================== -// -// iPhone PNG support: -// -// We optionally support converting iPhone-formatted PNGs (which store -// premultiplied BGRA) back to RGB, even though they're internally encoded -// differently. To enable this conversion, call -// stbi_convert_iphone_png_to_rgb(1). -// -// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per -// pixel to remove any premultiplied alpha *only* if the image file explicitly -// says there's premultiplied data (currently only happens in iPhone images, -// and only if iPhone convert-to-rgb processing is on). -// -// =========================================================================== -// -// ADDITIONAL CONFIGURATION -// -// - You can suppress implementation of any of the decoders to reduce -// your code footprint by #defining one or more of the following -// symbols before creating the implementation. -// -// STBI_NO_JPEG -// STBI_NO_PNG -// STBI_NO_BMP -// STBI_NO_PSD -// STBI_NO_TGA -// STBI_NO_GIF -// STBI_NO_HDR -// STBI_NO_PIC -// STBI_NO_PNM (.ppm and .pgm) -// -// - You can request *only* certain decoders and suppress all other ones -// (this will be more forward-compatible, as addition of new decoders -// doesn't require you to disable them explicitly): -// -// STBI_ONLY_JPEG -// STBI_ONLY_PNG -// STBI_ONLY_BMP -// STBI_ONLY_PSD -// STBI_ONLY_TGA -// STBI_ONLY_GIF -// STBI_ONLY_HDR -// STBI_ONLY_PIC -// STBI_ONLY_PNM (.ppm and .pgm) -// -// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still -// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB -// -// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater -// than that size (in either width or height) without further processing. -// This is to let programs in the wild set an upper bound to prevent -// denial-of-service attacks on untrusted data, as one could generate a -// valid image of gigantic dimensions and force stb_image to allocate a -// huge block of memory and spend disproportionate time decoding it. By -// default this is set to (1 << 24), which is 16777216, but that's still -// very big. - -#ifndef STBI_NO_STDIO -#include -#endif // STBI_NO_STDIO - -#define STBI_VERSION 1 - -enum -{ - STBI_default = 0, // only used for desired_channels - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4 -}; - -#include -typedef unsigned char stbi_uc; -typedef unsigned short stbi_us; - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef STBIDEF -#ifdef STB_IMAGE_STATIC -#define STBIDEF static -#else -#define STBIDEF extern -#endif -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// PRIMARY API - works on images of any type -// - -// -// load image by filename, open file, or memory buffer -// - -typedef struct -{ - int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read - void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative - int (*eof) (void *user); // returns nonzero if we are at end of file/data -} stbi_io_callbacks; - -//////////////////////////////////// -// -// 8-bits-per-channel interface -// - -STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -// for stbi_load_from_file, file pointer is left pointing immediately after image -#endif - -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -#endif - -#ifdef STBI_WINDOWS_UTF8 -STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); -#endif - -//////////////////////////////////// -// -// 16-bits-per-channel interface -// - -STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -#endif - -//////////////////////////////////// -// -// float-per-channel interface -// -#ifndef STBI_NO_LINEAR - STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - - #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); - #endif -#endif - -#ifndef STBI_NO_HDR - STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); - STBIDEF void stbi_hdr_to_ldr_scale(float scale); -#endif // STBI_NO_HDR - -#ifndef STBI_NO_LINEAR - STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); - STBIDEF void stbi_ldr_to_hdr_scale(float scale); -#endif // STBI_NO_LINEAR - -// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename); -STBIDEF int stbi_is_hdr_from_file(FILE *f); -#endif // STBI_NO_STDIO - - -// get a VERY brief reason for failure -// on most compilers (and ALL modern mainstream compilers) this is threadsafe -STBIDEF const char *stbi_failure_reason (void); - -// free the loaded image -- this is just free() -STBIDEF void stbi_image_free (void *retval_from_stbi_load); - -// get image dimensions & components without fully decoding -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit (char const *filename); -STBIDEF int stbi_is_16_bit_from_file(FILE *f); -#endif - - - -// for image formats that explicitly notate that they have premultiplied alpha, -// we just return the colors as stored in the file. set this flag to force -// unpremultiplication. results are undefined if the unpremultiply overflow. -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); - -// indicate whether we should process iphone images back to canonical format, -// or just pass them through "as-is" -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); - -// flip the image vertically, so the first pixel in the output array is the bottom left -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); - -// as above, but only applies to images loaded on the thread that calls the function -// this function is only available if your compiler supports thread-local variables; -// calling it will fail to link if your compiler doesn't -STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); -STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); -STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); - -// ZLIB client - used by PNG, available for other purposes - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); -STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - - -#ifdef __cplusplus -} -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // STBI_INCLUDE_STB_IMAGE_H - -#ifdef STB_IMAGE_IMPLEMENTATION - -#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ - || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ - || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ - || defined(STBI_ONLY_ZLIB) - #ifndef STBI_ONLY_JPEG - #define STBI_NO_JPEG - #endif - #ifndef STBI_ONLY_PNG - #define STBI_NO_PNG - #endif - #ifndef STBI_ONLY_BMP - #define STBI_NO_BMP - #endif - #ifndef STBI_ONLY_PSD - #define STBI_NO_PSD - #endif - #ifndef STBI_ONLY_TGA - #define STBI_NO_TGA - #endif - #ifndef STBI_ONLY_GIF - #define STBI_NO_GIF - #endif - #ifndef STBI_ONLY_HDR - #define STBI_NO_HDR - #endif - #ifndef STBI_ONLY_PIC - #define STBI_NO_PIC - #endif - #ifndef STBI_ONLY_PNM - #define STBI_NO_PNM - #endif -#endif - -#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) -#define STBI_NO_ZLIB -#endif - - -#include -#include // ptrdiff_t on osx -#include -#include -#include - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -#include // ldexp, pow -#endif - -#ifndef STBI_NO_STDIO -#include -#endif - -#ifndef STBI_ASSERT -#include -#define STBI_ASSERT(x) assert(x) -#endif - -#ifdef __cplusplus -#define STBI_EXTERN extern "C" -#else -#define STBI_EXTERN extern -#endif - - -#ifndef _MSC_VER - #ifdef __cplusplus - #define stbi_inline inline - #else - #define stbi_inline - #endif -#else - #define stbi_inline __forceinline -#endif - -#ifndef STBI_NO_THREAD_LOCALS - #if defined(__cplusplus) && __cplusplus >= 201103L - #define STBI_THREAD_LOCAL thread_local - #elif defined(__GNUC__) && __GNUC__ < 5 - #define STBI_THREAD_LOCAL __thread - #elif defined(_MSC_VER) - #define STBI_THREAD_LOCAL __declspec(thread) - #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) - #define STBI_THREAD_LOCAL _Thread_local - #endif - - #ifndef STBI_THREAD_LOCAL - #if defined(__GNUC__) - #define STBI_THREAD_LOCAL __thread - #endif - #endif -#endif - -#ifdef _MSC_VER -typedef unsigned short stbi__uint16; -typedef signed short stbi__int16; -typedef unsigned int stbi__uint32; -typedef signed int stbi__int32; -#else -#include -typedef uint16_t stbi__uint16; -typedef int16_t stbi__int16; -typedef uint32_t stbi__uint32; -typedef int32_t stbi__int32; -#endif - -// should produce compiler error if size is wrong -typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; - -#ifdef _MSC_VER -#define STBI_NOTUSED(v) (void)(v) -#else -#define STBI_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef _MSC_VER -#define STBI_HAS_LROTL -#endif - -#ifdef STBI_HAS_LROTL - #define stbi_lrot(x,y) _lrotl(x,y) -#else - #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) -#endif - -#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) -// ok -#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." -#endif - -#ifndef STBI_MALLOC -#define STBI_MALLOC(sz) malloc(sz) -#define STBI_REALLOC(p,newsz) realloc(p,newsz) -#define STBI_FREE(p) free(p) -#endif - -#ifndef STBI_REALLOC_SIZED -#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) -#endif - -// x86/x64 detection -#if defined(__x86_64__) || defined(_M_X64) -#define STBI__X64_TARGET -#elif defined(__i386) || defined(_M_IX86) -#define STBI__X86_TARGET -#endif - -#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) -// gcc doesn't support sse2 intrinsics unless you compile with -msse2, -// which in turn means it gets to use SSE2 everywhere. This is unfortunate, -// but previous attempts to provide the SSE2 functions with runtime -// detection caused numerous issues. The way architecture extensions are -// exposed in GCC/Clang is, sadly, not really suited for one-file libs. -// New behavior: if compiled with -msse2, we use SSE2 without any -// detection; if not, we don't use it at all. -#define STBI_NO_SIMD -#endif - -#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) -// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET -// -// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the -// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. -// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not -// simultaneously enabling "-mstackrealign". -// -// See https://github.com/nothings/stb/issues/81 for more information. -// -// So default to no SSE2 on 32-bit MinGW. If you've read this far and added -// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. -#define STBI_NO_SIMD -#endif - -#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) -#define STBI_SSE2 -#include - -#ifdef _MSC_VER - -#if _MSC_VER >= 1400 // not VC6 -#include // __cpuid -static int stbi__cpuid3(void) -{ - int info[4]; - __cpuid(info,1); - return info[3]; -} -#else -static int stbi__cpuid3(void) -{ - int res; - __asm { - mov eax,1 - cpuid - mov res,edx - } - return res; -} -#endif - -#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name - -#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) -static int stbi__sse2_available(void) -{ - int info3 = stbi__cpuid3(); - return ((info3 >> 26) & 1) != 0; -} -#endif - -#else // assume GCC-style if not VC++ -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) - -#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) -static int stbi__sse2_available(void) -{ - // If we're even attempting to compile this on GCC/Clang, that means - // -msse2 is on, which means the compiler is allowed to use SSE2 - // instructions at will, and so are we. - return 1; -} -#endif - -#endif -#endif - -// ARM NEON -#if defined(STBI_NO_SIMD) && defined(STBI_NEON) -#undef STBI_NEON -#endif - -#ifdef STBI_NEON -#include -#ifdef _MSC_VER -#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name -#else -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) -#endif -#endif - -#ifndef STBI_SIMD_ALIGN -#define STBI_SIMD_ALIGN(type, name) type name -#endif - -#ifndef STBI_MAX_DIMENSIONS -#define STBI_MAX_DIMENSIONS (1 << 24) -#endif - -/////////////////////////////////////////////// -// -// stbi__context struct and start_xxx functions - -// stbi__context structure is our basic context used by all images, so it -// contains all the IO context, plus some basic image information -typedef struct -{ - stbi__uint32 img_x, img_y; - int img_n, img_out_n; - - stbi_io_callbacks io; - void *io_user_data; - - int read_from_callbacks; - int buflen; - stbi_uc buffer_start[128]; - int callback_already_read; - - stbi_uc *img_buffer, *img_buffer_end; - stbi_uc *img_buffer_original, *img_buffer_original_end; -} stbi__context; - - -static void stbi__refill_buffer(stbi__context *s); - -// initialize a memory-decode context -static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) -{ - s->io.read = NULL; - s->read_from_callbacks = 0; - s->callback_already_read = 0; - s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; - s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; -} - -// initialize a callback-based context -static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) -{ - s->io = *c; - s->io_user_data = user; - s->buflen = sizeof(s->buffer_start); - s->read_from_callbacks = 1; - s->callback_already_read = 0; - s->img_buffer = s->img_buffer_original = s->buffer_start; - stbi__refill_buffer(s); - s->img_buffer_original_end = s->img_buffer_end; -} - -#ifndef STBI_NO_STDIO - -static int stbi__stdio_read(void *user, char *data, int size) -{ - return (int) fread(data,1,size,(FILE*) user); -} - -static void stbi__stdio_skip(void *user, int n) -{ - int ch; - fseek((FILE*) user, n, SEEK_CUR); - ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ - if (ch != EOF) { - ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ - } -} - -static int stbi__stdio_eof(void *user) -{ - return feof((FILE*) user) || ferror((FILE *) user); -} - -static stbi_io_callbacks stbi__stdio_callbacks = -{ - stbi__stdio_read, - stbi__stdio_skip, - stbi__stdio_eof, -}; - -static void stbi__start_file(stbi__context *s, FILE *f) -{ - stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); -} - -//static void stop_file(stbi__context *s) { } - -#endif // !STBI_NO_STDIO - -static void stbi__rewind(stbi__context *s) -{ - // conceptually rewind SHOULD rewind to the beginning of the stream, - // but we just rewind to the beginning of the initial buffer, because - // we only use it after doing 'test', which only ever looks at at most 92 bytes - s->img_buffer = s->img_buffer_original; - s->img_buffer_end = s->img_buffer_original_end; -} - -enum -{ - STBI_ORDER_RGB, - STBI_ORDER_BGR -}; - -typedef struct -{ - int bits_per_channel; - int num_channels; - int channel_order; -} stbi__result_info; - -#ifndef STBI_NO_JPEG -static int stbi__jpeg_test(stbi__context *s); -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNG -static int stbi__png_test(stbi__context *s); -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__png_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_BMP -static int stbi__bmp_test(stbi__context *s); -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_TGA -static int stbi__tga_test(stbi__context *s); -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s); -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__psd_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_HDR -static int stbi__hdr_test(stbi__context *s); -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_test(stbi__context *s); -static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_GIF -static int stbi__gif_test(stbi__context *s); -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNM -static int stbi__pnm_test(stbi__context *s); -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__pnm_is16(stbi__context *s); -#endif - -static -#ifdef STBI_THREAD_LOCAL -STBI_THREAD_LOCAL -#endif -const char *stbi__g_failure_reason; - -STBIDEF const char *stbi_failure_reason(void) -{ - return stbi__g_failure_reason; -} - -#ifndef STBI_NO_FAILURE_STRINGS -static int stbi__err(const char *str) -{ - stbi__g_failure_reason = str; - return 0; -} -#endif - -static void *stbi__malloc(size_t size) -{ - return STBI_MALLOC(size); -} - -// stb_image uses ints pervasively, including for offset calculations. -// therefore the largest decoded image size we can support with the -// current code, even on 64-bit targets, is INT_MAX. this is not a -// significant limitation for the intended use case. -// -// we do, however, need to make sure our size calculations don't -// overflow. hence a few helper functions for size calculations that -// multiply integers together, making sure that they're non-negative -// and no overflow occurs. - -// return 1 if the sum is valid, 0 on overflow. -// negative terms are considered invalid. -static int stbi__addsizes_valid(int a, int b) -{ - if (b < 0) return 0; - // now 0 <= b <= INT_MAX, hence also - // 0 <= INT_MAX - b <= INTMAX. - // And "a + b <= INT_MAX" (which might overflow) is the - // same as a <= INT_MAX - b (no overflow) - return a <= INT_MAX - b; -} - -// returns 1 if the product is valid, 0 on overflow. -// negative factors are considered invalid. -static int stbi__mul2sizes_valid(int a, int b) -{ - if (a < 0 || b < 0) return 0; - if (b == 0) return 1; // mul-by-0 is always safe - // portable way to check for no overflows in a*b - return a <= INT_MAX/b; -} - -#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) -// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow -static int stbi__mad2sizes_valid(int a, int b, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); -} -#endif - -// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow -static int stbi__mad3sizes_valid(int a, int b, int c, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__addsizes_valid(a*b*c, add); -} - -// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) -static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); -} -#endif - -#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) -// mallocs with size overflow checking -static void *stbi__malloc_mad2(int a, int b, int add) -{ - if (!stbi__mad2sizes_valid(a, b, add)) return NULL; - return stbi__malloc(a*b + add); -} -#endif - -static void *stbi__malloc_mad3(int a, int b, int c, int add) -{ - if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; - return stbi__malloc(a*b*c + add); -} - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) -static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) -{ - if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; - return stbi__malloc(a*b*c*d + add); -} -#endif - -// stbi__err - error -// stbi__errpf - error returning pointer to float -// stbi__errpuc - error returning pointer to unsigned char - -#ifdef STBI_NO_FAILURE_STRINGS - #define stbi__err(x,y) 0 -#elif defined(STBI_FAILURE_USERMSG) - #define stbi__err(x,y) stbi__err(y) -#else - #define stbi__err(x,y) stbi__err(x) -#endif - -#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) -#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) - -STBIDEF void stbi_image_free(void *retval_from_stbi_load) -{ - STBI_FREE(retval_from_stbi_load); -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); -#endif - -#ifndef STBI_NO_HDR -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); -#endif - -static int stbi__vertically_flip_on_load_global = 0; - -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load_global = flag_true_if_should_flip; -} - -#ifndef STBI_THREAD_LOCAL -#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global -#else -static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; - -STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load_local = flag_true_if_should_flip; - stbi__vertically_flip_on_load_set = 1; -} - -#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ - ? stbi__vertically_flip_on_load_local \ - : stbi__vertically_flip_on_load_global) -#endif // STBI_THREAD_LOCAL - -static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields - ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed - ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order - ri->num_channels = 0; - - // test the formats with a very explicit header first (at least a FOURCC - // or distinctive magic number first) - #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_BMP - if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_GIF - if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PSD - if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); - #else - STBI_NOTUSED(bpc); - #endif - #ifndef STBI_NO_PIC - if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); - #endif - - // then the formats that can end up attempting to load with just 1 or 2 - // bytes matching expectations; these are prone to false positives, so - // try them later - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNM - if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); - return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - - #ifndef STBI_NO_TGA - // test tga last because it's a crappy test! - if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp, ri); - #endif - - return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); -} - -static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi_uc *reduced; - - reduced = (stbi_uc *) stbi__malloc(img_len); - if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling - - STBI_FREE(orig); - return reduced; -} - -static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi__uint16 *enlarged; - - enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); - if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff - - STBI_FREE(orig); - return enlarged; -} - -static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) -{ - int row; - size_t bytes_per_row = (size_t)w * bytes_per_pixel; - stbi_uc temp[2048]; - stbi_uc *bytes = (stbi_uc *)image; - - for (row = 0; row < (h>>1); row++) { - stbi_uc *row0 = bytes + row*bytes_per_row; - stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; - // swap row0 with row1 - size_t bytes_left = bytes_per_row; - while (bytes_left) { - size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); - memcpy(temp, row0, bytes_copy); - memcpy(row0, row1, bytes_copy); - memcpy(row1, temp, bytes_copy); - row0 += bytes_copy; - row1 += bytes_copy; - bytes_left -= bytes_copy; - } - } -} - -#ifndef STBI_NO_GIF -static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) -{ - int slice; - int slice_size = w * h * bytes_per_pixel; - - stbi_uc *bytes = (stbi_uc *)image; - for (slice = 0; slice < z; ++slice) { - stbi__vertical_flip(bytes, w, h, bytes_per_pixel); - bytes += slice_size; - } -} -#endif - -static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); - - if (result == NULL) - return NULL; - - // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. - STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); - - if (ri.bits_per_channel != 8) { - result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 8; - } - - // @TODO: move stbi__convert_format to here - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); - } - - return (unsigned char *) result; -} - -static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); - - if (result == NULL) - return NULL; - - // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. - STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); - - if (ri.bits_per_channel != 16) { - result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 16; - } - - // @TODO: move stbi__convert_format16 to here - // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); - } - - return (stbi__uint16 *) result; -} - -#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) -static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) -{ - if (stbi__vertically_flip_on_load && result != NULL) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); - } -} -#endif - -#ifndef STBI_NO_STDIO - -#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) -STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); -STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); -#endif - -#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) -STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) -{ - return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); -} -#endif - -static FILE *stbi__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) - wchar_t wMode[64]; - wchar_t wFilename[1024]; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) - return 0; - - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) - return 0; - -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != _wfopen_s(&f, wFilename, wMode)) - f = 0; -#else - f = _wfopen(wFilename, wMode); -#endif - -#elif defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - - -STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - unsigned char *result; - if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__uint16 *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - stbi__uint16 *result; - if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file_16(f,x,y,comp,req_comp); - fclose(f); - return result; -} - - -#endif //!STBI_NO_STDIO - -STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_mem(&s,buffer,len); - - result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); - if (stbi__vertically_flip_on_load) { - stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); - } - - return result; -} -#endif - -#ifndef STBI_NO_LINEAR -static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - stbi__result_info ri; - float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); - if (hdr_data) - stbi__float_postprocess(hdr_data,x,y,comp,req_comp); - return hdr_data; - } - #endif - data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); - if (data) - return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); -} - -STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - float *result; - FILE *f = stbi__fopen(filename, "rb"); - if (!f) return stbi__errpf("can't fopen", "Unable to open file"); - result = stbi_loadf_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} -#endif // !STBI_NO_STDIO - -#endif // !STBI_NO_LINEAR - -// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is -// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always -// reports false! - -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(buffer); - STBI_NOTUSED(len); - return 0; - #endif -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result=0; - if (f) { - result = stbi_is_hdr_from_file(f); - fclose(f); - } - return result; -} - -STBIDEF int stbi_is_hdr_from_file(FILE *f) -{ - #ifndef STBI_NO_HDR - long pos = ftell(f); - int res; - stbi__context s; - stbi__start_file(&s,f); - res = stbi__hdr_test(&s); - fseek(f, pos, SEEK_SET); - return res; - #else - STBI_NOTUSED(f); - return 0; - #endif -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(clbk); - STBI_NOTUSED(user); - return 0; - #endif -} - -#ifndef STBI_NO_LINEAR -static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; - -STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } -STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } -#endif - -static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; - -STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } -STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } - - -////////////////////////////////////////////////////////////////////////////// -// -// Common code used by all image loaders -// - -enum -{ - STBI__SCAN_load=0, - STBI__SCAN_type, - STBI__SCAN_header -}; - -static void stbi__refill_buffer(stbi__context *s) -{ - int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); - s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); - if (n == 0) { - // at end of file, treat same as if from memory, but need to handle case - // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file - s->read_from_callbacks = 0; - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start+1; - *s->img_buffer = 0; - } else { - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start + n; - } -} - -stbi_inline static stbi_uc stbi__get8(stbi__context *s) -{ - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - if (s->read_from_callbacks) { - stbi__refill_buffer(s); - return *s->img_buffer++; - } - return 0; -} - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else -stbi_inline static int stbi__at_eof(stbi__context *s) -{ - if (s->io.read) { - if (!(s->io.eof)(s->io_user_data)) return 0; - // if feof() is true, check if buffer = end - // special case: we've only got the special 0 character at the end - if (s->read_from_callbacks == 0) return 1; - } - - return s->img_buffer >= s->img_buffer_end; -} -#endif - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) -// nothing -#else -static void stbi__skip(stbi__context *s, int n) -{ - if (n == 0) return; // already there! - if (n < 0) { - s->img_buffer = s->img_buffer_end; - return; - } - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - s->img_buffer = s->img_buffer_end; - (s->io.skip)(s->io_user_data, n - blen); - return; - } - } - s->img_buffer += n; -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) -// nothing -#else -static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) -{ - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - int res, count; - - memcpy(buffer, s->img_buffer, blen); - - count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); - res = (count == (n-blen)); - s->img_buffer = s->img_buffer_end; - return res; - } - } - - if (s->img_buffer+n <= s->img_buffer_end) { - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; - return 1; - } else - return 0; -} -#endif - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) -// nothing -#else -static int stbi__get16be(stbi__context *s) -{ - int z = stbi__get8(s); - return (z << 8) + stbi__get8(s); -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) -// nothing -#else -static stbi__uint32 stbi__get32be(stbi__context *s) -{ - stbi__uint32 z = stbi__get16be(s); - return (z << 16) + stbi__get16be(s); -} -#endif - -#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) -// nothing -#else -static int stbi__get16le(stbi__context *s) -{ - int z = stbi__get8(s); - return z + (stbi__get8(s) << 8); -} -#endif - -#ifndef STBI_NO_BMP -static stbi__uint32 stbi__get32le(stbi__context *s) -{ - stbi__uint32 z = stbi__get16le(s); - z += (stbi__uint32)stbi__get16le(s) << 16; - return z; -} -#endif - -#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings - -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else -////////////////////////////////////////////////////////////////////////////// -// -// generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (e.g. jpeg -// does all cases internally since it needs to colorspace convert anyway, -// and it never has alpha, so very few cases ). png can automatically -// interleave an alpha=255 channel, but falls back to this for other cases -// -// assume data buffer is malloced, so malloc a new one and free that one -// only failure mode is malloc failing - -static stbi_uc stbi__compute_y(int r, int g, int b) -{ - return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else -static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - unsigned char *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); - if (good == NULL) { - STBI_FREE(data); - return stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - unsigned char *src = data + j * x * img_n ; - unsigned char *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; - default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) -// nothing -#else -static stbi__uint16 stbi__compute_y_16(int r, int g, int b) -{ - return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) -// nothing -#else -static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - stbi__uint16 *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); - if (good == NULL) { - STBI_FREE(data); - return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - stbi__uint16 *src = data + j * x * img_n ; - stbi__uint16 *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; - default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} -#endif - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) -{ - int i,k,n; - float *output; - if (!data) return NULL; - output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); - } - } - if (n < comp) { - for (i=0; i < x*y; ++i) { - output[i*comp + n] = data[i*comp + n]/255.0f; - } - } - STBI_FREE(data); - return output; -} -#endif - -#ifndef STBI_NO_HDR -#define stbi__float2int(x) ((int) (x)) -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) -{ - int i,k,n; - stbi_uc *output; - if (!data) return NULL; - output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - if (k < comp) { - float z = data[i*comp+k] * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - } - STBI_FREE(data); - return output; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// "baseline" JPEG/JFIF decoder -// -// simple implementation -// - doesn't support delayed output of y-dimension -// - simple interface (only one output format: 8-bit interleaved RGB) -// - doesn't try to recover corrupt jpegs -// - doesn't allow partial loading, loading multiple at once -// - still fast on x86 (copying globals into locals doesn't help x86) -// - allocates lots of intermediate memory (full size of all components) -// - non-interleaved case requires this anyway -// - allows good upsampling (see next) -// high-quality -// - upsampled channels are bilinearly interpolated, even across blocks -// - quality integer IDCT derived from IJG's 'slow' -// performance -// - fast huffman; reasonable integer IDCT -// - some SIMD kernels for common paths on targets with SSE2/NEON -// - uses a lot of intermediate memory, could cache poorly - -#ifndef STBI_NO_JPEG - -// huffman decoding acceleration -#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache - -typedef struct -{ - stbi_uc fast[1 << FAST_BITS]; - // weirdly, repacking this into AoS is a 10% speed loss, instead of a win - stbi__uint16 code[256]; - stbi_uc values[256]; - stbi_uc size[257]; - unsigned int maxcode[18]; - int delta[17]; // old 'firstsymbol' - old 'firstcode' -} stbi__huffman; - -typedef struct -{ - stbi__context *s; - stbi__huffman huff_dc[4]; - stbi__huffman huff_ac[4]; - stbi__uint16 dequant[4][64]; - stbi__int16 fast_ac[4][1 << FAST_BITS]; - -// sizes for components, interleaved MCUs - int img_h_max, img_v_max; - int img_mcu_x, img_mcu_y; - int img_mcu_w, img_mcu_h; - -// definition of jpeg image component - struct - { - int id; - int h,v; - int tq; - int hd,ha; - int dc_pred; - - int x,y,w2,h2; - stbi_uc *data; - void *raw_data, *raw_coeff; - stbi_uc *linebuf; - short *coeff; // progressive only - int coeff_w, coeff_h; // number of 8x8 coefficient blocks - } img_comp[4]; - - stbi__uint32 code_buffer; // jpeg entropy-coded buffer - int code_bits; // number of valid bits - unsigned char marker; // marker seen while filling entropy buffer - int nomore; // flag if we saw a marker so must stop - - int progressive; - int spec_start; - int spec_end; - int succ_high; - int succ_low; - int eob_run; - int jfif; - int app14_color_transform; // Adobe APP14 tag - int rgb; - - int scan_n, order[4]; - int restart_interval, todo; - -// kernels - void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); - void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); - stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); -} stbi__jpeg; - -static int stbi__build_huffman(stbi__huffman *h, int *count) -{ - int i,j,k=0; - unsigned int code; - // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) - h->size[k++] = (stbi_uc) (i+1); - h->size[k] = 0; - - // compute actual symbols (from jpeg spec) - code = 0; - k = 0; - for(j=1; j <= 16; ++j) { - // compute delta to add to code to compute symbol id - h->delta[j] = k - code; - if (h->size[k] == j) { - while (h->size[k] == j) - h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); - } - // compute largest code + 1 for this size, preshifted as needed later - h->maxcode[j] = code << (16-j); - code <<= 1; - } - h->maxcode[j] = 0xffffffff; - - // build non-spec acceleration table; 255 is flag for not-accelerated - memset(h->fast, 255, 1 << FAST_BITS); - for (i=0; i < k; ++i) { - int s = h->size[i]; - if (s <= FAST_BITS) { - int c = h->code[i] << (FAST_BITS-s); - int m = 1 << (FAST_BITS-s); - for (j=0; j < m; ++j) { - h->fast[c+j] = (stbi_uc) i; - } - } - } - return 1; -} - -// build a table that decodes both magnitude and value of small ACs in -// one go. -static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) -{ - int i; - for (i=0; i < (1 << FAST_BITS); ++i) { - stbi_uc fast = h->fast[i]; - fast_ac[i] = 0; - if (fast < 255) { - int rs = h->values[fast]; - int run = (rs >> 4) & 15; - int magbits = rs & 15; - int len = h->size[fast]; - - if (magbits && len + magbits <= FAST_BITS) { - // magnitude code followed by receive_extend code - int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); - int m = 1 << (magbits - 1); - if (k < m) k += (~0U << magbits) + 1; - // if the result is small enough, we can fit it in fast_ac table - if (k >= -128 && k <= 127) - fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); - } - } - } -} - -static void stbi__grow_buffer_unsafe(stbi__jpeg *j) -{ - do { - unsigned int b = j->nomore ? 0 : stbi__get8(j->s); - if (b == 0xff) { - int c = stbi__get8(j->s); - while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes - if (c != 0) { - j->marker = (unsigned char) c; - j->nomore = 1; - return; - } - } - j->code_buffer |= b << (24 - j->code_bits); - j->code_bits += 8; - } while (j->code_bits <= 24); -} - -// (1 << n) - 1 -static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; - -// decode a jpeg huffman value from the bitstream -stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) -{ - unsigned int temp; - int c,k; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - // look at the top FAST_BITS and determine what symbol ID it is, - // if the code is <= FAST_BITS - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - k = h->fast[c]; - if (k < 255) { - int s = h->size[k]; - if (s > j->code_bits) - return -1; - j->code_buffer <<= s; - j->code_bits -= s; - return h->values[k]; - } - - // naive test is to shift the code_buffer down so k bits are - // valid, then test against maxcode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - temp = j->code_buffer >> 16; - for (k=FAST_BITS+1 ; ; ++k) - if (temp < h->maxcode[k]) - break; - if (k == 17) { - // error! code not found - j->code_bits -= 16; - return -1; - } - - if (k > j->code_bits) - return -1; - - // convert the huffman code to the symbol id - c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; - STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); - - // convert the id to a symbol - j->code_bits -= k; - j->code_buffer <<= k; - return h->values[c]; -} - -// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); - - sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k + (stbi__jbias[n] & (sgn - 1)); -} - -// get some unsigned bits -stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) -{ - unsigned int k; - if (j->code_bits < n) stbi__grow_buffer_unsafe(j); - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k; -} - -stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) -{ - unsigned int k; - if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); - k = j->code_buffer; - j->code_buffer <<= 1; - --j->code_bits; - return k & 0x80000000; -} - -// given a value that's at position X in the zigzag stream, -// where does it appear in the 8x8 matrix coded as row-major? -static const stbi_uc stbi__jpeg_dezigzag[64+15] = -{ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - // let corrupt input sample past end - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63 -}; - -// decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) -{ - int diff,dc,k; - int t; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); - - // 0 all the ac values now so we can do it 32-bits at a time - memset(data,0,64*sizeof(data[0])); - - diff = t ? stbi__extend_receive(j, t) : 0; - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc * dequant[0]); - - // decode AC components, see JPEG spec - k = 1; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) * dequant[zig]); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; - } else { - k += r; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); - } - } - } while (k < 64); - return 1; -} - -static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) -{ - int diff,dc; - int t; - if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - if (j->succ_high == 0) { - // first scan for DC coefficient, must be first - memset(data,0,64*sizeof(data[0])); // 0 all the ac values now - t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - diff = t ? stbi__extend_receive(j, t) : 0; - - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc * (1 << j->succ_low)); - } else { - // refinement scan for DC coefficient - if (stbi__jpeg_get_bit(j)) - data[0] += (short) (1 << j->succ_low); - } - return 1; -} - -// @OPTIMIZE: store non-zigzagged during the decode passes, -// and only de-zigzag when dequantizing -static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) -{ - int k; - if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->succ_high == 0) { - int shift = j->succ_low; - - if (j->eob_run) { - --j->eob_run; - return 1; - } - - k = j->spec_start; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) * (1 << shift)); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r); - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - --j->eob_run; - break; - } - k += 16; - } else { - k += r; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); - } - } - } while (k <= j->spec_end); - } else { - // refinement scan for these AC coefficients - - short bit = (short) (1 << j->succ_low); - - if (j->eob_run) { - --j->eob_run; - for (k = j->spec_start; k <= j->spec_end; ++k) { - short *p = &data[stbi__jpeg_dezigzag[k]]; - if (*p != 0) - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } - } else { - k = j->spec_start; - do { - int r,s; - int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r) - 1; - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - r = 64; // force end of block - } else { - // r=15 s=0 should write 16 0s, so we just do - // a run of 15 0s and then write s (which is 0), - // so we don't have to do anything special here - } - } else { - if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); - // sign bit - if (stbi__jpeg_get_bit(j)) - s = bit; - else - s = -bit; - } - - // advance by r - while (k <= j->spec_end) { - short *p = &data[stbi__jpeg_dezigzag[k++]]; - if (*p != 0) { - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } else { - if (r == 0) { - *p = (short) s; - break; - } - --r; - } - } - } while (k <= j->spec_end); - } - } - return 1; -} - -// take a -128..127 value and stbi__clamp it and convert to 0..255 -stbi_inline static stbi_uc stbi__clamp(int x) -{ - // trick to use a single test to catch both cases - if ((unsigned int) x > 255) { - if (x < 0) return 0; - if (x > 255) return 255; - } - return (stbi_uc) x; -} - -#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) -#define stbi__fsh(x) ((x) * 4096) - -// derived from jidctint -- DCT_ISLOW -#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ - int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ - p2 = s2; \ - p3 = s6; \ - p1 = (p2+p3) * stbi__f2f(0.5411961f); \ - t2 = p1 + p3*stbi__f2f(-1.847759065f); \ - t3 = p1 + p2*stbi__f2f( 0.765366865f); \ - p2 = s0; \ - p3 = s4; \ - t0 = stbi__fsh(p2+p3); \ - t1 = stbi__fsh(p2-p3); \ - x0 = t0+t3; \ - x3 = t0-t3; \ - x1 = t1+t2; \ - x2 = t1-t2; \ - t0 = s7; \ - t1 = s5; \ - t2 = s3; \ - t3 = s1; \ - p3 = t0+t2; \ - p4 = t1+t3; \ - p1 = t0+t3; \ - p2 = t1+t2; \ - p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ - t0 = t0*stbi__f2f( 0.298631336f); \ - t1 = t1*stbi__f2f( 2.053119869f); \ - t2 = t2*stbi__f2f( 3.072711026f); \ - t3 = t3*stbi__f2f( 1.501321110f); \ - p1 = p5 + p1*stbi__f2f(-0.899976223f); \ - p2 = p5 + p2*stbi__f2f(-2.562915447f); \ - p3 = p3*stbi__f2f(-1.961570560f); \ - p4 = p4*stbi__f2f(-0.390180644f); \ - t3 += p1+p4; \ - t2 += p2+p3; \ - t1 += p2+p4; \ - t0 += p1+p3; - -static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) -{ - int i,val[64],*v=val; - stbi_uc *o; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0]*4; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - // so we want to round that, which means adding 0.5 * 1<<17, - // aka 65536. Also, we'll end up with -128 to 127 that we want - // to encode as 0..255 by adding 128, so we'll add that before the shift - x0 += 65536 + (128<<17); - x1 += 65536 + (128<<17); - x2 += 65536 + (128<<17); - x3 += 65536 + (128<<17); - // tried computing the shifts into temps, or'ing the temps to see - // if any were out of range, but that was slower - o[0] = stbi__clamp((x0+t3) >> 17); - o[7] = stbi__clamp((x0-t3) >> 17); - o[1] = stbi__clamp((x1+t2) >> 17); - o[6] = stbi__clamp((x1-t2) >> 17); - o[2] = stbi__clamp((x2+t1) >> 17); - o[5] = stbi__clamp((x2-t1) >> 17); - o[3] = stbi__clamp((x3+t0) >> 17); - o[4] = stbi__clamp((x3-t0) >> 17); - } -} - -#ifdef STBI_SSE2 -// sse2 integer IDCT. not the fastest possible implementation but it -// produces bit-identical results to the generic C version so it's -// fully "transparent". -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - // This is constructed to match our regular (generic) integer IDCT exactly. - __m128i row0, row1, row2, row3, row4, row5, row6, row7; - __m128i tmp; - - // dot product constant: even elems=x, odd elems=y - #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) - - // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) - // out(1) = c1[even]*x + c1[odd]*y - #define dct_rot(out0,out1, x,y,c0,c1) \ - __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ - __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ - __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ - __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ - __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ - __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) - - // out = in << 12 (in 16-bit, out 32-bit) - #define dct_widen(out, in) \ - __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ - __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) - - // wide add - #define dct_wadd(out, a, b) \ - __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_add_epi32(a##_h, b##_h) - - // wide sub - #define dct_wsub(out, a, b) \ - __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) - - // butterfly a/b, add bias, then shift by "s" and pack - #define dct_bfly32o(out0, out1, a,b,bias,s) \ - { \ - __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ - __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ - dct_wadd(sum, abiased, b); \ - dct_wsub(dif, abiased, b); \ - out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ - out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ - } - - // 8-bit interleave step (for transposes) - #define dct_interleave8(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi8(a, b); \ - b = _mm_unpackhi_epi8(tmp, b) - - // 16-bit interleave step (for transposes) - #define dct_interleave16(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi16(a, b); \ - b = _mm_unpackhi_epi16(tmp, b) - - #define dct_pass(bias,shift) \ - { \ - /* even part */ \ - dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ - __m128i sum04 = _mm_add_epi16(row0, row4); \ - __m128i dif04 = _mm_sub_epi16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ - dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ - __m128i sum17 = _mm_add_epi16(row1, row7); \ - __m128i sum35 = _mm_add_epi16(row3, row5); \ - dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ - dct_wadd(x4, y0o, y4o); \ - dct_wadd(x5, y1o, y5o); \ - dct_wadd(x6, y2o, y5o); \ - dct_wadd(x7, y3o, y4o); \ - dct_bfly32o(row0,row7, x0,x7,bias,shift); \ - dct_bfly32o(row1,row6, x1,x6,bias,shift); \ - dct_bfly32o(row2,row5, x2,x5,bias,shift); \ - dct_bfly32o(row3,row4, x3,x4,bias,shift); \ - } - - __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); - __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); - __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); - __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); - __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); - __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); - __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); - __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); - - // rounding biases in column/row passes, see stbi__idct_block for explanation. - __m128i bias_0 = _mm_set1_epi32(512); - __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); - - // load - row0 = _mm_load_si128((const __m128i *) (data + 0*8)); - row1 = _mm_load_si128((const __m128i *) (data + 1*8)); - row2 = _mm_load_si128((const __m128i *) (data + 2*8)); - row3 = _mm_load_si128((const __m128i *) (data + 3*8)); - row4 = _mm_load_si128((const __m128i *) (data + 4*8)); - row5 = _mm_load_si128((const __m128i *) (data + 5*8)); - row6 = _mm_load_si128((const __m128i *) (data + 6*8)); - row7 = _mm_load_si128((const __m128i *) (data + 7*8)); - - // column pass - dct_pass(bias_0, 10); - - { - // 16bit 8x8 transpose pass 1 - dct_interleave16(row0, row4); - dct_interleave16(row1, row5); - dct_interleave16(row2, row6); - dct_interleave16(row3, row7); - - // transpose pass 2 - dct_interleave16(row0, row2); - dct_interleave16(row1, row3); - dct_interleave16(row4, row6); - dct_interleave16(row5, row7); - - // transpose pass 3 - dct_interleave16(row0, row1); - dct_interleave16(row2, row3); - dct_interleave16(row4, row5); - dct_interleave16(row6, row7); - } - - // row pass - dct_pass(bias_1, 17); - - { - // pack - __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 - __m128i p1 = _mm_packus_epi16(row2, row3); - __m128i p2 = _mm_packus_epi16(row4, row5); - __m128i p3 = _mm_packus_epi16(row6, row7); - - // 8bit 8x8 transpose pass 1 - dct_interleave8(p0, p2); // a0e0a1e1... - dct_interleave8(p1, p3); // c0g0c1g1... - - // transpose pass 2 - dct_interleave8(p0, p1); // a0c0e0g0... - dct_interleave8(p2, p3); // b0d0f0h0... - - // transpose pass 3 - dct_interleave8(p0, p2); // a0b0c0d0... - dct_interleave8(p1, p3); // a4b4c4d4... - - // store - _mm_storel_epi64((__m128i *) out, p0); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p2); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p1); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p3); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); - } - -#undef dct_const -#undef dct_rot -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_interleave8 -#undef dct_interleave16 -#undef dct_pass -} - -#endif // STBI_SSE2 - -#ifdef STBI_NEON - -// NEON integer IDCT. should produce bit-identical -// results to the generic C version. -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; - - int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); - int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); - int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); - int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); - int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); - int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); - int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); - int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); - int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); - int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); - int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); - int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); - -#define dct_long_mul(out, inq, coeff) \ - int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) - -#define dct_long_mac(out, acc, inq, coeff) \ - int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) - -#define dct_widen(out, inq) \ - int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ - int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) - -// wide add -#define dct_wadd(out, a, b) \ - int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vaddq_s32(a##_h, b##_h) - -// wide sub -#define dct_wsub(out, a, b) \ - int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vsubq_s32(a##_h, b##_h) - -// butterfly a/b, then shift using "shiftop" by "s" and pack -#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ - { \ - dct_wadd(sum, a, b); \ - dct_wsub(dif, a, b); \ - out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ - out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ - } - -#define dct_pass(shiftop, shift) \ - { \ - /* even part */ \ - int16x8_t sum26 = vaddq_s16(row2, row6); \ - dct_long_mul(p1e, sum26, rot0_0); \ - dct_long_mac(t2e, p1e, row6, rot0_1); \ - dct_long_mac(t3e, p1e, row2, rot0_2); \ - int16x8_t sum04 = vaddq_s16(row0, row4); \ - int16x8_t dif04 = vsubq_s16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - int16x8_t sum15 = vaddq_s16(row1, row5); \ - int16x8_t sum17 = vaddq_s16(row1, row7); \ - int16x8_t sum35 = vaddq_s16(row3, row5); \ - int16x8_t sum37 = vaddq_s16(row3, row7); \ - int16x8_t sumodd = vaddq_s16(sum17, sum35); \ - dct_long_mul(p5o, sumodd, rot1_0); \ - dct_long_mac(p1o, p5o, sum17, rot1_1); \ - dct_long_mac(p2o, p5o, sum35, rot1_2); \ - dct_long_mul(p3o, sum37, rot2_0); \ - dct_long_mul(p4o, sum15, rot2_1); \ - dct_wadd(sump13o, p1o, p3o); \ - dct_wadd(sump24o, p2o, p4o); \ - dct_wadd(sump23o, p2o, p3o); \ - dct_wadd(sump14o, p1o, p4o); \ - dct_long_mac(x4, sump13o, row7, rot3_0); \ - dct_long_mac(x5, sump24o, row5, rot3_1); \ - dct_long_mac(x6, sump23o, row3, rot3_2); \ - dct_long_mac(x7, sump14o, row1, rot3_3); \ - dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ - dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ - dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ - dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ - } - - // load - row0 = vld1q_s16(data + 0*8); - row1 = vld1q_s16(data + 1*8); - row2 = vld1q_s16(data + 2*8); - row3 = vld1q_s16(data + 3*8); - row4 = vld1q_s16(data + 4*8); - row5 = vld1q_s16(data + 5*8); - row6 = vld1q_s16(data + 6*8); - row7 = vld1q_s16(data + 7*8); - - // add DC bias - row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); - - // column pass - dct_pass(vrshrn_n_s32, 10); - - // 16bit 8x8 transpose - { -// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. -// whether compilers actually get this is another story, sadly. -#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } -#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } - - // pass 1 - dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 - dct_trn16(row2, row3); - dct_trn16(row4, row5); - dct_trn16(row6, row7); - - // pass 2 - dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 - dct_trn32(row1, row3); - dct_trn32(row4, row6); - dct_trn32(row5, row7); - - // pass 3 - dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 - dct_trn64(row1, row5); - dct_trn64(row2, row6); - dct_trn64(row3, row7); - -#undef dct_trn16 -#undef dct_trn32 -#undef dct_trn64 - } - - // row pass - // vrshrn_n_s32 only supports shifts up to 16, we need - // 17. so do a non-rounding shift of 16 first then follow - // up with a rounding shift by 1. - dct_pass(vshrn_n_s32, 16); - - { - // pack and round - uint8x8_t p0 = vqrshrun_n_s16(row0, 1); - uint8x8_t p1 = vqrshrun_n_s16(row1, 1); - uint8x8_t p2 = vqrshrun_n_s16(row2, 1); - uint8x8_t p3 = vqrshrun_n_s16(row3, 1); - uint8x8_t p4 = vqrshrun_n_s16(row4, 1); - uint8x8_t p5 = vqrshrun_n_s16(row5, 1); - uint8x8_t p6 = vqrshrun_n_s16(row6, 1); - uint8x8_t p7 = vqrshrun_n_s16(row7, 1); - - // again, these can translate into one instruction, but often don't. -#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } -#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } - - // sadly can't use interleaved stores here since we only write - // 8 bytes to each scan line! - - // 8x8 8-bit transpose pass 1 - dct_trn8_8(p0, p1); - dct_trn8_8(p2, p3); - dct_trn8_8(p4, p5); - dct_trn8_8(p6, p7); - - // pass 2 - dct_trn8_16(p0, p2); - dct_trn8_16(p1, p3); - dct_trn8_16(p4, p6); - dct_trn8_16(p5, p7); - - // pass 3 - dct_trn8_32(p0, p4); - dct_trn8_32(p1, p5); - dct_trn8_32(p2, p6); - dct_trn8_32(p3, p7); - - // store - vst1_u8(out, p0); out += out_stride; - vst1_u8(out, p1); out += out_stride; - vst1_u8(out, p2); out += out_stride; - vst1_u8(out, p3); out += out_stride; - vst1_u8(out, p4); out += out_stride; - vst1_u8(out, p5); out += out_stride; - vst1_u8(out, p6); out += out_stride; - vst1_u8(out, p7); - -#undef dct_trn8_8 -#undef dct_trn8_16 -#undef dct_trn8_32 - } - -#undef dct_long_mul -#undef dct_long_mac -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_pass -} - -#endif // STBI_NEON - -#define STBI__MARKER_none 0xff -// if there's a pending marker from the entropy stream, return that -// otherwise, fetch from the stream and get a marker. if there's no -// marker, return 0xff, which is never a valid marker value -static stbi_uc stbi__get_marker(stbi__jpeg *j) -{ - stbi_uc x; - if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } - x = stbi__get8(j->s); - if (x != 0xff) return STBI__MARKER_none; - while (x == 0xff) - x = stbi__get8(j->s); // consume repeated 0xff fill bytes - return x; -} - -// in each scan, we'll have scan_n components, and the order -// of the components is specified by order[] -#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) - -// after a restart interval, stbi__jpeg_reset the entropy decoder and -// the dc prediction -static void stbi__jpeg_reset(stbi__jpeg *j) -{ - j->code_bits = 0; - j->code_buffer = 0; - j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; - j->marker = STBI__MARKER_none; - j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; - j->eob_run = 0; - // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, - // since we don't even allow 1<<30 pixels -} - -static int stbi__parse_entropy_coded_data(stbi__jpeg *z) -{ - stbi__jpeg_reset(z); - if (!z->progressive) { - if (z->scan_n == 1) { - int i,j; - STBI_SIMD_ALIGN(short, data[64]); - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - STBI_SIMD_ALIGN(short, data[64]); - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } else { - if (z->scan_n == 1) { - int i,j; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - if (z->spec_start == 0) { - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } else { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) - return 0; - } - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x); - int y2 = (j*z->img_comp[n].v + y); - short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } -} - -static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) -{ - int i; - for (i=0; i < 64; ++i) - data[i] *= dequant[i]; -} - -static void stbi__jpeg_finish(stbi__jpeg *z) -{ - if (z->progressive) { - // dequantize and idct the data - int i,j,n; - for (n=0; n < z->s->img_n; ++n) { - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - } - } - } - } -} - -static int stbi__process_marker(stbi__jpeg *z, int m) -{ - int L; - switch (m) { - case STBI__MARKER_none: // no marker found - return stbi__err("expected marker","Corrupt JPEG"); - - case 0xDD: // DRI - specify restart interval - if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); - z->restart_interval = stbi__get16be(z->s); - return 1; - - case 0xDB: // DQT - define quantization table - L = stbi__get16be(z->s)-2; - while (L > 0) { - int q = stbi__get8(z->s); - int p = q >> 4, sixteen = (p != 0); - int t = q & 15,i; - if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); - if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); - - for (i=0; i < 64; ++i) - z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); - L -= (sixteen ? 129 : 65); - } - return L==0; - - case 0xC4: // DHT - define huffman table - L = stbi__get16be(z->s)-2; - while (L > 0) { - stbi_uc *v; - int sizes[16],i,n=0; - int q = stbi__get8(z->s); - int tc = q >> 4; - int th = q & 15; - if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); - for (i=0; i < 16; ++i) { - sizes[i] = stbi__get8(z->s); - n += sizes[i]; - } - L -= 17; - if (tc == 0) { - if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; - v = z->huff_dc[th].values; - } else { - if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; - v = z->huff_ac[th].values; - } - for (i=0; i < n; ++i) - v[i] = stbi__get8(z->s); - if (tc != 0) - stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); - L -= n; - } - return L==0; - } - - // check for comment block or APP blocks - if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - L = stbi__get16be(z->s); - if (L < 2) { - if (m == 0xFE) - return stbi__err("bad COM len","Corrupt JPEG"); - else - return stbi__err("bad APP len","Corrupt JPEG"); - } - L -= 2; - - if (m == 0xE0 && L >= 5) { // JFIF APP0 segment - static const unsigned char tag[5] = {'J','F','I','F','\0'}; - int ok = 1; - int i; - for (i=0; i < 5; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 5; - if (ok) - z->jfif = 1; - } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment - static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; - int ok = 1; - int i; - for (i=0; i < 6; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 6; - if (ok) { - stbi__get8(z->s); // version - stbi__get16be(z->s); // flags0 - stbi__get16be(z->s); // flags1 - z->app14_color_transform = stbi__get8(z->s); // color transform - L -= 6; - } - } - - stbi__skip(z->s, L); - return 1; - } - - return stbi__err("unknown marker","Corrupt JPEG"); -} - -// after we see SOS -static int stbi__process_scan_header(stbi__jpeg *z) -{ - int i; - int Ls = stbi__get16be(z->s); - z->scan_n = stbi__get8(z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); - for (i=0; i < z->scan_n; ++i) { - int id = stbi__get8(z->s), which; - int q = stbi__get8(z->s); - for (which = 0; which < z->s->img_n; ++which) - if (z->img_comp[which].id == id) - break; - if (which == z->s->img_n) return 0; // no match - z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); - z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); - z->order[i] = which; - } - - { - int aa; - z->spec_start = stbi__get8(z->s); - z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 - aa = stbi__get8(z->s); - z->succ_high = (aa >> 4); - z->succ_low = (aa & 15); - if (z->progressive) { - if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) - return stbi__err("bad SOS", "Corrupt JPEG"); - } else { - if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); - if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); - z->spec_end = 63; - } - } - - return 1; -} - -static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) -{ - int i; - for (i=0; i < ncomp; ++i) { - if (z->img_comp[i].raw_data) { - STBI_FREE(z->img_comp[i].raw_data); - z->img_comp[i].raw_data = NULL; - z->img_comp[i].data = NULL; - } - if (z->img_comp[i].raw_coeff) { - STBI_FREE(z->img_comp[i].raw_coeff); - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].coeff = 0; - } - if (z->img_comp[i].linebuf) { - STBI_FREE(z->img_comp[i].linebuf); - z->img_comp[i].linebuf = NULL; - } - } - return why; -} - -static int stbi__process_frame_header(stbi__jpeg *z, int scan) -{ - stbi__context *s = z->s; - int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG - p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline - s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG - s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires - if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - c = stbi__get8(s); - if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); - s->img_n = c; - for (i=0; i < c; ++i) { - z->img_comp[i].data = NULL; - z->img_comp[i].linebuf = NULL; - } - - if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); - - z->rgb = 0; - for (i=0; i < s->img_n; ++i) { - static const unsigned char rgb[3] = { 'R', 'G', 'B' }; - z->img_comp[i].id = stbi__get8(s); - if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) - ++z->rgb; - q = stbi__get8(s); - z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); - z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); - z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); - } - - if (scan != STBI__SCAN_load) return 1; - - if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); - - for (i=0; i < s->img_n; ++i) { - if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; - if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; - } - - // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios - // and I've never seen a non-corrupted JPEG file actually use them - for (i=0; i < s->img_n; ++i) { - if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); - if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); - } - - // compute interleaved mcu info - z->img_h_max = h_max; - z->img_v_max = v_max; - z->img_mcu_w = h_max * 8; - z->img_mcu_h = v_max * 8; - // these sizes can't be more than 17 bits - z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; - z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; - - for (i=0; i < s->img_n; ++i) { - // number of effective pixels (e.g. for non-interleaved MCU) - z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; - z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to decode - // the bogus oversized data from using interleaved MCUs and their - // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't - // discard the extra data until colorspace conversion - // - // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) - // so these muls can't overflow with 32-bit ints (which we require) - z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; - z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].coeff = 0; - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].linebuf = NULL; - z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); - if (z->img_comp[i].raw_data == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - // align blocks for idct using mmx/sse - z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); - if (z->progressive) { - // w2, h2 are multiples of 8 (see above) - z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; - z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; - z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); - if (z->img_comp[i].raw_coeff == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); - } - } - - return 1; -} - -// use comparisons since in some cases we handle more than one case (e.g. SOF) -#define stbi__DNL(x) ((x) == 0xdc) -#define stbi__SOI(x) ((x) == 0xd8) -#define stbi__EOI(x) ((x) == 0xd9) -#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) -#define stbi__SOS(x) ((x) == 0xda) - -#define stbi__SOF_progressive(x) ((x) == 0xc2) - -static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) -{ - int m; - z->jfif = 0; - z->app14_color_transform = -1; // valid values are 0,1,2 - z->marker = STBI__MARKER_none; // initialize cached marker to empty - m = stbi__get_marker(z); - if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); - if (scan == STBI__SCAN_type) return 1; - m = stbi__get_marker(z); - while (!stbi__SOF(m)) { - if (!stbi__process_marker(z,m)) return 0; - m = stbi__get_marker(z); - while (m == STBI__MARKER_none) { - // some files have extra padding after their blocks, so ok, we'll scan - if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); - m = stbi__get_marker(z); - } - } - z->progressive = stbi__SOF_progressive(m); - if (!stbi__process_frame_header(z, scan)) return 0; - return 1; -} - -// decode image to YCbCr format -static int stbi__decode_jpeg_image(stbi__jpeg *j) -{ - int m; - for (m = 0; m < 4; m++) { - j->img_comp[m].raw_data = NULL; - j->img_comp[m].raw_coeff = NULL; - } - j->restart_interval = 0; - if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; - m = stbi__get_marker(j); - while (!stbi__EOI(m)) { - if (stbi__SOS(m)) { - if (!stbi__process_scan_header(j)) return 0; - if (!stbi__parse_entropy_coded_data(j)) return 0; - if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } - // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 - } - } else if (stbi__DNL(m)) { - int Ld = stbi__get16be(j->s); - stbi__uint32 NL = stbi__get16be(j->s); - if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); - if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); - } else { - if (!stbi__process_marker(j, m)) return 0; - } - m = stbi__get_marker(j); - } - if (j->progressive) - stbi__jpeg_finish(j); - return 1; -} - -// static jfif-centered resampling (across block boundaries) - -typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, - int w, int hs); - -#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) - -static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - STBI_NOTUSED(out); - STBI_NOTUSED(in_far); - STBI_NOTUSED(w); - STBI_NOTUSED(hs); - return in_near; -} - -static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples vertically for every one in input - int i; - STBI_NOTUSED(hs); - for (i=0; i < w; ++i) - out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); - return out; -} - -static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples horizontally for every one in input - int i; - stbi_uc *input = in_near; - - if (w == 1) { - // if only one sample, can't do any interpolation - out[0] = out[1] = input[0]; - return out; - } - - out[0] = input[0]; - out[1] = stbi__div4(input[0]*3 + input[1] + 2); - for (i=1; i < w-1; ++i) { - int n = 3*input[i]+2; - out[i*2+0] = stbi__div4(n+input[i-1]); - out[i*2+1] = stbi__div4(n+input[i+1]); - } - out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); - out[i*2+1] = input[w-1]; - - STBI_NOTUSED(in_far); - STBI_NOTUSED(hs); - - return out; -} - -#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) - -static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i,t0,t1; - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - out[0] = stbi__div4(t1+2); - for (i=1; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i=0,t0,t1; - - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - // process groups of 8 pixels for as long as we can. - // note we can't handle the last pixel in a row in this loop - // because we need to handle the filter boundary conditions. - for (; i < ((w-1) & ~7); i += 8) { -#if defined(STBI_SSE2) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - __m128i zero = _mm_setzero_si128(); - __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); - __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); - __m128i farw = _mm_unpacklo_epi8(farb, zero); - __m128i nearw = _mm_unpacklo_epi8(nearb, zero); - __m128i diff = _mm_sub_epi16(farw, nearw); - __m128i nears = _mm_slli_epi16(nearw, 2); - __m128i curr = _mm_add_epi16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - __m128i prv0 = _mm_slli_si128(curr, 2); - __m128i nxt0 = _mm_srli_si128(curr, 2); - __m128i prev = _mm_insert_epi16(prv0, t1, 0); - __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - __m128i bias = _mm_set1_epi16(8); - __m128i curs = _mm_slli_epi16(curr, 2); - __m128i prvd = _mm_sub_epi16(prev, curr); - __m128i nxtd = _mm_sub_epi16(next, curr); - __m128i curb = _mm_add_epi16(curs, bias); - __m128i even = _mm_add_epi16(prvd, curb); - __m128i odd = _mm_add_epi16(nxtd, curb); - - // interleave even and odd pixels, then undo scaling. - __m128i int0 = _mm_unpacklo_epi16(even, odd); - __m128i int1 = _mm_unpackhi_epi16(even, odd); - __m128i de0 = _mm_srli_epi16(int0, 4); - __m128i de1 = _mm_srli_epi16(int1, 4); - - // pack and write output - __m128i outv = _mm_packus_epi16(de0, de1); - _mm_storeu_si128((__m128i *) (out + i*2), outv); -#elif defined(STBI_NEON) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - uint8x8_t farb = vld1_u8(in_far + i); - uint8x8_t nearb = vld1_u8(in_near + i); - int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); - int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); - int16x8_t curr = vaddq_s16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - int16x8_t prv0 = vextq_s16(curr, curr, 7); - int16x8_t nxt0 = vextq_s16(curr, curr, 1); - int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); - int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - int16x8_t curs = vshlq_n_s16(curr, 2); - int16x8_t prvd = vsubq_s16(prev, curr); - int16x8_t nxtd = vsubq_s16(next, curr); - int16x8_t even = vaddq_s16(curs, prvd); - int16x8_t odd = vaddq_s16(curs, nxtd); - - // undo scaling and round, then store with even/odd phases interleaved - uint8x8x2_t o; - o.val[0] = vqrshrun_n_s16(even, 4); - o.val[1] = vqrshrun_n_s16(odd, 4); - vst2_u8(out + i*2, o); -#endif - - // "previous" value for next iter - t1 = 3*in_near[i+7] + in_far[i+7]; - } - - t0 = t1; - t1 = 3*in_near[i] + in_far[i]; - out[i*2] = stbi__div16(3*t1 + t0 + 8); - - for (++i; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} -#endif - -static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // resample with nearest-neighbor - int i,j; - STBI_NOTUSED(in_far); - for (i=0; i < w; ++i) - for (j=0; j < hs; ++j) - out[i*hs+j] = in_near[i]; - return out; -} - -// this is a reduced-precision calculation of YCbCr-to-RGB introduced -// to make sure the code produces the same results in both SIMD and scalar -#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) -static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) -{ - int i = 0; - -#ifdef STBI_SSE2 - // step == 3 is pretty ugly on the final interleave, and i'm not convinced - // it's useful in practice (you wouldn't use it for textures, for example). - // so just accelerate step == 4 case. - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - __m128i signflip = _mm_set1_epi8(-0x80); - __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); - __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); - __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); - __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); - __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); - __m128i xw = _mm_set1_epi16(255); // alpha channel - - for (; i+7 < count; i += 8) { - // load - __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); - __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); - __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); - __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 - __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 - - // unpack to short (and left-shift cr, cb by 8) - __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); - __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); - __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); - - // color transform - __m128i yws = _mm_srli_epi16(yw, 4); - __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); - __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); - __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); - __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); - __m128i rws = _mm_add_epi16(cr0, yws); - __m128i gwt = _mm_add_epi16(cb0, yws); - __m128i bws = _mm_add_epi16(yws, cb1); - __m128i gws = _mm_add_epi16(gwt, cr1); - - // descale - __m128i rw = _mm_srai_epi16(rws, 4); - __m128i bw = _mm_srai_epi16(bws, 4); - __m128i gw = _mm_srai_epi16(gws, 4); - - // back to byte, set up for transpose - __m128i brb = _mm_packus_epi16(rw, bw); - __m128i gxb = _mm_packus_epi16(gw, xw); - - // transpose to interleave channels - __m128i t0 = _mm_unpacklo_epi8(brb, gxb); - __m128i t1 = _mm_unpackhi_epi8(brb, gxb); - __m128i o0 = _mm_unpacklo_epi16(t0, t1); - __m128i o1 = _mm_unpackhi_epi16(t0, t1); - - // store - _mm_storeu_si128((__m128i *) (out + 0), o0); - _mm_storeu_si128((__m128i *) (out + 16), o1); - out += 32; - } - } -#endif - -#ifdef STBI_NEON - // in this version, step=3 support would be easy to add. but is there demand? - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - uint8x8_t signflip = vdup_n_u8(0x80); - int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); - int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); - int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); - int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); - - for (; i+7 < count; i += 8) { - // load - uint8x8_t y_bytes = vld1_u8(y + i); - uint8x8_t cr_bytes = vld1_u8(pcr + i); - uint8x8_t cb_bytes = vld1_u8(pcb + i); - int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); - int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); - - // expand to s16 - int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); - int16x8_t crw = vshll_n_s8(cr_biased, 7); - int16x8_t cbw = vshll_n_s8(cb_biased, 7); - - // color transform - int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); - int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); - int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); - int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); - int16x8_t rws = vaddq_s16(yws, cr0); - int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); - int16x8_t bws = vaddq_s16(yws, cb1); - - // undo scaling, round, convert to byte - uint8x8x4_t o; - o.val[0] = vqrshrun_n_s16(rws, 4); - o.val[1] = vqrshrun_n_s16(gws, 4); - o.val[2] = vqrshrun_n_s16(bws, 4); - o.val[3] = vdup_n_u8(255); - - // store, interleaving r/g/b/a - vst4_u8(out, o); - out += 8*4; - } - } -#endif - - for (; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} -#endif - -// set up the kernels -static void stbi__setup_jpeg(stbi__jpeg *j) -{ - j->idct_block_kernel = stbi__idct_block; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; - -#ifdef STBI_SSE2 - if (stbi__sse2_available()) { - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; - } -#endif - -#ifdef STBI_NEON - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; -#endif -} - -// clean up the temporary component buffers -static void stbi__cleanup_jpeg(stbi__jpeg *j) -{ - stbi__free_jpeg_components(j, j->s->img_n, 0); -} - -typedef struct -{ - resample_row_func resample; - stbi_uc *line0,*line1; - int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion - int ystep; // how far through vertical expansion we are - int ypos; // which pre-expansion row we're on -} stbi__resample; - -// fast 0..255 * 0..255 => 0..255 rounded multiplication -static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) -{ - unsigned int t = x*y + 128; - return (stbi_uc) ((t + (t >>8)) >> 8); -} - -static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) -{ - int n, decode_n, is_rgb; - z->s->img_n = 0; // make stbi__cleanup_jpeg safe - - // validate req_comp - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - - // load a jpeg image from whichever source, but leave in YCbCr format - if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } - - // determine actual number of components to generate - n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; - - is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); - - if (z->s->img_n == 3 && n < 3 && !is_rgb) - decode_n = 1; - else - decode_n = z->s->img_n; - - // nothing to do if no components requested; check this now to avoid - // accessing uninitialized coutput[0] later - if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } - - // resample and color-convert - { - int k; - unsigned int i,j; - stbi_uc *output; - stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; - - stbi__resample res_comp[4]; - - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - - // allocate line buffer big enough for upsampling off the edges - // with upsample factor of 4 - z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); - if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - r->hs = z->img_h_max / z->img_comp[k].h; - r->vs = z->img_v_max / z->img_comp[k].v; - r->ystep = r->vs >> 1; - r->w_lores = (z->s->img_x + r->hs-1) / r->hs; - r->ypos = 0; - r->line0 = r->line1 = z->img_comp[k].data; - - if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; - else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; - else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; - else r->resample = stbi__resample_row_generic; - } - - // can't error after this so, this is safe - output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); - if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - // now go ahead and resample - for (j=0; j < z->s->img_y; ++j) { - stbi_uc *out = output + n * z->s->img_x * j; - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, - y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, - r->w_lores, r->hs); - if (++r->ystep >= r->vs) { - r->ystep = 0; - r->line0 = r->line1; - if (++r->ypos < z->img_comp[k].y) - r->line1 += z->img_comp[k].w2; - } - } - if (n >= 3) { - stbi_uc *y = coutput[0]; - if (z->s->img_n == 3) { - if (is_rgb) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = y[i]; - out[1] = coutput[1][i]; - out[2] = coutput[2][i]; - out[3] = 255; - out += n; - } - } else { - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else if (z->s->img_n == 4) { - if (z->app14_color_transform == 0) { // CMYK - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(coutput[0][i], m); - out[1] = stbi__blinn_8x8(coutput[1][i], m); - out[2] = stbi__blinn_8x8(coutput[2][i], m); - out[3] = 255; - out += n; - } - } else if (z->app14_color_transform == 2) { // YCCK - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(255 - out[0], m); - out[1] = stbi__blinn_8x8(255 - out[1], m); - out[2] = stbi__blinn_8x8(255 - out[2], m); - out += n; - } - } else { // YCbCr + alpha? Ignore the fourth channel for now - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else - for (i=0; i < z->s->img_x; ++i) { - out[0] = out[1] = out[2] = y[i]; - out[3] = 255; // not used if n==3 - out += n; - } - } else { - if (is_rgb) { - if (n == 1) - for (i=0; i < z->s->img_x; ++i) - *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - else { - for (i=0; i < z->s->img_x; ++i, out += 2) { - out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - out[1] = 255; - } - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); - stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); - stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); - out[0] = stbi__compute_y(r, g, b); - out[1] = 255; - out += n; - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); - out[1] = 255; - out += n; - } - } else { - stbi_uc *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } - } - } - } - stbi__cleanup_jpeg(z); - *out_x = z->s->img_x; - *out_y = z->s->img_y; - if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output - return output; - } -} - -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - unsigned char* result; - stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); - if (!j) return stbi__errpuc("outofmem", "Out of memory"); - STBI_NOTUSED(ri); - j->s = s; - stbi__setup_jpeg(j); - result = load_jpeg_image(j, x,y,comp,req_comp); - STBI_FREE(j); - return result; -} - -static int stbi__jpeg_test(stbi__context *s) -{ - int r; - stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); - if (!j) return stbi__err("outofmem", "Out of memory"); - j->s = s; - stbi__setup_jpeg(j); - r = stbi__decode_jpeg_header(j, STBI__SCAN_type); - stbi__rewind(s); - STBI_FREE(j); - return r; -} - -static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) -{ - if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { - stbi__rewind( j->s ); - return 0; - } - if (x) *x = j->s->img_x; - if (y) *y = j->s->img_y; - if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; - return 1; -} - -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) -{ - int result; - stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); - if (!j) return stbi__err("outofmem", "Out of memory"); - j->s = s; - result = stbi__jpeg_info_raw(j, x, y, comp); - STBI_FREE(j); - return result; -} -#endif - -// public domain zlib decode v0.2 Sean Barrett 2006-11-18 -// simple implementation -// - all input must be provided in an upfront buffer -// - all output is written to a single output buffer (can malloc/realloc) -// performance -// - fast huffman - -#ifndef STBI_NO_ZLIB - -// fast-way is faster to check than jpeg huffman, but slow way is slower -#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables -#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) -#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet - -// zlib-style huffman encoding -// (jpegs packs from left, zlib from right, so can't share code) -typedef struct -{ - stbi__uint16 fast[1 << STBI__ZFAST_BITS]; - stbi__uint16 firstcode[16]; - int maxcode[17]; - stbi__uint16 firstsymbol[16]; - stbi_uc size[STBI__ZNSYMS]; - stbi__uint16 value[STBI__ZNSYMS]; -} stbi__zhuffman; - -stbi_inline static int stbi__bitreverse16(int n) -{ - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -stbi_inline static int stbi__bit_reverse(int v, int bits) -{ - STBI_ASSERT(bits <= 16); - // to bit reverse n bits, reverse 16 and shift - // e.g. 11 bits, bit reverse and shift away 5 - return stbi__bitreverse16(v) >> (16-bits); -} - -static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) -{ - int i,k=0; - int code, next_code[16], sizes[17]; - - // DEFLATE spec for generating codes - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 0, sizeof(z->fast)); - for (i=0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i=1; i < 16; ++i) - if (sizes[i] > (1 << i)) - return stbi__err("bad sizes", "Corrupt PNG"); - code = 0; - for (i=1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (stbi__uint16) code; - z->firstsymbol[i] = (stbi__uint16) k; - code = (code + sizes[i]); - if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); - z->maxcode[i] = code << (16-i); // preshift for inner loop - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; // sentinel - for (i=0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); - z->size [c] = (stbi_uc ) s; - z->value[c] = (stbi__uint16) i; - if (s <= STBI__ZFAST_BITS) { - int j = stbi__bit_reverse(next_code[s],s); - while (j < (1 << STBI__ZFAST_BITS)) { - z->fast[j] = fastv; - j += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -// zlib-from-memory implementation for PNG reading -// because PNG allows splitting the zlib stream arbitrarily, -// and it's annoying structurally to have PNG call ZLIB call PNG, -// we require PNG read all the IDATs and combine them into a single -// memory buffer - -typedef struct -{ - stbi_uc *zbuffer, *zbuffer_end; - int num_bits; - stbi__uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - stbi__zhuffman z_length, z_distance; -} stbi__zbuf; - -stbi_inline static int stbi__zeof(stbi__zbuf *z) -{ - return (z->zbuffer >= z->zbuffer_end); -} - -stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) -{ - return stbi__zeof(z) ? 0 : *z->zbuffer++; -} - -static void stbi__fill_bits(stbi__zbuf *z) -{ - do { - if (z->code_buffer >= (1U << z->num_bits)) { - z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ - return; - } - z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) -{ - unsigned int k; - if (z->num_bits < n) stbi__fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s,k; - // not resolved by fast table, so compute it the slow way - // use jpeg approach, which requires MSbits at top - k = stbi__bit_reverse(a->code_buffer, 16); - for (s=STBI__ZFAST_BITS+1; ; ++s) - if (k < z->maxcode[s]) - break; - if (s >= 16) return -1; // invalid code! - // code size is s, so: - b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! - if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s; - if (a->num_bits < 16) { - if (stbi__zeof(a)) { - return -1; /* report error for unexpected end of data. */ - } - stbi__fill_bits(a); - } - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b) { - s = b >> 9; - a->code_buffer >>= s; - a->num_bits -= s; - return b & 511; - } - return stbi__zhuffman_decode_slowpath(a, z); -} - -static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes -{ - char *q; - unsigned int cur, limit, old_limit; - z->zout = zout; - if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); - cur = (unsigned int) (z->zout - z->zout_start); - limit = old_limit = (unsigned) (z->zout_end - z->zout_start); - if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); - while (cur + n > limit) { - if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); - limit *= 2; - } - q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); - STBI_NOTUSED(old_limit); - if (q == NULL) return stbi__err("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static const int stbi__zlength_base[31] = { - 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 int stbi__zlength_extra[31]= -{ 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,0,0 }; - -static const int stbi__zdist_base[32] = { 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 int stbi__zdist_extra[32] = -{ 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}; - -static int stbi__parse_huffman_block(stbi__zbuf *a) -{ - char *zout = a->zout; - for(;;) { - int z = stbi__zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes - if (zout >= a->zout_end) { - if (!stbi__zexpand(a, zout, 1)) return 0; - zout = a->zout; - } - *zout++ = (char) z; - } else { - stbi_uc *p; - int len,dist; - if (z == 256) { - a->zout = zout; - return 1; - } - z -= 257; - len = stbi__zlength_base[z]; - if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); - z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); - dist = stbi__zdist_base[z]; - if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); - if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { - if (!stbi__zexpand(a, zout, len)) return 0; - zout = a->zout; - } - p = (stbi_uc *) (zout - dist); - if (dist == 1) { // run of one byte; common in images. - stbi_uc v = *p; - if (len) { do *zout++ = v; while (--len); } - } else { - if (len) { do *zout++ = *p++; while (--len); } - } - } - } -} - -static int stbi__compute_huffman_codes(stbi__zbuf *a) -{ - static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - stbi__zhuffman z_codelength; - stbi_uc lencodes[286+32+137];//padding for maximum single op - stbi_uc codelength_sizes[19]; - int i,n; - - int hlit = stbi__zreceive(a,5) + 257; - int hdist = stbi__zreceive(a,5) + 1; - int hclen = stbi__zreceive(a,4) + 4; - int ntot = hlit + hdist; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i=0; i < hclen; ++i) { - int s = stbi__zreceive(a,3); - codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; - } - if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < ntot) { - int c = stbi__zhuffman_decode(a, &z_codelength); - if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); - if (c < 16) - lencodes[n++] = (stbi_uc) c; - else { - stbi_uc fill = 0; - if (c == 16) { - c = stbi__zreceive(a,2)+3; - if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); - fill = lencodes[n-1]; - } else if (c == 17) { - c = stbi__zreceive(a,3)+3; - } else if (c == 18) { - c = stbi__zreceive(a,7)+11; - } else { - return stbi__err("bad codelengths", "Corrupt PNG"); - } - if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); - memset(lencodes+n, fill, c); - n += c; - } - } - if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); - if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; - return 1; -} - -static int stbi__parse_uncompressed_block(stbi__zbuf *a) -{ - stbi_uc header[4]; - int len,nlen,k; - if (a->num_bits & 7) - stbi__zreceive(a, a->num_bits & 7); // discard - // drain the bit-packed data into header - k = 0; - while (a->num_bits > 0) { - header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check - a->code_buffer >>= 8; - a->num_bits -= 8; - } - if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); - // now fill header the normal way - while (k < 4) - header[k++] = stbi__zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!stbi__zexpand(a, a->zout, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int stbi__parse_zlib_header(stbi__zbuf *a) -{ - int cmf = stbi__zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = stbi__zget8(a); - if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png - if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = -{ - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 -}; -static const stbi_uc stbi__zdefault_distance[32] = -{ - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 -}; -/* -Init algorithm: -{ - int i; // use <= to match clearly with spec - for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; - for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; - for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; - for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; - - for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; -} -*/ - -static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) -{ - int final, type; - if (parse_header) - if (!stbi__parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = stbi__zreceive(a,1); - type = stbi__zreceive(a,2); - if (type == 0) { - if (!stbi__parse_uncompressed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - // use fixed code lengths - if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; - } else { - if (!stbi__compute_huffman_codes(a)) return 0; - } - if (!stbi__parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) -{ - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - - return stbi__parse_zlib(a, parse_header); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) -{ - return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(16384); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer+len; - if (stbi__do_zlib(&a, p, 16384, 1, 0)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) - return (int) (a.zout - a.zout_start); - else - return -1; -} -#endif - -// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 -// simple implementation -// - only 8-bit samples -// - no CRC checking -// - allocates lots of intermediate memory -// - avoids problem of streaming data between subsystems -// - avoids explicit window management -// performance -// - uses stb_zlib, a PD zlib implementation with fast huffman decoding - -#ifndef STBI_NO_PNG -typedef struct -{ - stbi__uint32 length; - stbi__uint32 type; -} stbi__pngchunk; - -static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) -{ - stbi__pngchunk c; - c.length = stbi__get32be(s); - c.type = stbi__get32be(s); - return c; -} - -static int stbi__check_png_header(stbi__context *s) -{ - static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; - int i; - for (i=0; i < 8; ++i) - if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); - return 1; -} - -typedef struct -{ - stbi__context *s; - stbi_uc *idata, *expanded, *out; - int depth; -} stbi__png; - - -enum { - STBI__F_none=0, - STBI__F_sub=1, - STBI__F_up=2, - STBI__F_avg=3, - STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static stbi_uc first_row_filter[5] = -{ - STBI__F_none, - STBI__F_sub, - STBI__F_none, - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static int stbi__paeth(int a, int b, int c) -{ - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; - -// create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) -{ - int bytes = (depth == 16? 2 : 1); - stbi__context *s = a->s; - stbi__uint32 i,j,stride = x*out_n*bytes; - stbi__uint32 img_len, img_width_bytes; - int k; - int img_n = s->img_n; // copy it into a local for later - - int output_bytes = out_n*bytes; - int filter_bytes = img_n*bytes; - int width = x; - - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into - if (!a->out) return stbi__err("outofmem", "Out of memory"); - - if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); - img_width_bytes = (((img_n * x * depth) + 7) >> 3); - img_len = (img_width_bytes + 1) * y; - - // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, - // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), - // so just check for raw_len < img_len always. - if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); - - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; - int filter = *raw++; - - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; - } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above - - // if first row, use special filter that doesn't sample previous row - if (j == 0) filter = first_row_filter[filter]; - - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; - } - - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } - - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop - stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range - - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - - if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); - } - if (k > 0) *cur++ = scale * ((*in >> 4) ); - } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); - } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); - } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); - } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; - if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; - } - } else { - STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; - } - } - } - } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } - } - - return 1; -} - -static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) -{ - int bytes = (depth == 16 ? 2 : 1); - int out_bytes = out_n * bytes; - stbi_uc *final; - int p; - if (!interlaced) - return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); - - // de-interlacing - final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); - if (!final) return stbi__err("outofmem", "Out of memory"); - for (p=0; p < 7; ++p) { - int xorig[] = { 0,4,0,2,0,1,0 }; - int yorig[] = { 0,0,4,0,2,0,1 }; - int xspc[] = { 8,8,4,4,2,2,1 }; - int yspc[] = { 8,8,8,4,4,2,2 }; - int i,j,x,y; - // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 - x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; - y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; - if (x && y) { - stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; - if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { - STBI_FREE(final); - return 0; - } - for (j=0; j < y; ++j) { - for (i=0; i < x; ++i) { - int out_y = j*yspc[p]+yorig[p]; - int out_x = i*xspc[p]+xorig[p]; - memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, - a->out + (j*x+i)*out_bytes, out_bytes); - } - } - STBI_FREE(a->out); - image_data += img_len; - image_data_len -= img_len; - } - } - a->out = final; - - return 1; -} - -static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - // compute color-based transparency, assuming we've - // already got 255 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i=0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i=0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi__uint16 *p = (stbi__uint16*) z->out; - - // compute color-based transparency, assuming we've - // already got 65535 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i = 0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 65535); - p += 2; - } - } else { - for (i = 0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) -{ - stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; - stbi_uc *p, *temp_out, *orig = a->out; - - p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); - if (p == NULL) return stbi__err("outofmem", "Out of memory"); - - // between here and free(out) below, exitting would leak - temp_out = p; - - if (pal_img_n == 3) { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p += 3; - } - } else { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p[3] = palette[n+3]; - p += 4; - } - } - STBI_FREE(a->out); - a->out = temp_out; - - STBI_NOTUSED(len); - - return 1; -} - -static int stbi__unpremultiply_on_load_global = 0; -static int stbi__de_iphone_flag_global = 0; - -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag_global = flag_true_if_should_convert; -} - -#ifndef STBI_THREAD_LOCAL -#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global -#define stbi__de_iphone_flag stbi__de_iphone_flag_global -#else -static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; -static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; - -STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; - stbi__unpremultiply_on_load_set = 1; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag_local = flag_true_if_should_convert; - stbi__de_iphone_flag_set = 1; -} - -#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ - ? stbi__unpremultiply_on_load_local \ - : stbi__unpremultiply_on_load_global) -#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ - ? stbi__de_iphone_flag_local \ - : stbi__de_iphone_flag_global) -#endif // STBI_THREAD_LOCAL - -static void stbi__de_iphone(stbi__png *z) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - if (s->img_out_n == 3) { // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 3; - } - } else { - STBI_ASSERT(s->img_out_n == 4); - if (stbi__unpremultiply_on_load) { - // convert bgr to rgb and unpremultiply - for (i=0; i < pixel_count; ++i) { - stbi_uc a = p[3]; - stbi_uc t = p[0]; - if (a) { - stbi_uc half = a / 2; - p[0] = (p[2] * 255 + half) / a; - p[1] = (p[1] * 255 + half) / a; - p[2] = ( t * 255 + half) / a; - } else { - p[0] = p[2]; - p[2] = t; - } - p += 4; - } - } else { - // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 4; - } - } - } -} - -#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) - -static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) -{ - stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]={0}; - stbi__uint16 tc16[3]; - stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, color=0, is_iphone=0; - stbi__context *s = z->s; - - z->expanded = NULL; - z->idata = NULL; - z->out = NULL; - - if (!stbi__check_png_header(s)) return 0; - - if (scan == STBI__SCAN_type) return 1; - - for (;;) { - stbi__pngchunk c = stbi__get_chunk_header(s); - switch (c.type) { - case STBI__PNG_TYPE('C','g','B','I'): - is_iphone = 1; - stbi__skip(s, c.length); - break; - case STBI__PNG_TYPE('I','H','D','R'): { - int comp,filter; - if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); - first = 0; - if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); - s->img_x = stbi__get32be(s); - s->img_y = stbi__get32be(s); - if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); - color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); - comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); - filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); - interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); - if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; - } else { - // if paletted, then pal_n is our final components, and - // img_n is # components to decompress/filter. - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS - } - break; - } - - case STBI__PNG_TYPE('P','L','T','E'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); - for (i=0; i < pal_len; ++i) { - palette[i*4+0] = stbi__get8(s); - palette[i*4+1] = stbi__get8(s); - palette[i*4+2] = stbi__get8(s); - palette[i*4+3] = 255; - } - break; - } - - case STBI__PNG_TYPE('t','R','N','S'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); - if (pal_img_n) { - if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } - if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); - if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); - pal_img_n = 4; - for (i=0; i < c.length; ++i) - palette[i*4+3] = stbi__get8(s); - } else { - if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); - if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); - has_trans = 1; - if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is - } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger - } - } - break; - } - - case STBI__PNG_TYPE('I','D','A','T'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } - if ((int)(ioff + c.length) < (int)ioff) return 0; - if (ioff + c.length > idata_limit) { - stbi__uint32 idata_limit_old = idata_limit; - stbi_uc *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - STBI_NOTUSED(idata_limit_old); - p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); - z->idata = p; - } - if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); - ioff += c.length; - break; - } - - case STBI__PNG_TYPE('I','E','N','D'): { - stbi__uint32 raw_len, bpl; - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (scan != STBI__SCAN_load) return 1; - if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); - // initial guess for decoded data size to avoid unnecessary reallocs - bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component - raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; - z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); - if (z->expanded == NULL) return 0; // zlib should set error - STBI_FREE(z->idata); z->idata = NULL; - if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n+1; - else - s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; - if (has_trans) { - if (z->depth == 16) { - if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; - } else { - if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; - } - } - if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) - stbi__de_iphone(z); - if (pal_img_n) { - // pal_img_n == 3 or 4 - s->img_n = pal_img_n; // record the actual colors we had - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } else if (has_trans) { - // non-paletted image with tRNS -> source image has (constant) alpha - ++s->img_n; - } - STBI_FREE(z->expanded); z->expanded = NULL; - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - return 1; - } - - default: - // if critical, fail - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if ((c.type & (1 << 29)) == 0) { - #ifndef STBI_NO_FAILURE_STRINGS - // not threadsafe - static char invalid_chunk[] = "XXXX PNG chunk not known"; - invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); - invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); - invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); - invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); - #endif - return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); - } - stbi__skip(s, c.length); - break; - } - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - } -} - -static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) -{ - void *result=NULL; - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - if (p->depth <= 8) - ri->bits_per_channel = 8; - else if (p->depth == 16) - ri->bits_per_channel = 16; - else - return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s->img_out_n) { - if (ri->bits_per_channel == 8) - result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - else - result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - p->s->img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s->img_x; - *y = p->s->img_y; - if (n) *n = p->s->img_n; - } - STBI_FREE(p->out); p->out = NULL; - STBI_FREE(p->expanded); p->expanded = NULL; - STBI_FREE(p->idata); p->idata = NULL; - - return result; -} - -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi__png p; - p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp, ri); -} - -static int stbi__png_test(stbi__context *s) -{ - int r; - r = stbi__check_png_header(s); - stbi__rewind(s); - return r; -} - -static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) -{ - if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { - stbi__rewind( p->s ); - return 0; - } - if (x) *x = p->s->img_x; - if (y) *y = p->s->img_y; - if (comp) *comp = p->s->img_n; - return 1; -} - -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__png p; - p.s = s; - return stbi__png_info_raw(&p, x, y, comp); -} - -static int stbi__png_is16(stbi__context *s) -{ - stbi__png p; - p.s = s; - if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) - return 0; - if (p.depth != 16) { - stbi__rewind(p.s); - return 0; - } - return 1; -} -#endif - -// Microsoft/Windows BMP image - -#ifndef STBI_NO_BMP -static int stbi__bmp_test_raw(stbi__context *s) -{ - int r; - int sz; - if (stbi__get8(s) != 'B') return 0; - if (stbi__get8(s) != 'M') return 0; - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - stbi__get32le(s); // discard data offset - sz = stbi__get32le(s); - r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); - return r; -} - -static int stbi__bmp_test(stbi__context *s) -{ - int r = stbi__bmp_test_raw(s); - stbi__rewind(s); - return r; -} - - -// returns 0..31 for the highest set bit -static int stbi__high_bit(unsigned int z) -{ - int n=0; - if (z == 0) return -1; - if (z >= 0x10000) { n += 16; z >>= 16; } - if (z >= 0x00100) { n += 8; z >>= 8; } - if (z >= 0x00010) { n += 4; z >>= 4; } - if (z >= 0x00004) { n += 2; z >>= 2; } - if (z >= 0x00002) { n += 1;/* >>= 1;*/ } - return n; -} - -static int stbi__bitcount(unsigned int a) -{ - a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 - a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits - a = (a + (a >> 8)); // max 16 per 8 bits - a = (a + (a >> 16)); // max 32 per 8 bits - return a & 0xff; -} - -// extract an arbitrarily-aligned N-bit value (N=bits) -// from v, and then make it 8-bits long and fractionally -// extend it to full full range. -static int stbi__shiftsigned(unsigned int v, int shift, int bits) -{ - static unsigned int mul_table[9] = { - 0, - 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, - 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, - }; - static unsigned int shift_table[9] = { - 0, 0,0,1,0,2,4,6,0, - }; - if (shift < 0) - v <<= -shift; - else - v >>= shift; - STBI_ASSERT(v < 256); - v >>= (8-bits); - STBI_ASSERT(bits >= 0 && bits <= 8); - return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; -} - -typedef struct -{ - int bpp, offset, hsz; - unsigned int mr,mg,mb,ma, all_a; - int extra_read; -} stbi__bmp_data; - -static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) -{ - // BI_BITFIELDS specifies masks explicitly, don't override - if (compress == 3) - return 1; - - if (compress == 0) { - if (info->bpp == 16) { - info->mr = 31u << 10; - info->mg = 31u << 5; - info->mb = 31u << 0; - } else if (info->bpp == 32) { - info->mr = 0xffu << 16; - info->mg = 0xffu << 8; - info->mb = 0xffu << 0; - info->ma = 0xffu << 24; - info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 - } else { - // otherwise, use defaults, which is all-0 - info->mr = info->mg = info->mb = info->ma = 0; - } - return 1; - } - return 0; // error -} - -static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) -{ - int hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - info->offset = stbi__get32le(s); - info->hsz = hsz = stbi__get32le(s); - info->mr = info->mg = info->mb = info->ma = 0; - info->extra_read = 14; - - if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); - - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); - if (hsz == 12) { - s->img_x = stbi__get16le(s); - s->img_y = stbi__get16le(s); - } else { - s->img_x = stbi__get32le(s); - s->img_y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); - info->bpp = stbi__get16le(s); - if (hsz != 12) { - int compress = stbi__get32le(s); - if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); - if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes - if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel - stbi__get32le(s); // discard sizeof - stbi__get32le(s); // discard hres - stbi__get32le(s); // discard vres - stbi__get32le(s); // discard colorsused - stbi__get32le(s); // discard max important - if (hsz == 40 || hsz == 56) { - if (hsz == 56) { - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - } - if (info->bpp == 16 || info->bpp == 32) { - if (compress == 0) { - stbi__bmp_set_mask_defaults(info, compress); - } else if (compress == 3) { - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->extra_read += 12; - // not documented, but generated by photoshop and handled by mspaint - if (info->mr == info->mg && info->mg == info->mb) { - // ?!?!? - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else { - // V4/V5 header - int i; - if (hsz != 108 && hsz != 124) - return stbi__errpuc("bad BMP", "bad BMP"); - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->ma = stbi__get32le(s); - if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs - stbi__bmp_set_mask_defaults(info, compress); - stbi__get32le(s); // discard color space - for (i=0; i < 12; ++i) - stbi__get32le(s); // discard color space parameters - if (hsz == 124) { - stbi__get32le(s); // discard rendering intent - stbi__get32le(s); // discard offset of profile data - stbi__get32le(s); // discard size of profile data - stbi__get32le(s); // discard reserved - } - } - } - return (void *) 1; -} - - -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - unsigned int mr=0,mg=0,mb=0,ma=0, all_a; - stbi_uc pal[256][4]; - int psize=0,i,j,width; - int flip_vertically, pad, target; - stbi__bmp_data info; - STBI_NOTUSED(ri); - - info.all_a = 255; - if (stbi__bmp_parse_header(s, &info) == NULL) - return NULL; // error code already set - - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - - if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - mr = info.mr; - mg = info.mg; - mb = info.mb; - ma = info.ma; - all_a = info.all_a; - - if (info.hsz == 12) { - if (info.bpp < 24) - psize = (info.offset - info.extra_read - 24) / 3; - } else { - if (info.bpp < 16) - psize = (info.offset - info.extra_read - info.hsz) >> 2; - } - if (psize == 0) { - if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { - return stbi__errpuc("bad offset", "Corrupt BMP"); - } - } - - if (info.bpp == 24 && ma == 0xff000000) - s->img_n = 3; - else - s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 - target = req_comp; - else - target = s->img_n; // if they want monochrome, we'll post-convert - - // sanity-check size - if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "Corrupt BMP"); - - out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - if (info.bpp < 16) { - int z=0; - if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } - for (i=0; i < psize; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - if (info.hsz != 12) stbi__get8(s); - pal[i][3] = 255; - } - stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); - if (info.bpp == 1) width = (s->img_x + 7) >> 3; - else if (info.bpp == 4) width = (s->img_x + 1) >> 1; - else if (info.bpp == 8) width = s->img_x; - else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } - pad = (-width)&3; - if (info.bpp == 1) { - for (j=0; j < (int) s->img_y; ++j) { - int bit_offset = 7, v = stbi__get8(s); - for (i=0; i < (int) s->img_x; ++i) { - int color = (v>>bit_offset)&0x1; - out[z++] = pal[color][0]; - out[z++] = pal[color][1]; - out[z++] = pal[color][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - if((--bit_offset) < 0) { - bit_offset = 7; - v = stbi__get8(s); - } - } - stbi__skip(s, pad); - } - } else { - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (info.bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (info.bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - } - stbi__skip(s, pad); - } - } - } else { - int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; - int z = 0; - int easy=0; - stbi__skip(s, info.offset - info.extra_read - info.hsz); - if (info.bpp == 24) width = 3 * s->img_x; - else if (info.bpp == 16) width = 2*s->img_x; - else /* bpp = 32 and pad = 0 */ width=0; - pad = (-width) & 3; - if (info.bpp == 24) { - easy = 1; - } else if (info.bpp == 32) { - if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) - easy = 2; - } - if (!easy) { - if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - // right shift amt to put high bit in position #7 - rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); - gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); - bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); - ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); - if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - } - for (j=0; j < (int) s->img_y; ++j) { - if (easy) { - for (i=0; i < (int) s->img_x; ++i) { - unsigned char a; - out[z+2] = stbi__get8(s); - out[z+1] = stbi__get8(s); - out[z+0] = stbi__get8(s); - z += 3; - a = (easy == 2 ? stbi__get8(s) : 255); - all_a |= a; - if (target == 4) out[z++] = a; - } - } else { - int bpp = info.bpp; - for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); - unsigned int a; - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); - a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); - all_a |= a; - if (target == 4) out[z++] = STBI__BYTECAST(a); - } - } - stbi__skip(s, pad); - } - } - - // if alpha channel is all 0s, replace with all 255s - if (target == 4 && all_a == 0) - for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) - out[i] = 255; - - if (flip_vertically) { - stbi_uc t; - for (j=0; j < (int) s->img_y>>1; ++j) { - stbi_uc *p1 = out + j *s->img_x*target; - stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; - for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i]; p1[i] = p2[i]; p2[i] = t; - } - } - } - - if (req_comp && req_comp != target) { - out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - return out; -} -#endif - -// Targa Truevision - TGA -// by Jonathan Dummer -#ifndef STBI_NO_TGA -// returns STBI_rgb or whatever, 0 on error -static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) -{ - // only RGB or RGBA (incl. 16bit) or grey allowed - if (is_rgb16) *is_rgb16 = 0; - switch(bits_per_pixel) { - case 8: return STBI_grey; - case 16: if(is_grey) return STBI_grey_alpha; - // fallthrough - case 15: if(is_rgb16) *is_rgb16 = 1; - return STBI_rgb; - case 24: // fallthrough - case 32: return bits_per_pixel/8; - default: return 0; - } -} - -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) -{ - int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; - int sz, tga_colormap_type; - stbi__get8(s); // discard Offset - tga_colormap_type = stbi__get8(s); // colormap type - if( tga_colormap_type > 1 ) { - stbi__rewind(s); - return 0; // only RGB or indexed allowed - } - tga_image_type = stbi__get8(s); // image type - if ( tga_colormap_type == 1 ) { // colormapped (paletted) image - if (tga_image_type != 1 && tga_image_type != 9) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip image x and y origin - tga_colormap_bpp = sz; - } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE - if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { - stbi__rewind(s); - return 0; // only RGB or grey allowed, +/- RLE - } - stbi__skip(s,9); // skip colormap specification and image x/y origin - tga_colormap_bpp = 0; - } - tga_w = stbi__get16le(s); - if( tga_w < 1 ) { - stbi__rewind(s); - return 0; // test width - } - tga_h = stbi__get16le(s); - if( tga_h < 1 ) { - stbi__rewind(s); - return 0; // test height - } - tga_bits_per_pixel = stbi__get8(s); // bits per pixel - stbi__get8(s); // ignore alpha bits - if (tga_colormap_bpp != 0) { - if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { - // when using a colormap, tga_bits_per_pixel is the size of the indexes - // I don't think anything but 8 or 16bit indexes makes sense - stbi__rewind(s); - return 0; - } - tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); - } else { - tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); - } - if(!tga_comp) { - stbi__rewind(s); - return 0; - } - if (x) *x = tga_w; - if (y) *y = tga_h; - if (comp) *comp = tga_comp; - return 1; // seems to have passed everything -} - -static int stbi__tga_test(stbi__context *s) -{ - int res = 0; - int sz, tga_color_type; - stbi__get8(s); // discard Offset - tga_color_type = stbi__get8(s); // color type - if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed - sz = stbi__get8(s); // image type - if ( tga_color_type == 1 ) { // colormapped (paletted) image - if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - stbi__skip(s,4); // skip image x and y origin - } else { // "normal" image w/o colormap - if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE - stbi__skip(s,9); // skip colormap specification and image x/y origin - } - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height - sz = stbi__get8(s); // bits per pixel - if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - - res = 1; // if we got this far, everything's good and we can return 1 instead of 0 - -errorEnd: - stbi__rewind(s); - return res; -} - -// read 16bit value and convert to 24bit RGB -static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) -{ - stbi__uint16 px = (stbi__uint16)stbi__get16le(s); - stbi__uint16 fiveBitMask = 31; - // we have 3 channels with 5bits each - int r = (px >> 10) & fiveBitMask; - int g = (px >> 5) & fiveBitMask; - int b = px & fiveBitMask; - // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later - out[0] = (stbi_uc)((r * 255)/31); - out[1] = (stbi_uc)((g * 255)/31); - out[2] = (stbi_uc)((b * 255)/31); - - // some people claim that the most significant bit might be used for alpha - // (possibly if an alpha-bit is set in the "image descriptor byte") - // but that only made 16bit test images completely translucent.. - // so let's treat all 15 and 16bit TGAs as RGB with no alpha. -} - -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - // read in the TGA header stuff - int tga_offset = stbi__get8(s); - int tga_indexed = stbi__get8(s); - int tga_image_type = stbi__get8(s); - int tga_is_RLE = 0; - int tga_palette_start = stbi__get16le(s); - int tga_palette_len = stbi__get16le(s); - int tga_palette_bits = stbi__get8(s); - int tga_x_origin = stbi__get16le(s); - int tga_y_origin = stbi__get16le(s); - int tga_width = stbi__get16le(s); - int tga_height = stbi__get16le(s); - int tga_bits_per_pixel = stbi__get8(s); - int tga_comp, tga_rgb16=0; - int tga_inverted = stbi__get8(s); - // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) - // image data - unsigned char *tga_data; - unsigned char *tga_palette = NULL; - int i, j; - unsigned char raw_data[4] = {0}; - int RLE_count = 0; - int RLE_repeating = 0; - int read_next_pixel = 1; - STBI_NOTUSED(ri); - STBI_NOTUSED(tga_x_origin); // @TODO - STBI_NOTUSED(tga_y_origin); // @TODO - - if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - // do a tiny bit of precessing - if ( tga_image_type >= 8 ) - { - tga_image_type -= 8; - tga_is_RLE = 1; - } - tga_inverted = 1 - ((tga_inverted >> 5) & 1); - - // If I'm paletted, then I'll use the number of bits from the palette - if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); - else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); - - if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency - return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); - - // tga info - *x = tga_width; - *y = tga_height; - if (comp) *comp = tga_comp; - - if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) - return stbi__errpuc("too large", "Corrupt TGA"); - - tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); - if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); - - // skip to the data's starting position (offset usually = 0) - stbi__skip(s, tga_offset ); - - if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { - for (i=0; i < tga_height; ++i) { - int row = tga_inverted ? tga_height -i - 1 : i; - stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; - stbi__getn(s, tga_row, tga_width * tga_comp); - } - } else { - // do I need to load a palette? - if ( tga_indexed) - { - if (tga_palette_len == 0) { /* you have to have at least one entry! */ - STBI_FREE(tga_data); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - - // any data to skip? (offset usually = 0) - stbi__skip(s, tga_palette_start ); - // load the palette - tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); - if (!tga_palette) { - STBI_FREE(tga_data); - return stbi__errpuc("outofmem", "Out of memory"); - } - if (tga_rgb16) { - stbi_uc *pal_entry = tga_palette; - STBI_ASSERT(tga_comp == STBI_rgb); - for (i=0; i < tga_palette_len; ++i) { - stbi__tga_read_rgb16(s, pal_entry); - pal_entry += tga_comp; - } - } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { - STBI_FREE(tga_data); - STBI_FREE(tga_palette); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - } - // load the data - for (i=0; i < tga_width * tga_height; ++i) - { - // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? - if ( tga_is_RLE ) - { - if ( RLE_count == 0 ) - { - // yep, get the next byte as a RLE command - int RLE_cmd = stbi__get8(s); - RLE_count = 1 + (RLE_cmd & 127); - RLE_repeating = RLE_cmd >> 7; - read_next_pixel = 1; - } else if ( !RLE_repeating ) - { - read_next_pixel = 1; - } - } else - { - read_next_pixel = 1; - } - // OK, if I need to read a pixel, do it now - if ( read_next_pixel ) - { - // load however much data we did have - if ( tga_indexed ) - { - // read in index, then perform the lookup - int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); - if ( pal_idx >= tga_palette_len ) { - // invalid index - pal_idx = 0; - } - pal_idx *= tga_comp; - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = tga_palette[pal_idx+j]; - } - } else if(tga_rgb16) { - STBI_ASSERT(tga_comp == STBI_rgb); - stbi__tga_read_rgb16(s, raw_data); - } else { - // read in the data raw - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = stbi__get8(s); - } - } - // clear the reading flag for the next pixel - read_next_pixel = 0; - } // end of reading a pixel - - // copy data - for (j = 0; j < tga_comp; ++j) - tga_data[i*tga_comp+j] = raw_data[j]; - - // in case we're in RLE mode, keep counting down - --RLE_count; - } - // do I need to invert the image? - if ( tga_inverted ) - { - for (j = 0; j*2 < tga_height; ++j) - { - int index1 = j * tga_width * tga_comp; - int index2 = (tga_height - 1 - j) * tga_width * tga_comp; - for (i = tga_width * tga_comp; i > 0; --i) - { - unsigned char temp = tga_data[index1]; - tga_data[index1] = tga_data[index2]; - tga_data[index2] = temp; - ++index1; - ++index2; - } - } - } - // clear my palette, if I had one - if ( tga_palette != NULL ) - { - STBI_FREE( tga_palette ); - } - } - - // swap RGB - if the source data was RGB16, it already is in the right order - if (tga_comp >= 3 && !tga_rgb16) - { - unsigned char* tga_pixel = tga_data; - for (i=0; i < tga_width * tga_height; ++i) - { - unsigned char temp = tga_pixel[0]; - tga_pixel[0] = tga_pixel[2]; - tga_pixel[2] = temp; - tga_pixel += tga_comp; - } - } - - // convert to target component count - if (req_comp && req_comp != tga_comp) - tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); - - // the things I do to get rid of an error message, and yet keep - // Microsoft's C compilers happy... [8^( - tga_palette_start = tga_palette_len = tga_palette_bits = - tga_x_origin = tga_y_origin = 0; - STBI_NOTUSED(tga_palette_start); - // OK, done - return tga_data; -} -#endif - -// ************************************************************************************************* -// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s) -{ - int r = (stbi__get32be(s) == 0x38425053); - stbi__rewind(s); - return r; -} - -static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) -{ - int count, nleft, len; - - count = 0; - while ((nleft = pixelCount - count) > 0) { - len = stbi__get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - if (len > nleft) return 0; // corrupt data - count += len; - while (len) { - *p = stbi__get8(s); - p += 4; - len--; - } - } else if (len > 128) { - stbi_uc val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len = 257 - len; - if (len > nleft) return 0; // corrupt data - val = stbi__get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - - return 1; -} - -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - int pixelCount; - int channelCount, compression; - int channel, i; - int bitdepth; - int w,h; - stbi_uc *out; - STBI_NOTUSED(ri); - - // Check identifier - if (stbi__get32be(s) != 0x38425053) // "8BPS" - return stbi__errpuc("not PSD", "Corrupt PSD image"); - - // Check file type version. - if (stbi__get16be(s) != 1) - return stbi__errpuc("wrong version", "Unsupported version of PSD image"); - - // Skip 6 reserved bytes. - stbi__skip(s, 6 ); - - // Read the number of channels (R, G, B, A, etc). - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) - return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); - - // Read the rows and columns of the image. - h = stbi__get32be(s); - w = stbi__get32be(s); - - if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - // Make sure the depth is 8 bits. - bitdepth = stbi__get16be(s); - if (bitdepth != 8 && bitdepth != 16) - return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); - - // Make sure the color mode is RGB. - // Valid options are: - // 0: Bitmap - // 1: Grayscale - // 2: Indexed color - // 3: RGB color - // 4: CMYK color - // 7: Multichannel - // 8: Duotone - // 9: Lab color - if (stbi__get16be(s) != 3) - return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); - - // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) - stbi__skip(s,stbi__get32be(s) ); - - // Skip the image resources. (resolution, pen tool paths, etc) - stbi__skip(s, stbi__get32be(s) ); - - // Skip the reserved data. - stbi__skip(s, stbi__get32be(s) ); - - // Find out if the data is compressed. - // Known values: - // 0: no compression - // 1: RLE compressed - compression = stbi__get16be(s); - if (compression > 1) - return stbi__errpuc("bad compression", "PSD has an unknown compression format"); - - // Check size - if (!stbi__mad3sizes_valid(4, w, h, 0)) - return stbi__errpuc("too large", "Corrupt PSD"); - - // Create the destination image. - - if (!compression && bitdepth == 16 && bpc == 16) { - out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); - ri->bits_per_channel = 16; - } else - out = (stbi_uc *) stbi__malloc(4 * w*h); - - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - pixelCount = w*h; - - // Initialize the data to zero. - //memset( out, 0, pixelCount * 4 ); - - // Finally, the image data. - if (compression) { - // RLE as used by .PSD and .TIFF - // Loop until you get the number of unpacked bytes you are expecting: - // Read the next source byte into n. - // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. - // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. - // Else if n is 128, noop. - // Endloop - - // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, - // which we're going to just skip. - stbi__skip(s, h * channelCount * 2 ); - - // Read the RLE data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out+channel; - if (channel >= channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++, p += 4) - *p = (channel == 3 ? 255 : 0); - } else { - // Read the RLE data. - if (!stbi__psd_decode_rle(s, p, pixelCount)) { - STBI_FREE(out); - return stbi__errpuc("corrupt", "bad RLE data"); - } - } - } - - } else { - // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. - - // Read the data by channel. - for (channel = 0; channel < 4; channel++) { - if (channel >= channelCount) { - // Fill this channel with default data. - if (bitdepth == 16 && bpc == 16) { - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - stbi__uint16 val = channel == 3 ? 65535 : 0; - for (i = 0; i < pixelCount; i++, q += 4) - *q = val; - } else { - stbi_uc *p = out+channel; - stbi_uc val = channel == 3 ? 255 : 0; - for (i = 0; i < pixelCount; i++, p += 4) - *p = val; - } - } else { - if (ri->bits_per_channel == 16) { // output bpc - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - for (i = 0; i < pixelCount; i++, q += 4) - *q = (stbi__uint16) stbi__get16be(s); - } else { - stbi_uc *p = out+channel; - if (bitdepth == 16) { // input bpc - for (i = 0; i < pixelCount; i++, p += 4) - *p = (stbi_uc) (stbi__get16be(s) >> 8); - } else { - for (i = 0; i < pixelCount; i++, p += 4) - *p = stbi__get8(s); - } - } - } - } - } - - // remove weird white matte from PSD - if (channelCount >= 4) { - if (ri->bits_per_channel == 16) { - for (i=0; i < w*h; ++i) { - stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; - if (pixel[3] != 0 && pixel[3] != 65535) { - float a = pixel[3] / 65535.0f; - float ra = 1.0f / a; - float inv_a = 65535.0f * (1 - ra); - pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); - pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); - pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); - } - } - } else { - for (i=0; i < w*h; ++i) { - unsigned char *pixel = out + 4*i; - if (pixel[3] != 0 && pixel[3] != 255) { - float a = pixel[3] / 255.0f; - float ra = 1.0f / a; - float inv_a = 255.0f * (1 - ra); - pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); - pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); - pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); - } - } - } - } - - // convert to desired output format - if (req_comp && req_comp != 4) { - if (ri->bits_per_channel == 16) - out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); - else - out = stbi__convert_format(out, 4, req_comp, w, h); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - if (comp) *comp = 4; - *y = h; - *x = w; - - return out; -} -#endif - -// ************************************************************************************************* -// Softimage PIC loader -// by Tom Seddon -// -// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format -// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ - -#ifndef STBI_NO_PIC -static int stbi__pic_is4(stbi__context *s,const char *str) -{ - int i; - for (i=0; i<4; ++i) - if (stbi__get8(s) != (stbi_uc)str[i]) - return 0; - - return 1; -} - -static int stbi__pic_test_core(stbi__context *s) -{ - int i; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) - return 0; - - for(i=0;i<84;++i) - stbi__get8(s); - - if (!stbi__pic_is4(s,"PICT")) - return 0; - - return 1; -} - -typedef struct -{ - stbi_uc size,type,channel; -} stbi__pic_packet; - -static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) -{ - int mask=0x80, i; - - for (i=0; i<4; ++i, mask>>=1) { - if (channel & mask) { - if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); - dest[i]=stbi__get8(s); - } - } - - return dest; -} - -static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) -{ - int mask=0x80,i; - - for (i=0;i<4; ++i, mask>>=1) - if (channel&mask) - dest[i]=src[i]; -} - -static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) -{ - int act_comp=0,num_packets=0,y,chained; - stbi__pic_packet packets[10]; - - // this will (should...) cater for even some bizarre stuff like having data - // for the same channel in multiple packets. - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return stbi__errpuc("bad format","too many packets"); - - packet = &packets[num_packets++]; - - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - - act_comp |= packet->channel; - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); - if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? - - for(y=0; ytype) { - default: - return stbi__errpuc("bad format","packet has bad compression type"); - - case 0: {//uncompressed - int x; - - for(x=0;xchannel,dest)) - return 0; - break; - } - - case 1://Pure RLE - { - int left=width, i; - - while (left>0) { - stbi_uc count,value[4]; - - count=stbi__get8(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); - - if (count > left) - count = (stbi_uc) left; - - if (!stbi__readval(s,packet->channel,value)) return 0; - - for(i=0; ichannel,dest,value); - left -= count; - } - } - break; - - case 2: {//Mixed RLE - int left=width; - while (left>0) { - int count = stbi__get8(s), i; - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); - - if (count >= 128) { // Repeated - stbi_uc value[4]; - - if (count==128) - count = stbi__get16be(s); - else - count -= 127; - if (count > left) - return stbi__errpuc("bad file","scanline overrun"); - - if (!stbi__readval(s,packet->channel,value)) - return 0; - - for(i=0;ichannel,dest,value); - } else { // Raw - ++count; - if (count>left) return stbi__errpuc("bad file","scanline overrun"); - - for(i=0;ichannel,dest)) - return 0; - } - left-=count; - } - break; - } - } - } - } - - return result; -} - -static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) -{ - stbi_uc *result; - int i, x,y, internal_comp; - STBI_NOTUSED(ri); - - if (!comp) comp = &internal_comp; - - for (i=0; i<92; ++i) - stbi__get8(s); - - x = stbi__get16be(s); - y = stbi__get16be(s); - - if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); - - stbi__get32be(s); //skip `ratio' - stbi__get16be(s); //skip `fields' - stbi__get16be(s); //skip `pad' - - // intermediate buffer is RGBA - result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); - if (!result) return stbi__errpuc("outofmem", "Out of memory"); - memset(result, 0xff, x*y*4); - - if (!stbi__pic_load_core(s,x,y,comp, result)) { - STBI_FREE(result); - result=0; - } - *px = x; - *py = y; - if (req_comp == 0) req_comp = *comp; - result=stbi__convert_format(result,4,req_comp,x,y); - - return result; -} - -static int stbi__pic_test(stbi__context *s) -{ - int r = stbi__pic_test_core(s); - stbi__rewind(s); - return r; -} -#endif - -// ************************************************************************************************* -// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb - -#ifndef STBI_NO_GIF -typedef struct -{ - stbi__int16 prefix; - stbi_uc first; - stbi_uc suffix; -} stbi__gif_lzw; - -typedef struct -{ - int w,h; - stbi_uc *out; // output buffer (always 4 components) - stbi_uc *background; // The current "background" as far as a gif is concerned - stbi_uc *history; - int flags, bgindex, ratio, transparent, eflags; - stbi_uc pal[256][4]; - stbi_uc lpal[256][4]; - stbi__gif_lzw codes[8192]; - stbi_uc *color_table; - int parse, step; - int lflags; - int start_x, start_y; - int max_x, max_y; - int cur_x, cur_y; - int line_size; - int delay; -} stbi__gif; - -static int stbi__gif_test_raw(stbi__context *s) -{ - int sz; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; - sz = stbi__get8(s); - if (sz != '9' && sz != '7') return 0; - if (stbi__get8(s) != 'a') return 0; - return 1; -} - -static int stbi__gif_test(stbi__context *s) -{ - int r = stbi__gif_test_raw(s); - stbi__rewind(s); - return r; -} - -static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) -{ - int i; - for (i=0; i < num_entries; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - pal[i][3] = transp == i ? 0 : 255; - } -} - -static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) -{ - stbi_uc version; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') - return stbi__err("not GIF", "Corrupt GIF"); - - version = stbi__get8(s); - if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); - if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); - - stbi__g_failure_reason = ""; - g->w = stbi__get16le(s); - g->h = stbi__get16le(s); - g->flags = stbi__get8(s); - g->bgindex = stbi__get8(s); - g->ratio = stbi__get8(s); - g->transparent = -1; - - if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); - - if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments - - if (is_info) return 1; - - if (g->flags & 0x80) - stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); - - return 1; -} - -static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); - if (!g) return stbi__err("outofmem", "Out of memory"); - if (!stbi__gif_header(s, g, comp, 1)) { - STBI_FREE(g); - stbi__rewind( s ); - return 0; - } - if (x) *x = g->w; - if (y) *y = g->h; - STBI_FREE(g); - return 1; -} - -static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) -{ - stbi_uc *p, *c; - int idx; - - // recurse to decode the prefixes, since the linked-list is backwards, - // and working backwards through an interleaved image would be nasty - if (g->codes[code].prefix >= 0) - stbi__out_gif_code(g, g->codes[code].prefix); - - if (g->cur_y >= g->max_y) return; - - idx = g->cur_x + g->cur_y; - p = &g->out[idx]; - g->history[idx / 4] = 1; - - c = &g->color_table[g->codes[code].suffix * 4]; - if (c[3] > 128) { // don't render transparent pixels; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; - } - g->cur_x += 4; - - if (g->cur_x >= g->max_x) { - g->cur_x = g->start_x; - g->cur_y += g->step; - - while (g->cur_y >= g->max_y && g->parse > 0) { - g->step = (1 << g->parse) * g->line_size; - g->cur_y = g->start_y + (g->step >> 1); - --g->parse; - } - } -} - -static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) -{ - stbi_uc lzw_cs; - stbi__int32 len, init_code; - stbi__uint32 first; - stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; - stbi__gif_lzw *p; - - lzw_cs = stbi__get8(s); - if (lzw_cs > 12) return NULL; - clear = 1 << lzw_cs; - first = 1; - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - bits = 0; - valid_bits = 0; - for (init_code = 0; init_code < clear; init_code++) { - g->codes[init_code].prefix = -1; - g->codes[init_code].first = (stbi_uc) init_code; - g->codes[init_code].suffix = (stbi_uc) init_code; - } - - // support no starting clear code - avail = clear+2; - oldcode = -1; - - len = 0; - for(;;) { - if (valid_bits < codesize) { - if (len == 0) { - len = stbi__get8(s); // start new block - if (len == 0) - return g->out; - } - --len; - bits |= (stbi__int32) stbi__get8(s) << valid_bits; - valid_bits += 8; - } else { - stbi__int32 code = bits & codemask; - bits >>= codesize; - valid_bits -= codesize; - // @OPTIMIZE: is there some way we can accelerate the non-clear path? - if (code == clear) { // clear code - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - avail = clear + 2; - oldcode = -1; - first = 0; - } else if (code == clear + 1) { // end of stream code - stbi__skip(s, len); - while ((len = stbi__get8(s)) > 0) - stbi__skip(s,len); - return g->out; - } else if (code <= avail) { - if (first) { - return stbi__errpuc("no clear code", "Corrupt GIF"); - } - - if (oldcode >= 0) { - p = &g->codes[avail++]; - if (avail > 8192) { - return stbi__errpuc("too many codes", "Corrupt GIF"); - } - - p->prefix = (stbi__int16) oldcode; - p->first = g->codes[oldcode].first; - p->suffix = (code == avail) ? p->first : g->codes[code].first; - } else if (code == avail) - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - - stbi__out_gif_code(g, (stbi__uint16) code); - - if ((avail & codemask) == 0 && avail <= 0x0FFF) { - codesize++; - codemask = (1 << codesize) - 1; - } - - oldcode = code; - } else { - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - } - } - } -} - -// this function is designed to support animated gifs, although stb_image doesn't support it -// two back is the image from two frames ago, used for a very specific disposal format -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) -{ - int dispose; - int first_frame; - int pi; - int pcount; - STBI_NOTUSED(req_comp); - - // on first frame, any non-written pixels get the background colour (non-transparent) - first_frame = 0; - if (g->out == 0) { - if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header - if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) - return stbi__errpuc("too large", "GIF image is too large"); - pcount = g->w * g->h; - g->out = (stbi_uc *) stbi__malloc(4 * pcount); - g->background = (stbi_uc *) stbi__malloc(4 * pcount); - g->history = (stbi_uc *) stbi__malloc(pcount); - if (!g->out || !g->background || !g->history) - return stbi__errpuc("outofmem", "Out of memory"); - - // image is treated as "transparent" at the start - ie, nothing overwrites the current background; - // background colour is only used for pixels that are not rendered first frame, after that "background" - // color refers to the color that was there the previous frame. - memset(g->out, 0x00, 4 * pcount); - memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) - memset(g->history, 0x00, pcount); // pixels that were affected previous frame - first_frame = 1; - } else { - // second frame - how do we dispose of the previous one? - dispose = (g->eflags & 0x1C) >> 2; - pcount = g->w * g->h; - - if ((dispose == 3) && (two_back == 0)) { - dispose = 2; // if I don't have an image to revert back to, default to the old background - } - - if (dispose == 3) { // use previous graphic - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); - } - } - } else if (dispose == 2) { - // restore what was changed last frame to background before that frame; - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); - } - } - } else { - // This is a non-disposal case eithe way, so just - // leave the pixels as is, and they will become the new background - // 1: do not dispose - // 0: not specified. - } - - // background is what out is after the undoing of the previou frame; - memcpy( g->background, g->out, 4 * g->w * g->h ); - } - - // clear my history; - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame - - for (;;) { - int tag = stbi__get8(s); - switch (tag) { - case 0x2C: /* Image Descriptor */ - { - stbi__int32 x, y, w, h; - stbi_uc *o; - - x = stbi__get16le(s); - y = stbi__get16le(s); - w = stbi__get16le(s); - h = stbi__get16le(s); - if (((x + w) > (g->w)) || ((y + h) > (g->h))) - return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); - - g->line_size = g->w * 4; - g->start_x = x * 4; - g->start_y = y * g->line_size; - g->max_x = g->start_x + w * 4; - g->max_y = g->start_y + h * g->line_size; - g->cur_x = g->start_x; - g->cur_y = g->start_y; - - // if the width of the specified rectangle is 0, that means - // we may not see *any* pixels or the image is malformed; - // to make sure this is caught, move the current y down to - // max_y (which is what out_gif_code checks). - if (w == 0) - g->cur_y = g->max_y; - - g->lflags = stbi__get8(s); - - if (g->lflags & 0x40) { - g->step = 8 * g->line_size; // first interlaced spacing - g->parse = 3; - } else { - g->step = g->line_size; - g->parse = 0; - } - - if (g->lflags & 0x80) { - stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); - g->color_table = (stbi_uc *) g->lpal; - } else if (g->flags & 0x80) { - g->color_table = (stbi_uc *) g->pal; - } else - return stbi__errpuc("missing color table", "Corrupt GIF"); - - o = stbi__process_gif_raster(s, g); - if (!o) return NULL; - - // if this was the first frame, - pcount = g->w * g->h; - if (first_frame && (g->bgindex > 0)) { - // if first frame, any pixel not drawn to gets the background color - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi] == 0) { - g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; - memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); - } - } - } - - return o; - } - - case 0x21: // Comment Extension. - { - int len; - int ext = stbi__get8(s); - if (ext == 0xF9) { // Graphic Control Extension. - len = stbi__get8(s); - if (len == 4) { - g->eflags = stbi__get8(s); - g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. - - // unset old transparent - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 255; - } - if (g->eflags & 0x01) { - g->transparent = stbi__get8(s); - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 0; - } - } else { - // don't need transparent - stbi__skip(s, 1); - g->transparent = -1; - } - } else { - stbi__skip(s, len); - break; - } - } - while ((len = stbi__get8(s)) != 0) { - stbi__skip(s, len); - } - break; - } - - case 0x3B: // gif stream termination code - return (stbi_uc *) s; // using '1' causes warning on some compilers - - default: - return stbi__errpuc("unknown code", "Corrupt GIF"); - } - } -} - -static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) -{ - STBI_FREE(g->out); - STBI_FREE(g->history); - STBI_FREE(g->background); - - if (out) STBI_FREE(out); - if (delays && *delays) STBI_FREE(*delays); - return stbi__errpuc("outofmem", "Out of memory"); -} - -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - if (stbi__gif_test(s)) { - int layers = 0; - stbi_uc *u = 0; - stbi_uc *out = 0; - stbi_uc *two_back = 0; - stbi__gif g; - int stride; - int out_size = 0; - int delays_size = 0; - - STBI_NOTUSED(out_size); - STBI_NOTUSED(delays_size); - - memset(&g, 0, sizeof(g)); - if (delays) { - *delays = 0; - } - - do { - u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - - if (u) { - *x = g.w; - *y = g.h; - ++layers; - stride = g.w * g.h * 4; - - if (out) { - void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); - if (!tmp) - return stbi__load_gif_main_outofmem(&g, out, delays); - else { - out = (stbi_uc*) tmp; - out_size = layers * stride; - } - - if (delays) { - int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); - if (!new_delays) - return stbi__load_gif_main_outofmem(&g, out, delays); - *delays = new_delays; - delays_size = layers * sizeof(int); - } - } else { - out = (stbi_uc*)stbi__malloc( layers * stride ); - if (!out) - return stbi__load_gif_main_outofmem(&g, out, delays); - out_size = layers * stride; - if (delays) { - *delays = (int*) stbi__malloc( layers * sizeof(int) ); - if (!*delays) - return stbi__load_gif_main_outofmem(&g, out, delays); - delays_size = layers * sizeof(int); - } - } - memcpy( out + ((layers - 1) * stride), u, stride ); - if (layers >= 2) { - two_back = out - 2 * stride; - } - - if (delays) { - (*delays)[layers - 1U] = g.delay; - } - } - } while (u != 0); - - // free temp buffer; - STBI_FREE(g.out); - STBI_FREE(g.history); - STBI_FREE(g.background); - - // do the final conversion after loading everything; - if (req_comp && req_comp != 4) - out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); - - *z = layers; - return out; - } else { - return stbi__errpuc("not GIF", "Image was not as a gif type."); - } -} - -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *u = 0; - stbi__gif g; - memset(&g, 0, sizeof(g)); - STBI_NOTUSED(ri); - - u = stbi__gif_load_next(s, &g, comp, req_comp, 0); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - if (u) { - *x = g.w; - *y = g.h; - - // moved conversion to after successful load so that the same - // can be done for multiple frames. - if (req_comp && req_comp != 4) - u = stbi__convert_format(u, 4, req_comp, g.w, g.h); - } else if (g.out) { - // if there was an error and we allocated an image buffer, free it! - STBI_FREE(g.out); - } - - // free buffers needed for multiple frame loading; - STBI_FREE(g.history); - STBI_FREE(g.background); - - return u; -} - -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) -{ - return stbi__gif_info_raw(s,x,y,comp); -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR loader -// originally by Nicolas Schulz -#ifndef STBI_NO_HDR -static int stbi__hdr_test_core(stbi__context *s, const char *signature) -{ - int i; - for (i=0; signature[i]; ++i) - if (stbi__get8(s) != signature[i]) - return 0; - stbi__rewind(s); - return 1; -} - -static int stbi__hdr_test(stbi__context* s) -{ - int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); - stbi__rewind(s); - if(!r) { - r = stbi__hdr_test_core(s, "#?RGBE\n"); - stbi__rewind(s); - } - return r; -} - -#define STBI__HDR_BUFLEN 1024 -static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) -{ - int len=0; - char c = '\0'; - - c = (char) stbi__get8(z); - - while (!stbi__at_eof(z) && c != '\n') { - buffer[len++] = c; - if (len == STBI__HDR_BUFLEN-1) { - // flush to end of line - while (!stbi__at_eof(z) && stbi__get8(z) != '\n') - ; - break; - } - c = (char) stbi__get8(z); - } - - buffer[len] = 0; - return buffer; -} - -static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) -{ - if ( input[3] != 0 ) { - float f1; - // Exponent - f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); - if (req_comp <= 2) - output[0] = (input[0] + input[1] + input[2]) * f1 / 3; - else { - output[0] = input[0] * f1; - output[1] = input[1] * f1; - output[2] = input[2] * f1; - } - if (req_comp == 2) output[1] = 1; - if (req_comp == 4) output[3] = 1; - } else { - switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ - case 3: output[0] = output[1] = output[2] = 0; - break; - case 2: output[1] = 1; /* fallthrough */ - case 1: output[0] = 0; - break; - } - } -} - -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - float *hdr_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - const char *headerToken; - STBI_NOTUSED(ri); - - // Check identifier - headerToken = stbi__hdr_gettoken(s,buffer); - if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) - return stbi__errpf("not HDR", "Corrupt HDR image"); - - // Parse header - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = (int) strtol(token, NULL, 10); - - if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); - if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); - - *x = width; - *y = height; - - if (comp) *comp = 3; - if (req_comp == 0) req_comp = 3; - - if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) - return stbi__errpf("too large", "HDR image is too large"); - - // Read data - hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); - if (!hdr_data) - return stbi__errpf("outofmem", "Out of memory"); - - // Load image data - // image data is stored as some number of sca - if ( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - stbi_uc rgbe[4]; - main_decode_loop: - stbi__getn(s, rgbe, 4); - stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); - } - } - } else { - // Read RLE-encoded data - scanline = NULL; - - for (j = 0; j < height; ++j) { - c1 = stbi__get8(s); - c2 = stbi__get8(s); - len = stbi__get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - stbi_uc rgbe[4]; - rgbe[0] = (stbi_uc) c1; - rgbe[1] = (stbi_uc) c2; - rgbe[2] = (stbi_uc) len; - rgbe[3] = (stbi_uc) stbi__get8(s); - stbi__hdr_convert(hdr_data, rgbe, req_comp); - i = 1; - j = 0; - STBI_FREE(scanline); - goto main_decode_loop; // yes, this makes no sense - } - len <<= 8; - len |= stbi__get8(s); - if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) { - scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); - if (!scanline) { - STBI_FREE(hdr_data); - return stbi__errpf("outofmem", "Out of memory"); - } - } - - for (k = 0; k < 4; ++k) { - int nleft; - i = 0; - while ((nleft = width - i) > 0) { - count = stbi__get8(s); - if (count > 128) { - // Run - value = stbi__get8(s); - count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = stbi__get8(s); - } - } - } - for (i=0; i < width; ++i) - stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); - } - if (scanline) - STBI_FREE(scanline); - } - - return hdr_data; -} - -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int dummy; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (stbi__hdr_test(s) == 0) { - stbi__rewind( s ); - return 0; - } - - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) { - stbi__rewind( s ); - return 0; - } - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *y = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *x = (int) strtol(token, NULL, 10); - *comp = 3; - return 1; -} -#endif // STBI_NO_HDR - -#ifndef STBI_NO_BMP -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) -{ - void *p; - stbi__bmp_data info; - - info.all_a = 255; - p = stbi__bmp_parse_header(s, &info); - if (p == NULL) { - stbi__rewind( s ); - return 0; - } - if (x) *x = s->img_x; - if (y) *y = s->img_y; - if (comp) { - if (info.bpp == 24 && info.ma == 0xff000000) - *comp = 3; - else - *comp = info.ma ? 4 : 3; - } - return 1; -} -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) -{ - int channelCount, dummy, depth; - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - *y = stbi__get32be(s); - *x = stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 8 && depth != 16) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 3) { - stbi__rewind( s ); - return 0; - } - *comp = 4; - return 1; -} - -static int stbi__psd_is16(stbi__context *s) -{ - int channelCount, depth; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - STBI_NOTUSED(stbi__get32be(s)); - STBI_NOTUSED(stbi__get32be(s)); - depth = stbi__get16be(s); - if (depth != 16) { - stbi__rewind( s ); - return 0; - } - return 1; -} -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) -{ - int act_comp=0,num_packets=0,chained,dummy; - stbi__pic_packet packets[10]; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { - stbi__rewind(s); - return 0; - } - - stbi__skip(s, 88); - - *x = stbi__get16be(s); - *y = stbi__get16be(s); - if (stbi__at_eof(s)) { - stbi__rewind( s); - return 0; - } - if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { - stbi__rewind( s ); - return 0; - } - - stbi__skip(s, 8); - - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return 0; - - packet = &packets[num_packets++]; - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - act_comp |= packet->channel; - - if (stbi__at_eof(s)) { - stbi__rewind( s ); - return 0; - } - if (packet->size != 8) { - stbi__rewind( s ); - return 0; - } - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); - - return 1; -} -#endif - -// ************************************************************************************************* -// Portable Gray Map and Portable Pixel Map loader -// by Ken Miller -// -// PGM: http://netpbm.sourceforge.net/doc/pgm.html -// PPM: http://netpbm.sourceforge.net/doc/ppm.html -// -// Known limitations: -// Does not support comments in the header section -// Does not support ASCII image data (formats P2 and P3) - -#ifndef STBI_NO_PNM - -static int stbi__pnm_test(stbi__context *s) -{ - char p, t; - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind( s ); - return 0; - } - return 1; -} - -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - STBI_NOTUSED(ri); - - ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); - if (ri->bits_per_channel == 0) - return 0; - - if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - - if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) - return stbi__errpuc("too large", "PNM too large"); - - out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); - - if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - return out; -} - -static int stbi__pnm_isspace(char c) -{ - return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; -} - -static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) -{ - for (;;) { - while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) - *c = (char) stbi__get8(s); - - if (stbi__at_eof(s) || *c != '#') - break; - - while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) - *c = (char) stbi__get8(s); - } -} - -static int stbi__pnm_isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int stbi__pnm_getinteger(stbi__context *s, char *c) -{ - int value = 0; - - while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { - value = value*10 + (*c - '0'); - *c = (char) stbi__get8(s); - } - - return value; -} - -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) -{ - int maxv, dummy; - char c, p, t; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - stbi__rewind(s); - - // Get identifier - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind(s); - return 0; - } - - *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm - - c = (char) stbi__get8(s); - stbi__pnm_skip_whitespace(s, &c); - - *x = stbi__pnm_getinteger(s, &c); // read width - stbi__pnm_skip_whitespace(s, &c); - - *y = stbi__pnm_getinteger(s, &c); // read height - stbi__pnm_skip_whitespace(s, &c); - - maxv = stbi__pnm_getinteger(s, &c); // read max value - if (maxv > 65535) - return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); - else if (maxv > 255) - return 16; - else - return 8; -} - -static int stbi__pnm_is16(stbi__context *s) -{ - if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) - return 1; - return 0; -} -#endif - -static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) -{ - #ifndef STBI_NO_JPEG - if (stbi__jpeg_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNG - if (stbi__png_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_GIF - if (stbi__gif_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_BMP - if (stbi__bmp_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PIC - if (stbi__pic_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNM - if (stbi__pnm_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_info(s, x, y, comp)) return 1; - #endif - - // test tga last because it's a crappy test! - #ifndef STBI_NO_TGA - if (stbi__tga_info(s, x, y, comp)) - return 1; - #endif - return stbi__err("unknown image type", "Image not of any known type, or corrupt"); -} - -static int stbi__is_16_main(stbi__context *s) -{ - #ifndef STBI_NO_PNG - if (stbi__png_is16(s)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_is16(s)) return 1; - #endif - - #ifndef STBI_NO_PNM - if (stbi__pnm_is16(s)) return 1; - #endif - return 0; -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_info_from_file(f, x, y, comp); - fclose(f); - return result; -} - -STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__info_main(&s,x,y,comp); - fseek(f,pos,SEEK_SET); - return r; -} - -STBIDEF int stbi_is_16_bit(char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_is_16_bit_from_file(f); - fclose(f); - return result; -} - -STBIDEF int stbi_is_16_bit_from_file(FILE *f) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__is_16_main(&s); - fseek(f,pos,SEEK_SET); - return r; -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__is_16_main(&s); -} - -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__is_16_main(&s); -} - -#endif // STB_IMAGE_IMPLEMENTATION - -/* - revision history: - 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug - 1-bit BMP - *_is_16_bit api - avoid warnings - 2.16 (2017-07-23) all functions have 16-bit variants; - STBI_NO_STDIO works again; - compilation fixes; - fix rounding in unpremultiply; - optimize vertical flip; - disable raw_len validation; - documentation fixes - 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; - warning fixes; disable run-time SSE detection on gcc; - uniform handling of optional "return" values; - thread-safe initialization of zlib tables - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) allocate large structures on the stack - remove white matting for transparent PSD - fix reported channel count for PNG & BMP - re-enable SSE2 in non-gcc 64-bit - support RGB-formatted JPEG - read 16-bit PNGs (only as 8-bit) - 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED - 2.09 (2016-01-16) allow comments in PNM files - 16-bit-per-pixel TGA (not bit-per-component) - info() for TGA could break due to .hdr handling - info() for BMP to shares code instead of sloppy parse - can use STBI_REALLOC_SIZED if allocator doesn't support realloc - code cleanup - 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA - 2.07 (2015-09-13) fix compiler warnings - partial animated GIF support - limited 16-bpc PSD support - #ifdef unused functions - bug with < 92 byte PIC,PNM,HDR,TGA - 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value - 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning - 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit - 2.03 (2015-04-12) extra corruption checking (mmozeiko) - stbi_set_flip_vertically_on_load (nguillemot) - fix NEON support; fix mingw support - 2.02 (2015-01-19) fix incorrect assert, fix warning - 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 - 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG - 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) - progressive JPEG (stb) - PGM/PPM support (Ken Miller) - STBI_MALLOC,STBI_REALLOC,STBI_FREE - GIF bugfix -- seemingly never worked - STBI_NO_*, STBI_ONLY_* - 1.48 (2014-12-14) fix incorrectly-named assert() - 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) - optimize PNG (ryg) - fix bug in interlaced PNG with user-specified channel count (stb) - 1.46 (2014-08-26) - fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG - 1.45 (2014-08-16) - fix MSVC-ARM internal compiler error by wrapping malloc - 1.44 (2014-08-07) - various warning fixes from Ronny Chevalier - 1.43 (2014-07-15) - fix MSVC-only compiler problem in code changed in 1.42 - 1.42 (2014-07-09) - don't define _CRT_SECURE_NO_WARNINGS (affects user code) - fixes to stbi__cleanup_jpeg path - added STBI_ASSERT to avoid requiring assert.h - 1.41 (2014-06-25) - fix search&replace from 1.36 that messed up comments/error messages - 1.40 (2014-06-22) - fix gcc struct-initialization warning - 1.39 (2014-06-15) - fix to TGA optimization when req_comp != number of components in TGA; - fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) - add support for BMP version 5 (more ignored fields) - 1.38 (2014-06-06) - suppress MSVC warnings on integer casts truncating values - fix accidental rename of 'skip' field of I/O - 1.37 (2014-06-04) - remove duplicate typedef - 1.36 (2014-06-03) - convert to header file single-file library - if de-iphone isn't set, load iphone images color-swapped instead of returning NULL - 1.35 (2014-05-27) - various warnings - fix broken STBI_SIMD path - fix bug where stbi_load_from_file no longer left file pointer in correct place - fix broken non-easy path for 32-bit BMP (possibly never used) - TGA optimization by Arseny Kapoulkine - 1.34 (unknown) - use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case - 1.33 (2011-07-14) - make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements - 1.32 (2011-07-13) - support for "info" function for all supported filetypes (SpartanJ) - 1.31 (2011-06-20) - a few more leak fixes, bug in PNG handling (SpartanJ) - 1.30 (2011-06-11) - added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) - removed deprecated format-specific test/load functions - removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway - error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) - fix inefficiency in decoding 32-bit BMP (David Woo) - 1.29 (2010-08-16) - various warning fixes from Aurelien Pocheville - 1.28 (2010-08-01) - fix bug in GIF palette transparency (SpartanJ) - 1.27 (2010-08-01) - cast-to-stbi_uc to fix warnings - 1.26 (2010-07-24) - fix bug in file buffering for PNG reported by SpartanJ - 1.25 (2010-07-17) - refix trans_data warning (Won Chun) - 1.24 (2010-07-12) - perf improvements reading from files on platforms with lock-heavy fgetc() - minor perf improvements for jpeg - deprecated type-specific functions so we'll get feedback if they're needed - attempt to fix trans_data warning (Won Chun) - 1.23 fixed bug in iPhone support - 1.22 (2010-07-10) - removed image *writing* support - stbi_info support from Jetro Lauha - GIF support from Jean-Marc Lienher - iPhone PNG-extensions from James Brown - warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) - 1.21 fix use of 'stbi_uc' in header (reported by jon blow) - 1.20 added support for Softimage PIC, by Tom Seddon - 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 (2008-08-02) - fix a threading bug (local mutable static) - 1.17 support interlaced PNG - 1.16 major bugfix - stbi__convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug - header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant - 0.50 (2006-11-19) - first released version -*/ - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/font/src/stb_leakcheck.h b/font/src/stb_leakcheck.h deleted file mode 100644 index 19ee6e7..0000000 --- a/font/src/stb_leakcheck.h +++ /dev/null @@ -1,194 +0,0 @@ -// stb_leakcheck.h - v0.6 - quick & dirty malloc leak-checking - public domain -// LICENSE -// -// See end of file. - -#ifdef STB_LEAKCHECK_IMPLEMENTATION -#undef STB_LEAKCHECK_IMPLEMENTATION // don't implement more than once - -// if we've already included leakcheck before, undefine the macros -#ifdef malloc -#undef malloc -#undef free -#undef realloc -#endif - -#ifndef STB_LEAKCHECK_OUTPUT_PIPE -#define STB_LEAKCHECK_OUTPUT_PIPE stdout -#endif - -#include -#include -#include -#include -#include -typedef struct malloc_info stb_leakcheck_malloc_info; - -struct malloc_info -{ - const char *file; - int line; - size_t size; - stb_leakcheck_malloc_info *next,*prev; -}; - -static stb_leakcheck_malloc_info *mi_head; - -void *stb_leakcheck_malloc(size_t sz, const char *file, int line) -{ - stb_leakcheck_malloc_info *mi = (stb_leakcheck_malloc_info *) malloc(sz + sizeof(*mi)); - if (mi == NULL) return mi; - mi->file = file; - mi->line = line; - mi->next = mi_head; - if (mi_head) - mi->next->prev = mi; - mi->prev = NULL; - mi->size = (int) sz; - mi_head = mi; - return mi+1; -} - -void stb_leakcheck_free(void *ptr) -{ - if (ptr != NULL) { - stb_leakcheck_malloc_info *mi = (stb_leakcheck_malloc_info *) ptr - 1; - mi->size = ~mi->size; - #ifndef STB_LEAKCHECK_SHOWALL - if (mi->prev == NULL) { - assert(mi_head == mi); - mi_head = mi->next; - } else - mi->prev->next = mi->next; - if (mi->next) - mi->next->prev = mi->prev; - free(mi); - #endif - } -} - -void *stb_leakcheck_realloc(void *ptr, size_t sz, const char *file, int line) -{ - if (ptr == NULL) { - return stb_leakcheck_malloc(sz, file, line); - } else if (sz == 0) { - stb_leakcheck_free(ptr); - return NULL; - } else { - stb_leakcheck_malloc_info *mi = (stb_leakcheck_malloc_info *) ptr - 1; - if (sz <= mi->size) - return ptr; - else { - #ifdef STB_LEAKCHECK_REALLOC_PRESERVE_MALLOC_FILELINE - void *q = stb_leakcheck_malloc(sz, mi->file, mi->line); - #else - void *q = stb_leakcheck_malloc(sz, file, line); - #endif - if (q) { - memcpy(q, ptr, mi->size); - stb_leakcheck_free(ptr); - } - return q; - } - } -} - -static void stblkck_internal_print(const char *reason, stb_leakcheck_malloc_info *mi) -{ -#if defined(_MSC_VER) && _MSC_VER < 1900 // 1900=VS 2015 - // Compilers that use the old MS C runtime library don't have %zd - // and the older ones don't even have %lld either... however, the old compilers - // without "long long" don't support 64-bit targets either, so here's the - // compromise: - #if _MSC_VER < 1400 // before VS 2005 - fprintf(STB_LEAKCHECK_OUTPUT_PIPE, "%s: %s (%4d): %8d bytes at %p\n", reason, mi->file, mi->line, (int)mi->size, (void*)(mi+1)); - #else - fprintf(STB_LEAKCHECK_OUTPUT_PIPE, "%s: %s (%4d): %16lld bytes at %p\n", reason, mi->file, mi->line, (long long)mi->size, (void*)(mi+1)); - #endif -#else - // Assume we have %zd on other targets. - #ifdef __MINGW32__ - __mingw_fprintf(STB_LEAKCHECK_OUTPUT_PIPE, "%s: %s (%4d): %zd bytes at %p\n", reason, mi->file, mi->line, mi->size, (void*)(mi+1)); - #else - fprintf(STB_LEAKCHECK_OUTPUT_PIPE, "%s: %s (%4d): %zd bytes at %p\n", reason, mi->file, mi->line, mi->size, (void*)(mi+1)); - #endif -#endif -} - -void stb_leakcheck_dumpmem(void) -{ - stb_leakcheck_malloc_info *mi = mi_head; - while (mi) { - if ((ptrdiff_t) mi->size >= 0) - stblkck_internal_print("LEAKED", mi); - mi = mi->next; - } - #ifdef STB_LEAKCHECK_SHOWALL - mi = mi_head; - while (mi) { - if ((ptrdiff_t) mi->size < 0) - stblkck_internal_print("FREED ", mi); - mi = mi->next; - } - #endif -} -#endif // STB_LEAKCHECK_IMPLEMENTATION - -#if !defined(INCLUDE_STB_LEAKCHECK_H) || !defined(malloc) -#define INCLUDE_STB_LEAKCHECK_H - -#include // we want to define the macros *after* stdlib to avoid a slew of errors - -#define malloc(sz) stb_leakcheck_malloc(sz, __FILE__, __LINE__) -#define free(p) stb_leakcheck_free(p) -#define realloc(p,sz) stb_leakcheck_realloc(p,sz, __FILE__, __LINE__) - -extern void * stb_leakcheck_malloc(size_t sz, const char *file, int line); -extern void * stb_leakcheck_realloc(void *ptr, size_t sz, const char *file, int line); -extern void stb_leakcheck_free(void *ptr); -extern void stb_leakcheck_dumpmem(void); - -#endif // INCLUDE_STB_LEAKCHECK_H - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/client/src/stddclmr.h b/shared/stddclmr.h similarity index 100% rename from client/src/stddclmr.h rename to shared/stddclmr.h diff --git a/shared/thirdparty/enet/.editorconfig b/shared/thirdparty/enet/.editorconfig new file mode 100644 index 0000000..89f760d --- /dev/null +++ b/shared/thirdparty/enet/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Set default charset +charset = utf-8 + +# 4 space indentation +indent_style = space +indent_size = 4 diff --git a/shared/thirdparty/enet/.gitignore b/shared/thirdparty/enet/.gitignore new file mode 100644 index 0000000..346d182 --- /dev/null +++ b/shared/thirdparty/enet/.gitignore @@ -0,0 +1,3 @@ +build/ +node_modules/ +.DS_store diff --git a/shared/thirdparty/enet/.travis.yml b/shared/thirdparty/enet/.travis.yml new file mode 100644 index 0000000..f15a486 --- /dev/null +++ b/shared/thirdparty/enet/.travis.yml @@ -0,0 +1,25 @@ +os: + - linux + - osx + +language: c +compiler: + - clang + +script: + - cmake . -DCMAKE_BUILD_TYPE=Release -DENET_TEST=1 -DENET_SHARED=1 + - cmake --build . + - ./enet_test + +before_deploy: + - export FILE_TO_UPLOAD=$(ls *.dylib *.so) + +deploy: + provider: releases + api_key: + - secure: "VZPCmJqX/MOBJv/k498MIrpKC7SBx+ZYqpOxo1Vp2dO3W8PzChCMePsuCdYL5ZvxLqNv9iClQ778HHQ8X976C7lIBv01p6jf/BBrvp8WguagYRIqQ+6Ehe3l1/I+6Yxx7MnzMZTTCO/EOUbVlfwF4Gql5ybvgH9Vs0CKr00jUaDPCI8pc0rV2WW97qFQ5irfFcvJx7XxuT7V48Ur27y+gkqV+L4YZthjwqN9fC5jULl2ha/WY5tRjv5gjpLpLPvBW94x3L8qET7/izP0g1MrjlPODwh6pr7o6I2SAlURREadgHdovLDm7K7N+DBVT0hZCWXlJ/Sh7Q9gs3CCAxtMyLDSoGjpWHBvRvJw702OtRxyIWriESq80kB8KUQ/piHB4DPMfDpkuWUaSNElX85NyW3fh5aXxlOvQf+CtABNACIuLC/oBshFguFgEu4kuqpFtmUG7EBssrEwnCiw2H3xpMC3G+ALEA03UKYJEZcyrmFo9PwIfaNf9ziJLzqq3haAN1/4pNyvl62MMPtNDyzXzVTWyL/Yi22tDIDgMGwCYpAyNoKHiyPpVGbOkL4561SN947g2TpIfxi6LlxzdLV6fPoPlLBsaha6LXV+YLNWsgpOJYK0zk8dxKNDzGxniSiPAaeEWhbc11UGJYgpk21ySus/lQfsQobEK/EqwOM5mpU=" + file_glob: true + file: "${FILE_TO_UPLOAD}" + skip_cleanup: true + on: + tags: true diff --git a/shared/thirdparty/enet/CMakeLists.txt b/shared/thirdparty/enet/CMakeLists.txt new file mode 100644 index 0000000..ba539ee --- /dev/null +++ b/shared/thirdparty/enet/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.0) +project(enet C) + +# defaults +set(ENET_STATIC ON) +set(ENET_SHARED OFF) +set(ENET_TEST OFF) + +# configure projects +if (ENET_STATIC) + add_library(enet STATIC test/library.c) + + if (WIN32) + target_link_libraries(enet PUBLIC winmm ws2_32) + endif() +endif() + +if (ENET_SHARED) + target_compile_definitions(enet PUBLIC -DENET_DLL) + add_library(enet SHARED test/library.c) + + if (WIN32) + target_link_libraries(enet PUBLIC winmm ws2_32) + endif() +endif() + +if (ENET_TEST) + add_executable(enet_test test/build.c) + target_include_directories(enet_test PRIVATE ${PROJECT_SOURCE_DIR}/include) + + if (WIN32) + target_link_libraries(enet_test PUBLIC winmm ws2_32) + endif() +endif() + + +if(MSVC) + target_compile_options(enet PRIVATE -W3) +else() + target_compile_options(enet PRIVATE -Wno-error) +endif() + +target_include_directories(enet PUBLIC ${PROJECT_SOURCE_DIR}/include) diff --git a/shared/thirdparty/enet/LICENSE b/shared/thirdparty/enet/LICENSE new file mode 100644 index 0000000..a588622 --- /dev/null +++ b/shared/thirdparty/enet/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2002-2016 Lee Salzman +Copyright (c) 2017-2021 Vladyslav Hrytsenko, Dominik Madarász + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/shared/thirdparty/enet/README.md b/shared/thirdparty/enet/README.md new file mode 100644 index 0000000..f3c6029 --- /dev/null +++ b/shared/thirdparty/enet/README.md @@ -0,0 +1,299 @@ +
+ ENet +
+ +
+ +
+ Build status + Build status + NPM version + Discord server + license +
+ +
+ +
+ ENet - Simple, lightweight and reliable UDP networking library written on pure C +
+ +
+ + Brought to you by + @lsalzman, + @inlife, + @zaklaus, + @nxrighthere + and other contributors! + +
+ +
+ +## Disclaimer + +This is a fork of the original library [lsalzman/enet](https://github.com/lsalzman/enet). While original repo offers a stable, time-tested wonderful library, +we are trying to change some things, things, which can't be reflected on the main repo, like: + +* integrated ipv6 support +* added monotonic time +* applied project-wide code style change +* cleaned up project +* single-header style code +* NPM package distribution +* removed a lot of older methods +* and many other various changes + +## Description + +ENet's purpose is to provide a relatively thin, simple and robust network communication +layer on top of UDP (User Datagram Protocol). The primary feature it provides is optional +reliable, in-order delivery of packets, and fragmentation. + +ENet omits certain higher level networking features such as authentication, lobbying, +server discovery, encryption, or other similar tasks that are particularly application +specific so that the library remains flexible, portable, and easily embeddable. + +## Installation (via npm) + +Install library using (omit `--save` if you don't have npm project initilized) + +```sh +$ npm install enet.c --save +``` + +Add include path to the library `node_modules/enet.c/include` to your makefile/ + +## Installation (manually) + +Download file [include/enet.h](https://raw.githubusercontent.com/zpl-c/enet/master/include/enet.h) and just add to your project. + +## Usage (Shared library) + +Build the shared library: + +```sh +$ mkdir build +$ cd build +$ cmake .. -DENET_SHARED=1 -DCMAKE_BUILD_TYPE=Release +$ cmake --build . +``` + +Use it: + +```c +#define ENET_DLL +#include +#include + +int main() { + if (enet_initialize () != 0) { + printf("An error occurred while initializing ENet.\n"); + return 1; + } + + enet_deinitialize(); + return 0; +} +``` + +## Usage (Static library) + +Build the static library: + +```sh +$ mkdir build +$ cd build +$ cmake .. -DENET_STATIC=1 -DCMAKE_BUILD_TYPE=Release +$ cmake --build . +``` + +Use it: + +```c +#include +#include + +int main() { + if (enet_initialize () != 0) { + printf("An error occurred while initializing ENet.\n"); + return 1; + } + + enet_deinitialize(); + return 0; +} +``` + +## Usage (Direct, Preferred) + +In this case, library will be embedded to the project itself. + +Make sure you add a define for `ENET_IMPLEMENTATION` exactly in one source file before including the `enet.h`. + +Here is a simple server and client demo, it will wait 1 second for events, and then exit if none were found: + +Server: + +```c +#define ENET_IMPLEMENTATION +#include +#include + +int main() { + if (enet_initialize () != 0) { + printf("An error occurred while initializing ENet.\n"); + return 1; + } + + ENetAddress address = {0}; + + address.host = ENET_HOST_ANY; /* Bind the server to the default localhost. */ + address.port = 7777; /* Bind the server to port 7777. */ + + #define MAX_CLIENTS 32 + + /* create a server */ + ENetHost * server = enet_host_create(&address, MAX_CLIENTS, 2, 0, 0); + + if (server == NULL) { + printf("An error occurred while trying to create an ENet server host.\n"); + return 1; + } + + printf("Started a server...\n"); + + ENetEvent event; + + /* Wait up to 1000 milliseconds for an event. (WARNING: blocking) */ + while (enet_host_service(server, &event, 1000) > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: + printf("A new client connected from %x:%u.\n", event.peer->address.host, event.peer->address.port); + /* Store any relevant client information here. */ + event.peer->data = "Client information"; + break; + + case ENET_EVENT_TYPE_RECEIVE: + printf("A packet of length %lu containing %s was received from %s on channel %u.\n", + event.packet->dataLength, + event.packet->data, + event.peer->data, + event.channelID); + /* Clean up the packet now that we're done using it. */ + enet_packet_destroy (event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + printf("%s disconnected.\n", event.peer->data); + /* Reset the peer's client information. */ + event.peer->data = NULL; + break; + + case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: + printf("%s disconnected due to timeout.\n", event.peer->data); + /* Reset the peer's client information. */ + event.peer->data = NULL; + break; + + case ENET_EVENT_TYPE_NONE: + break; + } + } + + enet_host_destroy(server); + enet_deinitialize(); + return 0; +} + +``` + +Client: + +```c +#include +#define ENET_IMPLEMENTATION +#include "enet.h" + +int main() { + if (enet_initialize() != 0) { + fprintf(stderr, "An error occurred while initializing ENet.\n"); + return EXIT_FAILURE; + } + + ENetHost* client = { 0 }; + client = enet_host_create(NULL /* create a client host */, + 1 /* only allow 1 outgoing connection */, + 2 /* allow up 2 channels to be used, 0 and 1 */, + 0 /* assume any amount of incoming bandwidth */, + 0 /* assume any amount of outgoing bandwidth */); + if (client == NULL) { + fprintf(stderr, + "An error occurred while trying to create an ENet client host.\n"); + exit(EXIT_FAILURE); + } + + ENetAddress address = { 0 }; + ENetEvent event = { 0 }; + ENetPeer* peer = { 0 }; + /* Connect to some.server.net:1234. */ + enet_address_set_host(&address, "127.0.0.1"); + address.port = 7777; + /* Initiate the connection, allocating the two channels 0 and 1. */ + peer = enet_host_connect(client, &address, 2, 0); + if (peer == NULL) { + fprintf(stderr, + "No available peers for initiating an ENet connection.\n"); + exit(EXIT_FAILURE); + } + /* Wait up to 5 seconds for the connection attempt to succeed. */ + if (enet_host_service(client, &event, 5000) > 0 && + event.type == ENET_EVENT_TYPE_CONNECT) { + puts("Connection to some.server.net:1234 succeeded."); + } else { + /* Either the 5 seconds are up or a disconnect event was */ + /* received. Reset the peer in the event the 5 seconds */ + /* had run out without any significant event. */ + enet_peer_reset(peer); + puts("Connection to some.server.net:1234 failed."); + } + + // Receive some events + enet_host_service(client, &event, 5000); + + // Disconnect + enet_peer_disconnect(peer, 0); + + uint8_t disconnected = false; + /* Allow up to 3 seconds for the disconnect to succeed + * and drop any packets received packets. + */ + while (enet_host_service(client, &event, 3000) > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_RECEIVE: + enet_packet_destroy(event.packet); + break; + case ENET_EVENT_TYPE_DISCONNECT: + puts("Disconnection succeeded."); + disconnected = true; + break; + } + } + + // Drop connection, since disconnection didn't successed + if (!disconnected) { + enet_peer_reset(peer); + } + + enet_host_destroy(client); + enet_deinitialize(); +} +``` + +## Tutorials + +More information, examples and tutorials can be found at the official site: http://enet.bespin.org/ + + + diff --git a/shared/thirdparty/enet/appveyor.yml b/shared/thirdparty/enet/appveyor.yml new file mode 100644 index 0000000..ac6ef1e --- /dev/null +++ b/shared/thirdparty/enet/appveyor.yml @@ -0,0 +1,30 @@ +image: Visual Studio 2017 + +platform: + - x64 + +configuration: + - Release + +before_build: + - cmake . -G "Visual Studio 15 2017 Win64" -DENET_SHARED=1 -DENET_TEST=1 + +build: + project: $(APPVEYOR_BUILD_FOLDER)\$(APPVEYOR_PROJECT_NAME).sln + +test_script: + - '%APPVEYOR_BUILD_FOLDER%\%CONFIGURATION%\enet_test.exe' + +artifacts: + - path: 'Release\*.dll' + name: Releases + +deploy: + provider: GitHub + auth_token: + secure: "tYm5oXMHHaO3oR5xd93zvnG95eqNZWw065Z9Qo6CAN3+2G7IlWbcmbsYlsl2XDFc" + artifact: /.*\.dll/ + draft: false + prerelease: false + on: + appveyor_repo_tag: true diff --git a/shared/thirdparty/enet/include/enet.h b/shared/thirdparty/enet/include/enet.h new file mode 100644 index 0000000..837c3f2 --- /dev/null +++ b/shared/thirdparty/enet/include/enet.h @@ -0,0 +1,6091 @@ +/** + * include/enet.h - a Single-Header auto-generated variant of enet.h library. + * + * Usage: + * #define ENET_IMPLEMENTATION exactly in ONE source file right BEFORE including the library, like: + * + * #define ENET_IMPLEMENTATION + * #include + * + * License: + * The MIT License (MIT) + * + * Copyright (c) 2002-2016 Lee Salzman + * Copyright (c) 2017-2021 Vladyslav Hrytsenko, Dominik Madarász + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#ifndef ENET_INCLUDE_H +#define ENET_INCLUDE_H + +#include +#include +#include +#include + +#define ENET_VERSION_MAJOR 2 +#define ENET_VERSION_MINOR 3 +#define ENET_VERSION_PATCH 0 +#define ENET_VERSION_CREATE(major, minor, patch) (((major)<<16) | ((minor)<<8) | (patch)) +#define ENET_VERSION_GET_MAJOR(version) (((version)>>16)&0xFF) +#define ENET_VERSION_GET_MINOR(version) (((version)>>8)&0xFF) +#define ENET_VERSION_GET_PATCH(version) ((version)&0xFF) +#define ENET_VERSION ENET_VERSION_CREATE(ENET_VERSION_MAJOR, ENET_VERSION_MINOR, ENET_VERSION_PATCH) + +#define ENET_TIME_OVERFLOW 86400000 +#define ENET_TIME_LESS(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW) +#define ENET_TIME_GREATER(a, b) ((b) - (a) >= ENET_TIME_OVERFLOW) +#define ENET_TIME_LESS_EQUAL(a, b) (! ENET_TIME_GREATER (a, b)) +#define ENET_TIME_GREATER_EQUAL(a, b) (! ENET_TIME_LESS (a, b)) +#define ENET_TIME_DIFFERENCE(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW ? (b) - (a) : (a) - (b)) + +// =======================================================================// +// ! +// ! System differences +// ! +// =======================================================================// + +#if defined(_WIN32) + #if defined(_MSC_VER) && defined(ENET_IMPLEMENTATION) + #pragma warning (disable: 4267) // size_t to int conversion + #pragma warning (disable: 4244) // 64bit to 32bit int + #pragma warning (disable: 4018) // signed/unsigned mismatch + #pragma warning (disable: 4146) // unary minus operator applied to unsigned type + #endif + + #ifndef ENET_NO_PRAGMA_LINK + #pragma comment(lib, "ws2_32.lib") + #pragma comment(lib, "winmm.lib") + #endif + + #if _MSC_VER >= 1910 + /* It looks like there were changes as of Visual Studio 2017 and there are no 32/64 bit + versions of _InterlockedExchange[operation], only InterlockedExchange[operation] + (without leading underscore), so we have to distinguish between compiler versions */ + #define NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + #endif + + #ifdef __GNUC__ + #if (_WIN32_WINNT < 0x0501) + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 + #endif + #endif + + #include + #include + #include + + #include + + #if defined(_WIN32) && defined(_MSC_VER) + #if _MSC_VER < 1900 + typedef struct timespec { + long tv_sec; + long tv_nsec; + }; + #endif + #define CLOCK_MONOTONIC 0 + #endif + + typedef SOCKET ENetSocket; + #define ENET_SOCKET_NULL INVALID_SOCKET + + #define ENET_HOST_TO_NET_16(value) (htons(value)) + #define ENET_HOST_TO_NET_32(value) (htonl(value)) + + #define ENET_NET_TO_HOST_16(value) (ntohs(value)) + #define ENET_NET_TO_HOST_32(value) (ntohl(value)) + + typedef struct { + size_t dataLength; + void * data; + } ENetBuffer; + + #define ENET_CALLBACK __cdecl + + #ifdef ENET_DLL + #ifdef ENET_IMPLEMENTATION + #define ENET_API __declspec( dllexport ) + #else + #define ENET_API __declspec( dllimport ) + #endif // ENET_IMPLEMENTATION + #else + #define ENET_API extern + #endif // ENET_DLL + + typedef fd_set ENetSocketSet; + + #define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO(&(sockset)) + #define ENET_SOCKETSET_ADD(sockset, socket) FD_SET(socket, &(sockset)) + #define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR(socket, &(sockset)) + #define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET(socket, &(sockset)) +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #ifdef __APPLE__ + #include + #include + #include + #endif + + #ifndef MSG_NOSIGNAL + #define MSG_NOSIGNAL 0 + #endif + + #ifdef MSG_MAXIOVLEN + #define ENET_BUFFER_MAXIMUM MSG_MAXIOVLEN + #endif + + typedef int ENetSocket; + + #define ENET_SOCKET_NULL -1 + + #define ENET_HOST_TO_NET_16(value) (htons(value)) /**< macro that converts host to net byte-order of a 16-bit value */ + #define ENET_HOST_TO_NET_32(value) (htonl(value)) /**< macro that converts host to net byte-order of a 32-bit value */ + + #define ENET_NET_TO_HOST_16(value) (ntohs(value)) /**< macro that converts net to host byte-order of a 16-bit value */ + #define ENET_NET_TO_HOST_32(value) (ntohl(value)) /**< macro that converts net to host byte-order of a 32-bit value */ + + typedef struct { + void * data; + size_t dataLength; + } ENetBuffer; + + #define ENET_CALLBACK + #define ENET_API extern + + typedef fd_set ENetSocketSet; + + #define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO(&(sockset)) + #define ENET_SOCKETSET_ADD(sockset, socket) FD_SET(socket, &(sockset)) + #define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR(socket, &(sockset)) + #define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET(socket, &(sockset)) +#endif + +#ifdef __GNUC__ +#define ENET_DEPRECATED(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define ENET_DEPRECATED(func) __declspec(deprecated) func +#else +#pragma message("WARNING: Please ENET_DEPRECATED for this compiler") +#define ENET_DEPRECATED(func) func +#endif + +#ifndef ENET_BUFFER_MAXIMUM +#define ENET_BUFFER_MAXIMUM (1 + 2 * ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS) +#endif + +#define ENET_UNUSED(x) (void)x; + +#define ENET_MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ENET_MIN(x, y) ((x) < (y) ? (x) : (y)) + +#define ENET_IPV6 1 +static const struct in6_addr enet_v4_anyaddr = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }}}; +static const struct in6_addr enet_v4_noaddr = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}; +static const struct in6_addr enet_v4_localhost = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01 }}}; +static const struct in6_addr enet_v6_anyaddr = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}}; +static const struct in6_addr enet_v6_noaddr = {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}; +static const struct in6_addr enet_v6_localhost = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}}; +#define ENET_HOST_ANY in6addr_any +#define ENET_HOST_BROADCAST 0xFFFFFFFFU +#define ENET_PORT_ANY 0 + +#ifdef __cplusplus +extern "C" { +#endif + +// =======================================================================// +// ! +// ! Basic stuff +// ! +// =======================================================================// + + typedef uint8_t enet_uint8; /**< unsigned 8-bit type */ + typedef uint16_t enet_uint16; /**< unsigned 16-bit type */ + typedef uint32_t enet_uint32; /**< unsigned 32-bit type */ + typedef uint64_t enet_uint64; /**< unsigned 64-bit type */ + + typedef enet_uint32 ENetVersion; + typedef struct _ENetPacket ENetPacket; + + typedef struct _ENetCallbacks { + void *(ENET_CALLBACK *malloc) (size_t size); + void (ENET_CALLBACK *free) (void *memory); + void (ENET_CALLBACK *no_memory) (void); + + ENetPacket *(ENET_CALLBACK *packet_create) (const void *data, size_t dataLength, enet_uint32 flags); + void (ENET_CALLBACK *packet_destroy) (ENetPacket *packet); + } ENetCallbacks; + + extern void *enet_malloc(size_t); + extern void enet_free(void *); + extern ENetPacket* enet_packet_create(const void*,size_t,enet_uint32); + extern ENetPacket* enet_packet_copy(ENetPacket*); + extern void enet_packet_destroy(ENetPacket*); + +// =======================================================================// +// ! +// ! List +// ! +// =======================================================================// + + typedef struct _ENetListNode { + struct _ENetListNode *next; + struct _ENetListNode *previous; + } ENetListNode; + + typedef ENetListNode *ENetListIterator; + + typedef struct _ENetList { + ENetListNode sentinel; + } ENetList; + + extern ENetListIterator enet_list_insert(ENetListIterator, void *); + extern ENetListIterator enet_list_move(ENetListIterator, void *, void *); + + extern void *enet_list_remove(ENetListIterator); + extern void enet_list_clear(ENetList *); + extern size_t enet_list_size(ENetList *); + + #define enet_list_begin(list) ((list)->sentinel.next) + #define enet_list_end(list) (&(list)->sentinel) + #define enet_list_empty(list) (enet_list_begin(list) == enet_list_end(list)) + #define enet_list_next(iterator) ((iterator)->next) + #define enet_list_previous(iterator) ((iterator)->previous) + #define enet_list_front(list) ((void *)(list)->sentinel.next) + #define enet_list_back(list) ((void *)(list)->sentinel.previous) + + +// =======================================================================// +// ! +// ! Protocol +// ! +// =======================================================================// + + enum { + ENET_PROTOCOL_MINIMUM_MTU = 576, + ENET_PROTOCOL_MAXIMUM_MTU = 4096, + ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS = 32, + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE = 4096, + ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE = 65536, + ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT = 1, + ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT = 255, + ENET_PROTOCOL_MAXIMUM_PEER_ID = 0xFFF, + ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT = 1024 * 1024 + }; + + typedef enum _ENetProtocolCommand { + ENET_PROTOCOL_COMMAND_NONE = 0, + ENET_PROTOCOL_COMMAND_ACKNOWLEDGE = 1, + ENET_PROTOCOL_COMMAND_CONNECT = 2, + ENET_PROTOCOL_COMMAND_VERIFY_CONNECT = 3, + ENET_PROTOCOL_COMMAND_DISCONNECT = 4, + ENET_PROTOCOL_COMMAND_PING = 5, + ENET_PROTOCOL_COMMAND_SEND_RELIABLE = 6, + ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE = 7, + ENET_PROTOCOL_COMMAND_SEND_FRAGMENT = 8, + ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED = 9, + ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT = 10, + ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE = 11, + ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT = 12, + ENET_PROTOCOL_COMMAND_COUNT = 13, + + ENET_PROTOCOL_COMMAND_MASK = 0x0F + } ENetProtocolCommand; + + typedef enum _ENetProtocolFlag { + ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE = (1 << 7), + ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED = (1 << 6), + + ENET_PROTOCOL_HEADER_FLAG_COMPRESSED = (1 << 14), + ENET_PROTOCOL_HEADER_FLAG_SENT_TIME = (1 << 15), + ENET_PROTOCOL_HEADER_FLAG_MASK = ENET_PROTOCOL_HEADER_FLAG_COMPRESSED | ENET_PROTOCOL_HEADER_FLAG_SENT_TIME, + + ENET_PROTOCOL_HEADER_SESSION_MASK = (3 << 12), + ENET_PROTOCOL_HEADER_SESSION_SHIFT = 12 + } ENetProtocolFlag; + + #ifdef _MSC_VER + #pragma pack(push, 1) + #define ENET_PACKED + #elif defined(__GNUC__) || defined(__clang__) + #define ENET_PACKED __attribute__ ((packed)) + #else + #define ENET_PACKED + #endif + + typedef struct _ENetProtocolHeader { + enet_uint16 peerID; + enet_uint16 sentTime; + } ENET_PACKED ENetProtocolHeader; + + typedef struct _ENetProtocolCommandHeader { + enet_uint8 command; + enet_uint8 channelID; + enet_uint16 reliableSequenceNumber; + } ENET_PACKED ENetProtocolCommandHeader; + + typedef struct _ENetProtocolAcknowledge { + ENetProtocolCommandHeader header; + enet_uint16 receivedReliableSequenceNumber; + enet_uint16 receivedSentTime; + } ENET_PACKED ENetProtocolAcknowledge; + + typedef struct _ENetProtocolConnect { + ENetProtocolCommandHeader header; + enet_uint16 outgoingPeerID; + enet_uint8 incomingSessionID; + enet_uint8 outgoingSessionID; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 channelCount; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 connectID; + enet_uint32 data; + } ENET_PACKED ENetProtocolConnect; + + typedef struct _ENetProtocolVerifyConnect { + ENetProtocolCommandHeader header; + enet_uint16 outgoingPeerID; + enet_uint8 incomingSessionID; + enet_uint8 outgoingSessionID; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 channelCount; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 connectID; + } ENET_PACKED ENetProtocolVerifyConnect; + + typedef struct _ENetProtocolBandwidthLimit { + ENetProtocolCommandHeader header; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; + } ENET_PACKED ENetProtocolBandwidthLimit; + + typedef struct _ENetProtocolThrottleConfigure { + ENetProtocolCommandHeader header; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + } ENET_PACKED ENetProtocolThrottleConfigure; + + typedef struct _ENetProtocolDisconnect { + ENetProtocolCommandHeader header; + enet_uint32 data; + } ENET_PACKED ENetProtocolDisconnect; + + typedef struct _ENetProtocolPing { + ENetProtocolCommandHeader header; + } ENET_PACKED ENetProtocolPing; + + typedef struct _ENetProtocolSendReliable { + ENetProtocolCommandHeader header; + enet_uint16 dataLength; + } ENET_PACKED ENetProtocolSendReliable; + + typedef struct _ENetProtocolSendUnreliable { + ENetProtocolCommandHeader header; + enet_uint16 unreliableSequenceNumber; + enet_uint16 dataLength; + } ENET_PACKED ENetProtocolSendUnreliable; + + typedef struct _ENetProtocolSendUnsequenced { + ENetProtocolCommandHeader header; + enet_uint16 unsequencedGroup; + enet_uint16 dataLength; + } ENET_PACKED ENetProtocolSendUnsequenced; + + typedef struct _ENetProtocolSendFragment { + ENetProtocolCommandHeader header; + enet_uint16 startSequenceNumber; + enet_uint16 dataLength; + enet_uint32 fragmentCount; + enet_uint32 fragmentNumber; + enet_uint32 totalLength; + enet_uint32 fragmentOffset; + } ENET_PACKED ENetProtocolSendFragment; + + typedef union _ENetProtocol { + ENetProtocolCommandHeader header; + ENetProtocolAcknowledge acknowledge; + ENetProtocolConnect connect; + ENetProtocolVerifyConnect verifyConnect; + ENetProtocolDisconnect disconnect; + ENetProtocolPing ping; + ENetProtocolSendReliable sendReliable; + ENetProtocolSendUnreliable sendUnreliable; + ENetProtocolSendUnsequenced sendUnsequenced; + ENetProtocolSendFragment sendFragment; + ENetProtocolBandwidthLimit bandwidthLimit; + ENetProtocolThrottleConfigure throttleConfigure; + } ENET_PACKED ENetProtocol; + + #ifdef _MSC_VER + #pragma pack(pop) + #endif + +// =======================================================================// +// ! +// ! General ENet structs/enums +// ! +// =======================================================================// + + typedef enum _ENetSocketType { + ENET_SOCKET_TYPE_STREAM = 1, + ENET_SOCKET_TYPE_DATAGRAM = 2 + } ENetSocketType; + + typedef enum _ENetSocketWait { + ENET_SOCKET_WAIT_NONE = 0, + ENET_SOCKET_WAIT_SEND = (1 << 0), + ENET_SOCKET_WAIT_RECEIVE = (1 << 1), + ENET_SOCKET_WAIT_INTERRUPT = (1 << 2) + } ENetSocketWait; + + typedef enum _ENetSocketOption { + ENET_SOCKOPT_NONBLOCK = 1, + ENET_SOCKOPT_BROADCAST = 2, + ENET_SOCKOPT_RCVBUF = 3, + ENET_SOCKOPT_SNDBUF = 4, + ENET_SOCKOPT_REUSEADDR = 5, + ENET_SOCKOPT_RCVTIMEO = 6, + ENET_SOCKOPT_SNDTIMEO = 7, + ENET_SOCKOPT_ERROR = 8, + ENET_SOCKOPT_NODELAY = 9, + ENET_SOCKOPT_IPV6_V6ONLY = 10, + } ENetSocketOption; + + typedef enum _ENetSocketShutdown { + ENET_SOCKET_SHUTDOWN_READ = 0, + ENET_SOCKET_SHUTDOWN_WRITE = 1, + ENET_SOCKET_SHUTDOWN_READ_WRITE = 2 + } ENetSocketShutdown; + + /** + * Portable internet address structure. + * + * The host must be specified in network byte-order, and the port must be in host + * byte-order. The constant ENET_HOST_ANY may be used to specify the default + * server host. The constant ENET_HOST_BROADCAST may be used to specify the + * broadcast address (255.255.255.255). This makes sense for enet_host_connect, + * but not for enet_host_create. Once a server responds to a broadcast, the + * address is updated from ENET_HOST_BROADCAST to the server's actual IP address. + */ + typedef struct _ENetAddress { + struct in6_addr host; + enet_uint16 port; + enet_uint16 sin6_scope_id; + } ENetAddress; + + #define in6_equal(in6_addr_a, in6_addr_b) (memcmp(&in6_addr_a, &in6_addr_b, sizeof(struct in6_addr)) == 0) + + /** + * Packet flag bit constants. + * + * The host must be specified in network byte-order, and the port must be in + * host byte-order. The constant ENET_HOST_ANY may be used to specify the + * default server host. + * + * @sa ENetPacket + */ + typedef enum _ENetPacketFlag { + ENET_PACKET_FLAG_RELIABLE = (1 << 0), /** packet must be received by the target peer and resend attempts should be made until the packet is delivered */ + ENET_PACKET_FLAG_UNSEQUENCED = (1 << 1), /** packet will not be sequenced with other packets not supported for reliable packets */ + ENET_PACKET_FLAG_NO_ALLOCATE = (1 << 2), /** packet will not allocate data, and user must supply it instead */ + ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT = (1 << 3), /** packet will be fragmented using unreliable (instead of reliable) sends if it exceeds the MTU */ + ENET_PACKET_FLAG_SENT = (1 << 8), /** whether the packet has been sent from all queues it has been entered into */ + } ENetPacketFlag; + + typedef void (ENET_CALLBACK *ENetPacketFreeCallback)(void *); + + /** + * ENet packet structure. + * + * An ENet data packet that may be sent to or received from a peer. The shown + * fields should only be read and never modified. The data field contains the + * allocated data for the packet. The dataLength fields specifies the length + * of the allocated data. The flags field is either 0 (specifying no flags), + * or a bitwise-or of any combination of the following flags: + * + * ENET_PACKET_FLAG_RELIABLE - packet must be received by the target peer and resend attempts should be made until the packet is delivered + * ENET_PACKET_FLAG_UNSEQUENCED - packet will not be sequenced with other packets (not supported for reliable packets) + * ENET_PACKET_FLAG_NO_ALLOCATE - packet will not allocate data, and user must supply it instead + * ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT - packet will be fragmented using unreliable (instead of reliable) sends if it exceeds the MTU + * ENET_PACKET_FLAG_SENT - whether the packet has been sent from all queues it has been entered into + * @sa ENetPacketFlag + */ + typedef struct _ENetPacket { + size_t referenceCount; /**< internal use only */ + enet_uint32 flags; /**< bitwise-or of ENetPacketFlag constants */ + enet_uint8 * data; /**< allocated data for packet */ + size_t dataLength; /**< length of data */ + ENetPacketFreeCallback freeCallback; /**< function to be called when the packet is no longer in use */ + void * userData; /**< application private data, may be freely modified */ + } ENetPacket; + + typedef struct _ENetAcknowledgement { + ENetListNode acknowledgementList; + enet_uint32 sentTime; + ENetProtocol command; + } ENetAcknowledgement; + + typedef struct _ENetOutgoingCommand { + ENetListNode outgoingCommandList; + enet_uint16 reliableSequenceNumber; + enet_uint16 unreliableSequenceNumber; + enet_uint32 sentTime; + enet_uint32 roundTripTimeout; + enet_uint32 roundTripTimeoutLimit; + enet_uint32 fragmentOffset; + enet_uint16 fragmentLength; + enet_uint16 sendAttempts; + ENetProtocol command; + ENetPacket * packet; + } ENetOutgoingCommand; + + typedef struct _ENetIncomingCommand { + ENetListNode incomingCommandList; + enet_uint16 reliableSequenceNumber; + enet_uint16 unreliableSequenceNumber; + ENetProtocol command; + enet_uint32 fragmentCount; + enet_uint32 fragmentsRemaining; + enet_uint32 *fragments; + ENetPacket * packet; + } ENetIncomingCommand; + + typedef enum _ENetPeerState { + ENET_PEER_STATE_DISCONNECTED = 0, + ENET_PEER_STATE_CONNECTING = 1, + ENET_PEER_STATE_ACKNOWLEDGING_CONNECT = 2, + ENET_PEER_STATE_CONNECTION_PENDING = 3, + ENET_PEER_STATE_CONNECTION_SUCCEEDED = 4, + ENET_PEER_STATE_CONNECTED = 5, + ENET_PEER_STATE_DISCONNECT_LATER = 6, + ENET_PEER_STATE_DISCONNECTING = 7, + ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT = 8, + ENET_PEER_STATE_ZOMBIE = 9 + } ENetPeerState; + + enum { + ENET_HOST_RECEIVE_BUFFER_SIZE = 256 * 1024, + ENET_HOST_SEND_BUFFER_SIZE = 256 * 1024, + ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL = 1000, + ENET_HOST_DEFAULT_MTU = 1400, + ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE = 32 * 1024 * 1024, + ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024, + + ENET_PEER_DEFAULT_ROUND_TRIP_TIME = 500, + ENET_PEER_DEFAULT_PACKET_THROTTLE = 32, + ENET_PEER_PACKET_THROTTLE_SCALE = 32, + ENET_PEER_PACKET_THROTTLE_COUNTER = 7, + ENET_PEER_PACKET_THROTTLE_ACCELERATION = 2, + ENET_PEER_PACKET_THROTTLE_DECELERATION = 2, + ENET_PEER_PACKET_THROTTLE_INTERVAL = 5000, + ENET_PEER_PACKET_LOSS_SCALE = (1 << 16), + ENET_PEER_PACKET_LOSS_INTERVAL = 10000, + ENET_PEER_WINDOW_SIZE_SCALE = 64 * 1024, + ENET_PEER_TIMEOUT_LIMIT = 32, + ENET_PEER_TIMEOUT_MINIMUM = 5000, + ENET_PEER_TIMEOUT_MAXIMUM = 30000, + ENET_PEER_PING_INTERVAL = 500, + ENET_PEER_UNSEQUENCED_WINDOWS = 64, + ENET_PEER_UNSEQUENCED_WINDOW_SIZE = 1024, + ENET_PEER_FREE_UNSEQUENCED_WINDOWS = 32, + ENET_PEER_RELIABLE_WINDOWS = 16, + ENET_PEER_RELIABLE_WINDOW_SIZE = 0x1000, + ENET_PEER_FREE_RELIABLE_WINDOWS = 8 + }; + + typedef struct _ENetChannel { + enet_uint16 outgoingReliableSequenceNumber; + enet_uint16 outgoingUnreliableSequenceNumber; + enet_uint16 usedReliableWindows; + enet_uint16 reliableWindows[ENET_PEER_RELIABLE_WINDOWS]; + enet_uint16 incomingReliableSequenceNumber; + enet_uint16 incomingUnreliableSequenceNumber; + ENetList incomingReliableCommands; + ENetList incomingUnreliableCommands; + } ENetChannel; + + /** + * An ENet peer which data packets may be sent or received from. + * + * No fields should be modified unless otherwise specified. + */ + typedef struct _ENetPeer { + ENetListNode dispatchList; + struct _ENetHost *host; + enet_uint16 outgoingPeerID; + enet_uint16 incomingPeerID; + enet_uint32 connectID; + enet_uint8 outgoingSessionID; + enet_uint8 incomingSessionID; + ENetAddress address; /**< Internet address of the peer */ + void * data; /**< Application private data, may be freely modified */ + ENetPeerState state; + ENetChannel * channels; + size_t channelCount; /**< Number of channels allocated for communication with peer */ + enet_uint32 incomingBandwidth; /**< Downstream bandwidth of the client in bytes/second */ + enet_uint32 outgoingBandwidth; /**< Upstream bandwidth of the client in bytes/second */ + enet_uint32 incomingBandwidthThrottleEpoch; + enet_uint32 outgoingBandwidthThrottleEpoch; + enet_uint32 incomingDataTotal; + enet_uint64 totalDataReceived; + enet_uint32 outgoingDataTotal; + enet_uint64 totalDataSent; + enet_uint32 lastSendTime; + enet_uint32 lastReceiveTime; + enet_uint32 nextTimeout; + enet_uint32 earliestTimeout; + enet_uint32 packetLossEpoch; + enet_uint32 packetsSent; + enet_uint64 totalPacketsSent; /**< total number of packets sent during a session */ + enet_uint32 packetsLost; + enet_uint32 totalPacketsLost; /**< total number of packets lost during a session */ + enet_uint32 packetLoss; /**< mean packet loss of reliable packets as a ratio with respect to the constant ENET_PEER_PACKET_LOSS_SCALE */ + enet_uint32 packetLossVariance; + enet_uint32 packetThrottle; + enet_uint32 packetThrottleLimit; + enet_uint32 packetThrottleCounter; + enet_uint32 packetThrottleEpoch; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 packetThrottleInterval; + enet_uint32 pingInterval; + enet_uint32 timeoutLimit; + enet_uint32 timeoutMinimum; + enet_uint32 timeoutMaximum; + enet_uint32 lastRoundTripTime; + enet_uint32 lowestRoundTripTime; + enet_uint32 lastRoundTripTimeVariance; + enet_uint32 highestRoundTripTimeVariance; + enet_uint32 roundTripTime; /**< mean round trip time (RTT), in milliseconds, between sending a reliable packet and receiving its acknowledgement */ + enet_uint32 roundTripTimeVariance; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 reliableDataInTransit; + enet_uint16 outgoingReliableSequenceNumber; + ENetList acknowledgements; + ENetList sentReliableCommands; + ENetList sentUnreliableCommands; + ENetList outgoingReliableCommands; + ENetList outgoingUnreliableCommands; + ENetList dispatchedCommands; + int needsDispatch; + enet_uint16 incomingUnsequencedGroup; + enet_uint16 outgoingUnsequencedGroup; + enet_uint32 unsequencedWindow[ENET_PEER_UNSEQUENCED_WINDOW_SIZE / 32]; + enet_uint32 eventData; + size_t totalWaitingData; + } ENetPeer; + + /** An ENet packet compressor for compressing UDP packets before socket sends or receives. */ + typedef struct _ENetCompressor { + /** Context data for the compressor. Must be non-NULL. */ + void *context; + + /** Compresses from inBuffers[0:inBufferCount-1], containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ + size_t(ENET_CALLBACK * compress) (void *context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit); + + /** Decompresses from inData, containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ + size_t(ENET_CALLBACK * decompress) (void *context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit); + + /** Destroys the context when compression is disabled or the host is destroyed. May be NULL. */ + void (ENET_CALLBACK * destroy)(void *context); + } ENetCompressor; + + /** Callback that computes the checksum of the data held in buffers[0:bufferCount-1] */ + typedef enet_uint32 (ENET_CALLBACK * ENetChecksumCallback)(const ENetBuffer *buffers, size_t bufferCount); + + /** Callback for intercepting received raw UDP packets. Should return 1 to intercept, 0 to ignore, or -1 to propagate an error. */ + typedef int (ENET_CALLBACK * ENetInterceptCallback)(struct _ENetHost *host, void *event); + + /** An ENet host for communicating with peers. + * + * No fields should be modified unless otherwise stated. + * + * @sa enet_host_create() + * @sa enet_host_destroy() + * @sa enet_host_connect() + * @sa enet_host_service() + * @sa enet_host_flush() + * @sa enet_host_broadcast() + * @sa enet_host_compress() + * @sa enet_host_channel_limit() + * @sa enet_host_bandwidth_limit() + * @sa enet_host_bandwidth_throttle() + */ + typedef struct _ENetHost { + ENetSocket socket; + ENetAddress address; /**< Internet address of the host */ + enet_uint32 incomingBandwidth; /**< downstream bandwidth of the host */ + enet_uint32 outgoingBandwidth; /**< upstream bandwidth of the host */ + enet_uint32 bandwidthThrottleEpoch; + enet_uint32 mtu; + enet_uint32 randomSeed; + int recalculateBandwidthLimits; + ENetPeer * peers; /**< array of peers allocated for this host */ + size_t peerCount; /**< number of peers allocated for this host */ + size_t channelLimit; /**< maximum number of channels allowed for connected peers */ + enet_uint32 serviceTime; + ENetList dispatchQueue; + int continueSending; + size_t packetSize; + enet_uint16 headerFlags; + ENetProtocol commands[ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS]; + size_t commandCount; + ENetBuffer buffers[ENET_BUFFER_MAXIMUM]; + size_t bufferCount; + ENetChecksumCallback checksum; /**< callback the user can set to enable packet checksums for this host */ + ENetCompressor compressor; + enet_uint8 packetData[2][ENET_PROTOCOL_MAXIMUM_MTU]; + ENetAddress receivedAddress; + enet_uint8 * receivedData; + size_t receivedDataLength; + enet_uint32 totalSentData; /**< total data sent, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalSentPackets; /**< total UDP packets sent, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalReceivedData; /**< total data received, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalReceivedPackets; /**< total UDP packets received, user should reset to 0 as needed to prevent overflow */ + ENetInterceptCallback intercept; /**< callback the user can set to intercept received raw UDP packets */ + size_t connectedPeers; + size_t bandwidthLimitedPeers; + size_t duplicatePeers; /**< optional number of allowed peers from duplicate IPs, defaults to ENET_PROTOCOL_MAXIMUM_PEER_ID */ + size_t maximumPacketSize; /**< the maximum allowable packet size that may be sent or received on a peer */ + size_t maximumWaitingData; /**< the maximum aggregate amount of buffer space a peer may use waiting for packets to be delivered */ + } ENetHost; + + /** + * An ENet event type, as specified in @ref ENetEvent. + */ + typedef enum _ENetEventType { + /** no event occurred within the specified time limit */ + ENET_EVENT_TYPE_NONE = 0, + + /** a connection request initiated by enet_host_connect has completed. + * The peer field contains the peer which successfully connected. + */ + ENET_EVENT_TYPE_CONNECT = 1, + + /** a peer has disconnected. This event is generated on a successful + * completion of a disconnect initiated by enet_peer_disconnect, if + * a peer has timed out. The peer field contains the peer + * which disconnected. The data field contains user supplied data + * describing the disconnection, or 0, if none is available. + */ + ENET_EVENT_TYPE_DISCONNECT = 2, + + /** a packet has been received from a peer. The peer field specifies the + * peer which sent the packet. The channelID field specifies the channel + * number upon which the packet was received. The packet field contains + * the packet that was received; this packet must be destroyed with + * enet_packet_destroy after use. + */ + ENET_EVENT_TYPE_RECEIVE = 3, + + /** a peer is disconnected because the host didn't receive the acknowledgment + * packet within certain maximum time out. The reason could be because of bad + * network connection or host crashed. + */ + ENET_EVENT_TYPE_DISCONNECT_TIMEOUT = 4, + } ENetEventType; + + /** + * An ENet event as returned by enet_host_service(). + * + * @sa enet_host_service + */ + typedef struct _ENetEvent { + ENetEventType type; /**< type of the event */ + ENetPeer * peer; /**< peer that generated a connect, disconnect or receive event */ + enet_uint8 channelID; /**< channel on the peer that generated the event, if appropriate */ + enet_uint32 data; /**< data associated with the event, if appropriate */ + ENetPacket * packet; /**< packet associated with the event, if appropriate */ + } ENetEvent; + +// =======================================================================// +// ! +// ! Public API +// ! +// =======================================================================// + + /** + * Initializes ENet globally. Must be called prior to using any functions in ENet. + * @returns 0 on success, < 0 on failure + */ + ENET_API int enet_initialize(void); + + /** + * Initializes ENet globally and supplies user-overridden callbacks. Must be called prior to using any functions in ENet. Do not use enet_initialize() if you use this variant. Make sure the ENetCallbacks structure is zeroed out so that any additional callbacks added in future versions will be properly ignored. + * + * @param version the constant ENET_VERSION should be supplied so ENet knows which version of ENetCallbacks struct to use + * @param inits user-overridden callbacks where any NULL callbacks will use ENet's defaults + * @returns 0 on success, < 0 on failure + */ + ENET_API int enet_initialize_with_callbacks(ENetVersion version, const ENetCallbacks * inits); + + /** + * Shuts down ENet globally. Should be called when a program that has initialized ENet exits. + */ + ENET_API void enet_deinitialize(void); + + /** + * Gives the linked version of the ENet library. + * @returns the version number + */ + ENET_API ENetVersion enet_linked_version(void); + + /** Returns the monotonic time in milliseconds. Its initial value is unspecified unless otherwise set. */ + ENET_API enet_uint32 enet_time_get(void); + + /** ENet socket functions */ + ENET_API ENetSocket enet_socket_create(ENetSocketType); + ENET_API int enet_socket_bind(ENetSocket, const ENetAddress *); + ENET_API int enet_socket_get_address(ENetSocket, ENetAddress *); + ENET_API int enet_socket_listen(ENetSocket, int); + ENET_API ENetSocket enet_socket_accept(ENetSocket, ENetAddress *); + ENET_API int enet_socket_connect(ENetSocket, const ENetAddress *); + ENET_API int enet_socket_send(ENetSocket, const ENetAddress *, const ENetBuffer *, size_t); + ENET_API int enet_socket_receive(ENetSocket, ENetAddress *, ENetBuffer *, size_t); + ENET_API int enet_socket_wait(ENetSocket, enet_uint32 *, enet_uint64); + ENET_API int enet_socket_set_option(ENetSocket, ENetSocketOption, int); + ENET_API int enet_socket_get_option(ENetSocket, ENetSocketOption, int *); + ENET_API int enet_socket_shutdown(ENetSocket, ENetSocketShutdown); + ENET_API void enet_socket_destroy(ENetSocket); + ENET_API int enet_socketset_select(ENetSocket, ENetSocketSet *, ENetSocketSet *, enet_uint32); + + /** Attempts to parse the printable form of the IP address in the parameter hostName + and sets the host field in the address parameter if successful. + @param address destination to store the parsed IP address + @param hostName IP address to parse + @retval 0 on success + @retval < 0 on failure + @returns the address of the given hostName in address on success + */ + ENET_API int enet_address_set_host_ip_old(ENetAddress * address, const char * hostName); + + /** Attempts to resolve the host named by the parameter hostName and sets + the host field in the address parameter if successful. + @param address destination to store resolved address + @param hostName host name to lookup + @retval 0 on success + @retval < 0 on failure + @returns the address of the given hostName in address on success + */ + ENET_API int enet_address_set_host_old(ENetAddress * address, const char * hostName); + + /** Gives the printable form of the IP address specified in the address parameter. + @param address address printed + @param hostName destination for name, must not be NULL + @param nameLength maximum length of hostName. + @returns the null-terminated name of the host in hostName on success + @retval 0 on success + @retval < 0 on failure + */ + ENET_API int enet_address_get_host_ip_old(const ENetAddress * address, char * hostName, size_t nameLength); + + /** Attempts to do a reverse lookup of the host field in the address parameter. + @param address address used for reverse lookup + @param hostName destination for name, must not be NULL + @param nameLength maximum length of hostName. + @returns the null-terminated name of the host in hostName on success + @retval 0 on success + @retval < 0 on failure + */ + ENET_API int enet_address_get_host_old(const ENetAddress * address, char * hostName, size_t nameLength); + + ENET_API int enet_address_set_host_ip_new(ENetAddress * address, const char * hostName); + ENET_API int enet_address_set_host_new(ENetAddress * address, const char * hostName); + ENET_API int enet_address_get_host_ip_new(const ENetAddress * address, char * hostName, size_t nameLength); + ENET_API int enet_address_get_host_new(const ENetAddress * address, char * hostName, size_t nameLength); + +#ifdef ENET_FEATURE_ADDRESS_MAPPING +#define enet_address_set_host_ip enet_address_set_host_ip_new +#define enet_address_set_host enet_address_set_host_new +#define enet_address_get_host_ip enet_address_get_host_ip_new +#define enet_address_get_host enet_address_get_host_new +#else +#define enet_address_set_host_ip enet_address_set_host_ip_old +#define enet_address_set_host enet_address_set_host_old +#define enet_address_get_host_ip enet_address_get_host_ip_old +#define enet_address_get_host enet_address_get_host_old +#endif + + ENET_API enet_uint32 enet_host_get_peers_count(ENetHost *); + ENET_API enet_uint32 enet_host_get_packets_sent(ENetHost *); + ENET_API enet_uint32 enet_host_get_packets_received(ENetHost *); + ENET_API enet_uint32 enet_host_get_bytes_sent(ENetHost *); + ENET_API enet_uint32 enet_host_get_bytes_received(ENetHost *); + ENET_API enet_uint32 enet_host_get_received_data(ENetHost *, enet_uint8** data); + ENET_API enet_uint32 enet_host_get_mtu(ENetHost *); + + ENET_API enet_uint32 enet_peer_get_id(ENetPeer *); + ENET_API enet_uint32 enet_peer_get_ip(ENetPeer *, char * ip, size_t ipLength); + ENET_API enet_uint16 enet_peer_get_port(ENetPeer *); + ENET_API enet_uint32 enet_peer_get_rtt(ENetPeer *); + ENET_API enet_uint64 enet_peer_get_packets_sent(ENetPeer *); + ENET_API enet_uint32 enet_peer_get_packets_lost(ENetPeer *); + ENET_API enet_uint64 enet_peer_get_bytes_sent(ENetPeer *); + ENET_API enet_uint64 enet_peer_get_bytes_received(ENetPeer *); + + ENET_API ENetPeerState enet_peer_get_state(ENetPeer *); + + ENET_API void * enet_peer_get_data(ENetPeer *); + ENET_API void enet_peer_set_data(ENetPeer *, const void *); + + ENET_API void * enet_packet_get_data(ENetPacket *); + ENET_API enet_uint32 enet_packet_get_length(ENetPacket *); + ENET_API void enet_packet_set_free_callback(ENetPacket *, void *); + + ENET_API ENetPacket * enet_packet_create_offset(const void *, size_t, size_t, enet_uint32); + ENET_API enet_uint32 enet_crc32(const ENetBuffer *, size_t); + + ENET_API ENetHost * enet_host_create(const ENetAddress *, size_t, size_t, enet_uint32, enet_uint32); + ENET_API void enet_host_destroy(ENetHost *); + ENET_API ENetPeer * enet_host_connect(ENetHost *, const ENetAddress *, size_t, enet_uint32); + ENET_API int enet_host_check_events(ENetHost *, ENetEvent *); + ENET_API int enet_host_service(ENetHost *, ENetEvent *, enet_uint32); + ENET_API int enet_host_send_raw(ENetHost *, const ENetAddress *, enet_uint8 *, size_t); + ENET_API int enet_host_send_raw_ex(ENetHost *host, const ENetAddress* address, enet_uint8* data, size_t skipBytes, size_t bytesToSend); + ENET_API void enet_host_set_intercept(ENetHost *, const ENetInterceptCallback); + ENET_API void enet_host_flush(ENetHost *); + ENET_API void enet_host_broadcast(ENetHost *, enet_uint8, ENetPacket *); + ENET_API void enet_host_compress(ENetHost *, const ENetCompressor *); + ENET_API void enet_host_channel_limit(ENetHost *, size_t); + ENET_API void enet_host_bandwidth_limit(ENetHost *, enet_uint32, enet_uint32); + extern void enet_host_bandwidth_throttle(ENetHost *); + extern enet_uint64 enet_host_random_seed(void); + + ENET_API int enet_peer_send(ENetPeer *, enet_uint8, ENetPacket *); + ENET_API ENetPacket * enet_peer_receive(ENetPeer *, enet_uint8 * channelID); + ENET_API void enet_peer_ping(ENetPeer *); + ENET_API void enet_peer_ping_interval(ENetPeer *, enet_uint32); + ENET_API void enet_peer_timeout(ENetPeer *, enet_uint32, enet_uint32, enet_uint32); + ENET_API void enet_peer_reset(ENetPeer *); + ENET_API void enet_peer_disconnect(ENetPeer *, enet_uint32); + ENET_API void enet_peer_disconnect_now(ENetPeer *, enet_uint32); + ENET_API void enet_peer_disconnect_later(ENetPeer *, enet_uint32); + ENET_API void enet_peer_throttle_configure(ENetPeer *, enet_uint32, enet_uint32, enet_uint32); + extern int enet_peer_throttle(ENetPeer *, enet_uint32); + extern void enet_peer_reset_queues(ENetPeer *); + extern void enet_peer_setup_outgoing_command(ENetPeer *, ENetOutgoingCommand *); + extern ENetOutgoingCommand * enet_peer_queue_outgoing_command(ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32, enet_uint16); + extern ENetIncomingCommand * enet_peer_queue_incoming_command(ENetPeer *, const ENetProtocol *, const void *, size_t, enet_uint32, enet_uint32); + extern ENetAcknowledgement * enet_peer_queue_acknowledgement(ENetPeer *, const ENetProtocol *, enet_uint16); + extern void enet_peer_dispatch_incoming_unreliable_commands(ENetPeer *, ENetChannel *); + extern void enet_peer_dispatch_incoming_reliable_commands(ENetPeer *, ENetChannel *); + extern void enet_peer_on_connect(ENetPeer *); + extern void enet_peer_on_disconnect(ENetPeer *); + + extern size_t enet_protocol_command_size (enet_uint8); + +#ifdef __cplusplus +} +#endif + +#if defined(ENET_IMPLEMENTATION) && !defined(ENET_IMPLEMENTATION_DONE) +#define ENET_IMPLEMENTATION_DONE 1 + +#ifdef __cplusplus +extern "C" { +#endif + +// =======================================================================// +// ! +// ! Atomics +// ! +// =======================================================================// + +#if defined(_MSC_VER) + + #define ENET_AT_CASSERT_PRED(predicate) sizeof(char[2 * !!(predicate)-1]) + #define ENET_IS_SUPPORTED_ATOMIC(size) ENET_AT_CASSERT_PRED(size == 1 || size == 2 || size == 4 || size == 8) + #define ENET_ATOMIC_SIZEOF(variable) (ENET_IS_SUPPORTED_ATOMIC(sizeof(*(variable))), sizeof(*(variable))) + + __inline int64_t enet_at_atomic_read(char *ptr, size_t size) + { + switch (size) { + case 1: + return _InterlockedExchangeAdd8((volatile char *)ptr, 0); + case 2: + return _InterlockedExchangeAdd16((volatile SHORT *)ptr, 0); + case 4: + #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchangeAdd((volatile LONG *)ptr, 0); + #else + return _InterlockedExchangeAdd((volatile LONG *)ptr, 0); + #endif + case 8: + #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchangeAdd64((volatile LONGLONG *)ptr, 0); + #else + return _InterlockedExchangeAdd64((volatile LONGLONG *)ptr, 0); + #endif + default: + return 0xbad13bad; /* never reached */ + } + } + + __inline int64_t enet_at_atomic_write(char *ptr, int64_t value, size_t size) + { + switch (size) { + case 1: + return _InterlockedExchange8((volatile char *)ptr, (char)value); + case 2: + return _InterlockedExchange16((volatile SHORT *)ptr, (SHORT)value); + case 4: + #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchange((volatile LONG *)ptr, (LONG)value); + #else + return _InterlockedExchange((volatile LONG *)ptr, (LONG)value); + #endif + case 8: + #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchange64((volatile LONGLONG *)ptr, (LONGLONG)value); + #else + return _InterlockedExchange64((volatile LONGLONG *)ptr, (LONGLONG)value); + #endif + default: + return 0xbad13bad; /* never reached */ + } + } + + __inline int64_t enet_at_atomic_cas(char *ptr, int64_t new_val, int64_t old_val, size_t size) + { + switch (size) { + case 1: + return _InterlockedCompareExchange8((volatile char *)ptr, (char)new_val, (char)old_val); + case 2: + return _InterlockedCompareExchange16((volatile SHORT *)ptr, (SHORT)new_val, + (SHORT)old_val); + case 4: + #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedCompareExchange((volatile LONG *)ptr, (LONG)new_val, (LONG)old_val); + #else + return _InterlockedCompareExchange((volatile LONG *)ptr, (LONG)new_val, (LONG)old_val); + #endif + case 8: + #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedCompareExchange64((volatile LONGLONG *)ptr, (LONGLONG)new_val, + (LONGLONG)old_val); + #else + return _InterlockedCompareExchange64((volatile LONGLONG *)ptr, (LONGLONG)new_val, + (LONGLONG)old_val); + #endif + default: + return 0xbad13bad; /* never reached */ + } + } + + __inline int64_t enet_at_atomic_inc(char *ptr, int64_t delta, size_t data_size) + { + switch (data_size) { + case 1: + return _InterlockedExchangeAdd8((volatile char *)ptr, (char)delta); + case 2: + return _InterlockedExchangeAdd16((volatile SHORT *)ptr, (SHORT)delta); + case 4: + #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchangeAdd((volatile LONG *)ptr, (LONG)delta); + #else + return _InterlockedExchangeAdd((volatile LONG *)ptr, (LONG)delta); + #endif + case 8: + #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchangeAdd64((volatile LONGLONG *)ptr, (LONGLONG)delta); + #else + return _InterlockedExchangeAdd64((volatile LONGLONG *)ptr, (LONGLONG)delta); + #endif + default: + return 0xbad13bad; /* never reached */ + } + } + + #define ENET_ATOMIC_READ(variable) enet_at_atomic_read((char *)(variable), ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_WRITE(variable, new_val) \ + enet_at_atomic_write((char *)(variable), (int64_t)(new_val), ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_CAS(variable, old_value, new_val) \ + enet_at_atomic_cas((char *)(variable), (int64_t)(new_val), (int64_t)(old_value), \ + ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_INC(variable) enet_at_atomic_inc((char *)(variable), 1, ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_DEC(variable) enet_at_atomic_inc((char *)(variable), -1, ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_INC_BY(variable, delta) \ + enet_at_atomic_inc((char *)(variable), (delta), ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_DEC_BY(variable, delta) \ + enet_at_atomic_inc((char *)(variable), -(delta), ENET_ATOMIC_SIZEOF(variable)) + +#elif defined(__GNUC__) || defined(__clang__) + + #if defined(__clang__) || (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) + #define AT_HAVE_ATOMICS + #endif + + /* We want to use __atomic built-ins if possible because the __sync primitives are + deprecated, because the __atomic build-ins allow us to use ENET_ATOMIC_WRITE on + uninitialized memory without running into undefined behavior, and because the + __atomic versions generate more efficient code since we don't need to rely on + CAS when we don't actually want it. + + Note that we use acquire-release memory order (like mutexes do). We could use + sequentially consistent memory order but that has lower performance and is + almost always unneeded. */ + #ifdef AT_HAVE_ATOMICS + #define ENET_ATOMIC_READ(ptr) __atomic_load_n((ptr), __ATOMIC_ACQUIRE) + #define ENET_ATOMIC_WRITE(ptr, value) __atomic_store_n((ptr), (value), __ATOMIC_RELEASE) + + #ifndef typeof + #define typeof __typeof__ + #endif + + /* clang_analyzer doesn't know that CAS writes to memory so it complains about + potentially lost data. Replace the code with the equivalent non-sync code. */ + #ifdef __clang_analyzer__ + + #define ENET_ATOMIC_CAS(ptr, old_value, new_value) \ + ({ \ + typeof(*(ptr)) ENET_ATOMIC_CAS_old_actual_ = (*(ptr)); \ + if (ATOMIC_CAS_old_actual_ == (old_value)) { \ + *(ptr) = new_value; \ + } \ + ENET_ATOMIC_CAS_old_actual_; \ + }) + + #else + + /* Could use __auto_type instead of typeof but that shouldn't work in C++. + The ({ }) syntax is a GCC extension called statement expression. It lets + us return a value out of the macro. + + TODO We should return bool here instead of the old value to avoid the ABA + problem. */ + #define ENET_ATOMIC_CAS(ptr, old_value, new_value) \ + ({ \ + typeof(*(ptr)) ENET_ATOMIC_CAS_expected_ = (old_value); \ + __atomic_compare_exchange_n((ptr), &ENET_ATOMIC_CAS_expected_, (new_value), false, \ + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE); \ + ENET_ATOMIC_CAS_expected_; \ + }) + + #endif /* __clang_analyzer__ */ + + #define ENET_ATOMIC_INC(ptr) __atomic_fetch_add((ptr), 1, __ATOMIC_ACQ_REL) + #define ENET_ATOMIC_DEC(ptr) __atomic_fetch_sub((ptr), 1, __ATOMIC_ACQ_REL) + #define ENET_ATOMIC_INC_BY(ptr, delta) __atomic_fetch_add((ptr), (delta), __ATOMIC_ACQ_REL) + #define ENET_ATOMIC_DEC_BY(ptr, delta) __atomic_fetch_sub((ptr), (delta), __ATOMIC_ACQ_REL) + + #else + + #define ENET_ATOMIC_READ(variable) __sync_fetch_and_add(variable, 0) + #define ENET_ATOMIC_WRITE(variable, new_val) \ + (void) __sync_val_compare_and_swap((variable), *(variable), (new_val)) + #define ENET_ATOMIC_CAS(variable, old_value, new_val) \ + __sync_val_compare_and_swap((variable), (old_value), (new_val)) + #define ENET_ATOMIC_INC(variable) __sync_fetch_and_add((variable), 1) + #define ENET_ATOMIC_DEC(variable) __sync_fetch_and_sub((variable), 1) + #define ENET_ATOMIC_INC_BY(variable, delta) __sync_fetch_and_add((variable), (delta), 1) + #define ENET_ATOMIC_DEC_BY(variable, delta) __sync_fetch_and_sub((variable), (delta), 1) + + #endif /* AT_HAVE_ATOMICS */ + + #undef AT_HAVE_ATOMICS + +#endif /* defined(_MSC_VER) */ + +// =======================================================================// +// ! +// ! Callbacks +// ! +// =======================================================================// + + ENetCallbacks callbacks = { malloc, free, abort, enet_packet_create, enet_packet_destroy }; + + int enet_initialize_with_callbacks(ENetVersion version, const ENetCallbacks *inits) { + if (version < ENET_VERSION_CREATE(1, 3, 0)) { + return -1; + } + + if (inits->malloc != NULL || inits->free != NULL) { + if (inits->malloc == NULL || inits->free == NULL) { + return -1; + } + + callbacks.malloc = inits->malloc; + callbacks.free = inits->free; + } + + if (inits->no_memory != NULL) { + callbacks.no_memory = inits->no_memory; + } + + if (inits->packet_create != NULL || inits->packet_destroy != NULL) { + if (inits->packet_create == NULL || inits->packet_destroy == NULL) { + return -1; + } + + callbacks.packet_create = inits->packet_create; + callbacks.packet_destroy = inits->packet_destroy; + } + + return enet_initialize(); + } + + ENetVersion enet_linked_version(void) { + return ENET_VERSION; + } + + void * enet_malloc(size_t size) { + void *memory = callbacks.malloc(size); + + if (memory == NULL) { + callbacks.no_memory(); + } + + return memory; + } + + void enet_free(void *memory) { + callbacks.free(memory); + } + +// =======================================================================// +// ! +// ! List +// ! +// =======================================================================// + + void enet_list_clear(ENetList *list) { + list->sentinel.next = &list->sentinel; + list->sentinel.previous = &list->sentinel; + } + + ENetListIterator enet_list_insert(ENetListIterator position, void *data) { + ENetListIterator result = (ENetListIterator)data; + + result->previous = position->previous; + result->next = position; + + result->previous->next = result; + position->previous = result; + + return result; + } + + void *enet_list_remove(ENetListIterator position) { + position->previous->next = position->next; + position->next->previous = position->previous; + + return position; + } + + ENetListIterator enet_list_move(ENetListIterator position, void *dataFirst, void *dataLast) { + ENetListIterator first = (ENetListIterator)dataFirst; + ENetListIterator last = (ENetListIterator)dataLast; + + first->previous->next = last->next; + last->next->previous = first->previous; + + first->previous = position->previous; + last->next = position; + + first->previous->next = first; + position->previous = last; + + return first; + } + + size_t enet_list_size(ENetList *list) { + size_t size = 0; + ENetListIterator position; + + for (position = enet_list_begin(list); position != enet_list_end(list); position = enet_list_next(position)) { + ++size; + } + + return size; + } + +// =======================================================================// +// ! +// ! Packet +// ! +// =======================================================================// + + /** + * Creates a packet that may be sent to a peer. + * @param data initial contents of the packet's data; the packet's data will remain uninitialized if data is NULL. + * @param dataLength size of the data allocated for this packet + * @param flags flags for this packet as described for the ENetPacket structure. + * @returns the packet on success, NULL on failure + */ + ENetPacket *enet_packet_create(const void *data, size_t dataLength, enet_uint32 flags) { + ENetPacket *packet; + if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) { + packet = (ENetPacket *)enet_malloc(sizeof (ENetPacket)); + if (packet == NULL) { + return NULL; + } + + packet->data = (enet_uint8 *)data; + } + else { + packet = (ENetPacket *)enet_malloc(sizeof (ENetPacket) + dataLength); + if (packet == NULL) { + return NULL; + } + + packet->data = (enet_uint8 *)packet + sizeof(ENetPacket); + + if (data != NULL) { + memcpy(packet->data, data, dataLength); + } + } + + packet->referenceCount = 0; + packet->flags = flags; + packet->dataLength = dataLength; + packet->freeCallback = NULL; + packet->userData = NULL; + + return packet; + } + + ENetPacket *enet_packet_create_offset(const void *data, size_t dataLength, size_t dataOffset, enet_uint32 flags) { + ENetPacket *packet; + if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) { + packet = (ENetPacket *)enet_malloc(sizeof (ENetPacket)); + if (packet == NULL) { + return NULL; + } + + packet->data = (enet_uint8 *)data; + } + else { + packet = (ENetPacket *)enet_malloc(sizeof (ENetPacket) + dataLength + dataOffset); + if (packet == NULL) { + return NULL; + } + + packet->data = (enet_uint8 *)packet + sizeof(ENetPacket); + + if (data != NULL) { + memcpy(packet->data + dataOffset, data, dataLength); + } + } + + packet->referenceCount = 0; + packet->flags = flags; + packet->dataLength = dataLength + dataOffset; + packet->freeCallback = NULL; + packet->userData = NULL; + + return packet; + } + + ENetPacket *enet_packet_copy(ENetPacket *packet) { + return enet_packet_create(packet->data, packet->dataLength, packet->flags); + } + + /** + * Destroys the packet and deallocates its data. + * @param packet packet to be destroyed + */ + void enet_packet_destroy(ENetPacket *packet) { + if (packet == NULL) { + return; + } + + if (packet->freeCallback != NULL) { + (*packet->freeCallback)((void *)packet); + } + + enet_free(packet); + } + + static int initializedCRC32 = 0; + static enet_uint32 crcTable[256]; + + static enet_uint32 reflect_crc(int val, int bits) { + int result = 0, bit; + + for (bit = 0; bit < bits; bit++) { + if (val & 1) { result |= 1 << (bits - 1 - bit); } + val >>= 1; + } + + return result; + } + + static void initialize_crc32(void) { + int byte; + + for (byte = 0; byte < 256; ++byte) { + enet_uint32 crc = reflect_crc(byte, 8) << 24; + int offset; + + for (offset = 0; offset < 8; ++offset) { + if (crc & 0x80000000) { + crc = (crc << 1) ^ 0x04c11db7; + } else { + crc <<= 1; + } + } + + crcTable[byte] = reflect_crc(crc, 32); + } + + initializedCRC32 = 1; + } + + enet_uint32 enet_crc32(const ENetBuffer *buffers, size_t bufferCount) { + enet_uint32 crc = 0xFFFFFFFF; + + if (!initializedCRC32) { initialize_crc32(); } + + while (bufferCount-- > 0) { + const enet_uint8 *data = (const enet_uint8 *)buffers->data; + const enet_uint8 *dataEnd = &data[buffers->dataLength]; + + while (data < dataEnd) { + crc = (crc >> 8) ^ crcTable[(crc & 0xFF) ^ *data++]; + } + + ++buffers; + } + + return ENET_HOST_TO_NET_32(~crc); + } + +// =======================================================================// +// ! +// ! Protocol +// ! +// =======================================================================// + + static size_t commandSizes[ENET_PROTOCOL_COMMAND_COUNT] = { + 0, + sizeof(ENetProtocolAcknowledge), + sizeof(ENetProtocolConnect), + sizeof(ENetProtocolVerifyConnect), + sizeof(ENetProtocolDisconnect), + sizeof(ENetProtocolPing), + sizeof(ENetProtocolSendReliable), + sizeof(ENetProtocolSendUnreliable), + sizeof(ENetProtocolSendFragment), + sizeof(ENetProtocolSendUnsequenced), + sizeof(ENetProtocolBandwidthLimit), + sizeof(ENetProtocolThrottleConfigure), + sizeof(ENetProtocolSendFragment) + }; + + size_t enet_protocol_command_size(enet_uint8 commandNumber) { + return commandSizes[commandNumber & ENET_PROTOCOL_COMMAND_MASK]; + } + + static void enet_protocol_change_state(ENetHost *host, ENetPeer *peer, ENetPeerState state) { + ENET_UNUSED(host) + + if (state == ENET_PEER_STATE_CONNECTED || state == ENET_PEER_STATE_DISCONNECT_LATER) { + enet_peer_on_connect(peer); + } else { + enet_peer_on_disconnect(peer); + } + + peer->state = state; + } + + static void enet_protocol_dispatch_state(ENetHost *host, ENetPeer *peer, ENetPeerState state) { + enet_protocol_change_state(host, peer, state); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&host->dispatchQueue), &peer->dispatchList); + peer->needsDispatch = 1; + } + } + + static int enet_protocol_dispatch_incoming_commands(ENetHost *host, ENetEvent *event) { + while (!enet_list_empty(&host->dispatchQueue)) { + ENetPeer *peer = (ENetPeer *) enet_list_remove(enet_list_begin(&host->dispatchQueue)); + peer->needsDispatch = 0; + + switch (peer->state) { + case ENET_PEER_STATE_CONNECTION_PENDING: + case ENET_PEER_STATE_CONNECTION_SUCCEEDED: + enet_protocol_change_state(host, peer, ENET_PEER_STATE_CONNECTED); + + event->type = ENET_EVENT_TYPE_CONNECT; + event->peer = peer; + event->data = peer->eventData; + + return 1; + + case ENET_PEER_STATE_ZOMBIE: + host->recalculateBandwidthLimits = 1; + + event->type = ENET_EVENT_TYPE_DISCONNECT; + event->peer = peer; + event->data = peer->eventData; + + enet_peer_reset(peer); + + return 1; + + case ENET_PEER_STATE_CONNECTED: + if (enet_list_empty(&peer->dispatchedCommands)) { + continue; + } + + event->packet = enet_peer_receive(peer, &event->channelID); + if (event->packet == NULL) { + continue; + } + + event->type = ENET_EVENT_TYPE_RECEIVE; + event->peer = peer; + + if (!enet_list_empty(&peer->dispatchedCommands)) { + peer->needsDispatch = 1; + enet_list_insert(enet_list_end(&host->dispatchQueue), &peer->dispatchList); + } + + return 1; + + default: + break; + } + } + + return 0; + } /* enet_protocol_dispatch_incoming_commands */ + + static void enet_protocol_notify_connect(ENetHost *host, ENetPeer *peer, ENetEvent *event) { + host->recalculateBandwidthLimits = 1; + + if (event != NULL) { + enet_protocol_change_state(host, peer, ENET_PEER_STATE_CONNECTED); + + peer->totalDataSent = 0; + peer->totalDataReceived = 0; + peer->totalPacketsSent = 0; + peer->totalPacketsLost = 0; + + event->type = ENET_EVENT_TYPE_CONNECT; + event->peer = peer; + event->data = peer->eventData; + } else { + enet_protocol_dispatch_state(host, peer, peer->state == ENET_PEER_STATE_CONNECTING ? ENET_PEER_STATE_CONNECTION_SUCCEEDED : ENET_PEER_STATE_CONNECTION_PENDING); + } + } + + static void enet_protocol_notify_disconnect(ENetHost *host, ENetPeer *peer, ENetEvent *event) { + if (peer->state >= ENET_PEER_STATE_CONNECTION_PENDING) { + host->recalculateBandwidthLimits = 1; + } + + if (peer->state != ENET_PEER_STATE_CONNECTING && peer->state < ENET_PEER_STATE_CONNECTION_SUCCEEDED) { + enet_peer_reset(peer); + } else if (event != NULL) { + event->type = ENET_EVENT_TYPE_DISCONNECT; + event->peer = peer; + event->data = 0; + + enet_peer_reset(peer); + } else { + peer->eventData = 0; + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + } + } + + static void enet_protocol_notify_disconnect_timeout (ENetHost * host, ENetPeer * peer, ENetEvent * event) { + if (peer->state >= ENET_PEER_STATE_CONNECTION_PENDING) { + host->recalculateBandwidthLimits = 1; + } + + if (peer->state != ENET_PEER_STATE_CONNECTING && peer->state < ENET_PEER_STATE_CONNECTION_SUCCEEDED) { + enet_peer_reset (peer); + } + else if (event != NULL) { + event->type = ENET_EVENT_TYPE_DISCONNECT_TIMEOUT; + event->peer = peer; + event->data = 0; + + enet_peer_reset(peer); + } + else { + peer->eventData = 0; + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + } + } + + static void enet_protocol_remove_sent_unreliable_commands(ENetPeer *peer) { + ENetOutgoingCommand *outgoingCommand; + + while (!enet_list_empty(&peer->sentUnreliableCommands)) { + outgoingCommand = (ENetOutgoingCommand *) enet_list_front(&peer->sentUnreliableCommands); + enet_list_remove(&outgoingCommand->outgoingCommandList); + + if (outgoingCommand->packet != NULL) { + --outgoingCommand->packet->referenceCount; + + if (outgoingCommand->packet->referenceCount == 0) { + outgoingCommand->packet->flags |= ENET_PACKET_FLAG_SENT; + callbacks.packet_destroy(outgoingCommand->packet); + } + } + + enet_free(outgoingCommand); + } + } + + static ENetProtocolCommand enet_protocol_remove_sent_reliable_command(ENetPeer *peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID) { + ENetOutgoingCommand *outgoingCommand = NULL; + ENetListIterator currentCommand; + ENetProtocolCommand commandNumber; + int wasSent = 1; + + for (currentCommand = enet_list_begin(&peer->sentReliableCommands); + currentCommand != enet_list_end(&peer->sentReliableCommands); + currentCommand = enet_list_next(currentCommand) + ) { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + if (outgoingCommand->reliableSequenceNumber == reliableSequenceNumber && outgoingCommand->command.header.channelID == channelID) { + break; + } + } + + if (currentCommand == enet_list_end(&peer->sentReliableCommands)) { + for (currentCommand = enet_list_begin(&peer->outgoingReliableCommands); + currentCommand != enet_list_end(&peer->outgoingReliableCommands); + currentCommand = enet_list_next(currentCommand) + ) { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + if (outgoingCommand->sendAttempts < 1) { return ENET_PROTOCOL_COMMAND_NONE; } + if (outgoingCommand->reliableSequenceNumber == reliableSequenceNumber && outgoingCommand->command.header.channelID == channelID) { + break; + } + } + + if (currentCommand == enet_list_end(&peer->outgoingReliableCommands)) { + return ENET_PROTOCOL_COMMAND_NONE; + } + + wasSent = 0; + } + + if (outgoingCommand == NULL) { + return ENET_PROTOCOL_COMMAND_NONE; + } + + if (channelID < peer->channelCount) { + ENetChannel *channel = &peer->channels[channelID]; + enet_uint16 reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + if (channel->reliableWindows[reliableWindow] > 0) { + --channel->reliableWindows[reliableWindow]; + if (!channel->reliableWindows[reliableWindow]) { + channel->usedReliableWindows &= ~(1 << reliableWindow); + } + } + } + + commandNumber = (ENetProtocolCommand) (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK); + enet_list_remove(&outgoingCommand->outgoingCommandList); + + if (outgoingCommand->packet != NULL) { + if (wasSent) { + peer->reliableDataInTransit -= outgoingCommand->fragmentLength; + } + + --outgoingCommand->packet->referenceCount; + + if (outgoingCommand->packet->referenceCount == 0) { + outgoingCommand->packet->flags |= ENET_PACKET_FLAG_SENT; + callbacks.packet_destroy(outgoingCommand->packet); + } + } + + enet_free(outgoingCommand); + + if (enet_list_empty(&peer->sentReliableCommands)) { + return commandNumber; + } + + outgoingCommand = (ENetOutgoingCommand *) enet_list_front(&peer->sentReliableCommands); + peer->nextTimeout = outgoingCommand->sentTime + outgoingCommand->roundTripTimeout; + + return commandNumber; + } /* enet_protocol_remove_sent_reliable_command */ + + static ENetPeer * enet_protocol_handle_connect(ENetHost *host, ENetProtocolHeader *header, ENetProtocol *command) { + ENET_UNUSED(header) + + enet_uint8 incomingSessionID, outgoingSessionID; + enet_uint32 mtu, windowSize; + ENetChannel *channel; + size_t channelCount, duplicatePeers = 0; + ENetPeer *currentPeer, *peer = NULL; + ENetProtocol verifyCommand; + + channelCount = ENET_NET_TO_HOST_32(command->connect.channelCount); + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) { + return NULL; + } + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED) { + if (peer == NULL) { + peer = currentPeer; + } + } else if (currentPeer->state != ENET_PEER_STATE_CONNECTING && in6_equal(currentPeer->address.host, host->receivedAddress.host)) { + if (currentPeer->address.port == host->receivedAddress.port && currentPeer->connectID == command->connect.connectID) { + return NULL; + } + + ++duplicatePeers; + } + } + + if (peer == NULL || duplicatePeers >= host->duplicatePeers) { + return NULL; + } + + if (channelCount > host->channelLimit) { + channelCount = host->channelLimit; + } + peer->channels = (ENetChannel *) enet_malloc(channelCount * sizeof(ENetChannel)); + if (peer->channels == NULL) { + return NULL; + } + peer->channelCount = channelCount; + peer->state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT; + peer->connectID = command->connect.connectID; + peer->address = host->receivedAddress; + peer->outgoingPeerID = ENET_NET_TO_HOST_16(command->connect.outgoingPeerID); + peer->incomingBandwidth = ENET_NET_TO_HOST_32(command->connect.incomingBandwidth); + peer->outgoingBandwidth = ENET_NET_TO_HOST_32(command->connect.outgoingBandwidth); + peer->packetThrottleInterval = ENET_NET_TO_HOST_32(command->connect.packetThrottleInterval); + peer->packetThrottleAcceleration = ENET_NET_TO_HOST_32(command->connect.packetThrottleAcceleration); + peer->packetThrottleDeceleration = ENET_NET_TO_HOST_32(command->connect.packetThrottleDeceleration); + peer->eventData = ENET_NET_TO_HOST_32(command->connect.data); + + incomingSessionID = command->connect.incomingSessionID == 0xFF ? peer->outgoingSessionID : command->connect.incomingSessionID; + incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + if (incomingSessionID == peer->outgoingSessionID) { + incomingSessionID = (incomingSessionID + 1) + & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + } + peer->outgoingSessionID = incomingSessionID; + + outgoingSessionID = command->connect.outgoingSessionID == 0xFF ? peer->incomingSessionID : command->connect.outgoingSessionID; + outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + if (outgoingSessionID == peer->incomingSessionID) { + outgoingSessionID = (outgoingSessionID + 1) + & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + } + peer->incomingSessionID = outgoingSessionID; + + for (channel = peer->channels; channel < &peer->channels[channelCount]; ++channel) { + channel->outgoingReliableSequenceNumber = 0; + channel->outgoingUnreliableSequenceNumber = 0; + channel->incomingReliableSequenceNumber = 0; + channel->incomingUnreliableSequenceNumber = 0; + + enet_list_clear(&channel->incomingReliableCommands); + enet_list_clear(&channel->incomingUnreliableCommands); + + channel->usedReliableWindows = 0; + memset(channel->reliableWindows, 0, sizeof(channel->reliableWindows)); + } + + mtu = ENET_NET_TO_HOST_32(command->connect.mtu); + + if (mtu < ENET_PROTOCOL_MINIMUM_MTU) { + mtu = ENET_PROTOCOL_MINIMUM_MTU; + } else if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) { + mtu = ENET_PROTOCOL_MAXIMUM_MTU; + } + + peer->mtu = mtu; + + if (host->outgoingBandwidth == 0 && peer->incomingBandwidth == 0) { + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + } else if (host->outgoingBandwidth == 0 || peer->incomingBandwidth == 0) { + peer->windowSize = (ENET_MAX(host->outgoingBandwidth, peer->incomingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } else { + peer->windowSize = (ENET_MIN(host->outgoingBandwidth, peer->incomingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } + + if (peer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { + peer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } else if (peer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + } + + if (host->incomingBandwidth == 0) { + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + } else { + windowSize = (host->incomingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } + + if (windowSize > ENET_NET_TO_HOST_32(command->connect.windowSize)) { + windowSize = ENET_NET_TO_HOST_32(command->connect.windowSize); + } + + if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { + windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } else if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + } + + verifyCommand.header.command = ENET_PROTOCOL_COMMAND_VERIFY_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + verifyCommand.header.channelID = 0xFF; + verifyCommand.verifyConnect.outgoingPeerID = ENET_HOST_TO_NET_16(peer->incomingPeerID); + verifyCommand.verifyConnect.incomingSessionID = incomingSessionID; + verifyCommand.verifyConnect.outgoingSessionID = outgoingSessionID; + verifyCommand.verifyConnect.mtu = ENET_HOST_TO_NET_32(peer->mtu); + verifyCommand.verifyConnect.windowSize = ENET_HOST_TO_NET_32(windowSize); + verifyCommand.verifyConnect.channelCount = ENET_HOST_TO_NET_32(channelCount); + verifyCommand.verifyConnect.incomingBandwidth = ENET_HOST_TO_NET_32(host->incomingBandwidth); + verifyCommand.verifyConnect.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth); + verifyCommand.verifyConnect.packetThrottleInterval = ENET_HOST_TO_NET_32(peer->packetThrottleInterval); + verifyCommand.verifyConnect.packetThrottleAcceleration = ENET_HOST_TO_NET_32(peer->packetThrottleAcceleration); + verifyCommand.verifyConnect.packetThrottleDeceleration = ENET_HOST_TO_NET_32(peer->packetThrottleDeceleration); + verifyCommand.verifyConnect.connectID = peer->connectID; + + enet_peer_queue_outgoing_command(peer, &verifyCommand, NULL, 0, 0); + return peer; + } /* enet_protocol_handle_connect */ + + static int enet_protocol_handle_send_reliable(ENetHost *host, ENetPeer *peer, const ENetProtocol *command, enet_uint8 **currentData) { + size_t dataLength; + + if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) { + return -1; + } + + dataLength = ENET_NET_TO_HOST_16(command->sendReliable.dataLength); + *currentData += dataLength; + + if (dataLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { + return -1; + } + + if (enet_peer_queue_incoming_command(peer, command, (const enet_uint8 *) command + sizeof(ENetProtocolSendReliable), dataLength, ENET_PACKET_FLAG_RELIABLE, 0) == NULL) { + return -1; + } + + return 0; + } + + static int enet_protocol_handle_send_unsequenced(ENetHost *host, ENetPeer *peer, const ENetProtocol *command, enet_uint8 **currentData) { + enet_uint32 unsequencedGroup, index; + size_t dataLength; + + if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) { + return -1; + } + + dataLength = ENET_NET_TO_HOST_16(command->sendUnsequenced.dataLength); + *currentData += dataLength; + if (dataLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { + return -1; + } + + unsequencedGroup = ENET_NET_TO_HOST_16(command->sendUnsequenced.unsequencedGroup); + index = unsequencedGroup % ENET_PEER_UNSEQUENCED_WINDOW_SIZE; + + if (unsequencedGroup < peer->incomingUnsequencedGroup) { + unsequencedGroup += 0x10000; + } + + if (unsequencedGroup >= (enet_uint32) peer->incomingUnsequencedGroup + ENET_PEER_FREE_UNSEQUENCED_WINDOWS * ENET_PEER_UNSEQUENCED_WINDOW_SIZE) { + return 0; + } + + unsequencedGroup &= 0xFFFF; + + if (unsequencedGroup - index != peer->incomingUnsequencedGroup) { + peer->incomingUnsequencedGroup = unsequencedGroup - index; + memset(peer->unsequencedWindow, 0, sizeof(peer->unsequencedWindow)); + } else if (peer->unsequencedWindow[index / 32] & (1 << (index % 32))) { + return 0; + } + + if (enet_peer_queue_incoming_command(peer, command, (const enet_uint8 *) command + sizeof(ENetProtocolSendUnsequenced), dataLength, ENET_PACKET_FLAG_UNSEQUENCED,0) == NULL) { + return -1; + } + + peer->unsequencedWindow[index / 32] |= 1 << (index % 32); + + return 0; + } /* enet_protocol_handle_send_unsequenced */ + + static int enet_protocol_handle_send_unreliable(ENetHost *host, ENetPeer *peer, const ENetProtocol *command, + enet_uint8 **currentData) { + size_t dataLength; + + if (command->header.channelID >= peer->channelCount || + (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) + { + return -1; + } + + dataLength = ENET_NET_TO_HOST_16(command->sendUnreliable.dataLength); + *currentData += dataLength; + if (dataLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { + return -1; + } + + if (enet_peer_queue_incoming_command(peer, command, (const enet_uint8 *) command + sizeof(ENetProtocolSendUnreliable), dataLength, 0, 0) == NULL) { + return -1; + } + + return 0; + } + + static int enet_protocol_handle_send_fragment(ENetHost *host, ENetPeer *peer, const ENetProtocol *command, enet_uint8 **currentData) { + enet_uint32 fragmentNumber, fragmentCount, fragmentOffset, fragmentLength, startSequenceNumber, totalLength; + ENetChannel *channel; + enet_uint16 startWindow, currentWindow; + ENetListIterator currentCommand; + ENetIncomingCommand *startCommand = NULL; + + if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) { + return -1; + } + + fragmentLength = ENET_NET_TO_HOST_16(command->sendFragment.dataLength); + *currentData += fragmentLength; + if (fragmentLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { + return -1; + } + + channel = &peer->channels[command->header.channelID]; + startSequenceNumber = ENET_NET_TO_HOST_16(command->sendFragment.startSequenceNumber); + startWindow = startSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (startSequenceNumber < channel->incomingReliableSequenceNumber) { + startWindow += ENET_PEER_RELIABLE_WINDOWS; + } + + if (startWindow < currentWindow || startWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) { + return 0; + } + + fragmentNumber = ENET_NET_TO_HOST_32(command->sendFragment.fragmentNumber); + fragmentCount = ENET_NET_TO_HOST_32(command->sendFragment.fragmentCount); + fragmentOffset = ENET_NET_TO_HOST_32(command->sendFragment.fragmentOffset); + totalLength = ENET_NET_TO_HOST_32(command->sendFragment.totalLength); + + if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT || + fragmentNumber >= fragmentCount || + totalLength > host->maximumPacketSize || + fragmentOffset >= totalLength || + fragmentLength > totalLength - fragmentOffset + ) { + return -1; + } + + for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingReliableCommands)); + currentCommand != enet_list_end(&channel->incomingReliableCommands); + currentCommand = enet_list_previous(currentCommand) + ) { + ENetIncomingCommand *incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (startSequenceNumber >= channel->incomingReliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { + continue; + } + } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber <= startSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < startSequenceNumber) { + break; + } + + if ((incomingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) != + ENET_PROTOCOL_COMMAND_SEND_FRAGMENT || + totalLength != incomingCommand->packet->dataLength || + fragmentCount != incomingCommand->fragmentCount + ) { + return -1; + } + + startCommand = incomingCommand; + break; + } + } + + if (startCommand == NULL) { + ENetProtocol hostCommand = *command; + hostCommand.header.reliableSequenceNumber = startSequenceNumber; + startCommand = enet_peer_queue_incoming_command(peer, &hostCommand, NULL, totalLength, ENET_PACKET_FLAG_RELIABLE, fragmentCount); + if (startCommand == NULL) { + return -1; + } + } + + if ((startCommand->fragments[fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0) { + --startCommand->fragmentsRemaining; + startCommand->fragments[fragmentNumber / 32] |= (1 << (fragmentNumber % 32)); + + if (fragmentOffset + fragmentLength > startCommand->packet->dataLength) { + fragmentLength = startCommand->packet->dataLength - fragmentOffset; + } + + memcpy(startCommand->packet->data + fragmentOffset, (enet_uint8 *) command + sizeof(ENetProtocolSendFragment), fragmentLength); + + if (startCommand->fragmentsRemaining <= 0) { + enet_peer_dispatch_incoming_reliable_commands(peer, channel); + } + } + + return 0; + } /* enet_protocol_handle_send_fragment */ + + static int enet_protocol_handle_send_unreliable_fragment(ENetHost *host, ENetPeer *peer, const ENetProtocol *command, enet_uint8 **currentData) { + enet_uint32 fragmentNumber, fragmentCount, fragmentOffset, fragmentLength, reliableSequenceNumber, startSequenceNumber, totalLength; + enet_uint16 reliableWindow, currentWindow; + ENetChannel *channel; + ENetListIterator currentCommand; + ENetIncomingCommand *startCommand = NULL; + + if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) { + return -1; + } + + fragmentLength = ENET_NET_TO_HOST_16(command->sendFragment.dataLength); + *currentData += fragmentLength; + if (fragmentLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { + return -1; + } + + channel = &peer->channels[command->header.channelID]; + reliableSequenceNumber = command->header.reliableSequenceNumber; + startSequenceNumber = ENET_NET_TO_HOST_16(command->sendFragment.startSequenceNumber); + + reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (reliableSequenceNumber < channel->incomingReliableSequenceNumber) { + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + } + + if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) { + return 0; + } + + if (reliableSequenceNumber == channel->incomingReliableSequenceNumber && startSequenceNumber <= channel->incomingUnreliableSequenceNumber) { + return 0; + } + + fragmentNumber = ENET_NET_TO_HOST_32(command->sendFragment.fragmentNumber); + fragmentCount = ENET_NET_TO_HOST_32(command->sendFragment.fragmentCount); + fragmentOffset = ENET_NET_TO_HOST_32(command->sendFragment.fragmentOffset); + totalLength = ENET_NET_TO_HOST_32(command->sendFragment.totalLength); + + if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT || + fragmentNumber >= fragmentCount || + totalLength > host->maximumPacketSize || + fragmentOffset >= totalLength || + fragmentLength > totalLength - fragmentOffset + ) { + return -1; + } + + for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingUnreliableCommands)); + currentCommand != enet_list_end(&channel->incomingUnreliableCommands); + currentCommand = enet_list_previous(currentCommand) + ) { + ENetIncomingCommand *incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { + continue; + } + } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber > reliableSequenceNumber) { + continue; + } + + if (incomingCommand->unreliableSequenceNumber <= startSequenceNumber) { + if (incomingCommand->unreliableSequenceNumber < startSequenceNumber) { + break; + } + + if ((incomingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) != + ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT || + totalLength != incomingCommand->packet->dataLength || + fragmentCount != incomingCommand->fragmentCount + ) { + return -1; + } + + startCommand = incomingCommand; + break; + } + } + + if (startCommand == NULL) { + startCommand = enet_peer_queue_incoming_command(peer, command, NULL, totalLength, + ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT, fragmentCount); + if (startCommand == NULL) { + return -1; + } + } + + if ((startCommand->fragments[fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0) { + --startCommand->fragmentsRemaining; + startCommand->fragments[fragmentNumber / 32] |= (1 << (fragmentNumber % 32)); + + if (fragmentOffset + fragmentLength > startCommand->packet->dataLength) { + fragmentLength = startCommand->packet->dataLength - fragmentOffset; + } + + memcpy(startCommand->packet->data + fragmentOffset, (enet_uint8 *) command + sizeof(ENetProtocolSendFragment), fragmentLength); + + if (startCommand->fragmentsRemaining <= 0) { + enet_peer_dispatch_incoming_unreliable_commands(peer, channel); + } + } + + return 0; + } /* enet_protocol_handle_send_unreliable_fragment */ + + static int enet_protocol_handle_ping(ENetHost *host, ENetPeer *peer, const ENetProtocol *command) { + ENET_UNUSED(host) + ENET_UNUSED(command) + + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { + return -1; + } + + return 0; + } + + static int enet_protocol_handle_bandwidth_limit(ENetHost *host, ENetPeer *peer, const ENetProtocol *command) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { + return -1; + } + + if (peer->incomingBandwidth != 0) { + --host->bandwidthLimitedPeers; + } + + peer->incomingBandwidth = ENET_NET_TO_HOST_32(command->bandwidthLimit.incomingBandwidth); + peer->outgoingBandwidth = ENET_NET_TO_HOST_32(command->bandwidthLimit.outgoingBandwidth); + + if (peer->incomingBandwidth != 0) { + ++host->bandwidthLimitedPeers; + } + + if (peer->incomingBandwidth == 0 && host->outgoingBandwidth == 0) { + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + } else if (peer->incomingBandwidth == 0 || host->outgoingBandwidth == 0) { + peer->windowSize = (ENET_MAX(peer->incomingBandwidth, host->outgoingBandwidth) + / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } else { + peer->windowSize = (ENET_MIN(peer->incomingBandwidth, host->outgoingBandwidth) + / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } + + if (peer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { + peer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } else if (peer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + } + + return 0; + } /* enet_protocol_handle_bandwidth_limit */ + + static int enet_protocol_handle_throttle_configure(ENetHost *host, ENetPeer *peer, const ENetProtocol *command) { + ENET_UNUSED(host) + + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { + return -1; + } + + peer->packetThrottleInterval = ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleInterval); + peer->packetThrottleAcceleration = ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleAcceleration); + peer->packetThrottleDeceleration = ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleDeceleration); + + return 0; + } + + static int enet_protocol_handle_disconnect(ENetHost *host, ENetPeer *peer, const ENetProtocol *command) { + if (peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ZOMBIE || + peer->state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT + ) { + return 0; + } + + enet_peer_reset_queues(peer); + + if (peer->state == ENET_PEER_STATE_CONNECTION_SUCCEEDED || peer->state == ENET_PEER_STATE_DISCONNECTING || peer->state == ENET_PEER_STATE_CONNECTING) { + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + } + else if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { + if (peer->state == ENET_PEER_STATE_CONNECTION_PENDING) { host->recalculateBandwidthLimits = 1; } + enet_peer_reset(peer); + } + else if (command->header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { + enet_protocol_change_state(host, peer, ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT); + } + else { + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + } + + if (peer->state != ENET_PEER_STATE_DISCONNECTED) { + peer->eventData = ENET_NET_TO_HOST_32(command->disconnect.data); + } + + return 0; + } + + static int enet_protocol_handle_acknowledge(ENetHost *host, ENetEvent *event, ENetPeer *peer, const ENetProtocol *command) { + enet_uint32 roundTripTime, receivedSentTime, receivedReliableSequenceNumber; + ENetProtocolCommand commandNumber; + + if (peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ZOMBIE) { + return 0; + } + + receivedSentTime = ENET_NET_TO_HOST_16(command->acknowledge.receivedSentTime); + receivedSentTime |= host->serviceTime & 0xFFFF0000; + if ((receivedSentTime & 0x8000) > (host->serviceTime & 0x8000)) { + receivedSentTime -= 0x10000; + } + + if (ENET_TIME_LESS(host->serviceTime, receivedSentTime)) { + return 0; + } + + peer->lastReceiveTime = host->serviceTime; + peer->earliestTimeout = 0; + roundTripTime = ENET_TIME_DIFFERENCE(host->serviceTime, receivedSentTime); + + enet_peer_throttle(peer, roundTripTime); + peer->roundTripTimeVariance -= peer->roundTripTimeVariance / 4; + + if (roundTripTime >= peer->roundTripTime) { + peer->roundTripTime += (roundTripTime - peer->roundTripTime) / 8; + peer->roundTripTimeVariance += (roundTripTime - peer->roundTripTime) / 4; + } else { + peer->roundTripTime -= (peer->roundTripTime - roundTripTime) / 8; + peer->roundTripTimeVariance += (peer->roundTripTime - roundTripTime) / 4; + } + + if (peer->roundTripTime < peer->lowestRoundTripTime) { + peer->lowestRoundTripTime = peer->roundTripTime; + } + + if (peer->roundTripTimeVariance > peer->highestRoundTripTimeVariance) { + peer->highestRoundTripTimeVariance = peer->roundTripTimeVariance; + } + + if (peer->packetThrottleEpoch == 0 || + ENET_TIME_DIFFERENCE(host->serviceTime, peer->packetThrottleEpoch) >= peer->packetThrottleInterval + ) { + peer->lastRoundTripTime = peer->lowestRoundTripTime; + peer->lastRoundTripTimeVariance = peer->highestRoundTripTimeVariance; + peer->lowestRoundTripTime = peer->roundTripTime; + peer->highestRoundTripTimeVariance = peer->roundTripTimeVariance; + peer->packetThrottleEpoch = host->serviceTime; + } + + receivedReliableSequenceNumber = ENET_NET_TO_HOST_16(command->acknowledge.receivedReliableSequenceNumber); + commandNumber = enet_protocol_remove_sent_reliable_command(peer, receivedReliableSequenceNumber, command->header.channelID); + + switch (peer->state) { + case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: + if (commandNumber != ENET_PROTOCOL_COMMAND_VERIFY_CONNECT) { + return -1; + } + + enet_protocol_notify_connect(host, peer, event); + break; + + case ENET_PEER_STATE_DISCONNECTING: + if (commandNumber != ENET_PROTOCOL_COMMAND_DISCONNECT) { + return -1; + } + + enet_protocol_notify_disconnect(host, peer, event); + break; + + case ENET_PEER_STATE_DISCONNECT_LATER: + if (enet_list_empty(&peer->outgoingReliableCommands) && + enet_list_empty(&peer->outgoingUnreliableCommands) && + enet_list_empty(&peer->sentReliableCommands)) + { + enet_peer_disconnect(peer, peer->eventData); + } + break; + + default: + break; + } + + return 0; + } /* enet_protocol_handle_acknowledge */ + + static int enet_protocol_handle_verify_connect(ENetHost *host, ENetEvent *event, ENetPeer *peer, const ENetProtocol *command) { + enet_uint32 mtu, windowSize; + size_t channelCount; + + if (peer->state != ENET_PEER_STATE_CONNECTING) { + return 0; + } + + channelCount = ENET_NET_TO_HOST_32(command->verifyConnect.channelCount); + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT || + ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleInterval) != peer->packetThrottleInterval || + ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleAcceleration) != peer->packetThrottleAcceleration || + ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleDeceleration) != peer->packetThrottleDeceleration || + command->verifyConnect.connectID != peer->connectID + ) { + peer->eventData = 0; + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + return -1; + } + + enet_protocol_remove_sent_reliable_command(peer, 1, 0xFF); + + if (channelCount < peer->channelCount) { + peer->channelCount = channelCount; + } + + peer->outgoingPeerID = ENET_NET_TO_HOST_16(command->verifyConnect.outgoingPeerID); + peer->incomingSessionID = command->verifyConnect.incomingSessionID; + peer->outgoingSessionID = command->verifyConnect.outgoingSessionID; + + mtu = ENET_NET_TO_HOST_32(command->verifyConnect.mtu); + + if (mtu < ENET_PROTOCOL_MINIMUM_MTU) { + mtu = ENET_PROTOCOL_MINIMUM_MTU; + } else if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) { + mtu = ENET_PROTOCOL_MAXIMUM_MTU; + } + + if (mtu < peer->mtu) { + peer->mtu = mtu; + } + + windowSize = ENET_NET_TO_HOST_32(command->verifyConnect.windowSize); + if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { + windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } + + if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + } + + if (windowSize < peer->windowSize) { + peer->windowSize = windowSize; + } + + peer->incomingBandwidth = ENET_NET_TO_HOST_32(command->verifyConnect.incomingBandwidth); + peer->outgoingBandwidth = ENET_NET_TO_HOST_32(command->verifyConnect.outgoingBandwidth); + + enet_protocol_notify_connect(host, peer, event); + return 0; + } /* enet_protocol_handle_verify_connect */ + + static int enet_protocol_handle_incoming_commands(ENetHost *host, ENetEvent *event) { + ENetProtocolHeader *header; + ENetProtocol *command; + ENetPeer *peer; + enet_uint8 *currentData; + size_t headerSize; + enet_uint16 peerID, flags; + enet_uint8 sessionID; + + if (host->receivedDataLength < (size_t) &((ENetProtocolHeader *) 0)->sentTime) { + return 0; + } + + header = (ENetProtocolHeader *) host->receivedData; + + peerID = ENET_NET_TO_HOST_16(header->peerID); + sessionID = (peerID & ENET_PROTOCOL_HEADER_SESSION_MASK) >> ENET_PROTOCOL_HEADER_SESSION_SHIFT; + flags = peerID & ENET_PROTOCOL_HEADER_FLAG_MASK; + peerID &= ~(ENET_PROTOCOL_HEADER_FLAG_MASK | ENET_PROTOCOL_HEADER_SESSION_MASK); + + headerSize = (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME ? sizeof(ENetProtocolHeader) : (size_t) &((ENetProtocolHeader *) 0)->sentTime); + if (host->checksum != NULL) { + headerSize += sizeof(enet_uint32); + } + + if (peerID == ENET_PROTOCOL_MAXIMUM_PEER_ID) { + peer = NULL; + } else if (peerID >= host->peerCount) { + return 0; + } else { + peer = &host->peers[peerID]; + + if (peer->state == ENET_PEER_STATE_DISCONNECTED || + peer->state == ENET_PEER_STATE_ZOMBIE || + ((!in6_equal(host->receivedAddress.host , peer->address.host) || + host->receivedAddress.port != peer->address.port) && + 1 /* no broadcast in ipv6 !in6_equal(peer->address.host , ENET_HOST_BROADCAST)*/) || + (peer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID && + sessionID != peer->incomingSessionID) + ) { + return 0; + } + } + + if (flags & ENET_PROTOCOL_HEADER_FLAG_COMPRESSED) { + size_t originalSize; + if (host->compressor.context == NULL || host->compressor.decompress == NULL) { + return 0; + } + + originalSize = host->compressor.decompress(host->compressor.context, + host->receivedData + headerSize, + host->receivedDataLength - headerSize, + host->packetData[1] + headerSize, + sizeof(host->packetData[1]) - headerSize + ); + + if (originalSize <= 0 || originalSize > sizeof(host->packetData[1]) - headerSize) { + return 0; + } + + memcpy(host->packetData[1], header, headerSize); + host->receivedData = host->packetData[1]; + host->receivedDataLength = headerSize + originalSize; + } + + if (host->checksum != NULL) { + enet_uint32 *checksum = (enet_uint32 *) &host->receivedData[headerSize - sizeof(enet_uint32)]; + enet_uint32 desiredChecksum = *checksum; + ENetBuffer buffer; + + *checksum = peer != NULL ? peer->connectID : 0; + + buffer.data = host->receivedData; + buffer.dataLength = host->receivedDataLength; + + if (host->checksum(&buffer, 1) != desiredChecksum) { + return 0; + } + } + + if (peer != NULL) { + peer->address.host = host->receivedAddress.host; + peer->address.port = host->receivedAddress.port; + peer->incomingDataTotal += host->receivedDataLength; + peer->totalDataReceived += host->receivedDataLength; + } + + currentData = host->receivedData + headerSize; + + while (currentData < &host->receivedData[host->receivedDataLength]) { + enet_uint8 commandNumber; + size_t commandSize; + + command = (ENetProtocol *) currentData; + + if (currentData + sizeof(ENetProtocolCommandHeader) > &host->receivedData[host->receivedDataLength]) { + break; + } + + commandNumber = command->header.command & ENET_PROTOCOL_COMMAND_MASK; + if (commandNumber >= ENET_PROTOCOL_COMMAND_COUNT) { + break; + } + + commandSize = commandSizes[commandNumber]; + if (commandSize == 0 || currentData + commandSize > &host->receivedData[host->receivedDataLength]) { + break; + } + + currentData += commandSize; + + if (peer == NULL && (commandNumber != ENET_PROTOCOL_COMMAND_CONNECT || currentData < &host->receivedData[host->receivedDataLength])) { + break; + } + + command->header.reliableSequenceNumber = ENET_NET_TO_HOST_16(command->header.reliableSequenceNumber); + + switch (commandNumber) { + case ENET_PROTOCOL_COMMAND_ACKNOWLEDGE: + if (enet_protocol_handle_acknowledge(host, event, peer, command)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_CONNECT: + if (peer != NULL) { + goto commandError; + } + peer = enet_protocol_handle_connect(host, header, command); + if (peer == NULL) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_VERIFY_CONNECT: + if (enet_protocol_handle_verify_connect(host, event, peer, command)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_DISCONNECT: + if (enet_protocol_handle_disconnect(host, peer, command)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_PING: + if (enet_protocol_handle_ping(host, peer, command)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + if (enet_protocol_handle_send_reliable(host, peer, command, ¤tData)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + if (enet_protocol_handle_send_unreliable(host, peer, command, ¤tData)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + if (enet_protocol_handle_send_unsequenced(host, peer, command, ¤tData)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + if (enet_protocol_handle_send_fragment(host, peer, command, ¤tData)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT: + if (enet_protocol_handle_bandwidth_limit(host, peer, command)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE: + if (enet_protocol_handle_throttle_configure(host, peer, command)) { + goto commandError; + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT: + if (enet_protocol_handle_send_unreliable_fragment(host, peer, command, ¤tData)) { + goto commandError; + } + break; + + default: + goto commandError; + } + + if (peer != NULL && (command->header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0) { + enet_uint16 sentTime; + + if (!(flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME)) { + break; + } + + sentTime = ENET_NET_TO_HOST_16(header->sentTime); + + switch (peer->state) { + case ENET_PEER_STATE_DISCONNECTING: + case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: + case ENET_PEER_STATE_DISCONNECTED: + case ENET_PEER_STATE_ZOMBIE: + break; + + case ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT: + if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) { + enet_peer_queue_acknowledgement(peer, command, sentTime); + } + break; + + default: + enet_peer_queue_acknowledgement(peer, command, sentTime); + break; + } + } + } + + commandError: + if (event != NULL && event->type != ENET_EVENT_TYPE_NONE) { + return 1; + } + + return 0; + } /* enet_protocol_handle_incoming_commands */ + + static int enet_protocol_receive_incoming_commands(ENetHost *host, ENetEvent *event) { + int packets; + + for (packets = 0; packets < 256; ++packets) { + int receivedLength; + ENetBuffer buffer; + + buffer.data = host->packetData[0]; + // buffer.dataLength = sizeof (host->packetData[0]); + buffer.dataLength = host->mtu; + + receivedLength = enet_socket_receive(host->socket, &host->receivedAddress, &buffer, 1); + + if (receivedLength == -2) + continue; + + if (receivedLength < 0) { + return -1; + } + + if (receivedLength == 0) { + return 0; + } + + host->receivedData = host->packetData[0]; + host->receivedDataLength = receivedLength; + + host->totalReceivedData += receivedLength; + host->totalReceivedPackets++; + + if (host->intercept != NULL) { + switch (host->intercept(host, (void *)event)) { + case 1: + if (event != NULL && event->type != ENET_EVENT_TYPE_NONE) { + return 1; + } + + continue; + + case -1: + return -1; + + default: + break; + } + } + + switch (enet_protocol_handle_incoming_commands(host, event)) { + case 1: + return 1; + + case -1: + return -1; + + default: + break; + } + } + + return -1; + } /* enet_protocol_receive_incoming_commands */ + + static void enet_protocol_send_acknowledgements(ENetHost *host, ENetPeer *peer) { + ENetProtocol *command = &host->commands[host->commandCount]; + ENetBuffer *buffer = &host->buffers[host->bufferCount]; + ENetAcknowledgement *acknowledgement; + ENetListIterator currentAcknowledgement; + enet_uint16 reliableSequenceNumber; + + currentAcknowledgement = enet_list_begin(&peer->acknowledgements); + + while (currentAcknowledgement != enet_list_end(&peer->acknowledgements)) { + if (command >= &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] || + buffer >= &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] || + peer->mtu - host->packetSize < sizeof(ENetProtocolAcknowledge) + ) { + host->continueSending = 1; + break; + } + + acknowledgement = (ENetAcknowledgement *) currentAcknowledgement; + currentAcknowledgement = enet_list_next(currentAcknowledgement); + + buffer->data = command; + buffer->dataLength = sizeof(ENetProtocolAcknowledge); + host->packetSize += buffer->dataLength; + + reliableSequenceNumber = ENET_HOST_TO_NET_16(acknowledgement->command.header.reliableSequenceNumber); + + command->header.command = ENET_PROTOCOL_COMMAND_ACKNOWLEDGE; + command->header.channelID = acknowledgement->command.header.channelID; + command->header.reliableSequenceNumber = reliableSequenceNumber; + command->acknowledge.receivedReliableSequenceNumber = reliableSequenceNumber; + command->acknowledge.receivedSentTime = ENET_HOST_TO_NET_16(acknowledgement->sentTime); + + if ((acknowledgement->command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) { + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + } + + enet_list_remove(&acknowledgement->acknowledgementList); + enet_free(acknowledgement); + + ++command; + ++buffer; + } + + host->commandCount = command - host->commands; + host->bufferCount = buffer - host->buffers; + } /* enet_protocol_send_acknowledgements */ + + static void enet_protocol_send_unreliable_outgoing_commands(ENetHost *host, ENetPeer *peer) { + ENetProtocol *command = &host->commands[host->commandCount]; + ENetBuffer *buffer = &host->buffers[host->bufferCount]; + ENetOutgoingCommand *outgoingCommand; + ENetListIterator currentCommand; + + currentCommand = enet_list_begin(&peer->outgoingUnreliableCommands); + while (currentCommand != enet_list_end(&peer->outgoingUnreliableCommands)) { + size_t commandSize; + + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + commandSize = commandSizes[outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK]; + + if (command >= &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] || + buffer + 1 >= &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] || + peer->mtu - host->packetSize < commandSize || + (outgoingCommand->packet != NULL && + peer->mtu - host->packetSize < commandSize + outgoingCommand->fragmentLength) + ) { + host->continueSending = 1; + break; + } + + currentCommand = enet_list_next(currentCommand); + + if (outgoingCommand->packet != NULL && outgoingCommand->fragmentOffset == 0) { + peer->packetThrottleCounter += ENET_PEER_PACKET_THROTTLE_COUNTER; + peer->packetThrottleCounter %= ENET_PEER_PACKET_THROTTLE_SCALE; + + if (peer->packetThrottleCounter > peer->packetThrottle) { + enet_uint16 reliableSequenceNumber = outgoingCommand->reliableSequenceNumber; + enet_uint16 unreliableSequenceNumber = outgoingCommand->unreliableSequenceNumber; + for (;;) { + --outgoingCommand->packet->referenceCount; + + if (outgoingCommand->packet->referenceCount == 0) { + callbacks.packet_destroy(outgoingCommand->packet); + } + + enet_list_remove(&outgoingCommand->outgoingCommandList); + enet_free(outgoingCommand); + + if (currentCommand == enet_list_end(&peer->outgoingUnreliableCommands)) { + break; + } + + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + if (outgoingCommand->reliableSequenceNumber != reliableSequenceNumber || outgoingCommand->unreliableSequenceNumber != unreliableSequenceNumber) { + break; + } + + currentCommand = enet_list_next(currentCommand); + } + + continue; + } + } + + buffer->data = command; + buffer->dataLength = commandSize; + host->packetSize += buffer->dataLength; + *command = outgoingCommand->command; + enet_list_remove(&outgoingCommand->outgoingCommandList); + + if (outgoingCommand->packet != NULL) { + ++buffer; + + buffer->data = outgoingCommand->packet->data + outgoingCommand->fragmentOffset; + buffer->dataLength = outgoingCommand->fragmentLength; + + host->packetSize += buffer->dataLength; + + enet_list_insert(enet_list_end(&peer->sentUnreliableCommands), outgoingCommand); + } else { + enet_free(outgoingCommand); + } + + ++command; + ++buffer; + } + + host->commandCount = command - host->commands; + host->bufferCount = buffer - host->buffers; + + if (peer->state == ENET_PEER_STATE_DISCONNECT_LATER && + enet_list_empty(&peer->outgoingReliableCommands) && + enet_list_empty(&peer->outgoingUnreliableCommands) && + enet_list_empty(&peer->sentReliableCommands)) + { + enet_peer_disconnect(peer, peer->eventData); + } + } /* enet_protocol_send_unreliable_outgoing_commands */ + + static int enet_protocol_check_timeouts(ENetHost *host, ENetPeer *peer, ENetEvent *event) { + ENetOutgoingCommand *outgoingCommand; + ENetListIterator currentCommand, insertPosition; + + currentCommand = enet_list_begin(&peer->sentReliableCommands); + insertPosition = enet_list_begin(&peer->outgoingReliableCommands); + + while (currentCommand != enet_list_end(&peer->sentReliableCommands)) { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + currentCommand = enet_list_next(currentCommand); + + if (ENET_TIME_DIFFERENCE(host->serviceTime, outgoingCommand->sentTime) < outgoingCommand->roundTripTimeout) { + continue; + } + + if (peer->earliestTimeout == 0 || ENET_TIME_LESS(outgoingCommand->sentTime, peer->earliestTimeout)) { + peer->earliestTimeout = outgoingCommand->sentTime; + } + + if (peer->earliestTimeout != 0 && + (ENET_TIME_DIFFERENCE(host->serviceTime, peer->earliestTimeout) >= peer->timeoutMaximum || + (outgoingCommand->roundTripTimeout >= outgoingCommand->roundTripTimeoutLimit && + ENET_TIME_DIFFERENCE(host->serviceTime, peer->earliestTimeout) >= peer->timeoutMinimum)) + ) { + enet_protocol_notify_disconnect_timeout(host, peer, event); + return 1; + } + + if (outgoingCommand->packet != NULL) { + peer->reliableDataInTransit -= outgoingCommand->fragmentLength; + } + + ++peer->packetsLost; + ++peer->totalPacketsLost; + + /* Replaced exponential backoff time with something more linear */ + /* Source: http://lists.cubik.org/pipermail/enet-discuss/2014-May/002308.html */ + outgoingCommand->roundTripTimeout = peer->roundTripTime + 4 * peer->roundTripTimeVariance; + outgoingCommand->roundTripTimeoutLimit = peer->timeoutLimit * outgoingCommand->roundTripTimeout; + + enet_list_insert(insertPosition, enet_list_remove(&outgoingCommand->outgoingCommandList)); + + if (currentCommand == enet_list_begin(&peer->sentReliableCommands) && !enet_list_empty(&peer->sentReliableCommands)) { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + peer->nextTimeout = outgoingCommand->sentTime + outgoingCommand->roundTripTimeout; + } + } + + return 0; + } /* enet_protocol_check_timeouts */ + + static int enet_protocol_send_reliable_outgoing_commands(ENetHost *host, ENetPeer *peer) { + ENetProtocol *command = &host->commands[host->commandCount]; + ENetBuffer *buffer = &host->buffers[host->bufferCount]; + ENetOutgoingCommand *outgoingCommand; + ENetListIterator currentCommand; + ENetChannel *channel; + enet_uint16 reliableWindow; + size_t commandSize; + int windowExceeded = 0, windowWrap = 0, canPing = 1; + + currentCommand = enet_list_begin(&peer->outgoingReliableCommands); + + while (currentCommand != enet_list_end(&peer->outgoingReliableCommands)) { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + channel = outgoingCommand->command.header.channelID < peer->channelCount ? &peer->channels[outgoingCommand->command.header.channelID] : NULL; + reliableWindow = outgoingCommand->reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + if (channel != NULL) { + if (!windowWrap && + outgoingCommand->sendAttempts < 1 && + !(outgoingCommand->reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) && + (channel->reliableWindows[(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) + % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE || + channel->usedReliableWindows & ((((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) << reliableWindow) + | (((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) >> (ENET_PEER_RELIABLE_WINDOWS - reliableWindow)))) + ) { + windowWrap = 1; + } + + if (windowWrap) { + currentCommand = enet_list_next(currentCommand); + continue; + } + } + + if (outgoingCommand->packet != NULL) { + if (!windowExceeded) { + enet_uint32 windowSize = (peer->packetThrottle * peer->windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE; + + if (peer->reliableDataInTransit + outgoingCommand->fragmentLength > ENET_MAX(windowSize, peer->mtu)) { + windowExceeded = 1; + } + } + if (windowExceeded) { + currentCommand = enet_list_next(currentCommand); + + continue; + } + } + + canPing = 0; + + commandSize = commandSizes[outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK]; + if (command >= &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] || + buffer + 1 >= &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] || + peer->mtu - host->packetSize < commandSize || + (outgoingCommand->packet != NULL && + (enet_uint16) (peer->mtu - host->packetSize) < (enet_uint16) (commandSize + outgoingCommand->fragmentLength)) + ) { + host->continueSending = 1; + break; + } + + currentCommand = enet_list_next(currentCommand); + + if (channel != NULL && outgoingCommand->sendAttempts < 1) { + channel->usedReliableWindows |= 1 << reliableWindow; + ++channel->reliableWindows[reliableWindow]; + } + + ++outgoingCommand->sendAttempts; + + if (outgoingCommand->roundTripTimeout == 0) { + outgoingCommand->roundTripTimeout = peer->roundTripTime + 4 * peer->roundTripTimeVariance; + outgoingCommand->roundTripTimeoutLimit = peer->timeoutLimit * outgoingCommand->roundTripTimeout; + } + + if (enet_list_empty(&peer->sentReliableCommands)) { + peer->nextTimeout = host->serviceTime + outgoingCommand->roundTripTimeout; + } + + enet_list_insert(enet_list_end(&peer->sentReliableCommands), enet_list_remove(&outgoingCommand->outgoingCommandList)); + + outgoingCommand->sentTime = host->serviceTime; + + buffer->data = command; + buffer->dataLength = commandSize; + + host->packetSize += buffer->dataLength; + host->headerFlags |= ENET_PROTOCOL_HEADER_FLAG_SENT_TIME; + + *command = outgoingCommand->command; + + if (outgoingCommand->packet != NULL) { + ++buffer; + buffer->data = outgoingCommand->packet->data + outgoingCommand->fragmentOffset; + buffer->dataLength = outgoingCommand->fragmentLength; + host->packetSize += outgoingCommand->fragmentLength; + peer->reliableDataInTransit += outgoingCommand->fragmentLength; + } + + ++peer->packetsSent; + ++peer->totalPacketsSent; + + ++command; + ++buffer; + } + + host->commandCount = command - host->commands; + host->bufferCount = buffer - host->buffers; + + return canPing; + } /* enet_protocol_send_reliable_outgoing_commands */ + + static int enet_protocol_send_outgoing_commands(ENetHost *host, ENetEvent *event, int checkForTimeouts) { + enet_uint8 headerData[sizeof(ENetProtocolHeader) + sizeof(enet_uint32)]; + ENetProtocolHeader *header = (ENetProtocolHeader *) headerData; + ENetPeer *currentPeer; + int sentLength; + size_t shouldCompress = 0; + host->continueSending = 1; + + while (host->continueSending) + for (host->continueSending = 0, currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED || currentPeer->state == ENET_PEER_STATE_ZOMBIE) { + continue; + } + + host->headerFlags = 0; + host->commandCount = 0; + host->bufferCount = 1; + host->packetSize = sizeof(ENetProtocolHeader); + + if (!enet_list_empty(¤tPeer->acknowledgements)) { + enet_protocol_send_acknowledgements(host, currentPeer); + } + + if (checkForTimeouts != 0 && + !enet_list_empty(¤tPeer->sentReliableCommands) && + ENET_TIME_GREATER_EQUAL(host->serviceTime, currentPeer->nextTimeout) && + enet_protocol_check_timeouts(host, currentPeer, event) == 1 + ) { + if (event != NULL && event->type != ENET_EVENT_TYPE_NONE) { + return 1; + } else { + continue; + } + } + + if ((enet_list_empty(¤tPeer->outgoingReliableCommands) || + enet_protocol_send_reliable_outgoing_commands(host, currentPeer)) && + enet_list_empty(¤tPeer->sentReliableCommands) && + ENET_TIME_DIFFERENCE(host->serviceTime, currentPeer->lastReceiveTime) >= currentPeer->pingInterval && + currentPeer->mtu - host->packetSize >= sizeof(ENetProtocolPing) + ) { + enet_peer_ping(currentPeer); + enet_protocol_send_reliable_outgoing_commands(host, currentPeer); + } + + if (!enet_list_empty(¤tPeer->outgoingUnreliableCommands)) { + enet_protocol_send_unreliable_outgoing_commands(host, currentPeer); + } + + if (host->commandCount == 0) { + continue; + } + + if (currentPeer->packetLossEpoch == 0) { + currentPeer->packetLossEpoch = host->serviceTime; + } else if (ENET_TIME_DIFFERENCE(host->serviceTime, currentPeer->packetLossEpoch) >= ENET_PEER_PACKET_LOSS_INTERVAL && currentPeer->packetsSent > 0) { + enet_uint32 packetLoss = currentPeer->packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer->packetsSent; + + #ifdef ENET_DEBUG + printf( + "peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u/%u outgoing, %u/%u incoming\n", currentPeer->incomingPeerID, + currentPeer->packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, + currentPeer->packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer->roundTripTime, currentPeer->roundTripTimeVariance, + currentPeer->packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, + enet_list_size(¤tPeer->outgoingReliableCommands), + enet_list_size(¤tPeer->outgoingUnreliableCommands), + currentPeer->channels != NULL ? enet_list_size( ¤tPeer->channels->incomingReliableCommands) : 0, + currentPeer->channels != NULL ? enet_list_size(¤tPeer->channels->incomingUnreliableCommands) : 0 + ); + #endif + + currentPeer->packetLossVariance -= currentPeer->packetLossVariance / 4; + + if (packetLoss >= currentPeer->packetLoss) { + currentPeer->packetLoss += (packetLoss - currentPeer->packetLoss) / 8; + currentPeer->packetLossVariance += (packetLoss - currentPeer->packetLoss) / 4; + } else { + currentPeer->packetLoss -= (currentPeer->packetLoss - packetLoss) / 8; + currentPeer->packetLossVariance += (currentPeer->packetLoss - packetLoss) / 4; + } + + currentPeer->packetLossEpoch = host->serviceTime; + currentPeer->packetsSent = 0; + currentPeer->packetsLost = 0; + } + + host->buffers->data = headerData; + if (host->headerFlags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME) { + header->sentTime = ENET_HOST_TO_NET_16(host->serviceTime & 0xFFFF); + host->buffers->dataLength = sizeof(ENetProtocolHeader); + } else { + host->buffers->dataLength = (size_t) &((ENetProtocolHeader *) 0)->sentTime; + } + + shouldCompress = 0; + if (host->compressor.context != NULL && host->compressor.compress != NULL) { + size_t originalSize = host->packetSize - sizeof(ENetProtocolHeader), + compressedSize = host->compressor.compress(host->compressor.context, &host->buffers[1], host->bufferCount - 1, originalSize, host->packetData[1], originalSize); + if (compressedSize > 0 && compressedSize < originalSize) { + host->headerFlags |= ENET_PROTOCOL_HEADER_FLAG_COMPRESSED; + shouldCompress = compressedSize; + #ifdef ENET_DEBUG_COMPRESS + printf("peer %u: compressed %u->%u (%u%%)\n", currentPeer->incomingPeerID, originalSize, compressedSize, (compressedSize * 100) / originalSize); + #endif + } + } + + if (currentPeer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID) { + host->headerFlags |= currentPeer->outgoingSessionID << ENET_PROTOCOL_HEADER_SESSION_SHIFT; + } + header->peerID = ENET_HOST_TO_NET_16(currentPeer->outgoingPeerID | host->headerFlags); + if (host->checksum != NULL) { + enet_uint32 *checksum = (enet_uint32 *) &headerData[host->buffers->dataLength]; + *checksum = currentPeer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID ? currentPeer->connectID : 0; + host->buffers->dataLength += sizeof(enet_uint32); + *checksum = host->checksum(host->buffers, host->bufferCount); + } + + if (shouldCompress > 0) { + host->buffers[1].data = host->packetData[1]; + host->buffers[1].dataLength = shouldCompress; + host->bufferCount = 2; + } + + currentPeer->lastSendTime = host->serviceTime; + sentLength = enet_socket_send(host->socket, ¤tPeer->address, host->buffers, host->bufferCount); + enet_protocol_remove_sent_unreliable_commands(currentPeer); + + if (sentLength < 0) { + return -1; + } + + host->totalSentData += sentLength; + currentPeer->totalDataSent += sentLength; + host->totalSentPackets++; + } + + return 0; + } /* enet_protocol_send_outgoing_commands */ + + /** Sends any queued packets on the host specified to its designated peers. + * + * @param host host to flush + * @remarks this function need only be used in circumstances where one wishes to send queued packets earlier than in a call to enet_host_service(). + * @ingroup host + */ + void enet_host_flush(ENetHost *host) { + host->serviceTime = enet_time_get(); + enet_protocol_send_outgoing_commands(host, NULL, 0); + } + + /** Checks for any queued events on the host and dispatches one if available. + * + * @param host host to check for events + * @param event an event structure where event details will be placed if available + * @retval > 0 if an event was dispatched + * @retval 0 if no events are available + * @retval < 0 on failure + * @ingroup host + */ + int enet_host_check_events(ENetHost *host, ENetEvent *event) { + if (event == NULL) { return -1; } + + event->type = ENET_EVENT_TYPE_NONE; + event->peer = NULL; + event->packet = NULL; + + return enet_protocol_dispatch_incoming_commands(host, event); + } + + /** Waits for events on the host specified and shuttles packets between + * the host and its peers. + * + * @param host host to service + * @param event an event structure where event details will be placed if one occurs + * if event == NULL then no events will be delivered + * @param timeout number of milliseconds that ENet should wait for events + * @retval > 0 if an event occurred within the specified time limit + * @retval 0 if no event occurred + * @retval < 0 on failure + * @remarks enet_host_service should be called fairly regularly for adequate performance + * @ingroup host + */ + int enet_host_service(ENetHost *host, ENetEvent *event, enet_uint32 timeout) { + enet_uint32 waitCondition; + + if (event != NULL) { + event->type = ENET_EVENT_TYPE_NONE; + event->peer = NULL; + event->packet = NULL; + + switch (enet_protocol_dispatch_incoming_commands(host, event)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error dispatching incoming packets"); + #endif + + return -1; + + default: + break; + } + } + + host->serviceTime = enet_time_get(); + timeout += host->serviceTime; + + do { + if (ENET_TIME_DIFFERENCE(host->serviceTime, host->bandwidthThrottleEpoch) >= ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) { + enet_host_bandwidth_throttle(host); + } + + switch (enet_protocol_send_outgoing_commands(host, event, 1)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error sending outgoing packets"); + #endif + + return -1; + + default: + break; + } + + switch (enet_protocol_receive_incoming_commands(host, event)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error receiving incoming packets"); + #endif + + return -1; + + default: + break; + } + + switch (enet_protocol_send_outgoing_commands(host, event, 1)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error sending outgoing packets"); + #endif + + return -1; + + default: + break; + } + + if (event != NULL) { + switch (enet_protocol_dispatch_incoming_commands(host, event)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error dispatching incoming packets"); + #endif + + return -1; + + default: + break; + } + } + + if (ENET_TIME_GREATER_EQUAL(host->serviceTime, timeout)) { + return 0; + } + + do { + host->serviceTime = enet_time_get(); + + if (ENET_TIME_GREATER_EQUAL(host->serviceTime, timeout)) { + return 0; + } + + waitCondition = ENET_SOCKET_WAIT_RECEIVE | ENET_SOCKET_WAIT_INTERRUPT; + if (enet_socket_wait(host->socket, &waitCondition, ENET_TIME_DIFFERENCE(timeout, host->serviceTime)) != 0) { + return -1; + } + } while (waitCondition & ENET_SOCKET_WAIT_INTERRUPT); + + host->serviceTime = enet_time_get(); + } while (waitCondition & ENET_SOCKET_WAIT_RECEIVE); + + return 0; + } /* enet_host_service */ + + +// =======================================================================// +// ! +// ! Peer +// ! +// =======================================================================// + + /** Configures throttle parameter for a peer. + * + * Unreliable packets are dropped by ENet in response to the varying conditions + * of the Internet connection to the peer. The throttle represents a probability + * that an unreliable packet should not be dropped and thus sent by ENet to the peer. + * The lowest mean round trip time from the sending of a reliable packet to the + * receipt of its acknowledgement is measured over an amount of time specified by + * the interval parameter in milliseconds. If a measured round trip time happens to + * be significantly less than the mean round trip time measured over the interval, + * then the throttle probability is increased to allow more traffic by an amount + * specified in the acceleration parameter, which is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE + * constant. If a measured round trip time happens to be significantly greater than + * the mean round trip time measured over the interval, then the throttle probability + * is decreased to limit traffic by an amount specified in the deceleration parameter, which + * is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE constant. When the throttle has + * a value of ENET_PEER_PACKET_THROTTLE_SCALE, no unreliable packets are dropped by + * ENet, and so 100% of all unreliable packets will be sent. When the throttle has a + * value of 0, all unreliable packets are dropped by ENet, and so 0% of all unreliable + * packets will be sent. Intermediate values for the throttle represent intermediate + * probabilities between 0% and 100% of unreliable packets being sent. The bandwidth + * limits of the local and foreign hosts are taken into account to determine a + * sensible limit for the throttle probability above which it should not raise even in + * the best of conditions. + * + * @param peer peer to configure + * @param interval interval, in milliseconds, over which to measure lowest mean RTT; the default value is ENET_PEER_PACKET_THROTTLE_INTERVAL. + * @param acceleration rate at which to increase the throttle probability as mean RTT declines + * @param deceleration rate at which to decrease the throttle probability as mean RTT increases + */ + void enet_peer_throttle_configure(ENetPeer *peer, enet_uint32 interval, enet_uint32 acceleration, enet_uint32 deceleration) { + ENetProtocol command; + + peer->packetThrottleInterval = interval; + peer->packetThrottleAcceleration = acceleration; + peer->packetThrottleDeceleration = deceleration; + + command.header.command = ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + + command.throttleConfigure.packetThrottleInterval = ENET_HOST_TO_NET_32(interval); + command.throttleConfigure.packetThrottleAcceleration = ENET_HOST_TO_NET_32(acceleration); + command.throttleConfigure.packetThrottleDeceleration = ENET_HOST_TO_NET_32(deceleration); + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + } + + int enet_peer_throttle(ENetPeer *peer, enet_uint32 rtt) { + if (peer->lastRoundTripTime <= peer->lastRoundTripTimeVariance) { + peer->packetThrottle = peer->packetThrottleLimit; + } + else if (rtt < peer->lastRoundTripTime) { + peer->packetThrottle += peer->packetThrottleAcceleration; + + if (peer->packetThrottle > peer->packetThrottleLimit) { + peer->packetThrottle = peer->packetThrottleLimit; + } + + return 1; + } + else if (rtt > peer->lastRoundTripTime + 2 * peer->lastRoundTripTimeVariance) { + if (peer->packetThrottle > peer->packetThrottleDeceleration) { + peer->packetThrottle -= peer->packetThrottleDeceleration; + } else { + peer->packetThrottle = 0; + } + + return -1; + } + + return 0; + } + + /* Extended functionality for easier binding in other programming languages */ + enet_uint32 enet_host_get_peers_count(ENetHost *host) { + return host->connectedPeers; + } + + enet_uint32 enet_host_get_packets_sent(ENetHost *host) { + return host->totalSentPackets; + } + + enet_uint32 enet_host_get_packets_received(ENetHost *host) { + return host->totalReceivedPackets; + } + + enet_uint32 enet_host_get_bytes_sent(ENetHost *host) { + return host->totalSentData; + } + + enet_uint32 enet_host_get_bytes_received(ENetHost *host) { + return host->totalReceivedData; + } + + /** Gets received data buffer. Returns buffer length. + * @param host host to access recevie buffer + * @param data ouput parameter for recevied data + * @retval buffer length + */ + enet_uint32 enet_host_get_received_data(ENetHost *host, /*out*/ enet_uint8** data) { + *data = host->receivedData; + return host->receivedDataLength; + } + + enet_uint32 enet_host_get_mtu(ENetHost *host) { + return host->mtu; + } + + enet_uint32 enet_peer_get_id(ENetPeer *peer) { + return peer->connectID; + } + + enet_uint32 enet_peer_get_ip(ENetPeer *peer, char *ip, size_t ipLength) { + return enet_address_get_host_ip(&peer->address, ip, ipLength); + } + + enet_uint16 enet_peer_get_port(ENetPeer *peer) { + return peer->address.port; + } + + ENetPeerState enet_peer_get_state(ENetPeer *peer) { + return peer->state; + } + + enet_uint32 enet_peer_get_rtt(ENetPeer *peer) { + return peer->roundTripTime; + } + + enet_uint64 enet_peer_get_packets_sent(ENetPeer *peer) { + return peer->totalPacketsSent; + } + + enet_uint32 enet_peer_get_packets_lost(ENetPeer *peer) { + return peer->totalPacketsLost; + } + + enet_uint64 enet_peer_get_bytes_sent(ENetPeer *peer) { + return peer->totalDataSent; + } + + enet_uint64 enet_peer_get_bytes_received(ENetPeer *peer) { + return peer->totalDataReceived; + } + + void * enet_peer_get_data(ENetPeer *peer) { + return peer->data; + } + + void enet_peer_set_data(ENetPeer *peer, const void *data) { + peer->data = (enet_uint32 *) data; + } + + void * enet_packet_get_data(ENetPacket *packet) { + return (void *) packet->data; + } + + enet_uint32 enet_packet_get_length(ENetPacket *packet) { + return packet->dataLength; + } + + void enet_packet_set_free_callback(ENetPacket *packet, void *callback) { + packet->freeCallback = (ENetPacketFreeCallback)callback; + } + + /** Queues a packet to be sent. + * @param peer destination for the packet + * @param channelID channel on which to send + * @param packet packet to send + * @retval 0 on success + * @retval < 0 on failure + */ + int enet_peer_send(ENetPeer *peer, enet_uint8 channelID, ENetPacket *packet) { + ENetChannel *channel = &peer->channels[channelID]; + ENetProtocol command; + size_t fragmentLength; + + if (peer->state != ENET_PEER_STATE_CONNECTED || channelID >= peer->channelCount || packet->dataLength > peer->host->maximumPacketSize) { + return -1; + } + + fragmentLength = peer->mtu - sizeof(ENetProtocolHeader) - sizeof(ENetProtocolSendFragment); + if (peer->host->checksum != NULL) { + fragmentLength -= sizeof(enet_uint32); + } + + if (packet->dataLength > fragmentLength) { + enet_uint32 fragmentCount = (packet->dataLength + fragmentLength - 1) / fragmentLength, fragmentNumber, fragmentOffset; + enet_uint8 commandNumber; + enet_uint16 startSequenceNumber; + ENetList fragments; + ENetOutgoingCommand *fragment; + + if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT) { + return -1; + } + + if ((packet->flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT)) == + ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT && + channel->outgoingUnreliableSequenceNumber < 0xFFFF) + { + commandNumber = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT; + startSequenceNumber = ENET_HOST_TO_NET_16(channel->outgoingUnreliableSequenceNumber + 1); + } else { + commandNumber = ENET_PROTOCOL_COMMAND_SEND_FRAGMENT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + startSequenceNumber = ENET_HOST_TO_NET_16(channel->outgoingReliableSequenceNumber + 1); + } + + enet_list_clear(&fragments); + + for (fragmentNumber = 0, fragmentOffset = 0; fragmentOffset < packet->dataLength; ++fragmentNumber, fragmentOffset += fragmentLength) { + if (packet->dataLength - fragmentOffset < fragmentLength) { + fragmentLength = packet->dataLength - fragmentOffset; + } + + fragment = (ENetOutgoingCommand *) enet_malloc(sizeof(ENetOutgoingCommand)); + + if (fragment == NULL) { + while (!enet_list_empty(&fragments)) { + fragment = (ENetOutgoingCommand *) enet_list_remove(enet_list_begin(&fragments)); + + enet_free(fragment); + } + + return -1; + } + + fragment->fragmentOffset = fragmentOffset; + fragment->fragmentLength = fragmentLength; + fragment->packet = packet; + fragment->command.header.command = commandNumber; + fragment->command.header.channelID = channelID; + + fragment->command.sendFragment.startSequenceNumber = startSequenceNumber; + + fragment->command.sendFragment.dataLength = ENET_HOST_TO_NET_16(fragmentLength); + fragment->command.sendFragment.fragmentCount = ENET_HOST_TO_NET_32(fragmentCount); + fragment->command.sendFragment.fragmentNumber = ENET_HOST_TO_NET_32(fragmentNumber); + fragment->command.sendFragment.totalLength = ENET_HOST_TO_NET_32(packet->dataLength); + fragment->command.sendFragment.fragmentOffset = ENET_NET_TO_HOST_32(fragmentOffset); + + enet_list_insert(enet_list_end(&fragments), fragment); + } + + packet->referenceCount += fragmentNumber; + + while (!enet_list_empty(&fragments)) { + fragment = (ENetOutgoingCommand *) enet_list_remove(enet_list_begin(&fragments)); + enet_peer_setup_outgoing_command(peer, fragment); + } + + return 0; + } + + command.header.channelID = channelID; + + if ((packet->flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNSEQUENCED)) == ENET_PACKET_FLAG_UNSEQUENCED) { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + command.sendUnsequenced.dataLength = ENET_HOST_TO_NET_16(packet->dataLength); + } + else if (packet->flags & ENET_PACKET_FLAG_RELIABLE || channel->outgoingUnreliableSequenceNumber >= 0xFFFF) { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_RELIABLE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.sendReliable.dataLength = ENET_HOST_TO_NET_16(packet->dataLength); + } + else { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE; + command.sendUnreliable.dataLength = ENET_HOST_TO_NET_16(packet->dataLength); + } + + if (enet_peer_queue_outgoing_command(peer, &command, packet, 0, packet->dataLength) == NULL) { + return -1; + } + + return 0; + } // enet_peer_send + + /** Attempts to dequeue any incoming queued packet. + * @param peer peer to dequeue packets from + * @param channelID holds the channel ID of the channel the packet was received on success + * @returns a pointer to the packet, or NULL if there are no available incoming queued packets + */ + ENetPacket * enet_peer_receive(ENetPeer *peer, enet_uint8 *channelID) { + ENetIncomingCommand *incomingCommand; + ENetPacket *packet; + + if (enet_list_empty(&peer->dispatchedCommands)) { + return NULL; + } + + incomingCommand = (ENetIncomingCommand *) enet_list_remove(enet_list_begin(&peer->dispatchedCommands)); + + if (channelID != NULL) { + *channelID = incomingCommand->command.header.channelID; + } + + packet = incomingCommand->packet; + --packet->referenceCount; + + if (incomingCommand->fragments != NULL) { + enet_free(incomingCommand->fragments); + } + + enet_free(incomingCommand); + peer->totalWaitingData -= packet->dataLength; + + return packet; + } + + static void enet_peer_reset_outgoing_commands(ENetList *queue) { + ENetOutgoingCommand *outgoingCommand; + + while (!enet_list_empty(queue)) { + outgoingCommand = (ENetOutgoingCommand *) enet_list_remove(enet_list_begin(queue)); + + if (outgoingCommand->packet != NULL) { + --outgoingCommand->packet->referenceCount; + + if (outgoingCommand->packet->referenceCount == 0) { + callbacks.packet_destroy(outgoingCommand->packet); + } + } + + enet_free(outgoingCommand); + } + } + + static void enet_peer_remove_incoming_commands(ENetList *queue, ENetListIterator startCommand, ENetListIterator endCommand) { + ENET_UNUSED(queue) + + ENetListIterator currentCommand; + + for (currentCommand = startCommand; currentCommand != endCommand;) { + ENetIncomingCommand *incomingCommand = (ENetIncomingCommand *) currentCommand; + + currentCommand = enet_list_next(currentCommand); + enet_list_remove(&incomingCommand->incomingCommandList); + + if (incomingCommand->packet != NULL) { + --incomingCommand->packet->referenceCount; + + if (incomingCommand->packet->referenceCount == 0) { + callbacks.packet_destroy(incomingCommand->packet); + } + } + + if (incomingCommand->fragments != NULL) { + enet_free(incomingCommand->fragments); + } + + enet_free(incomingCommand); + } + } + + static void enet_peer_reset_incoming_commands(ENetList *queue) { + enet_peer_remove_incoming_commands(queue, enet_list_begin(queue), enet_list_end(queue)); + } + + void enet_peer_reset_queues(ENetPeer *peer) { + ENetChannel *channel; + + if (peer->needsDispatch) { + enet_list_remove(&peer->dispatchList); + peer->needsDispatch = 0; + } + + while (!enet_list_empty(&peer->acknowledgements)) { + enet_free(enet_list_remove(enet_list_begin(&peer->acknowledgements))); + } + + enet_peer_reset_outgoing_commands(&peer->sentReliableCommands); + enet_peer_reset_outgoing_commands(&peer->sentUnreliableCommands); + enet_peer_reset_outgoing_commands(&peer->outgoingReliableCommands); + enet_peer_reset_outgoing_commands(&peer->outgoingUnreliableCommands); + enet_peer_reset_incoming_commands(&peer->dispatchedCommands); + + if (peer->channels != NULL && peer->channelCount > 0) { + for (channel = peer->channels; channel < &peer->channels[peer->channelCount]; ++channel) { + enet_peer_reset_incoming_commands(&channel->incomingReliableCommands); + enet_peer_reset_incoming_commands(&channel->incomingUnreliableCommands); + } + + enet_free(peer->channels); + } + + peer->channels = NULL; + peer->channelCount = 0; + } + + void enet_peer_on_connect(ENetPeer *peer) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { + if (peer->incomingBandwidth != 0) { + ++peer->host->bandwidthLimitedPeers; + } + + ++peer->host->connectedPeers; + } + } + + void enet_peer_on_disconnect(ENetPeer *peer) { + if (peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { + if (peer->incomingBandwidth != 0) { + --peer->host->bandwidthLimitedPeers; + } + + --peer->host->connectedPeers; + } + } + + /** Forcefully disconnects a peer. + * @param peer peer to forcefully disconnect + * @remarks The foreign host represented by the peer is not notified of the disconnection and will timeout + * on its connection to the local host. + */ + void enet_peer_reset(ENetPeer *peer) { + enet_peer_on_disconnect(peer); + + // We don't want to reset connectID here, otherwise, we can't get it in the Disconnect event + // peer->connectID = 0; + peer->outgoingPeerID = ENET_PROTOCOL_MAXIMUM_PEER_ID; + peer->state = ENET_PEER_STATE_DISCONNECTED; + peer->incomingBandwidth = 0; + peer->outgoingBandwidth = 0; + peer->incomingBandwidthThrottleEpoch = 0; + peer->outgoingBandwidthThrottleEpoch = 0; + peer->incomingDataTotal = 0; + peer->totalDataReceived = 0; + peer->outgoingDataTotal = 0; + peer->totalDataSent = 0; + peer->lastSendTime = 0; + peer->lastReceiveTime = 0; + peer->nextTimeout = 0; + peer->earliestTimeout = 0; + peer->packetLossEpoch = 0; + peer->packetsSent = 0; + peer->totalPacketsSent = 0; + peer->packetsLost = 0; + peer->totalPacketsLost = 0; + peer->packetLoss = 0; + peer->packetLossVariance = 0; + peer->packetThrottle = ENET_PEER_DEFAULT_PACKET_THROTTLE; + peer->packetThrottleLimit = ENET_PEER_PACKET_THROTTLE_SCALE; + peer->packetThrottleCounter = 0; + peer->packetThrottleEpoch = 0; + peer->packetThrottleAcceleration = ENET_PEER_PACKET_THROTTLE_ACCELERATION; + peer->packetThrottleDeceleration = ENET_PEER_PACKET_THROTTLE_DECELERATION; + peer->packetThrottleInterval = ENET_PEER_PACKET_THROTTLE_INTERVAL; + peer->pingInterval = ENET_PEER_PING_INTERVAL; + peer->timeoutLimit = ENET_PEER_TIMEOUT_LIMIT; + peer->timeoutMinimum = ENET_PEER_TIMEOUT_MINIMUM; + peer->timeoutMaximum = ENET_PEER_TIMEOUT_MAXIMUM; + peer->lastRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer->lowestRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer->lastRoundTripTimeVariance = 0; + peer->highestRoundTripTimeVariance = 0; + peer->roundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer->roundTripTimeVariance = 0; + peer->mtu = peer->host->mtu; + peer->reliableDataInTransit = 0; + peer->outgoingReliableSequenceNumber = 0; + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + peer->incomingUnsequencedGroup = 0; + peer->outgoingUnsequencedGroup = 0; + peer->eventData = 0; + peer->totalWaitingData = 0; + + memset(peer->unsequencedWindow, 0, sizeof(peer->unsequencedWindow)); + enet_peer_reset_queues(peer); + } + + /** Sends a ping request to a peer. + * @param peer destination for the ping request + * @remarks ping requests factor into the mean round trip time as designated by the + * roundTripTime field in the ENetPeer structure. ENet automatically pings all connected + * peers at regular intervals, however, this function may be called to ensure more + * frequent ping requests. + */ + void enet_peer_ping(ENetPeer *peer) { + ENetProtocol command; + + if (peer->state != ENET_PEER_STATE_CONNECTED) { + return; + } + + command.header.command = ENET_PROTOCOL_COMMAND_PING | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + } + + /** Sets the interval at which pings will be sent to a peer. + * + * Pings are used both to monitor the liveness of the connection and also to dynamically + * adjust the throttle during periods of low traffic so that the throttle has reasonable + * responsiveness during traffic spikes. + * + * @param peer the peer to adjust + * @param pingInterval the interval at which to send pings; defaults to ENET_PEER_PING_INTERVAL if 0 + */ + void enet_peer_ping_interval(ENetPeer *peer, enet_uint32 pingInterval) { + peer->pingInterval = pingInterval ? pingInterval : (enet_uint32)ENET_PEER_PING_INTERVAL; + } + + /** Sets the timeout parameters for a peer. + * + * The timeout parameter control how and when a peer will timeout from a failure to acknowledge + * reliable traffic. Timeout values use an exponential backoff mechanism, where if a reliable + * packet is not acknowledge within some multiple of the average RTT plus a variance tolerance, + * the timeout will be doubled until it reaches a set limit. If the timeout is thus at this + * limit and reliable packets have been sent but not acknowledged within a certain minimum time + * period, the peer will be disconnected. Alternatively, if reliable packets have been sent + * but not acknowledged for a certain maximum time period, the peer will be disconnected regardless + * of the current timeout limit value. + * + * @param peer the peer to adjust + * @param timeoutLimit the timeout limit; defaults to ENET_PEER_TIMEOUT_LIMIT if 0 + * @param timeoutMinimum the timeout minimum; defaults to ENET_PEER_TIMEOUT_MINIMUM if 0 + * @param timeoutMaximum the timeout maximum; defaults to ENET_PEER_TIMEOUT_MAXIMUM if 0 + */ + + void enet_peer_timeout(ENetPeer *peer, enet_uint32 timeoutLimit, enet_uint32 timeoutMinimum, enet_uint32 timeoutMaximum) { + peer->timeoutLimit = timeoutLimit ? timeoutLimit : (enet_uint32)ENET_PEER_TIMEOUT_LIMIT; + peer->timeoutMinimum = timeoutMinimum ? timeoutMinimum : (enet_uint32)ENET_PEER_TIMEOUT_MINIMUM; + peer->timeoutMaximum = timeoutMaximum ? timeoutMaximum : (enet_uint32)ENET_PEER_TIMEOUT_MAXIMUM; + } + + /** Force an immediate disconnection from a peer. + * @param peer peer to disconnect + * @param data data describing the disconnection + * @remarks No ENET_EVENT_DISCONNECT event will be generated. The foreign peer is not + * guaranteed to receive the disconnect notification, and is reset immediately upon + * return from this function. + */ + void enet_peer_disconnect_now(ENetPeer *peer, enet_uint32 data) { + ENetProtocol command; + + if (peer->state == ENET_PEER_STATE_DISCONNECTED) { + return; + } + + if (peer->state != ENET_PEER_STATE_ZOMBIE && peer->state != ENET_PEER_STATE_DISCONNECTING) { + enet_peer_reset_queues(peer); + + command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + command.header.channelID = 0xFF; + command.disconnect.data = ENET_HOST_TO_NET_32(data); + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + enet_host_flush(peer->host); + } + + enet_peer_reset(peer); + } + + /** Request a disconnection from a peer. + * @param peer peer to request a disconnection + * @param data data describing the disconnection + * @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() + * once the disconnection is complete. + */ + void enet_peer_disconnect(ENetPeer *peer, enet_uint32 data) { + ENetProtocol command; + + if (peer->state == ENET_PEER_STATE_DISCONNECTING || + peer->state == ENET_PEER_STATE_DISCONNECTED || + peer->state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT || + peer->state == ENET_PEER_STATE_ZOMBIE + ) { + return; + } + + enet_peer_reset_queues(peer); + + command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT; + command.header.channelID = 0xFF; + command.disconnect.data = ENET_HOST_TO_NET_32(data); + + if (peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { + command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + } else { + command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + } + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + + if (peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { + enet_peer_on_disconnect(peer); + + peer->state = ENET_PEER_STATE_DISCONNECTING; + } else { + enet_host_flush(peer->host); + enet_peer_reset(peer); + } + } + + /** Request a disconnection from a peer, but only after all queued outgoing packets are sent. + * @param peer peer to request a disconnection + * @param data data describing the disconnection + * @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() + * once the disconnection is complete. + */ + void enet_peer_disconnect_later(ENetPeer *peer, enet_uint32 data) { + if ((peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) && + !(enet_list_empty(&peer->outgoingReliableCommands) && + enet_list_empty(&peer->outgoingUnreliableCommands) && + enet_list_empty(&peer->sentReliableCommands)) + ) { + peer->state = ENET_PEER_STATE_DISCONNECT_LATER; + peer->eventData = data; + } else { + enet_peer_disconnect(peer, data); + } + } + + ENetAcknowledgement *enet_peer_queue_acknowledgement(ENetPeer *peer, const ENetProtocol *command, enet_uint16 sentTime) { + ENetAcknowledgement *acknowledgement; + + if (command->header.channelID < peer->channelCount) { + ENetChannel *channel = &peer->channels[command->header.channelID]; + enet_uint16 reliableWindow = command->header.reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + enet_uint16 currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (command->header.reliableSequenceNumber < channel->incomingReliableSequenceNumber) { + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + } + + if (reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1 && reliableWindow <= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS) { + return NULL; + } + } + + acknowledgement = (ENetAcknowledgement *) enet_malloc(sizeof(ENetAcknowledgement)); + if (acknowledgement == NULL) { + return NULL; + } + + peer->outgoingDataTotal += sizeof(ENetProtocolAcknowledge); + + acknowledgement->sentTime = sentTime; + acknowledgement->command = *command; + + enet_list_insert(enet_list_end(&peer->acknowledgements), acknowledgement); + return acknowledgement; + } + + void enet_peer_setup_outgoing_command(ENetPeer *peer, ENetOutgoingCommand *outgoingCommand) { + ENetChannel *channel = &peer->channels[outgoingCommand->command.header.channelID]; + peer->outgoingDataTotal += enet_protocol_command_size(outgoingCommand->command.header.command) + outgoingCommand->fragmentLength; + + if (outgoingCommand->command.header.channelID == 0xFF) { + ++peer->outgoingReliableSequenceNumber; + + outgoingCommand->reliableSequenceNumber = peer->outgoingReliableSequenceNumber; + outgoingCommand->unreliableSequenceNumber = 0; + } + else if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { + ++channel->outgoingReliableSequenceNumber; + channel->outgoingUnreliableSequenceNumber = 0; + + outgoingCommand->reliableSequenceNumber = channel->outgoingReliableSequenceNumber; + outgoingCommand->unreliableSequenceNumber = 0; + } + else if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED) { + ++peer->outgoingUnsequencedGroup; + + outgoingCommand->reliableSequenceNumber = 0; + outgoingCommand->unreliableSequenceNumber = 0; + } + else { + if (outgoingCommand->fragmentOffset == 0) { + ++channel->outgoingUnreliableSequenceNumber; + } + + outgoingCommand->reliableSequenceNumber = channel->outgoingReliableSequenceNumber; + outgoingCommand->unreliableSequenceNumber = channel->outgoingUnreliableSequenceNumber; + } + + outgoingCommand->sendAttempts = 0; + outgoingCommand->sentTime = 0; + outgoingCommand->roundTripTimeout = 0; + outgoingCommand->roundTripTimeoutLimit = 0; + outgoingCommand->command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16(outgoingCommand->reliableSequenceNumber); + + switch (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) { + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + outgoingCommand->command.sendUnreliable.unreliableSequenceNumber = ENET_HOST_TO_NET_16(outgoingCommand->unreliableSequenceNumber); + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + outgoingCommand->command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16(peer->outgoingUnsequencedGroup); + break; + + default: + break; + } + + if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { + enet_list_insert(enet_list_end(&peer->outgoingReliableCommands), outgoingCommand); + } else { + enet_list_insert(enet_list_end(&peer->outgoingUnreliableCommands), outgoingCommand); + } + } + + ENetOutgoingCommand * enet_peer_queue_outgoing_command(ENetPeer *peer, const ENetProtocol *command, ENetPacket *packet, enet_uint32 offset, enet_uint16 length) { + ENetOutgoingCommand *outgoingCommand = (ENetOutgoingCommand *) enet_malloc(sizeof(ENetOutgoingCommand)); + + if (outgoingCommand == NULL) { + return NULL; + } + + outgoingCommand->command = *command; + outgoingCommand->fragmentOffset = offset; + outgoingCommand->fragmentLength = length; + outgoingCommand->packet = packet; + if (packet != NULL) { + ++packet->referenceCount; + } + + enet_peer_setup_outgoing_command(peer, outgoingCommand); + return outgoingCommand; + } + + void enet_peer_dispatch_incoming_unreliable_commands(ENetPeer *peer, ENetChannel *channel) { + ENetListIterator droppedCommand, startCommand, currentCommand; + + for (droppedCommand = startCommand = currentCommand = enet_list_begin(&channel->incomingUnreliableCommands); + currentCommand != enet_list_end(&channel->incomingUnreliableCommands); + currentCommand = enet_list_next(currentCommand) + ) { + ENetIncomingCommand *incomingCommand = (ENetIncomingCommand *) currentCommand; + + if ((incomingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) { + continue; + } + + if (incomingCommand->reliableSequenceNumber == channel->incomingReliableSequenceNumber) { + if (incomingCommand->fragmentsRemaining <= 0) { + channel->incomingUnreliableSequenceNumber = incomingCommand->unreliableSequenceNumber; + continue; + } + + if (startCommand != currentCommand) { + enet_list_move(enet_list_end(&peer->dispatchedCommands), startCommand, enet_list_previous(currentCommand)); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); + peer->needsDispatch = 1; + } + + droppedCommand = currentCommand; + } else if (droppedCommand != currentCommand) { + droppedCommand = enet_list_previous(currentCommand); + } + } else { + enet_uint16 reliableWindow = incomingCommand->reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + enet_uint16 currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + } + + if (reliableWindow >= currentWindow && reliableWindow < currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) { + break; + } + + droppedCommand = enet_list_next(currentCommand); + + if (startCommand != currentCommand) { + enet_list_move(enet_list_end(&peer->dispatchedCommands), startCommand, enet_list_previous(currentCommand)); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); + peer->needsDispatch = 1; + } + } + } + + startCommand = enet_list_next(currentCommand); + } + + if (startCommand != currentCommand) { + enet_list_move(enet_list_end(&peer->dispatchedCommands), startCommand, enet_list_previous(currentCommand)); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); + peer->needsDispatch = 1; + } + + droppedCommand = currentCommand; + } + + enet_peer_remove_incoming_commands(&channel->incomingUnreliableCommands,enet_list_begin(&channel->incomingUnreliableCommands), droppedCommand); + } + + void enet_peer_dispatch_incoming_reliable_commands(ENetPeer *peer, ENetChannel *channel) { + ENetListIterator currentCommand; + + for (currentCommand = enet_list_begin(&channel->incomingReliableCommands); + currentCommand != enet_list_end(&channel->incomingReliableCommands); + currentCommand = enet_list_next(currentCommand) + ) { + ENetIncomingCommand *incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (incomingCommand->fragmentsRemaining > 0 || incomingCommand->reliableSequenceNumber != (enet_uint16) (channel->incomingReliableSequenceNumber + 1)) { + break; + } + + channel->incomingReliableSequenceNumber = incomingCommand->reliableSequenceNumber; + + if (incomingCommand->fragmentCount > 0) { + channel->incomingReliableSequenceNumber += incomingCommand->fragmentCount - 1; + } + } + + if (currentCommand == enet_list_begin(&channel->incomingReliableCommands)) { + return; + } + + channel->incomingUnreliableSequenceNumber = 0; + enet_list_move(enet_list_end(&peer->dispatchedCommands), enet_list_begin(&channel->incomingReliableCommands), enet_list_previous(currentCommand)); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); + peer->needsDispatch = 1; + } + + if (!enet_list_empty(&channel->incomingUnreliableCommands)) { + enet_peer_dispatch_incoming_unreliable_commands(peer, channel); + } + } + + ENetIncomingCommand * enet_peer_queue_incoming_command(ENetPeer *peer, const ENetProtocol *command, const void *data, size_t dataLength, enet_uint32 flags, enet_uint32 fragmentCount) { + static ENetIncomingCommand dummyCommand; + + ENetChannel *channel = &peer->channels[command->header.channelID]; + enet_uint32 unreliableSequenceNumber = 0, reliableSequenceNumber = 0; + enet_uint16 reliableWindow, currentWindow; + ENetIncomingCommand *incomingCommand; + ENetListIterator currentCommand; + ENetPacket *packet = NULL; + + if (peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { + goto discardCommand; + } + + if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) { + reliableSequenceNumber = command->header.reliableSequenceNumber; + reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (reliableSequenceNumber < channel->incomingReliableSequenceNumber) { + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + } + + if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) { + goto discardCommand; + } + } + + switch (command->header.command & ENET_PROTOCOL_COMMAND_MASK) { + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + if (reliableSequenceNumber == channel->incomingReliableSequenceNumber) { + goto discardCommand; + } + + for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingReliableCommands)); + currentCommand != enet_list_end(&channel->incomingReliableCommands); + currentCommand = enet_list_previous(currentCommand) + ) { + incomingCommand = (ENetIncomingCommand *) currentCommand; + + if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { + continue; + } + } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber <= reliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber) { + break; + } + + goto discardCommand; + } + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT: + unreliableSequenceNumber = ENET_NET_TO_HOST_16(command->sendUnreliable.unreliableSequenceNumber); + + if (reliableSequenceNumber == channel->incomingReliableSequenceNumber && unreliableSequenceNumber <= channel->incomingUnreliableSequenceNumber) { + goto discardCommand; + } + + for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingUnreliableCommands)); + currentCommand != enet_list_end(&channel->incomingUnreliableCommands); + currentCommand = enet_list_previous(currentCommand) + ) { + incomingCommand = (ENetIncomingCommand *) currentCommand; + + if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) { + continue; + } + + if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { + continue; + } + } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber > reliableSequenceNumber) { + continue; + } + + if (incomingCommand->unreliableSequenceNumber <= unreliableSequenceNumber) { + if (incomingCommand->unreliableSequenceNumber < unreliableSequenceNumber) { + break; + } + + goto discardCommand; + } + } + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + currentCommand = enet_list_end(&channel->incomingUnreliableCommands); + break; + + default: + goto discardCommand; + } + + if (peer->totalWaitingData >= peer->host->maximumWaitingData) { + goto notifyError; + } + + packet = callbacks.packet_create(data, dataLength, flags); + if (packet == NULL) { + goto notifyError; + } + + incomingCommand = (ENetIncomingCommand *) enet_malloc(sizeof(ENetIncomingCommand)); + if (incomingCommand == NULL) { + goto notifyError; + } + + incomingCommand->reliableSequenceNumber = command->header.reliableSequenceNumber; + incomingCommand->unreliableSequenceNumber = unreliableSequenceNumber & 0xFFFF; + incomingCommand->command = *command; + incomingCommand->fragmentCount = fragmentCount; + incomingCommand->fragmentsRemaining = fragmentCount; + incomingCommand->packet = packet; + incomingCommand->fragments = NULL; + + if (fragmentCount > 0) { + if (fragmentCount <= ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT) { + incomingCommand->fragments = (enet_uint32 *) enet_malloc((fragmentCount + 31) / 32 * sizeof(enet_uint32)); + } + + if (incomingCommand->fragments == NULL) { + enet_free(incomingCommand); + + goto notifyError; + } + + memset(incomingCommand->fragments, 0, (fragmentCount + 31) / 32 * sizeof(enet_uint32)); + } + + if (packet != NULL) { + ++packet->referenceCount; + peer->totalWaitingData += packet->dataLength; + } + + enet_list_insert(enet_list_next(currentCommand), incomingCommand); + + switch (command->header.command & ENET_PROTOCOL_COMMAND_MASK) { + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + enet_peer_dispatch_incoming_reliable_commands(peer, channel); + break; + + default: + enet_peer_dispatch_incoming_unreliable_commands(peer, channel); + break; + } + + return incomingCommand; + + discardCommand: + if (fragmentCount > 0) { + goto notifyError; + } + + if (packet != NULL && packet->referenceCount == 0) { + callbacks.packet_destroy(packet); + } + + return &dummyCommand; + + notifyError: + if (packet != NULL && packet->referenceCount == 0) { + callbacks.packet_destroy(packet); + } + + return NULL; + } /* enet_peer_queue_incoming_command */ + +// =======================================================================// +// ! +// ! Host +// ! +// =======================================================================// + + /** Creates a host for communicating to peers. + * + * @param address the address at which other peers may connect to this host. If NULL, then no peers may connect to the host. + * @param peerCount the maximum number of peers that should be allocated for the host. + * @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT + * @param incomingBandwidth downstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. + * @param outgoingBandwidth upstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. + * + * @returns the host on success and NULL on failure + * + * @remarks ENet will strategically drop packets on specific sides of a connection between hosts + * to ensure the host's bandwidth is not overwhelmed. The bandwidth parameters also determine + * the window size of a connection which limits the amount of reliable packets that may be in transit + * at any given time. + */ + ENetHost * enet_host_create(const ENetAddress *address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) { + ENetHost *host; + ENetPeer *currentPeer; + + if (peerCount > ENET_PROTOCOL_MAXIMUM_PEER_ID) { + return NULL; + } + + host = (ENetHost *) enet_malloc(sizeof(ENetHost)); + if (host == NULL) { return NULL; } + memset(host, 0, sizeof(ENetHost)); + + host->peers = (ENetPeer *) enet_malloc(peerCount * sizeof(ENetPeer)); + if (host->peers == NULL) { + enet_free(host); + return NULL; + } + + memset(host->peers, 0, peerCount * sizeof(ENetPeer)); + + host->socket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); + if (host->socket != ENET_SOCKET_NULL) { + enet_socket_set_option (host->socket, ENET_SOCKOPT_IPV6_V6ONLY, 0); + } + + if (host->socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind(host->socket, address) < 0)) { + if (host->socket != ENET_SOCKET_NULL) { + enet_socket_destroy(host->socket); + } + + enet_free(host->peers); + enet_free(host); + + return NULL; + } + + enet_socket_set_option(host->socket, ENET_SOCKOPT_NONBLOCK, 1); + enet_socket_set_option(host->socket, ENET_SOCKOPT_BROADCAST, 1); + enet_socket_set_option(host->socket, ENET_SOCKOPT_RCVBUF, ENET_HOST_RECEIVE_BUFFER_SIZE); + enet_socket_set_option(host->socket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE); + enet_socket_set_option(host->socket, ENET_SOCKOPT_IPV6_V6ONLY, 0); + + if (address != NULL && enet_socket_get_address(host->socket, &host->address) < 0) { + host->address = *address; + } + + if (!channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) { + channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + } else if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) { + channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + } + + host->randomSeed = (enet_uint32) (size_t) host; + host->randomSeed += enet_host_random_seed(); + host->randomSeed = (host->randomSeed << 16) | (host->randomSeed >> 16); + host->channelLimit = channelLimit; + host->incomingBandwidth = incomingBandwidth; + host->outgoingBandwidth = outgoingBandwidth; + host->bandwidthThrottleEpoch = 0; + host->recalculateBandwidthLimits = 0; + host->mtu = ENET_HOST_DEFAULT_MTU; + host->peerCount = peerCount; + host->commandCount = 0; + host->bufferCount = 0; + host->checksum = NULL; + host->receivedAddress.host = ENET_HOST_ANY; + host->receivedAddress.port = 0; + host->receivedData = NULL; + host->receivedDataLength = 0; + host->totalSentData = 0; + host->totalSentPackets = 0; + host->totalReceivedData = 0; + host->totalReceivedPackets = 0; + host->connectedPeers = 0; + host->bandwidthLimitedPeers = 0; + host->duplicatePeers = ENET_PROTOCOL_MAXIMUM_PEER_ID; + host->maximumPacketSize = ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE; + host->maximumWaitingData = ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA; + host->compressor.context = NULL; + host->compressor.compress = NULL; + host->compressor.decompress = NULL; + host->compressor.destroy = NULL; + host->intercept = NULL; + + enet_list_clear(&host->dispatchQueue); + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + currentPeer->host = host; + currentPeer->incomingPeerID = currentPeer - host->peers; + currentPeer->outgoingSessionID = currentPeer->incomingSessionID = 0xFF; + currentPeer->data = NULL; + + enet_list_clear(¤tPeer->acknowledgements); + enet_list_clear(¤tPeer->sentReliableCommands); + enet_list_clear(¤tPeer->sentUnreliableCommands); + enet_list_clear(¤tPeer->outgoingReliableCommands); + enet_list_clear(¤tPeer->outgoingUnreliableCommands); + enet_list_clear(¤tPeer->dispatchedCommands); + + enet_peer_reset(currentPeer); + } + + return host; + } /* enet_host_create */ + + /** Destroys the host and all resources associated with it. + * @param host pointer to the host to destroy + */ + void enet_host_destroy(ENetHost *host) { + ENetPeer *currentPeer; + + if (host == NULL) { + return; + } + + enet_socket_destroy(host->socket); + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + enet_peer_reset(currentPeer); + } + + if (host->compressor.context != NULL && host->compressor.destroy) { + (*host->compressor.destroy)(host->compressor.context); + } + + enet_free(host->peers); + enet_free(host); + } + + /** Initiates a connection to a foreign host. + * @param host host seeking the connection + * @param address destination for the connection + * @param channelCount number of channels to allocate + * @param data user data supplied to the receiving host + * @returns a peer representing the foreign host on success, NULL on failure + * @remarks The peer returned will have not completed the connection until enet_host_service() + * notifies of an ENET_EVENT_TYPE_CONNECT event for the peer. + */ + ENetPeer * enet_host_connect(ENetHost *host, const ENetAddress *address, size_t channelCount, enet_uint32 data) { + ENetPeer *currentPeer; + ENetChannel *channel; + ENetProtocol command; + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) { + channelCount = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + } else if (channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) { + channelCount = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + } + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED) { + break; + } + } + + if (currentPeer >= &host->peers[host->peerCount]) { + return NULL; + } + + currentPeer->channels = (ENetChannel *) enet_malloc(channelCount * sizeof(ENetChannel)); + if (currentPeer->channels == NULL) { + return NULL; + } + + currentPeer->channelCount = channelCount; + currentPeer->state = ENET_PEER_STATE_CONNECTING; + currentPeer->address = *address; + currentPeer->connectID = ++host->randomSeed; + + if (host->outgoingBandwidth == 0) { + currentPeer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + } else { + currentPeer->windowSize = (host->outgoingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } + + if (currentPeer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { + currentPeer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + } else if (currentPeer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { + currentPeer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + } + + for (channel = currentPeer->channels; channel < ¤tPeer->channels[channelCount]; ++channel) { + channel->outgoingReliableSequenceNumber = 0; + channel->outgoingUnreliableSequenceNumber = 0; + channel->incomingReliableSequenceNumber = 0; + channel->incomingUnreliableSequenceNumber = 0; + + enet_list_clear(&channel->incomingReliableCommands); + enet_list_clear(&channel->incomingUnreliableCommands); + + channel->usedReliableWindows = 0; + memset(channel->reliableWindows, 0, sizeof(channel->reliableWindows)); + } + + command.header.command = ENET_PROTOCOL_COMMAND_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + command.connect.outgoingPeerID = ENET_HOST_TO_NET_16(currentPeer->incomingPeerID); + command.connect.incomingSessionID = currentPeer->incomingSessionID; + command.connect.outgoingSessionID = currentPeer->outgoingSessionID; + command.connect.mtu = ENET_HOST_TO_NET_32(currentPeer->mtu); + command.connect.windowSize = ENET_HOST_TO_NET_32(currentPeer->windowSize); + command.connect.channelCount = ENET_HOST_TO_NET_32(channelCount); + command.connect.incomingBandwidth = ENET_HOST_TO_NET_32(host->incomingBandwidth); + command.connect.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth); + command.connect.packetThrottleInterval = ENET_HOST_TO_NET_32(currentPeer->packetThrottleInterval); + command.connect.packetThrottleAcceleration = ENET_HOST_TO_NET_32(currentPeer->packetThrottleAcceleration); + command.connect.packetThrottleDeceleration = ENET_HOST_TO_NET_32(currentPeer->packetThrottleDeceleration); + command.connect.connectID = currentPeer->connectID; + command.connect.data = ENET_HOST_TO_NET_32(data); + + enet_peer_queue_outgoing_command(currentPeer, &command, NULL, 0, 0); + + return currentPeer; + } /* enet_host_connect */ + + /** Queues a packet to be sent to all peers associated with the host. + * @param host host on which to broadcast the packet + * @param channelID channel on which to broadcast + * @param packet packet to broadcast + */ + void enet_host_broadcast(ENetHost *host, enet_uint8 channelID, ENetPacket *packet) { + ENetPeer *currentPeer; + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + if (currentPeer->state != ENET_PEER_STATE_CONNECTED) { + continue; + } + + enet_peer_send(currentPeer, channelID, packet); + } + + if (packet->referenceCount == 0) { + callbacks.packet_destroy(packet); + } + } + + /** Sends raw data to specified address. Useful when you want to send unconnected data using host's socket. + * @param host host sending data + * @param address destination address + * @param data data pointer + * @param dataLength length of data to send + * @retval >=0 bytes sent + * @retval <0 error + * @sa enet_socket_send + */ + int enet_host_send_raw(ENetHost *host, const ENetAddress* address, enet_uint8* data, size_t dataLength) { + ENetBuffer buffer; + buffer.data = data; + buffer.dataLength = dataLength; + return enet_socket_send(host->socket, address, &buffer, 1); + } + + /** Sends raw data to specified address with extended arguments. Allows to send only part of data, handy for other programming languages. + * I.e. if you have data =- { 0, 1, 2, 3 } and call function as enet_host_send_raw_ex(data, 1, 2) then it will skip 1 byte and send 2 bytes { 1, 2 }. + * @param host host sending data + * @param address destination address + * @param data data pointer + * @param skipBytes number of bytes to skip from start of data + * @param bytesToSend number of bytes to send + * @retval >=0 bytes sent + * @retval <0 error + * @sa enet_socket_send + */ + int enet_host_send_raw_ex(ENetHost *host, const ENetAddress* address, enet_uint8* data, size_t skipBytes, size_t bytesToSend) { + ENetBuffer buffer; + buffer.data = data + skipBytes; + buffer.dataLength = bytesToSend; + return enet_socket_send(host->socket, address, &buffer, 1); + } + + /** Sets intercept callback for the host. + * @param host host to set a callback + * @param callback intercept callback + */ + void enet_host_set_intercept(ENetHost *host, const ENetInterceptCallback callback) { + host->intercept = callback; + } + + /** Sets the packet compressor the host should use to compress and decompress packets. + * @param host host to enable or disable compression for + * @param compressor callbacks for for the packet compressor; if NULL, then compression is disabled + */ + void enet_host_compress(ENetHost *host, const ENetCompressor *compressor) { + if (host->compressor.context != NULL && host->compressor.destroy) { + (*host->compressor.destroy)(host->compressor.context); + } + + if (compressor) { + host->compressor = *compressor; + } else { + host->compressor.context = NULL; + } + } + + /** Limits the maximum allowed channels of future incoming connections. + * @param host host to limit + * @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT + */ + void enet_host_channel_limit(ENetHost *host, size_t channelLimit) { + if (!channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) { + channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + } else if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) { + channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + } + + host->channelLimit = channelLimit; + } + + /** Adjusts the bandwidth limits of a host. + * @param host host to adjust + * @param incomingBandwidth new incoming bandwidth + * @param outgoingBandwidth new outgoing bandwidth + * @remarks the incoming and outgoing bandwidth parameters are identical in function to those + * specified in enet_host_create(). + */ + void enet_host_bandwidth_limit(ENetHost *host, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) { + host->incomingBandwidth = incomingBandwidth; + host->outgoingBandwidth = outgoingBandwidth; + host->recalculateBandwidthLimits = 1; + } + + void enet_host_bandwidth_throttle(ENetHost *host) { + enet_uint32 timeCurrent = enet_time_get(); + enet_uint32 elapsedTime = timeCurrent - host->bandwidthThrottleEpoch; + enet_uint32 peersRemaining = (enet_uint32) host->connectedPeers; + enet_uint32 dataTotal = ~0; + enet_uint32 bandwidth = ~0; + enet_uint32 throttle = 0; + enet_uint32 bandwidthLimit = 0; + + int needsAdjustment = host->bandwidthLimitedPeers > 0 ? 1 : 0; + ENetPeer *peer; + ENetProtocol command; + + if (elapsedTime < ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) { + return; + } + + if (host->outgoingBandwidth == 0 && host->incomingBandwidth == 0) { + return; + } + + host->bandwidthThrottleEpoch = timeCurrent; + + if (peersRemaining == 0) { + return; + } + + if (host->outgoingBandwidth != 0) { + dataTotal = 0; + bandwidth = (host->outgoingBandwidth * elapsedTime) / 1000; + + for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { + continue; + } + + dataTotal += peer->outgoingDataTotal; + } + } + + while (peersRemaining > 0 && needsAdjustment != 0) { + needsAdjustment = 0; + + if (dataTotal <= bandwidth) { + throttle = ENET_PEER_PACKET_THROTTLE_SCALE; + } else { + throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal; + } + + for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { + enet_uint32 peerBandwidth; + + if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) || + peer->incomingBandwidth == 0 || + peer->outgoingBandwidthThrottleEpoch == timeCurrent + ) { + continue; + } + + peerBandwidth = (peer->incomingBandwidth * elapsedTime) / 1000; + if ((throttle * peer->outgoingDataTotal) / ENET_PEER_PACKET_THROTTLE_SCALE <= peerBandwidth) { + continue; + } + + peer->packetThrottleLimit = (peerBandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / peer->outgoingDataTotal; + + if (peer->packetThrottleLimit == 0) { + peer->packetThrottleLimit = 1; + } + + if (peer->packetThrottle > peer->packetThrottleLimit) { + peer->packetThrottle = peer->packetThrottleLimit; + } + + peer->outgoingBandwidthThrottleEpoch = timeCurrent; + + peer->incomingDataTotal = 0; + peer->outgoingDataTotal = 0; + + needsAdjustment = 1; + --peersRemaining; + bandwidth -= peerBandwidth; + dataTotal -= peerBandwidth; + } + } + + if (peersRemaining > 0) { + if (dataTotal <= bandwidth) { + throttle = ENET_PEER_PACKET_THROTTLE_SCALE; + } else { + throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal; + } + + for (peer = host->peers; + peer < &host->peers[host->peerCount]; + ++peer) + { + if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) || peer->outgoingBandwidthThrottleEpoch == timeCurrent) { + continue; + } + + peer->packetThrottleLimit = throttle; + + if (peer->packetThrottle > peer->packetThrottleLimit) { + peer->packetThrottle = peer->packetThrottleLimit; + } + + peer->incomingDataTotal = 0; + peer->outgoingDataTotal = 0; + } + } + + if (host->recalculateBandwidthLimits) { + host->recalculateBandwidthLimits = 0; + + peersRemaining = (enet_uint32) host->connectedPeers; + bandwidth = host->incomingBandwidth; + needsAdjustment = 1; + + if (bandwidth == 0) { + bandwidthLimit = 0; + } else { + while (peersRemaining > 0 && needsAdjustment != 0) { + needsAdjustment = 0; + bandwidthLimit = bandwidth / peersRemaining; + + for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { + if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) || + peer->incomingBandwidthThrottleEpoch == timeCurrent + ) { + continue; + } + + if (peer->outgoingBandwidth > 0 && peer->outgoingBandwidth >= bandwidthLimit) { + continue; + } + + peer->incomingBandwidthThrottleEpoch = timeCurrent; + + needsAdjustment = 1; + --peersRemaining; + bandwidth -= peer->outgoingBandwidth; + } + } + } + + for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { + continue; + } + + command.header.command = ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + command.bandwidthLimit.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth); + + if (peer->incomingBandwidthThrottleEpoch == timeCurrent) { + command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32(peer->outgoingBandwidth); + } else { + command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32(bandwidthLimit); + } + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + } + } + } /* enet_host_bandwidth_throttle */ + +// =======================================================================// +// ! +// ! Time +// ! +// =======================================================================// + + #ifdef _WIN32 + static LARGE_INTEGER getFILETIMEoffset() { + SYSTEMTIME s; + FILETIME f; + LARGE_INTEGER t; + + s.wYear = 1970; + s.wMonth = 1; + s.wDay = 1; + s.wHour = 0; + s.wMinute = 0; + s.wSecond = 0; + s.wMilliseconds = 0; + SystemTimeToFileTime(&s, &f); + t.QuadPart = f.dwHighDateTime; + t.QuadPart <<= 32; + t.QuadPart |= f.dwLowDateTime; + return (t); + } + + int clock_gettime(int X, struct timespec *tv) { + LARGE_INTEGER t; + FILETIME f; + double microseconds; + static LARGE_INTEGER offset; + static double frequencyToMicroseconds; + static int initialized = 0; + static BOOL usePerformanceCounter = 0; + + if (!initialized) { + LARGE_INTEGER performanceFrequency; + initialized = 1; + usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); + if (usePerformanceCounter) { + QueryPerformanceCounter(&offset); + frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; + } else { + offset = getFILETIMEoffset(); + frequencyToMicroseconds = 10.; + } + } + if (usePerformanceCounter) { + QueryPerformanceCounter(&t); + } else { + GetSystemTimeAsFileTime(&f); + t.QuadPart = f.dwHighDateTime; + t.QuadPart <<= 32; + t.QuadPart |= f.dwLowDateTime; + } + + t.QuadPart -= offset.QuadPart; + microseconds = (double)t.QuadPart / frequencyToMicroseconds; + t.QuadPart = (LONGLONG)microseconds; + tv->tv_sec = (long)(t.QuadPart / 1000000); + tv->tv_nsec = t.QuadPart % 1000000 * 1000; + return (0); + } + #elif __APPLE__ && __MAC_OS_X_VERSION_MIN_REQUIRED < 101200 + #define CLOCK_MONOTONIC 0 + + int clock_gettime(int X, struct timespec *ts) { + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + ts->tv_sec = mts.tv_sec; + ts->tv_nsec = mts.tv_nsec; + + return 0; + } + #endif + + enet_uint32 enet_time_get() { + // TODO enet uses 32 bit timestamps. We should modify it to use + // 64 bit timestamps, but this is not trivial since we'd end up + // changing half the structs in enet. For now, retain 32 bits, but + // use an offset so we don't run out of bits. Basically, the first + // call of enet_time_get() will always return 1, and follow-up calls + // indicate elapsed time since the first call. + // + // Note that we don't want to return 0 from the first call, in case + // some part of enet uses 0 as a special value (meaning time not set + // for example). + static uint64_t start_time_ns = 0; + + struct timespec ts; + #if defined(CLOCK_MONOTONIC_RAW) + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + #else + clock_gettime(CLOCK_MONOTONIC, &ts); + #endif + + static const uint64_t ns_in_s = 1000 * 1000 * 1000; + static const uint64_t ns_in_ms = 1000 * 1000; + uint64_t current_time_ns = ts.tv_nsec + (uint64_t)ts.tv_sec * ns_in_s; + + // Most of the time we just want to atomically read the start time. We + // could just use a single CAS instruction instead of this if, but it + // would be slower in the average case. + // + // Note that statics are auto-initialized to zero, and starting a thread + // implies a memory barrier. So we know that whatever thread calls this, + // it correctly sees the start_time_ns as 0 initially. + uint64_t offset_ns = ENET_ATOMIC_READ(&start_time_ns); + if (offset_ns == 0) { + // We still need to CAS, since two different threads can get here + // at the same time. + // + // We assume that current_time_ns is > 1ms. + // + // Set the value of the start_time_ns, such that the first timestamp + // is at 1ms. This ensures 0 remains a special value. + uint64_t want_value = current_time_ns - 1 * ns_in_ms; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + uint64_t old_value = ENET_ATOMIC_CAS(&start_time_ns, 0, want_value); +#pragma GCC diagnostic pop + offset_ns = old_value == 0 ? want_value : old_value; + } + + uint64_t result_in_ns = current_time_ns - offset_ns; + return (enet_uint32)(result_in_ns / ns_in_ms); + } + + void enet_inaddr_map4to6(struct in_addr in, struct in6_addr *out) + { + if (in.s_addr == 0x00000000) { /* 0.0.0.0 */ + *out = enet_v6_anyaddr; + } else if (in.s_addr == 0xFFFFFFFF) { /* 255.255.255.255 */ + *out = enet_v6_noaddr; + } else { + *out = enet_v4_anyaddr; + out->s6_addr[10] = 0xFF; + out->s6_addr[11] = 0xFF; + out->s6_addr[12] = ((uint8_t *)&in.s_addr)[0]; + out->s6_addr[13] = ((uint8_t *)&in.s_addr)[1]; + out->s6_addr[14] = ((uint8_t *)&in.s_addr)[2]; + out->s6_addr[15] = ((uint8_t *)&in.s_addr)[3]; + } + } + void enet_inaddr_map6to4(const struct in6_addr *in, struct in_addr *out) + { + memset(out, 0, sizeof(struct in_addr)); + ((uint8_t *)&out->s_addr)[0] = in->s6_addr[12]; + ((uint8_t *)&out->s_addr)[1] = in->s6_addr[13]; + ((uint8_t *)&out->s_addr)[2] = in->s6_addr[14]; + ((uint8_t *)&out->s_addr)[3] = in->s6_addr[15]; + } + + int enet_in6addr_lookup_host(const char *name, bool nodns, ENetAddress *out) { + struct addrinfo hints, *resultList = NULL, *result = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + + if (nodns) + { + hints.ai_flags = AI_NUMERICHOST; /* prevent actual DNS lookups! */ + } + + if (getaddrinfo(name, NULL, &hints, &resultList) != 0) { + if (resultList != NULL) { + freeaddrinfo(resultList); + } + + return -1; + } + + for (result = resultList; result != NULL; result = result->ai_next) { + if (result->ai_addr != NULL) { + if (result->ai_family == AF_INET || (result->ai_family == AF_UNSPEC && result->ai_addrlen == sizeof(struct sockaddr_in))) { + enet_inaddr_map4to6(((struct sockaddr_in*)result->ai_addr)->sin_addr, &out->host); + out->sin6_scope_id = 0; + + if (resultList != NULL) { + freeaddrinfo(resultList); + } + + return 0; + } else if (result->ai_family == AF_INET6 || (result->ai_family == AF_UNSPEC && result->ai_addrlen == sizeof(struct sockaddr_in6))) { + memcpy(&out->host, &((struct sockaddr_in6*)result->ai_addr)->sin6_addr, sizeof(struct in6_addr)); + out->sin6_scope_id = (enet_uint16) ((struct sockaddr_in6*)result->ai_addr)->sin6_scope_id; + + if (resultList != NULL) { + freeaddrinfo(resultList); + } + + return 0; + } + } + } + + if (resultList != NULL) { + freeaddrinfo(resultList); + } + + return -1; + } + + int enet_address_set_host_ip_new(ENetAddress *address, const char *name) { + return enet_in6addr_lookup_host(name, true, address); + } + + int enet_address_set_host_new(ENetAddress *address, const char *name) { + return enet_in6addr_lookup_host(name, false, address); + } + + int enet_address_get_host_ip_new(const ENetAddress *address, char *name, size_t nameLength) { + if (IN6_IS_ADDR_V4MAPPED(&address->host)) { + struct in_addr buf; + enet_inaddr_map6to4(&address->host, &buf); + + if (inet_ntop(AF_INET, &buf, name, nameLength) == NULL) { + return -1; + } + } + else { + if (inet_ntop(AF_INET6, &address->host, name, nameLength) == NULL) { + return -1; + } + } + + return 0; + } /* enet_address_get_host_ip_new */ + + int enet_address_get_host_new(const ENetAddress *address, char *name, size_t nameLength) { + struct sockaddr_in6 sin; + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + int err; + + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16 (address->port); + sin.sin6_addr = address->host; + sin.sin6_scope_id = address->sin6_scope_id; + + err = getnameinfo((struct sockaddr *) &sin, sizeof(sin), name, nameLength, NULL, 0, NI_NAMEREQD); + if (!err) { + if (name != NULL && nameLength > 0 && !memchr(name, '\0', nameLength)) { + return -1; + } + return 0; + } + if (err != EAI_NONAME) { + return -1; + } + + return enet_address_get_host_ip_new(address, name, nameLength); + } /* enet_address_get_host_new */ + +// =======================================================================// +// ! +// ! Platform Specific (Unix) +// ! +// =======================================================================// + + #ifndef _WIN32 + + #if defined(__MINGW32__) && defined(ENET_MINGW_COMPAT) + // inet_ntop/inet_pton for MinGW from http://mingw-users.1079350.n2.nabble.com/IPv6-getaddrinfo-amp-inet-ntop-td5891996.html + const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) { + if (af == AF_INET) { + struct sockaddr_in in; + memset(&in, 0, sizeof(in)); + in.sin_family = AF_INET; + memcpy(&in.sin_addr, src, sizeof(struct in_addr)); + getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in), dst, cnt, NULL, 0, NI_NUMERICHOST); + return dst; + } + else if (af == AF_INET6) { + struct sockaddr_in6 in; + memset(&in, 0, sizeof(in)); + in.sin6_family = AF_INET6; + memcpy(&in.sin6_addr, src, sizeof(struct in_addr6)); + getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in6), dst, cnt, NULL, 0, NI_NUMERICHOST); + return dst; + } + + return NULL; + } + + #define NS_INADDRSZ 4 + #define NS_IN6ADDRSZ 16 + #define NS_INT16SZ 2 + + int inet_pton4(const char *src, char *dst) { + uint8_t tmp[NS_INADDRSZ], *tp; + + int saw_digit = 0; + int octets = 0; + *(tp = tmp) = 0; + + int ch; + while ((ch = *src++) != '\0') + { + if (ch >= '0' && ch <= '9') + { + uint32_t n = *tp * 10 + (ch - '0'); + + if (saw_digit && *tp == 0) + return 0; + + if (n > 255) + return 0; + + *tp = n; + if (!saw_digit) + { + if (++octets > 4) + return 0; + saw_digit = 1; + } + } + else if (ch == '.' && saw_digit) + { + if (octets == 4) + return 0; + *++tp = 0; + saw_digit = 0; + } + else + return 0; + } + if (octets < 4) + return 0; + + memcpy(dst, tmp, NS_INADDRSZ); + + return 1; + } + + int inet_pton6(const char *src, char *dst) { + static const char xdigits[] = "0123456789abcdef"; + uint8_t tmp[NS_IN6ADDRSZ]; + + uint8_t *tp = (uint8_t*) memset(tmp, '\0', NS_IN6ADDRSZ); + uint8_t *endp = tp + NS_IN6ADDRSZ; + uint8_t *colonp = NULL; + + /* Leading :: requires some special handling. */ + if (*src == ':') + { + if (*++src != ':') + return 0; + } + + const char *curtok = src; + int saw_xdigit = 0; + uint32_t val = 0; + int ch; + while ((ch = tolower(*src++)) != '\0') + { + const char *pch = strchr(xdigits, ch); + if (pch != NULL) + { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return 0; + saw_xdigit = 1; + continue; + } + if (ch == ':') + { + curtok = src; + if (!saw_xdigit) + { + if (colonp) + return 0; + colonp = tp; + continue; + } + else if (*src == '\0') + { + return 0; + } + if (tp + NS_INT16SZ > endp) + return 0; + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && + inet_pton4(curtok, (char*) tp) > 0) + { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return 0; + } + if (saw_xdigit) + { + if (tp + NS_INT16SZ > endp) + return 0; + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + } + if (colonp != NULL) + { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + + if (tp == endp) + return 0; + + for (int i = 1; i <= n; i++) + { + endp[-i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return 0; + + memcpy(dst, tmp, NS_IN6ADDRSZ); + + return 1; + } + + + int inet_pton(int af, const char *src, struct in6_addr *dst) { + switch (af) + { + case AF_INET: + return inet_pton4(src, (char *)dst); + case AF_INET6: + return inet_pton6(src, (char *)dst); + default: + return -1; + } + } + #endif // __MINGW__ + + int enet_initialize(void) { + return 0; + } + + void enet_deinitialize(void) {} + + enet_uint64 enet_host_random_seed(void) { + return (enet_uint64) time(NULL); + } + + int enet_address_set_host_ip_old(ENetAddress *address, const char *name) { + if (!inet_pton(AF_INET6, name, &address->host)) { + return -1; + } + + return 0; + } + + int enet_address_set_host_old(ENetAddress *address, const char *name) { + struct addrinfo hints, *resultList = NULL, *result = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + + if (getaddrinfo(name, NULL, &hints, &resultList) != 0) { + return -1; + } + + for (result = resultList; result != NULL; result = result->ai_next) { + if (result->ai_addr != NULL && result->ai_addrlen >= sizeof(struct sockaddr_in)) { + if (result->ai_family == AF_INET) { + struct sockaddr_in * sin = (struct sockaddr_in *) result->ai_addr; + + ((uint32_t *)&address->host.s6_addr)[0] = 0; + ((uint32_t *)&address->host.s6_addr)[1] = 0; + ((uint32_t *)&address->host.s6_addr)[2] = htonl(0xffff); + ((uint32_t *)&address->host.s6_addr)[3] = sin->sin_addr.s_addr; + + freeaddrinfo(resultList); + + return 0; + } + else if(result->ai_family == AF_INET6) { + struct sockaddr_in6 * sin = (struct sockaddr_in6 *)result->ai_addr; + + address->host = sin->sin6_addr; + address->sin6_scope_id = sin->sin6_scope_id; + + freeaddrinfo(resultList); + + return 0; + } + } + } + + + if (resultList != NULL) { + freeaddrinfo(resultList); + } + + return enet_address_set_host_ip(address, name); + } /* enet_address_set_host_old */ + + int enet_address_get_host_ip_old(const ENetAddress *address, char *name, size_t nameLength) { + if (inet_ntop(AF_INET6, &address->host, name, nameLength) == NULL) { + return -1; + } + + return 0; + } + + int enet_address_get_host_old(const ENetAddress *address, char *name, size_t nameLength) { + struct sockaddr_in6 sin; + int err; + memset(&sin, 0, sizeof(struct sockaddr_in6)); + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16 (address->port); + sin.sin6_addr = address->host; + sin.sin6_scope_id = address->sin6_scope_id; + err = getnameinfo((struct sockaddr *) &sin, sizeof(sin), name, nameLength, NULL, 0, NI_NAMEREQD); + if (!err) { + if (name != NULL && nameLength > 0 && !memchr(name, '\0', nameLength)) { + return -1; + } + return 0; + } + if (err != EAI_NONAME) { + return -1; + } + return enet_address_get_host_ip(address, name, nameLength); + } /* enet_address_get_host_old */ + + int enet_socket_bind(ENetSocket socket, const ENetAddress *address) { + struct sockaddr_in6 sin; + memset(&sin, 0, sizeof(struct sockaddr_in6)); + sin.sin6_family = AF_INET6; + + if (address != NULL) { + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->host; + sin.sin6_scope_id = address->sin6_scope_id; + } else { + sin.sin6_port = 0; + sin.sin6_addr = ENET_HOST_ANY; + sin.sin6_scope_id = 0; + } + + return bind(socket, (struct sockaddr *)&sin, sizeof(struct sockaddr_in6)); + } + + int enet_socket_get_address(ENetSocket socket, ENetAddress *address) { + struct sockaddr_in6 sin; + socklen_t sinLength = sizeof(struct sockaddr_in6); + + if (getsockname(socket, (struct sockaddr *) &sin, &sinLength) == -1) { + return -1; + } + + address->host = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin.sin6_port); + address->sin6_scope_id = sin.sin6_scope_id; + + return 0; + } + + int enet_socket_listen(ENetSocket socket, int backlog) { + return listen(socket, backlog < 0 ? SOMAXCONN : backlog); + } + + ENetSocket enet_socket_create(ENetSocketType type) { + return socket(PF_INET6, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); + } + + int enet_socket_set_option(ENetSocket socket, ENetSocketOption option, int value) { + int result = -1; + + switch (option) { + case ENET_SOCKOPT_NONBLOCK: + result = fcntl(socket, F_SETFL, (value ? O_NONBLOCK : 0) | (fcntl(socket, F_GETFL) & ~O_NONBLOCK)); + break; + + case ENET_SOCKOPT_BROADCAST: + result = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_REUSEADDR: + result = setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_RCVBUF: + result = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_SNDBUF: + result = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_RCVTIMEO: { + struct timeval timeVal; + timeVal.tv_sec = value / 1000; + timeVal.tv_usec = (value % 1000) * 1000; + result = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeVal, sizeof(struct timeval)); + break; + } + + case ENET_SOCKOPT_SNDTIMEO: { + struct timeval timeVal; + timeVal.tv_sec = value / 1000; + timeVal.tv_usec = (value % 1000) * 1000; + result = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeVal, sizeof(struct timeval)); + break; + } + + case ENET_SOCKOPT_NODELAY: + result = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_IPV6_V6ONLY: + result = setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&value, sizeof(int)); + break; + + default: + break; + } + return result == -1 ? -1 : 0; + } /* enet_socket_set_option */ + + int enet_socket_get_option(ENetSocket socket, ENetSocketOption option, int *value) { + int result = -1; + socklen_t len; + + switch (option) { + case ENET_SOCKOPT_ERROR: + len = sizeof(int); + result = getsockopt(socket, SOL_SOCKET, SO_ERROR, value, &len); + break; + + default: + break; + } + return result == -1 ? -1 : 0; + } + + int enet_socket_connect(ENetSocket socket, const ENetAddress *address) { + struct sockaddr_in6 sin; + int result; + + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->host; + sin.sin6_scope_id = address->sin6_scope_id; + + result = connect(socket, (struct sockaddr *)&sin, sizeof(struct sockaddr_in6)); + if (result == -1 && errno == EINPROGRESS) { + return 0; + } + + return result; + } + + ENetSocket enet_socket_accept(ENetSocket socket, ENetAddress *address) { + int result; + struct sockaddr_in6 sin; + socklen_t sinLength = sizeof(struct sockaddr_in6); + + result = accept(socket,address != NULL ? (struct sockaddr *) &sin : NULL, address != NULL ? &sinLength : NULL); + + if (result == -1) { + return ENET_SOCKET_NULL; + } + + if (address != NULL) { + address->host = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16 (sin.sin6_port); + address->sin6_scope_id = sin.sin6_scope_id; + } + + return result; + } + + int enet_socket_shutdown(ENetSocket socket, ENetSocketShutdown how) { + return shutdown(socket, (int) how); + } + + void enet_socket_destroy(ENetSocket socket) { + if (socket != -1) { + close(socket); + } + } + + int enet_socket_send(ENetSocket socket, const ENetAddress *address, const ENetBuffer *buffers, size_t bufferCount) { + struct msghdr msgHdr; + struct sockaddr_in6 sin; + int sentLength; + + memset(&msgHdr, 0, sizeof(struct msghdr)); + + if (address != NULL) { + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->host; + sin.sin6_scope_id = address->sin6_scope_id; + + msgHdr.msg_name = &sin; + msgHdr.msg_namelen = sizeof(struct sockaddr_in6); + } + + msgHdr.msg_iov = (struct iovec *) buffers; + msgHdr.msg_iovlen = bufferCount; + + sentLength = sendmsg(socket, &msgHdr, MSG_NOSIGNAL); + + if (sentLength == -1) { + if (errno == EWOULDBLOCK) { + return 0; + } + + return -1; + } + + return sentLength; + } /* enet_socket_send */ + + int enet_socket_receive(ENetSocket socket, ENetAddress *address, ENetBuffer *buffers, size_t bufferCount) { + struct msghdr msgHdr; + struct sockaddr_in6 sin; + int recvLength; + + memset(&msgHdr, 0, sizeof(struct msghdr)); + + if (address != NULL) { + msgHdr.msg_name = &sin; + msgHdr.msg_namelen = sizeof(struct sockaddr_in6); + } + + msgHdr.msg_iov = (struct iovec *) buffers; + msgHdr.msg_iovlen = bufferCount; + + recvLength = recvmsg(socket, &msgHdr, MSG_NOSIGNAL); + + if (recvLength == -1) { + if (errno == EWOULDBLOCK) { + return 0; + } + + return -1; + } + + if (msgHdr.msg_flags & MSG_TRUNC) { + return -1; + } + + if (address != NULL) { + address->host = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin.sin6_port); + address->sin6_scope_id = sin.sin6_scope_id; + } + + return recvLength; + } /* enet_socket_receive */ + + int enet_socketset_select(ENetSocket maxSocket, ENetSocketSet *readSet, ENetSocketSet *writeSet, enet_uint32 timeout) { + struct timeval timeVal; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + return select(maxSocket + 1, readSet, writeSet, NULL, &timeVal); + } + + int enet_socket_wait(ENetSocket socket, enet_uint32 *condition, enet_uint64 timeout) { + struct pollfd pollSocket; + int pollCount; + + pollSocket.fd = socket; + pollSocket.events = 0; + + if (*condition & ENET_SOCKET_WAIT_SEND) { + pollSocket.events |= POLLOUT; + } + + if (*condition & ENET_SOCKET_WAIT_RECEIVE) { + pollSocket.events |= POLLIN; + } + + pollCount = poll(&pollSocket, 1, timeout); + + if (pollCount < 0) { + if (errno == EINTR && *condition & ENET_SOCKET_WAIT_INTERRUPT) { + *condition = ENET_SOCKET_WAIT_INTERRUPT; + + return 0; + } + + return -1; + } + + *condition = ENET_SOCKET_WAIT_NONE; + + if (pollCount == 0) { + return 0; + } + + if (pollSocket.revents & POLLOUT) { + *condition |= ENET_SOCKET_WAIT_SEND; + } + + if (pollSocket.revents & POLLIN) { + *condition |= ENET_SOCKET_WAIT_RECEIVE; + } + + return 0; + } /* enet_socket_wait */ + + #endif // !_WIN32 + + +// =======================================================================// +// ! +// ! Platform Specific (Win) +// ! +// =======================================================================// + + #ifdef _WIN32 + + int enet_initialize(void) { + WORD versionRequested = MAKEWORD(1, 1); + WSADATA wsaData; + + if (WSAStartup(versionRequested, &wsaData)) { + return -1; + } + + if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { + WSACleanup(); + return -1; + } + + timeBeginPeriod(1); + return 0; + } + + void enet_deinitialize(void) { + timeEndPeriod(1); + WSACleanup(); + } + + enet_uint64 enet_host_random_seed(void) { + return (enet_uint64) timeGetTime(); + } + + int enet_address_set_host_ip_old(ENetAddress *address, const char *name) { + enet_uint8 vals[4] = { 0, 0, 0, 0 }; + int i; + + for (i = 0; i < 4; ++i) { + const char *next = name + 1; + if (*name != '0') { + long val = strtol(name, (char **) &next, 10); + if (val < 0 || val > 255 || next == name || next - name > 3) { + return -1; + } + vals[i] = (enet_uint8) val; + } + + if (*next != (i < 3 ? '.' : '\0')) { + return -1; + } + name = next + 1; + } + + memcpy(&address->host, vals, sizeof(enet_uint32)); + return 0; + } + + int enet_address_set_host_old(ENetAddress *address, const char *name) { + struct hostent *hostEntry = NULL; + hostEntry = gethostbyname(name); + + if (hostEntry == NULL || hostEntry->h_addrtype != AF_INET) { + if (!inet_pton(AF_INET6, name, &address->host)) { + return -1; + } + + return 0; + } + + ((enet_uint32 *)&address->host.s6_addr)[0] = 0; + ((enet_uint32 *)&address->host.s6_addr)[1] = 0; + ((enet_uint32 *)&address->host.s6_addr)[2] = htonl(0xffff); + ((enet_uint32 *)&address->host.s6_addr)[3] = *(enet_uint32 *)hostEntry->h_addr_list[0]; + + return 0; + } + + int enet_address_get_host_ip_old(const ENetAddress *address, char *name, size_t nameLength) { + if (inet_ntop(AF_INET6, (PVOID)&address->host, name, nameLength) == NULL) { + return -1; + } + + return 0; + } + + int enet_address_get_host_old(const ENetAddress *address, char *name, size_t nameLength) { + struct in6_addr in; + struct hostent *hostEntry = NULL; + in = address->host; + hostEntry = gethostbyaddr((char *)&in, sizeof(struct in6_addr), AF_INET6); + if (hostEntry == NULL) { + return enet_address_get_host_ip(address, name, nameLength); + } else { + size_t hostLen = strlen(hostEntry->h_name); + if (hostLen >= nameLength) { + return -1; + } + memcpy(name, hostEntry->h_name, hostLen + 1); + } + return 0; + } + + int enet_socket_bind(ENetSocket socket, const ENetAddress *address) { + struct sockaddr_in6 sin; + memset(&sin, 0, sizeof(struct sockaddr_in6)); + sin.sin6_family = AF_INET6; + + if (address != NULL) { + sin.sin6_port = ENET_HOST_TO_NET_16 (address->port); + sin.sin6_addr = address->host; + sin.sin6_scope_id = address->sin6_scope_id; + } else { + sin.sin6_port = 0; + sin.sin6_addr = in6addr_any; + sin.sin6_scope_id = 0; + } + + return bind(socket, (struct sockaddr *) &sin, sizeof(struct sockaddr_in6)) == SOCKET_ERROR ? -1 : 0; + } + + int enet_socket_get_address(ENetSocket socket, ENetAddress *address) { + struct sockaddr_in6 sin; + int sinLength = sizeof(struct sockaddr_in6); + + if (getsockname(socket, (struct sockaddr *) &sin, &sinLength) == -1) { + return -1; + } + + address->host = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin.sin6_port); + address->sin6_scope_id = sin.sin6_scope_id; + + return 0; + } + + int enet_socket_listen(ENetSocket socket, int backlog) { + return listen(socket, backlog < 0 ? SOMAXCONN : backlog) == SOCKET_ERROR ? -1 : 0; + } + + ENetSocket enet_socket_create(ENetSocketType type) { + return socket(PF_INET6, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); + } + + int enet_socket_set_option(ENetSocket socket, ENetSocketOption option, int value) { + int result = SOCKET_ERROR; + + switch (option) { + case ENET_SOCKOPT_NONBLOCK: { + u_long nonBlocking = (u_long) value; + result = ioctlsocket(socket, FIONBIO, &nonBlocking); + break; + } + + case ENET_SOCKOPT_BROADCAST: + result = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_REUSEADDR: + result = setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_RCVBUF: + result = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_SNDBUF: + result = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_RCVTIMEO: + result = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_SNDTIMEO: + result = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_NODELAY: + result = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int)); + break; + + case ENET_SOCKOPT_IPV6_V6ONLY: + result = setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&value, sizeof(int)); + break; + + default: + break; + } + return result == SOCKET_ERROR ? -1 : 0; + } /* enet_socket_set_option */ + + int enet_socket_get_option(ENetSocket socket, ENetSocketOption option, int *value) { + int result = SOCKET_ERROR, len; + + switch (option) { + case ENET_SOCKOPT_ERROR: + len = sizeof(int); + result = getsockopt(socket, SOL_SOCKET, SO_ERROR, (char *)value, &len); + break; + + default: + break; + } + return result == SOCKET_ERROR ? -1 : 0; + } + + int enet_socket_connect(ENetSocket socket, const ENetAddress *address) { + struct sockaddr_in6 sin; + int result; + + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->host; + sin.sin6_scope_id = address->sin6_scope_id; + + result = connect(socket, (struct sockaddr *) &sin, sizeof(struct sockaddr_in6)); + if (result == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) { + return -1; + } + + return 0; + } + + ENetSocket enet_socket_accept(ENetSocket socket, ENetAddress *address) { + SOCKET result; + struct sockaddr_in6 sin; + int sinLength = sizeof(struct sockaddr_in6); + + result = accept(socket, address != NULL ? (struct sockaddr *)&sin : NULL, address != NULL ? &sinLength : NULL); + + if (result == INVALID_SOCKET) { + return ENET_SOCKET_NULL; + } + + if (address != NULL) { + address->host = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin.sin6_port); + address->sin6_scope_id = sin.sin6_scope_id; + } + + return result; + } + + int enet_socket_shutdown(ENetSocket socket, ENetSocketShutdown how) { + return shutdown(socket, (int) how) == SOCKET_ERROR ? -1 : 0; + } + + void enet_socket_destroy(ENetSocket socket) { + if (socket != INVALID_SOCKET) { + closesocket(socket); + } + } + + int enet_socket_send(ENetSocket socket, const ENetAddress *address, const ENetBuffer *buffers, size_t bufferCount) { + struct sockaddr_in6 sin; + DWORD sentLength; + + if (address != NULL) { + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->host; + sin.sin6_scope_id = address->sin6_scope_id; + } + + if (WSASendTo(socket, + (LPWSABUF) buffers, + (DWORD) bufferCount, + &sentLength, + 0, + address != NULL ? (struct sockaddr *) &sin : NULL, + address != NULL ? sizeof(struct sockaddr_in6) : 0, + NULL, + NULL) == SOCKET_ERROR + ) { + return (WSAGetLastError() == WSAEWOULDBLOCK) ? 0 : -1; + } + + return (int) sentLength; + } + + int enet_socket_receive(ENetSocket socket, ENetAddress *address, ENetBuffer *buffers, size_t bufferCount) { + INT sinLength = sizeof(struct sockaddr_in6); + DWORD flags = 0, recvLength; + struct sockaddr_in6 sin; + + if (WSARecvFrom(socket, + (LPWSABUF) buffers, + (DWORD) bufferCount, + &recvLength, + &flags, + address != NULL ? (struct sockaddr *) &sin : NULL, + address != NULL ? &sinLength : NULL, + NULL, + NULL) == SOCKET_ERROR + ) { + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + case WSAECONNRESET: + return 0; + } + + return -1; + } + + if (flags & MSG_PARTIAL) { + return -1; + } + + if (address != NULL) { + address->host = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin.sin6_port); + address->sin6_scope_id = sin.sin6_scope_id; + } + + return (int) recvLength; + } /* enet_socket_receive */ + + int enet_socketset_select(ENetSocket maxSocket, ENetSocketSet *readSet, ENetSocketSet *writeSet, enet_uint32 timeout) { + struct timeval timeVal; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + return select(maxSocket + 1, readSet, writeSet, NULL, &timeVal); + } + + int enet_socket_wait(ENetSocket socket, enet_uint32 *condition, enet_uint64 timeout) { + fd_set readSet, writeSet; + struct timeval timeVal; + int selectCount; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + + if (*condition & ENET_SOCKET_WAIT_SEND) { + FD_SET(socket, &writeSet); + } + + if (*condition & ENET_SOCKET_WAIT_RECEIVE) { + FD_SET(socket, &readSet); + } + + selectCount = select(socket + 1, &readSet, &writeSet, NULL, &timeVal); + + if (selectCount < 0) { + return -1; + } + + *condition = ENET_SOCKET_WAIT_NONE; + + if (selectCount == 0) { + return 0; + } + + if (FD_ISSET(socket, &writeSet)) { + *condition |= ENET_SOCKET_WAIT_SEND; + } + + if (FD_ISSET(socket, &readSet)) { + *condition |= ENET_SOCKET_WAIT_RECEIVE; + } + + return 0; + } /* enet_socket_wait */ + + #endif // _WIN32 + + +#ifdef __cplusplus +} +#endif + +#endif // ENET_IMPLEMENTATION +#endif // ENET_INCLUDE_H diff --git a/shared/thirdparty/enet/misc/ChangeLog b/shared/thirdparty/enet/misc/ChangeLog new file mode 100644 index 0000000..ec3c764 --- /dev/null +++ b/shared/thirdparty/enet/misc/ChangeLog @@ -0,0 +1,194 @@ +ENet 2.0.0 (April 22, 2018): +* changed library format to single header +* applied project-wide code style change +* integrated ipv6 changes from @proller +* changed packet creation to have one less malloc and free per packet. @boardwalk +* removed enet_packet_resize +* added timeout disconnect notification event @airtame +* added total packets sent and lost to enet_peer @airtame +* force a connect packet not to have extra bytes @pisto +* enforce mtu while receiving packets, and don't give up receiving if one too big comes through @pisto +* add metrics for total data send and received @airtame +* replaced exp backoff time for rel packet timeout with linear @airtame +* added monotonic time @airtame +* removed enet_time_set +* removed a lot of wide platform support code, might bring a lot of issues on older/non-standart platforms +* removed range coder compression code + +ENet 1.3.13 (April 30, 2015): + +* miscellaneous bug fixes +* added premake and cmake support +* miscellaneous documentation cleanups + +ENet 1.3.12 (April 24, 2014): + +* added maximumPacketSize and maximumWaitingData fields to ENetHost to limit the amount of +data waiting to be delivered on a peer (beware that the default maximumPacketSize is +32MB and should be set higher if desired as should maximumWaitingData) + +ENet 1.3.11 (December 26, 2013): + +* allow an ENetHost to connect to itself +* fixed possible bug with disconnect notifications during connect attempts +* fixed some preprocessor definition bugs + +ENet 1.3.10 (October 23, 2013); + +* doubled maximum reliable window size +* fixed RCVTIMEO/SNDTIMEO socket options and also added NODELAY + +ENet 1.3.9 (August 19, 2013): + +* added duplicatePeers option to ENetHost which can limit the number of peers from duplicate IPs +* added enet_socket_get_option() and ENET_SOCKOPT_ERROR +* added enet_host_random_seed() platform stub + +ENet 1.3.8 (June 2, 2013): + +* added enet_linked_version() for checking the linked version +* added enet_socket_get_address() for querying the local address of a socket +* silenced some debugging prints unless ENET_DEBUG is defined during compilation +* handle EINTR in enet_socket_wait() so that enet_host_service() doesn't propagate errors from signals +* optimized enet_host_bandwidth_throttle() to be less expensive for large numbers of peers + +ENet 1.3.7 (March 6, 2013): + +* added ENET_PACKET_FLAG_SENT to indicate that a packet is being freed because it has been sent +* added userData field to ENetPacket +* changed how random seed is generated on Windows to avoid import warnings +* fixed case where disconnects could be generated with no preceding connect event + +ENet 1.3.6 (December 11, 2012): + +* added support for intercept callback in ENetHost that can be used to process raw packets before ENet +* added enet_socket_shutdown() for issuing shutdown on a socket +* fixed enet_socket_connect() to not error on non-blocking connects +* fixed bug in MTU negotiation during connections + +ENet 1.3.5 (July 31, 2012): + +* fixed bug in unreliable packet fragment queuing + +ENet 1.3.4 (May 29, 2012): + +* added enet_peer_ping_interval() for configuring per-peer ping intervals +* added enet_peer_timeout() for configuring per-peer timeouts +* added protocol packet size limits + +ENet 1.3.3 (June 28, 2011): + +* fixed bug with simultaneous disconnects not dispatching events + +ENet 1.3.2 (May 31, 2011): + +* added support for unreliable packet fragmenting via the packet flag +ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT +* fixed regression in unreliable packet queuing +* added check against received port to limit some forms of IP-spoofing + +ENet 1.3.1 (February 10, 2011): + +* fixed bug in tracking of reliable data in transit +* reliable data window size now scales with the throttle +* fixed bug in fragment length calculation when checksums are used + +ENet 1.3.0 (June 5, 2010): + +* enet_host_create() now requires the channel limit to be specified as +a parameter +* enet_host_connect() now accepts a data parameter which is supplied +to the receiving receiving host in the event data field for a connect event +* added an adaptive order-2 PPM range coder as a built-in compressor option +which can be set with enet_host_compress_with_range_coder() +* added support for packet compression configurable with a callback +* improved session number handling to not rely on the packet checksum +field, saving 4 bytes per packet unless the checksum option is used +* removed the dependence on the rand callback for session number handling + +Caveats: This version is not protocol compatible with the 1.2 series or +earlier. The enet_host_connect and enet_host_create API functions require +supplying additional parameters. + +ENet 1.2.5 (June 28, 2011): + +* fixed bug with simultaneous disconnects not dispatching events + +ENet 1.2.4 (May 31, 2011): + +* fixed regression in unreliable packet queuing +* added check against received port to limit some forms of IP-spoofing + +ENet 1.2.3 (February 10, 2011): + +* fixed bug in tracking reliable data in transit + +ENet 1.2.2 (June 5, 2010): + +* checksum functionality is now enabled by setting a checksum callback +inside ENetHost instead of being a configure script option +* added totalSentData, totalSentPackets, totalReceivedData, and +totalReceivedPackets counters inside ENetHost for getting usage +statistics +* added enet_host_channel_limit() for limiting the maximum number of +channels allowed by connected peers +* now uses dispatch queues for event dispatch rather than potentially +unscalable array walking +* added no_memory callback that is called when a malloc attempt fails, +such that if no_memory returns rather than aborts (the default behavior), +then the error is propagated to the return value of the API calls +* now uses packed attribute for protocol structures on platforms with +strange alignment rules +* improved autoconf build system contributed by Nathan Brink allowing +for easier building as a shared library + +Caveats: If you were using the compile-time option that enabled checksums, +make sure to set the checksum callback inside ENetHost to enet_crc32 to +regain the old behavior. The ENetCallbacks structure has added new fields, +so make sure to clear the structure to zero before use if +using enet_initialize_with_callbacks(). + +ENet 1.2.1 (November 12, 2009): + +* fixed bug that could cause disconnect events to be dropped +* added thin wrapper around select() for portable usage +* added ENET_SOCKOPT_REUSEADDR socket option +* factored enet_socket_bind()/enet_socket_listen() out of enet_socket_create() +* added contributed Code::Blocks build file + +ENet 1.2 (February 12, 2008): + +* fixed bug in VERIFY_CONNECT acknowledgement that could cause connect +attempts to occasionally timeout +* fixed acknowledgements to check both the outgoing and sent queues +when removing acknowledged packets +* fixed accidental bit rot in the MSVC project file +* revised sequence number overflow handling to address some possible +disconnect bugs +* added enet_host_check_events() for getting only local queued events +* factored out socket option setting into enet_socket_set_option() so +that socket options are now set separately from enet_socket_create() + +Caveats: While this release is superficially protocol compatible with 1.1, +differences in the sequence number overflow handling can potentially cause +random disconnects. + +ENet 1.1 (June 6, 2007): + +* optional CRC32 just in case someone needs a stronger checksum than UDP +provides (--enable-crc32 configure option) +* the size of packet headers are half the size they used to be (so less +overhead when sending small packets) +* enet_peer_disconnect_later() that waits till all queued outgoing +packets get sent before issuing an actual disconnect +* freeCallback field in individual packets for notification of when a +packet is about to be freed +* ENET_PACKET_FLAG_NO_ALLOCATE for supplying pre-allocated data to a +packet (can be used in concert with freeCallback to support some custom +allocation schemes that the normal memory allocation callbacks would +normally not allow) +* enet_address_get_host_ip() for printing address numbers +* promoted the enet_socket_*() functions to be part of the API now +* a few stability/crash fixes + + diff --git a/shared/thirdparty/enet/misc/docs/FAQ.dox b/shared/thirdparty/enet/misc/docs/FAQ.dox new file mode 100644 index 0000000..c3f45fb --- /dev/null +++ b/shared/thirdparty/enet/misc/docs/FAQ.dox @@ -0,0 +1,24 @@ +/** + @page FAQ Frequently Answered Questions + +@section Q1 Is ENet thread-safe? + +ENet does not use any significant global variables, the vast majority +of state is encapsulated in the ENetHost structure. As such, as long +as the application guards access to this structure, then ENet should +operate fine in a multi-threaded environment. + +@section Q2 Isn't ENet just re-inventing TCP?! What's the point? + +In a perfect world, that would be true. But as many have found, using +TCP either in lieu of or in conjunction with UDP can lead to all kinds +of nightmares. TCP is a good, solid protocol, however it simply isn't +up to the task of real-time games. Too much of TCP's implementation +dictates a policy that isn't practical for games. If you want to use +TCP, then do so -- this library is for people that either don't want +to use TCP or have tried and ended up being discouraged with the +performance. + +*/ + + diff --git a/shared/thirdparty/enet/misc/docs/design.dox b/shared/thirdparty/enet/misc/docs/design.dox new file mode 100644 index 0000000..f74a2bd --- /dev/null +++ b/shared/thirdparty/enet/misc/docs/design.dox @@ -0,0 +1,126 @@ +/** +@page Features Features and Architecture + +ENet evolved specifically as a UDP networking layer for the +multiplayer first person shooter Cube. Cube necessitated low latency +communication with data sent out very frequently, so TCP was an +unsuitable choice due to its high latency and stream orientation. UDP, +however, lacks many sometimes necessary features from TCP such as +reliability, sequencing, unrestricted packet sizes, and connection +management. So UDP by itself was not suitable as a network protocol +either. No suitable freely available networking libraries existed at +the time of ENet's creation to fill this niche. + +UDP and TCP could have been used together in Cube to benefit somewhat +from both of their features, however, the resulting combinations of +protocols still leaves much to be desired. TCP lacks multiple streams +of communication without resorting to opening many sockets and +complicates delineation of packets due to its buffering behavior. UDP +lacks sequencing, connection management, management of bandwidth +resources, and imposes limitations on the size of packets. A +significant investment is required to integrate these two protocols, +and the end result is worse off in features and performance than the +uniform protocol presented by ENet. + +ENet thus attempts to address these issues and provide a single, +uniform protocol layered over UDP to the developer with the best +features of UDP and TCP as well as some useful features neither +provide, with a much cleaner integration than any resulting from a +mixture of UDP and TCP. + +@section CM Connection Management + +ENet provides a simple connection interface over which to communicate +with a foreign host. The liveness of the connection is actively +monitored by pinging the foreign host at frequent intervals, and also +monitors the network conditions from the local host to the foreign +host such as the mean round trip time and packet loss in this fashion. + +@section Sequencing Sequencing + +Rather than a single byte stream that complicates the delineation of +packets, ENet presents connections as multiple, properly sequenced +packet streams that simplify the transfer of various types of data. + +ENet provides sequencing for all packets by assigning to each sent +packet a sequence number that is incremented as packets are sent. ENet +guarantees that no packet with a higher sequence number will be +delivered before a packet with a lower sequence number, thus ensuring +packets are delivered exactly in the order they are sent. + +For unreliable packets, ENet will simply discard the lower sequence +number packet if a packet with a higher sequence number has already +been delivered. This allows the packets to be dispatched immediately +as they arrive, and reduce latency of unreliable packets to an +absolute minimum. For reliable packets, if a higher sequence number +packet arrives, but the preceding packets in the sequence have not yet +arrived, ENet will stall delivery of the higher sequence number +packets until its predecessors have arrived. + +@section Channels Channels + +Since ENet will stall delivery of reliable packets to ensure proper +sequencing, and consequently any packets of higher sequence number +whether reliable or unreliable, in the event the reliable packet's +predecessors have not yet arrived, this can introduce latency into the +delivery of other packets which may not need to be as strictly ordered +with respect to the packet that stalled their delivery. + +To combat this latency and reduce the ordering restrictions on +packets, ENet provides multiple channels of communication over a given +connection. Each channel is independently sequenced, and so the +delivery status of a packet in one channel will not stall the delivery +of other packets in another channel. + +@section Reliability Reliability + +ENet provides optional reliability of packet delivery by ensuring the +foreign host acknowledges receipt of all reliable packets. ENet will +attempt to resend the packet up to a reasonable amount of times, if no +acknowledgement of the packet's receipt happens within a specified +timeout. Retry timeouts are progressive and become more lenient with +every failed attempt to allow for temporary turbulence in network +conditions. + +@section FaR Fragmentation and Reassembly + +ENet will send and deliver packets regardless of size. Large packets +are fragmented into many smaller packets of suitable size, and +reassembled on the foreign host to recover the original packet for +delivery. The process is entirely transparent to the developer. + +@section Aggregation Aggregation + +ENet aggregates all protocol commands, including acknowledgements and +packet transfer, into larger protocol packets to ensure the proper +utilization of the connection and to limit the opportunities for +packet loss that might otherwise result in further delivery latency. + +@section Adaptability Adaptability + +ENet provides an in-flight data window for reliable packets to ensure +connections are not overwhelmed by volumes of packets. It also +provides a static bandwidth allocation mechanism to ensure the total +volume of packets sent and received to a host don't exceed the host's +capabilities. Further, ENet also provides a dynamic throttle that +responds to deviations from normal network connections to rectify +various types of network congestion by further limiting the volume of +packets sent. + +@section Portability Portability + +ENet works on Windows and any other Unix or Unix-like platform +providing a BSD sockets interface. The library has a small and stable +code base that can easily be extended to support other platforms and +integrates easily. ENet makes no assumptions about the underlying +platform's endianess or word size. + +@section Freedom Freedom + +ENet demands no royalties and doesn't carry a viral license that would +restrict you in how you might use it in your programs. ENet is +licensed under a short-and-sweet MIT-style license, which gives you +the freedom to do anything you want with it (well, almost anything). + +*/ + diff --git a/shared/thirdparty/enet/misc/docs/install.dox b/shared/thirdparty/enet/misc/docs/install.dox new file mode 100644 index 0000000..b9730d7 --- /dev/null +++ b/shared/thirdparty/enet/misc/docs/install.dox @@ -0,0 +1,63 @@ +/** +@page Installation Installation + +ENet should be trivially simple to integrate with most applications. +First, make sure you download the latest source distribution at @ref Downloads. + +@section Unix Unix-like Operating Systems + +If you are using an ENet release, then you should simply be able to build it +by doing the following: + +./configure && make && make install + +If you obtained the package from github, you must have automake and autoconf +available to generate the build system first by doing the following command +before using the above mentioned build procedure: + +autoreconf -vfi + + +@subsection SolarisBSD Solaris and BSD + +When building ENet under Solaris, you must specify the -lsocket and +-lnsl parameters to your compiler to ensure that the sockets library +is linked in. + +@section Windows Microsoft Windows + +You may simply use the included "enet.lib" or "enet64.lib" static libraries. +However, if you wish to build the library yourself, then the following +instructions apply: + +There is an included MSVC 6 project (enet.dsp) which you may use to +build a suitable library file. Alternatively, you may simply drag all +the ENet source files into your main project. + +You will have to link to the Winsock2 libraries, so make sure to add +ws2_32.lib and winmm.lib to your library list (Project Settings | Link | +Object/library modules). + +@subsection enet.dsp Building with the included enet.dsp + +Load the included enet.dsp. MSVC may ask you to convert it if you +are on a newer version of MSVC - just allow the conversion and save +the resulting project as "enet" or similar. After you build this +project, it will output an "enet.lib" file to either the "Debug/" +or "Release/" directory, depending on which configuration you have +selected to build. By default, it should produce "Debug/enet.lib". + +You may then copy the resulting "enet.lib" file and the header files +found in the "include/" directory to your other projects and add it to +their library lists. Make sure to also link against "ws2_32.lib" and +"winmm.lib" as described above. + +@subsection DLL DLL + +If you wish to build ENet as a DLL you must first define ENET_DLL +within the project (Project Settings | C/C++ | Preprocessor | +Preprocessor definitions) or, more invasively, simply define ENET_DLL +at the top of enet.h. + +*/ + diff --git a/shared/thirdparty/enet/misc/docs/license.dox b/shared/thirdparty/enet/misc/docs/license.dox new file mode 100644 index 0000000..44876d3 --- /dev/null +++ b/shared/thirdparty/enet/misc/docs/license.dox @@ -0,0 +1,26 @@ +/** + @page License License + +Copyright (c) 2002-2016 Lee Salzman + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + diff --git a/shared/thirdparty/enet/misc/docs/mainpage.dox b/shared/thirdparty/enet/misc/docs/mainpage.dox new file mode 100644 index 0000000..7fbbee1 --- /dev/null +++ b/shared/thirdparty/enet/misc/docs/mainpage.dox @@ -0,0 +1,59 @@ +/** @mainpage ENet + +ENet's purpose is to provide a relatively thin, simple and robust +network communication layer on top of UDP (User Datagram Protocol). +The primary feature it provides is optional reliable, in-order +delivery of packets. + +ENet omits certain higher level networking features such as authentication, +lobbying, server discovery, encryption, or other similar tasks that are +particularly application specific so that the library remains flexible, +portable, and easily embeddable. + +@ref Features + +@ref Downloads + +@ref Installation + +@ref Tutorial + +@ref MailingList + +@ref IRCChannel + +@ref FAQ + +@ref License + +Documentation + + */ + +/** +@page Downloads Downloads + +You can retrieve the source to ENet by downloading it in either .tar.gz form +or accessing the github distribution directly. + +The most recent stable release (1.3.13) can be downloaded here. +The last release that is protocol compatible with the 1.2 series or earlier (1.2.5) can be downloaded here. + +You can find the most recent ENet source at the github repository. + +*/ + +/** +@page MailingList Mailing List + +The enet-discuss list is for discussion of ENet, including bug reports or feature requests. + +*/ + +/** +@page IRCChannel IRC Channel + +Join the \#enet channel on the freenode IRC network (irc.freenode.net) for real-time discussion about the ENet library. + +*/ + diff --git a/shared/thirdparty/enet/misc/docs/tutorial.dox b/shared/thirdparty/enet/misc/docs/tutorial.dox new file mode 100644 index 0000000..e91eae8 --- /dev/null +++ b/shared/thirdparty/enet/misc/docs/tutorial.dox @@ -0,0 +1,366 @@ +/** +@page Tutorial Tutorial + +@ref Initialization + +@ref CreateServer + +@ref CreateClient + +@ref ManageHost + +@ref SendingPacket + +@ref Disconnecting + +@ref Connecting + +@section Initialization Initialization + +You should include the file when using ENet. Do not +include without the directory prefix, as this may cause +file name conflicts on some systems. + +Before using ENet, you must call enet_initialize() to initialize the +library. Upon program exit, you should call enet_deinitialize() so +that the library may clean up any used resources. + +@code +#include + +int +main (int argc, char ** argv) +{ + if (enet_initialize () != 0) + { + fprintf (stderr, "An error occurred while initializing ENet.\n"); + return EXIT_FAILURE; + } + atexit (enet_deinitialize); + ... + ... + ... +} +@endcode + +@section CreateServer Creating an ENet server + +Servers in ENet are constructed with enet_host_create(). You must +specify an address on which to receive data and new connections, as +well as the maximum allowable numbers of connected peers. You may +optionally specify the incoming and outgoing bandwidth of the server +in bytes per second so that ENet may try to statically manage +bandwidth resources among connected peers in addition to its dynamic +throttling algorithm; specifying 0 for these two options will cause +ENet to rely entirely upon its dynamic throttling algorithm to manage +bandwidth. + +When done with a host, the host may be destroyed with +enet_host_destroy(). All connected peers to the host will be reset, +and the resources used by the host will be freed. + +@code + ENetAddress address; + ENetHost * server; + + /* Bind the server to the default localhost. */ + /* A specific host address can be specified by */ + /* enet_address_set_host (& address, "x.x.x.x"); */ + + address.host = ENET_HOST_ANY; + /* Bind the server to port 1234. */ + address.port = 1234; + + server = enet_host_create (& address /* the address to bind the server host to */, + 32 /* allow up to 32 clients and/or outgoing connections */, + 2 /* allow up to 2 channels to be used, 0 and 1 */, + 0 /* assume any amount of incoming bandwidth */, + 0 /* assume any amount of outgoing bandwidth */); + if (server == NULL) + { + fprintf (stderr, + "An error occurred while trying to create an ENet server host.\n"); + exit (EXIT_FAILURE); + } + ... + ... + ... + enet_host_destroy(server); +@endcode + +@section CreateClient Creating an ENet client + +Clients in ENet are similarly constructed with enet_host_create() when +no address is specified to bind the host to. Bandwidth may be +specified for the client host as in the above example. The peer count +controls the maximum number of connections to other server hosts that +may be simultaneously open. + +@code + ENetHost * client; + + client = enet_host_create (NULL /* create a client host */, + 1 /* only allow 1 outgoing connection */, + 2 /* allow up 2 channels to be used, 0 and 1 */, + 57600 / 8 /* 56K modem with 56 Kbps downstream bandwidth */, + 14400 / 8 /* 56K modem with 14 Kbps upstream bandwidth */); + + if (client == NULL) + { + fprintf (stderr, + "An error occurred while trying to create an ENet client host.\n"); + exit (EXIT_FAILURE); + } + ... + ... + ... + enet_host_destroy(client); +@endcode + +@section ManageHost Managing an ENet host + +ENet uses a polled event model to notify the programmer of significant +events. ENet hosts are polled for events with enet_host_service(), +where an optional timeout value in milliseconds may be specified to +control how long ENet will poll; if a timeout of 0 is specified, +enet_host_service() will return immediately if there are no events to +dispatch. enet_host_service() will return 1 if an event was dispatched +within the specified timeout. + +Beware that most processing of the network with the ENet stack is done +inside enet_host_service(). Both hosts that make up the sides of a connection +must regularly call this function to ensure packets are actually sent and +received. A common symptom of not actively calling enet_host_service() +on both ends is that one side receives events while the other does not. +The best way to schedule this activity to ensure adequate service is, for +example, to call enet_host_service() with a 0 timeout (meaning non-blocking) +at the beginning of every frame in a game loop. + +Currently there are only four types of significant events in ENet: + +An event of type ENET_EVENT_TYPE_NONE is returned if no event occurred +within the specified time limit. enet_host_service() will return 0 +with this event. + +An event of type ENET_EVENT_TYPE_CONNECT is returned when either a new client +host has connected to the server host or when an attempt to establish a +connection with a foreign host has succeeded. Only the "peer" field of the +event structure is valid for this event and contains the newly connected peer. + +An event of type ENET_EVENT_TYPE_RECEIVE is returned when a packet is received +from a connected peer. The "peer" field contains the peer the packet was +received from, "channelID" is the channel on which the packet was sent, and +"packet" is the packet that was sent. The packet contained in the "packet" +field must be destroyed with enet_packet_destroy() when you are done +inspecting its contents. + +An event of type ENET_EVENT_TYPE_DISCONNECT is returned when a connected peer +has either explicitly disconnected or timed out. Only the "peer" field of the +event structure is valid for this event and contains the peer that +disconnected. Only the "data" field of the peer is still valid on a +disconnect event and must be explicitly reset. + +@code + ENetEvent event; + + /* Wait up to 1000 milliseconds for an event. */ + while (enet_host_service (client, & event, 1000) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + printf ("A new client connected from %x:%u.\n", + event.peer -> address.host, + event.peer -> address.port); + + /* Store any relevant client information here. */ + event.peer -> data = "Client information"; + + break; + + case ENET_EVENT_TYPE_RECEIVE: + printf ("A packet of length %u containing %s was received from %s on channel %u.\n", + event.packet -> dataLength, + event.packet -> data, + event.peer -> data, + event.channelID); + + /* Clean up the packet now that we're done using it. */ + enet_packet_destroy (event.packet); + + break; + + case ENET_EVENT_TYPE_DISCONNECT: + printf ("%s disconnected.\n", event.peer -> data); + + /* Reset the peer's client information. */ + + event.peer -> data = NULL; + } + } + ... + ... + ... +@endcode + +@section SendingPacket Sending a packet to an ENet peer + +Packets in ENet are created with enet_packet_create(), where the size +of the packet must be specified. Optionally, initial data may be +specified to copy into the packet. + +Certain flags may also be supplied to enet_packet_create() to control +various packet features: + +ENET_PACKET_FLAG_RELIABLE specifies that the packet must use reliable +delivery. A reliable packet is guaranteed to be delivered, and a +number of retry attempts will be made until an acknowledgement is +received from the foreign host the packet is sent to. If a certain +number of retry attempts is reached without any acknowledgement, ENet +will assume the peer has disconnected and forcefully reset the +connection. If this flag is not specified, the packet is assumed an +unreliable packet, and no retry attempts will be made nor +acknowledgements generated. + +A packet may be resized (extended or truncated) with +enet_packet_resize(). + +A packet is sent to a foreign host with +enet_peer_send(). enet_peer_send() accepts a channel id over which to +send the packet to a given peer. Once the packet is handed over to +ENet with enet_peer_send(), ENet will handle its deallocation and +enet_packet_destroy() should not be used upon it. + +One may also use enet_host_broadcast() to send a packet to all +connected peers on a given host over a specified channel id, as with +enet_peer_send(). + +Queued packets will be sent on a call to enet_host_service(). +Alternatively, enet_host_flush() will send out queued packets without +dispatching any events. + +@code + /* Create a reliable packet of size 7 containing "packet\0" */ + ENetPacket * packet = enet_packet_create ("packet", + strlen ("packet") + 1, + ENET_PACKET_FLAG_RELIABLE); + + /* Extend the packet so and append the string "foo", so it now */ + /* contains "packetfoo\0" */ + enet_packet_resize (packet, strlen ("packetfoo") + 1); + strcpy (& packet -> data [strlen ("packet")], "foo"); + + /* Send the packet to the peer over channel id 0. */ + /* One could also broadcast the packet by */ + /* enet_host_broadcast (host, 0, packet); */ + enet_peer_send (peer, 0, packet); + ... + ... + ... + /* One could just use enet_host_service() instead. */ + enet_host_flush (host); +@endcode + +@section Disconnecting Disconnecting an ENet peer + +Peers may be gently disconnected with enet_peer_disconnect(). A +disconnect request will be sent to the foreign host, and ENet will +wait for an acknowledgement from the foreign host before finally +disconnecting. An event of type ENET_EVENT_TYPE_DISCONNECT will be +generated once the disconnection succeeds. Normally timeouts apply to +the disconnect acknowledgement, and so if no acknowledgement is +received after a length of time the peer will be forcefully +disconnected. + +enet_peer_reset() will forcefully disconnect a peer. The foreign host +will get no notification of a disconnect and will time out on the +foreign host. No event is generated. + +@code + ENetEvent event; + + enet_peer_disconnect (peer, 0); + + /* Allow up to 3 seconds for the disconnect to succeed + * and drop any packets received packets. + */ + while (enet_host_service (client, & event, 3000) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_RECEIVE: + enet_packet_destroy (event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + puts ("Disconnection succeeded."); + return; + ... + ... + ... + } + } + + /* We've arrived here, so the disconnect attempt didn't */ + /* succeed yet. Force the connection down. */ + enet_peer_reset (peer); + ... + ... + ... +@endcode + +@section Connecting Connecting to an ENet host + +A connection to a foreign host is initiated with enet_host_connect(). +It accepts the address of a foreign host to connect to, and the number +of channels that should be allocated for communication. If N channels +are allocated for use, their channel ids will be numbered 0 through +N-1. A peer representing the connection attempt is returned, or NULL +if there were no available peers over which to initiate the +connection. When the connection attempt succeeds, an event of type +ENET_EVENT_TYPE_CONNECT will be generated. If the connection attempt +times out or otherwise fails, an event of type +ENET_EVENT_TYPE_DISCONNECT will be generated. + +@code + ENetAddress address; + ENetEvent event; + ENetPeer *peer; + + /* Connect to some.server.net:1234. */ + enet_address_set_host (& address, "some.server.net"); + address.port = 1234; + + /* Initiate the connection, allocating the two channels 0 and 1. */ + peer = enet_host_connect (client, & address, 2, 0); + + if (peer == NULL) + { + fprintf (stderr, + "No available peers for initiating an ENet connection.\n"); + exit (EXIT_FAILURE); + } + + /* Wait up to 5 seconds for the connection attempt to succeed. */ + if (enet_host_service (client, & event, 5000) > 0 && + event.type == ENET_EVENT_TYPE_CONNECT) + { + puts ("Connection to some.server.net:1234 succeeded."); + ... + ... + ... + } + else + { + /* Either the 5 seconds are up or a disconnect event was */ + /* received. Reset the peer in the event the 5 seconds */ + /* had run out without any significant event. */ + enet_peer_reset (peer); + + puts ("Connection to some.server.net:1234 failed."); + } + ... + ... + ... +@endcode +*/ diff --git a/shared/thirdparty/enet/package.json b/shared/thirdparty/enet/package.json new file mode 100644 index 0000000..ff65c2c --- /dev/null +++ b/shared/thirdparty/enet/package.json @@ -0,0 +1,34 @@ +{ + "name": "enet.c", + "version": "2.3.0", + "description": "ENet - Simple, lightweight and reliable UDP networking library written on pure C.", + "main": "include/enet.h", + "directories": { + "doc": "misc/docs" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/zpl-c/enet-c.git" + }, + "keywords": [ + "udp", + "networking", + "c", + "cpp", + "c++" + ], + "author": "Lee Salzman", + "contributors": [ + "Vladyslav Hrytsenko", + "Dominik Madarász" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/zpl-c/enet-c/issues" + }, + "homepage": "https://github.com/zpl-c/enet-c#readme", + "dependencies": {} +} diff --git a/shared/thirdparty/enet/test/build.C b/shared/thirdparty/enet/test/build.C new file mode 100644 index 0000000..a7e4b92 --- /dev/null +++ b/shared/thirdparty/enet/test/build.C @@ -0,0 +1,108 @@ +#define _WINSOCK_DEPRECATED_NO_WARNINGS + +#define ENET_IMPLEMENTATION +#include "enet.h" +#include + +typedef struct { + ENetHost *host; + ENetPeer *peer; +} Client; + +void host_server(ENetHost *server) { + ENetEvent event; + while (enet_host_service(server, &event, 2) > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: + printf("A new client connected from ::1:%u.\n", event.peer->address.port); + /* Store any relevant client information here. */ + event.peer->data = "Client information"; + break; + case ENET_EVENT_TYPE_RECEIVE: + printf("A packet of length %zu containing %s was received from %s on channel %u.\n", + event.packet->dataLength, + event.packet->data, + (char *)event.peer->data, + event.channelID); + + /* Clean up the packet now that we're done using it. */ + enet_packet_destroy (event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + printf ("%s disconnected.\n", (char *)event.peer->data); + /* Reset the peer's client information. */ + event.peer->data = NULL; + break; + + case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: + printf ("%s timeout.\n", (char *)event.peer->data); + event.peer->data = NULL; + break; + + case ENET_EVENT_TYPE_NONE: break; + } + } +} + +int main() { + if (enet_initialize() != 0) { + printf("An error occurred while initializing ENet.\n"); + return 1; + } + + #define MAX_CLIENTS 32 + + int i = 0; + ENetHost *server; + Client clients[MAX_CLIENTS]; + ENetAddress address = {0}; + + address.host = ENET_HOST_ANY; /* Bind the server to the default localhost. */ + address.port = 7777; /* Bind the server to port 7777. */ + + + /* create a server */ + printf("starting server...\n"); + server = enet_host_create(&address, MAX_CLIENTS, 2, 0, 0); + if (server == NULL) { + printf("An error occurred while trying to create an ENet server host.\n"); + return 1; + } + + printf("starting clients...\n"); + for (i = 0; i < MAX_CLIENTS; ++i) { + enet_address_set_host(&address, "127.0.0.1"); + clients[i].host = enet_host_create(NULL, 1, 2, 0, 0); + clients[i].peer = enet_host_connect(clients[i].host, &address, 2, 0); + if (clients[i].peer == NULL) { + printf("coundlnt connect\n"); + return 1; + } + } + + // program will make N iterations, and then exit + static int counter = 1000; + + do { + host_server(server); + + ENetEvent event; + for (i = 0; i < MAX_CLIENTS; ++i) { + enet_host_service(clients[i].host, &event, 0); + } + + counter--; + } while (counter > 0); + + for (i = 0; i < MAX_CLIENTS; ++i) { + enet_peer_disconnect_now(clients[i].peer, 0); + enet_host_destroy(clients[i].host); + } + + host_server(server); + + enet_host_destroy(server); + enet_deinitialize(); + return 0; +} diff --git a/shared/thirdparty/enet/test/library.C b/shared/thirdparty/enet/test/library.C new file mode 100644 index 0000000..3d585d9 --- /dev/null +++ b/shared/thirdparty/enet/test/library.C @@ -0,0 +1,3 @@ +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#define ENET_IMPLEMENTATION +#include "enet.h" diff --git a/client/src/thirdparty/memwatch/FAQ b/shared/thirdparty/memwatch/FAQ similarity index 100% rename from client/src/thirdparty/memwatch/FAQ rename to shared/thirdparty/memwatch/FAQ diff --git a/client/src/thirdparty/memwatch/Makefile b/shared/thirdparty/memwatch/Makefile similarity index 100% rename from client/src/thirdparty/memwatch/Makefile rename to shared/thirdparty/memwatch/Makefile diff --git a/client/src/thirdparty/memwatch/README b/shared/thirdparty/memwatch/README similarity index 100% rename from client/src/thirdparty/memwatch/README rename to shared/thirdparty/memwatch/README diff --git a/client/src/thirdparty/memwatch/USING b/shared/thirdparty/memwatch/USING similarity index 100% rename from client/src/thirdparty/memwatch/USING rename to shared/thirdparty/memwatch/USING diff --git a/client/src/thirdparty/memwatch/gpl.txt b/shared/thirdparty/memwatch/gpl.txt similarity index 100% rename from client/src/thirdparty/memwatch/gpl.txt rename to shared/thirdparty/memwatch/gpl.txt diff --git a/client/src/thirdparty/memwatch/memwatch.c b/shared/thirdparty/memwatch/memwatch.c similarity index 100% rename from client/src/thirdparty/memwatch/memwatch.c rename to shared/thirdparty/memwatch/memwatch.c diff --git a/client/src/thirdparty/memwatch/memwatch.h b/shared/thirdparty/memwatch/memwatch.h similarity index 97% rename from client/src/thirdparty/memwatch/memwatch.h rename to shared/thirdparty/memwatch/memwatch.h index d63fd76..4a42cb9 100644 --- a/client/src/thirdparty/memwatch/memwatch.h +++ b/shared/thirdparty/memwatch/memwatch.h @@ -26,19 +26,19 @@ This file is part of MEMWATCH. - MEMWATCH is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. + MEMWATCH is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. - MEMWATCH is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + MEMWATCH is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with MEMWATCH; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + You should have received a copy of the GNU General Public License + along with MEMWATCH; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ************************************************************************ ** @@ -333,6 +333,9 @@ #include /*lint -restore */ +/* strdup() */ +#include + #ifdef __cplusplus extern "C" { #endif @@ -651,7 +654,7 @@ extern void mwDummyTraceFunction(const char *,...); #endif /* !__MEMWATCH_C */ #ifdef __cplusplus - } + } #endif #if 0 /* 980317: disabled C++ */ @@ -686,9 +689,9 @@ extern const char *mwNFile; extern int mwNLine; class MemWatch { public: - MemWatch(); - ~MemWatch(); - }; + MemWatch(); + ~MemWatch(); + }; void * operator new(size_t); void * operator new(size_t,const char *,int); void * operator new[] (size_t,const char *,int); // hjc 07/16/02 diff --git a/client/src/thirdparty/memwatch/memwatch.lsm b/shared/thirdparty/memwatch/memwatch.lsm similarity index 100% rename from client/src/thirdparty/memwatch/memwatch.lsm rename to shared/thirdparty/memwatch/memwatch.lsm diff --git a/shared/thirdparty/memwatch/memwatch.o b/shared/thirdparty/memwatch/memwatch.o new file mode 100644 index 0000000..0e6b4d0 Binary files /dev/null and b/shared/thirdparty/memwatch/memwatch.o differ diff --git a/client/src/thirdparty/memwatch/test.C b/shared/thirdparty/memwatch/test.C similarity index 100% rename from client/src/thirdparty/memwatch/test.C rename to shared/thirdparty/memwatch/test.C diff --git a/client/src/thirdparty/stb_ds.h b/shared/thirdparty/stb_ds.h similarity index 100% rename from client/src/thirdparty/stb_ds.h rename to shared/thirdparty/stb_ds.h diff --git a/client/src/thirdparty/stb_image.h b/shared/thirdparty/stb_image.h similarity index 100% rename from client/src/thirdparty/stb_image.h rename to shared/thirdparty/stb_image.h