Index: /tags/2.02/.clang-format
===================================================================
--- /tags/2.02/.clang-format	(revision 191)
+++ /tags/2.02/.clang-format	(revision 191)
@@ -0,0 +1,15 @@
+---
+# $Id$
+Language: Cpp
+UseTab: Always
+TabWidth: 8
+IndentWidth: 8
+PointerAlignment: Left
+ColumnLimit: 1024
+AllowShortIfStatementsOnASingleLine: Always
+AllowShortBlocksOnASingleLine: Never
+AllowShortLoopsOnASingleLine: true
+SpaceBeforeParens: Never
+AlignEscapedNewlines: DontAlign
+SortIncludes: false
+AllowShortEnumsOnASingleLine: false
Index: /tags/2.02/.github/workflows/build.yml
===================================================================
--- /tags/2.02/.github/workflows/build.yml	(revision 191)
+++ /tags/2.02/.github/workflows/build.yml	(revision 191)
@@ -0,0 +1,99 @@
+# $Id$
+
+name: "Build Tewi HTTPd"
+
+on:
+  workflow_dispatch:
+  push:
+
+concurrency:
+  group: "build"
+  cancel-in-progress: true
+
+jobs:
+  build-win32:
+    name: "Build for Windows 32-bit"
+    runs-on: ubuntu-latest
+
+    permissions:
+      contents: write
+
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v4
+    - name: Install packages
+      run: sudo apt-get install nsis mingw-w64
+    - name: Get OpenSSL
+      run: git clone https://github.com/clamwin/openssl
+    - name: Make it use the correct library
+      run: cp openssl/lib/mingw/x86/*.a openssl/lib/
+    - name: Build
+      run: ./installer.sh win32
+    - name: Rename
+      run: mv install-nossl.exe tewiinst32-nossl.exe && mv install-ssl.exe tewiinst32-ssl.exe
+    - name: Create a zipfile
+      run: zip -rv tewiinst32.zip tewiinst32-*.exe
+    - name: Upload artifact
+      uses: actions/upload-artifact@v4
+      with:
+        name: build-win32
+        path: tewiinst32.zip
+
+  build-win64:
+    name: "Build for Windows 64-bit"
+    runs-on: ubuntu-latest
+
+    permissions:
+      contents: write
+
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v4
+    - name: Install packages
+      run: sudo apt-get install nsis mingw-w64
+    - name: Get OpenSSL
+      run: git clone https://github.com/clamwin/openssl
+    - name: Make it use the correct library
+      run: cp openssl/lib/mingw/x64/*.a openssl/lib/
+    - name: Build
+      run: ./installer.sh win64
+    - name: Rename
+      run: mv install-nossl.exe tewiinst64-nossl.exe && mv install-ssl.exe tewiinst64-ssl.exe
+    - name: Create a zipfile
+      run: zip -rv tewiinst64.zip tewiinst64-*.exe
+    - name: Upload artifact
+      uses: actions/upload-artifact@v4
+      with:
+        name: build-win64
+        path: tewiinst64.zip
+
+  release:
+    name: "Release"
+    runs-on: ubuntu-latest
+
+    permissions:
+      contents: write
+
+    needs: [build-win32, build-win64]
+
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v4
+    - name: Download from build-win32
+      uses: actions/download-artifact@v4
+      with:
+        name: build-win32
+    - name: Download from build-win64
+      uses: actions/download-artifact@v4
+      with:
+        name: build-win64
+    - name: Extract
+      run: unzip tewiinst32.zip && unzip tewiinst64.zip
+    - name: Delete old release
+      run: gh release delete "$(make get-version)" --cleanup-tag -y || true
+      env:
+        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+    - name: Release
+      run: gh release create -t "v$(make get-version)" "$(make get-version)"  tewiinst32*.exe tewiinst64*.exe -n "Version \`$(make get-version)\` was build in this release"
+      env:
+        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Index: /tags/2.02/Common/Makefile
===================================================================
--- /tags/2.02/Common/Makefile	(revision 191)
+++ /tags/2.02/Common/Makefile	(revision 191)
@@ -0,0 +1,19 @@
+# $Id$
+
+include $(PWD)/Platform/$(PLATFORM).mk
+
+.PHONY: all clean
+.SUFFIXES: .c .o
+
+OBJS = string.o log.o dir.o
+
+all: common.a
+
+common.a: $(OBJS)
+	$(AR) rcs $@ $(OBJS)
+
+.c.o:
+	$(CC) $(CFLAGS) -c -o $@ $<
+
+clean:
+	rm -f *.o *.a
Index: /tags/2.02/Common/cm_dir.h
===================================================================
--- /tags/2.02/Common/cm_dir.h	(revision 191)
+++ /tags/2.02/Common/cm_dir.h	(revision 191)
@@ -0,0 +1,8 @@
+/* $Id$ */
+
+#ifndef __CM_DIR_H__
+#define __CM_DIR_H__
+
+char** cm_scandir(const char* path);
+
+#endif
Index: /tags/2.02/Common/cm_log.h
===================================================================
--- /tags/2.02/Common/cm_log.h	(revision 191)
+++ /tags/2.02/Common/cm_log.h	(revision 191)
@@ -0,0 +1,9 @@
+/* $Id$ */
+
+#ifndef __CM_LOG_H__
+#define __CM_LOG_H__
+
+void cm_log(const char* name, const char* log, ...);
+void cm_force_log(const char* log);
+
+#endif
Index: /tags/2.02/Common/cm_string.h
===================================================================
--- /tags/2.02/Common/cm_string.h	(revision 191)
+++ /tags/2.02/Common/cm_string.h	(revision 191)
@@ -0,0 +1,22 @@
+/* $Id$ */
+
+#ifndef __CM_STRING_H__
+#define __CM_STRING_H__
+
+#include <stdbool.h>
+
+int cm_hex(const char* str, int len);
+bool cm_nocase_endswith(const char* str, const char* end);
+bool cm_endswith(const char* str, const char* end);
+char* cm_html_escape(const char* str);
+char* cm_url_escape(const char* str);
+char* cm_strcat(const char* a, const char* b);
+char* cm_strcat3(const char* a, const char* b, const char* c);
+char* cm_strdup(const char* str);
+char* cm_trimstart(const char* str);
+char* cm_trimend(const char* str);
+char* cm_trim(const char* str);
+char** cm_split(const char* str, const char* by);
+bool cm_strcaseequ(const char* a, const char* b);
+
+#endif
Index: /tags/2.02/Common/dir.c
===================================================================
--- /tags/2.02/Common/dir.c	(revision 191)
+++ /tags/2.02/Common/dir.c	(revision 191)
@@ -0,0 +1,63 @@
+/* $Id$ */
+
+#include "cm_dir.h"
+
+#include "cm_string.h"
+
+#include <sys/stat.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+
+int cm_sort(const void* _a, const void* _b) {
+	char* a = *(char**)_a;
+	char* b = *(char**)_b;
+	return strcmp(a, b);
+}
+
+char** cm_scandir(const char* path) {
+	DIR* dir = opendir(path);
+	if(dir != NULL) {
+		char** r = malloc(sizeof(*r));
+		r[0] = NULL;
+		struct dirent* d;
+		while((d = readdir(dir)) != NULL) {
+			if(strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) {
+				struct stat s;
+				char* p = cm_strcat3(path, "/", d->d_name);
+				stat(p, &s);
+				free(p);
+
+				char** old = r;
+				int i;
+				for(i = 0; old[i] != NULL; i++)
+					;
+				r = malloc(sizeof(*r) * (i + 2));
+				for(i = 0; old[i] != NULL; i++) r[i] = old[i];
+				r[i] = cm_strcat(d->d_name, S_ISDIR(s.st_mode) ? "/" : "");
+				r[i + 1] = NULL;
+				free(old);
+			}
+		}
+		int len;
+		for(len = 0; r[len] != NULL; len++)
+			;
+		qsort(r, len, sizeof(char*), cm_sort);
+
+		char** old = r;
+		int i;
+		for(i = 0; old[i] != NULL; i++)
+			;
+		r = malloc(sizeof(*r) * (i + 2));
+		for(i = 0; old[i] != NULL; i++) r[i + 1] = old[i];
+		r[0] = cm_strdup("../");
+		r[i + 1] = NULL;
+		free(old);
+
+		closedir(dir);
+
+		return r;
+	} else {
+		return NULL;
+	}
+}
Index: /tags/2.02/Common/log.c
===================================================================
--- /tags/2.02/Common/log.c	(revision 191)
+++ /tags/2.02/Common/log.c	(revision 191)
@@ -0,0 +1,95 @@
+/* $Id$ */
+
+#include "cm_log.h"
+
+#include "cm_string.h"
+
+#include <time.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#ifdef _PSP
+#include <pspdebug.h>
+#endif
+
+#ifdef __PPU__
+extern void tt_printf(const char* tmpl, ...);
+#endif
+
+FILE* logfile;
+
+bool cm_do_log = false;
+
+#define LOGNAME_LENGTH 12
+
+void cm_force_log(const char* log) {
+	time_t t = time(NULL);
+	struct tm* tm = localtime(&t);
+	char date[513];
+	strftime(date, 512, "%a %b %d %H:%M:%S %Z %Y", tm);
+#ifdef _PSP
+	pspDebugScreenPrintf("[%s] %s\n", date, log);
+#elif defined(__PPU__)
+	tt_printf("[%s] %s\n", date, log);
+#else
+	fprintf(logfile, "[%s] %s\n", date, log);
+#endif
+	fflush(logfile);
+}
+
+void cm_log(const char* name, const char* log, ...) {
+	if(!cm_do_log) return;
+	va_list args;
+	va_start(args, log);
+	char namebuf[LOGNAME_LENGTH + 1];
+	memset(namebuf, '.', LOGNAME_LENGTH);
+	namebuf[LOGNAME_LENGTH] = 0;
+	int i;
+	for(i = 0; name[i] != 0 && i < LOGNAME_LENGTH; i++) {
+		namebuf[i] = name[i];
+	}
+
+	char* result = malloc(1);
+	result[0] = 0;
+
+	char cbuf[2];
+	cbuf[1] = 0;
+
+	for(i = 0; log[i] != 0; i++) {
+		if(log[i] == '%') {
+			i++;
+			if(log[i] == 's') {
+				char* tmp = result;
+				char* c = va_arg(args, char*);
+				result = cm_strcat(tmp, c == NULL ? "(null)" : c);
+				free(tmp);
+			} else if(log[i] == 'd') {
+				int a = va_arg(args, int);
+				char buf[128];
+				sprintf(buf, "%d", a);
+				char* tmp = result;
+				result = cm_strcat(tmp, buf);
+				free(tmp);
+			}
+		} else {
+			cbuf[0] = log[i];
+			char* tmp = result;
+			result = cm_strcat(tmp, cbuf);
+			free(tmp);
+		}
+	}
+
+#ifdef _PSP
+	pspDebugScreenPrintf("%s %s\n", namebuf, result);
+#elif defined(__PPU__)
+	tt_printf("%s %s\n", namebuf, result);
+#else
+	fprintf(logfile, "%s %s\n", namebuf, result);
+#endif
+	va_end(args);
+
+	free(result);
+}
Index: /tags/2.02/Common/string.c
===================================================================
--- /tags/2.02/Common/string.c	(revision 191)
+++ /tags/2.02/Common/string.c	(revision 191)
@@ -0,0 +1,204 @@
+/* $Id$ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <ctype.h>
+
+char* cm_strcat(const char* a, const char* b) {
+	if(a == NULL) a = "";
+	if(b == NULL) b = "";
+	char* str = malloc(strlen(a) + strlen(b) + 1);
+	memcpy(str, a, strlen(a));
+	memcpy(str + strlen(a), b, strlen(b));
+	str[strlen(a) + strlen(b)] = 0;
+	return str;
+}
+
+char* cm_strcat3(const char* a, const char* b, const char* c) {
+	char* tmp = cm_strcat(a, b);
+	char* str = cm_strcat(tmp, c);
+	free(tmp);
+	return str;
+}
+
+char* cm_strdup(const char* str) { return cm_strcat(str, ""); }
+
+bool cm_endswith(const char* str, const char* end) {
+	if(strlen(str) < strlen(end)) return false;
+	int i;
+	for(i = strlen(str) - strlen(end); i < strlen(str); i++) {
+		if(str[i] != end[i - strlen(str) + strlen(end)]) return false;
+	}
+	return true;
+}
+
+bool cm_nocase_endswith(const char* str, const char* end) {
+	if(strlen(str) < strlen(end)) return false;
+	int i;
+	for(i = strlen(str) - strlen(end); i < strlen(str); i++) {
+		if(tolower(str[i]) != tolower(end[i - strlen(str) + strlen(end)])) return false;
+	}
+	return true;
+}
+
+char* cm_trimstart(const char* str) {
+	int i;
+	for(i = 0; str[i] != 0; i++) {
+		if(str[i] != ' ' && str[i] != '\t') {
+			return cm_strdup(str + i);
+		}
+	}
+	return cm_strdup("");
+}
+
+char* cm_trimend(const char* str) {
+	char* s = cm_strdup(str);
+	int i;
+	for(i = strlen(s) - 1; i >= 0; i--) {
+		if(s[i] != '\t' && s[i] != ' ') {
+			s[i + 1] = 0;
+			break;
+		}
+	}
+	return s;
+}
+
+char* cm_trim(const char* str) {
+	char* tmp = cm_trimstart(str);
+	char* s = cm_trimend(tmp);
+	free(tmp);
+	return s;
+}
+
+char** cm_split(const char* str, const char* by) {
+	int i;
+	char** r = malloc(sizeof(*r));
+	r[0] = NULL;
+	char* b = malloc(1);
+	b[0] = 0;
+	char cbuf[2];
+	cbuf[1] = 0;
+	bool dq = false;
+	bool sq = false;
+	for(i = 0;; i++) {
+		int j;
+		bool has = false;
+		for(j = 0; by[j] != 0; j++) {
+			if(by[j] == str[i]) {
+				has = true;
+				break;
+			}
+		}
+		if(!(dq || sq) && (has || str[i] == 0)) {
+			if(strlen(b) > 0) {
+				char** old = r;
+				int j;
+				for(j = 0; old[j] != NULL; j++)
+					;
+				r = malloc(sizeof(*r) * (j + 2));
+				for(j = 0; old[j] != NULL; j++) r[j] = old[j];
+				r[j] = b;
+				r[j + 1] = NULL;
+				free(old);
+			}
+			b = malloc(1);
+			b[0] = 0;
+			if(str[i] == 0) break;
+		} else {
+			if(str[i] == '"' && !sq) {
+				dq = !dq;
+			} else if(str[i] == '\'' && !dq) {
+				sq = !sq;
+			} else {
+				cbuf[0] = str[i];
+				char* tmp = b;
+				b = cm_strcat(tmp, cbuf);
+				free(tmp);
+			}
+		}
+	}
+	free(b);
+	return r;
+}
+
+bool cm_strcaseequ(const char* a, const char* b) {
+	if(a == NULL) return false;
+	if(b == NULL) return false;
+	if(strlen(a) != strlen(b)) return false;
+	int i;
+	for(i = 0; a[i] != 0; i++) {
+		if(tolower(a[i]) != tolower(b[i])) return false;
+	}
+	return true;
+}
+
+int cm_hex(const char* str, int len) {
+	int n = 0;
+	int i;
+	for(i = 0; i < len; i++) {
+		char c = str[i];
+		n *= 16;
+		if('0' <= c && c <= '9') {
+			n += c - '0';
+		} else if('a' <= c && c <= 'f') {
+			n += c - 'a' + 10;
+		} else if('A' <= c && c <= 'F') {
+			n += c - 'A' + 10;
+		}
+	}
+	return n;
+}
+
+char* cm_html_escape(const char* str) {
+	int i;
+	char* result = malloc(1);
+	result[0] = 0;
+	char cbuf[2];
+	cbuf[1] = 0;
+	for(i = 0; str[i] != 0; i++) {
+		cbuf[0] = str[i];
+		if(str[i] == '&') {
+			char* tmp = result;
+			result = cm_strcat(tmp, "&amp;");
+			free(tmp);
+		} else if(str[i] == '<') {
+			char* tmp = result;
+			result = cm_strcat(tmp, "&lt;");
+			free(tmp);
+		} else if(str[i] == '>') {
+			char* tmp = result;
+			result = cm_strcat(tmp, "&gt;");
+			free(tmp);
+		} else {
+			char* tmp = result;
+			result = cm_strcat(tmp, cbuf);
+			free(tmp);
+		}
+	}
+	return result;
+}
+
+char* cm_url_escape(const char* str) {
+	int i;
+	char* result = malloc(1);
+	result[0] = 0;
+	char cbuf[2];
+	cbuf[1] = 0;
+	for(i = 0; str[i] != 0; i++) {
+		cbuf[0] = str[i];
+		if('!' <= str[i] && str[i] <= '@' && str[i] != '.' && str[i] != '-' && str[i] != '/' && !('0' <= str[i] && str[i] <= '9')) {
+			char code[4];
+			sprintf(code, "%%%02X", str[i]);
+			char* tmp = result;
+			result = cm_strcat(tmp, code);
+			free(tmp);
+		} else {
+			char* tmp = result;
+			result = cm_strcat(tmp, cbuf);
+			free(tmp);
+		}
+	}
+	return result;
+}
Index: /tags/2.02/Icons/README
===================================================================
--- /tags/2.02/Icons/README	(revision 191)
+++ /tags/2.02/Icons/README	(revision 191)
@@ -0,0 +1,1 @@
+Thanks to https://www.apache.org/icons/small/
Index: /tags/2.02/LICENSE
===================================================================
--- /tags/2.02/LICENSE	(revision 191)
+++ /tags/2.02/LICENSE	(revision 191)
@@ -0,0 +1,24 @@
+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.
+
+For more information, please refer to <http://unlicense.org/>
Index: /tags/2.02/Makefile
===================================================================
--- /tags/2.02/Makefile	(revision 191)
+++ /tags/2.02/Makefile	(revision 191)
@@ -0,0 +1,55 @@
+# $Id$
+
+PWD = `pwd`
+PLATFORM = generic
+PREFIX = /usr/local
+MODULE = ./Module
+
+include Platform/$(PLATFORM).mk
+
+FLAGS = PWD=$(PWD) PLATFORM=$(PLATFORM) PREFIX=$(PREFIX)
+
+.PHONY: all format clean ./Server ./Common ./Module get-version
+
+all: ./Server $(MODULE) ./Tool/genconf ./Tool/itworks
+
+./Tool/option: ./Tool/option.c config.h
+	cc -o $@ ./Tool/option.c
+
+./Tool/genconf: ./Tool/genconf.c config.h
+	cc -o $@ ./Tool/genconf.c
+
+./Tool/itworks: ./Tool/itworks.c config.h
+	cc -o $@ ./Tool/itworks.c
+
+./Server:: ./Common ./Tool/option
+	$(MAKE) -C $@ $(FLAGS) EXTOBJS="`./Tool/option objs ../`" EXTLIBS="`./Tool/option libs ../`" EXTCFLAGS="`./Tool/option cflags ../`" EXTLDFLAGS="`./Tool/option ldflags ../`"
+
+./Module:: ./Common
+	$(MAKE) -C $@ $(FLAGS)
+
+./Common::
+	$(MAKE) -C $@ $(FLAGS)
+
+./README: ./README.tmpl ./Server/tw_version.h
+	sed "s/@VERSION@/`grep "define TW_VERSION" Server/tw_version.h | grep -Eo '"[^\]+' | sed -E 's/^"//g'`/g" ./README.tmpl > $@
+
+install: all ./Tool/genconf ./Tool/itworks
+	mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(PREFIX)/lib/tewi $(DESTDIR)$(PREFIX)/etc $(DESTDIR)$(PREFIX)/www
+	if [ ! -e $(DESTDIR)$(PREFIX)/etc/tewi.conf ]; then ( ./Tool/genconf $(PREFIX) lib/tewi so > $(DESTDIR)$(PREFIX)/etc/tewi.conf || ( rm $(DESTDIR)$(PREFIX)/etc/tewi.conf ; exit 1 ) ) ; fi
+	if [ ! -e $(DESTDIR)$(PREFIX)/www/index.html ]; then ( ./Tool/itworks > $(DESTDIR)$(PREFIX)/www/index.html || ( rm $(DESTDIR)$(PREFIX)/www/index.html ; exit 1 ) ) ; fi
+	if [ ! -e $(DESTDIR)$(PREFIX)/www/pbtewi.gif ]; then ( cp Binary/pbtewi.gif $(DESTDIR)$(PREFIX)/www/ || ( rm $(DESTDIR)$(PREFIX)/www/pbtewi.gif ; exit 1 ) ) ; fi
+	-cp ./Server/tewi $(DESTDIR)$(PREFIX)/bin/
+	-cp ./Module/*.so $(DESTDIR)$(PREFIX)/lib/tewi/
+
+format:
+	clang-format --verbose -i `find ./Server ./Common ./Module ./Tool "(" -name "*.c" -or -name "*.h" ")" -and -not -name "strptime.*"` config.h
+
+get-version:
+	@grep "define TW_VERSION" Server/tw_version.h | grep -Eo '"[^\]+' | sed -E 's/^"//g'
+
+clean:
+	$(MAKE) -C ./Server $(FLAGS) clean
+	$(MAKE) -C ./Module $(FLAGS) clean
+	$(MAKE) -C ./Common $(FLAGS) clean
+	rm -f ./Tool/option ./Tool/genconf ./Tool/itworks
Index: /tags/2.02/Module/Makefile
===================================================================
--- /tags/2.02/Module/Makefile	(revision 191)
+++ /tags/2.02/Module/Makefile	(revision 191)
@@ -0,0 +1,17 @@
+# $Id$
+
+include $(PWD)/Platform/$(PLATFORM).mk
+
+.PHONY: all clean
+.SUFFIXES: .c .o $(LIB)
+
+all: mod_cgi$(LIB) mod_proxy$(LIB)
+
+.o$(LIB):
+	$(CC) $(LDFLAGS) -shared -o $@ $< ../Common/common.a $(LIBS)
+
+.c.o:
+	$(CC) $(CFLAGS) -c -o $@ $<
+
+clean:
+	rm -f *.o *.so *.a *.dll
Index: /tags/2.02/Module/mod_cgi.c
===================================================================
--- /tags/2.02/Module/mod_cgi.c	(revision 191)
+++ /tags/2.02/Module/mod_cgi.c	(revision 191)
@@ -0,0 +1,23 @@
+/* $Id$ */
+
+#include "../Server/tw_module.h"
+
+#include <cm_string.h>
+
+int mod_init(struct tw_config* config, struct tw_tool* tools) {
+	tools->log("CGI", "Initializing CGI module");
+	tools->add_version("CGI/1.1");
+	return 0;
+}
+
+int mod_config(struct tw_tool* tools, char** argv, int argc) {
+	if(cm_strcaseequ(argv[0], "AllowCGI")) {
+		return TW_CONFIG_PARSED;
+	}
+	return TW_CONFIG_NOTME;
+}
+
+int mod_request(struct tw_tool* tools, struct tw_http_request* req, struct tw_http_response* res) {
+	res->status = 403;
+	return TW_MODULE_STOP;
+}
Index: /tags/2.02/Module/mod_example.c
===================================================================
--- /tags/2.02/Module/mod_example.c	(revision 191)
+++ /tags/2.02/Module/mod_example.c	(revision 191)
@@ -0,0 +1,14 @@
+/* $Id$ */
+/* This module will accept all directives, and always return 403 on the access. */
+
+#include "../Server/tw_module.h"
+
+int mod_init(struct tw_config* config, struct tw_tool* tools) {
+	tools->log("Example", "This is an example module");
+	tools->add_version("Example/0.0");
+	return 0;
+}
+
+int mod_config(struct tw_tool* tools, char** argv, int argc) { return TW_CONFIG_PARSED; }
+
+int mod_request(struct tw_tool* tools, struct tw_http_request* req, struct tw_http_response* res) { return TW_MODULE_ERROR(403); }
Index: /tags/2.02/Module/mod_proxy.c
===================================================================
--- /tags/2.02/Module/mod_proxy.c	(revision 191)
+++ /tags/2.02/Module/mod_proxy.c	(revision 191)
@@ -0,0 +1,15 @@
+/* $Id$ */
+
+#include "../Server/tw_module.h"
+
+#include <cm_string.h>
+
+int mod_init(struct tw_config* config, struct tw_tool* tools) {
+	tools->log("CGI", "Initializing Proxy module");
+	tools->add_version("Proxy/1.0");
+	return 0;
+}
+
+int mod_config(struct tw_tool* tools, char** argv, int argc) { return TW_CONFIG_NOTME; }
+
+int mod_request(struct tw_tool* tools, struct tw_http_request* req, struct tw_http_response* res) { return TW_MODULE_PASS; }
Index: /tags/2.02/Platform/cygwin.mk
===================================================================
--- /tags/2.02/Platform/cygwin.mk	(revision 191)
+++ /tags/2.02/Platform/cygwin.mk	(revision 191)
@@ -0,0 +1,9 @@
+# $Id$
+
+CC = cc
+AR = ar
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common
+LDFLAGS =
+LIBS =
+EXEC =
+LIB = .so
Index: /tags/2.02/Platform/generic.mk
===================================================================
--- /tags/2.02/Platform/generic.mk	(revision 191)
+++ /tags/2.02/Platform/generic.mk	(revision 191)
@@ -0,0 +1,9 @@
+# $Id$
+
+CC = cc
+AR = ar
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -fPIC
+LDFLAGS =
+LIBS =
+EXEC =
+LIB = .so
Index: /tags/2.02/Platform/haiku.mk
===================================================================
--- /tags/2.02/Platform/haiku.mk	(revision 191)
+++ /tags/2.02/Platform/haiku.mk	(revision 191)
@@ -0,0 +1,9 @@
+# $Id$
+
+CC = cc
+AR = ar
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -fPIC
+LDFLAGS =
+LIBS = -lnetwork
+EXEC =
+LIB = .so
Index: /tags/2.02/Platform/linux.mk
===================================================================
--- /tags/2.02/Platform/linux.mk	(revision 191)
+++ /tags/2.02/Platform/linux.mk	(revision 191)
@@ -0,0 +1,9 @@
+# $Id$
+
+CC = cc
+AR = ar
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -D_DEFAULT_SOURCE -fPIC -D_XOPEN_SOURCE=600
+LDFLAGS =
+LIBS =
+EXEC =
+LIB = .so
Index: /tags/2.02/Platform/netbsd.mk
===================================================================
--- /tags/2.02/Platform/netbsd.mk	(revision 191)
+++ /tags/2.02/Platform/netbsd.mk	(revision 191)
@@ -0,0 +1,9 @@
+# $Id$
+
+CC = cc
+AR = ar
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -fPIC
+LDFLAGS =
+LIBS =
+EXEC =
+LIB = .so
Index: /tags/2.02/Platform/ps2.mk
===================================================================
--- /tags/2.02/Platform/ps2.mk	(revision 191)
+++ /tags/2.02/Platform/ps2.mk	(revision 191)
@@ -0,0 +1,13 @@
+# $Id$
+
+PREFIX = /dev_hdd0/httpd
+
+CC = mips64r5900el-ps2-elf-gcc
+AR = mips64r5900el-ps2-elf-ar
+CFLAGS = -O2 -D_EE -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -I /usr/local/ps2dev/ps2sdk/ee/include -I /usr/local/ps2dev/ps2sdk/common/include
+LDFLAGS = -T/usr/local/ps2dev/ps2sdk/ee/startup/linkfile -L /usr/local/ps2dev/ps2sdk/ee/lib -O2
+LIBS = -ldebug -lsocket
+EXEC = .elf
+LIB = .so
+MODULE =
+SERVADD = mips64r5900el-ps2-elf-strip tewi.elf -o tewi_strip.elf
Index: /tags/2.02/Platform/ps3.mk
===================================================================
--- /tags/2.02/Platform/ps3.mk	(revision 191)
+++ /tags/2.02/Platform/ps3.mk	(revision 191)
@@ -0,0 +1,14 @@
+# $Id$
+
+PREFIX = /dev_hdd0/httpd
+
+CC = ppu-gcc
+AR = ppu-ar
+CFLAGS = -g -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -I /usr/local/ps3dev/ppu/include
+LDFLAGS = -L /usr/local/ps3dev/ppu/lib
+LIBS = -lnet -lsysmodule -lsysutil -lrt -llv2 -lrsx -lgcm_sys
+EXEC = .elf
+LIB = .so
+MODULE =
+SERVADD = ppu-strip tewi.elf -o tewi_strip.elf
+TARGET = tewi.pkg
Index: /tags/2.02/Platform/psp.mk
===================================================================
--- /tags/2.02/Platform/psp.mk	(revision 191)
+++ /tags/2.02/Platform/psp.mk	(revision 191)
@@ -0,0 +1,14 @@
+# $Id$
+
+PREFIX = ms0:/PSP/GAME/httpd
+
+CC = psp-gcc
+AR = psp-ar
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -I /usr/local/pspdev/psp/sdk/include -D_PSP_FW_VERSION=600
+LDFLAGS = -Wl,-zmax-page-size=128 -L /usr/local/pspdev/psp/sdk/lib
+LIBS = -lpspgum -lpspgu -lpspdebug -lpspdisplay -lpspge -lpspctrl -lpspnet -lpspnet_apctl -lcglue -lpspwlan
+EXEC = .elf
+LIB = .so
+MODULE =
+SERVADD = psp-fixup-imports tewi.elf && psp-strip tewi.elf -o tewi_strip.elf
+TARGET = tewi.pbp
Index: /tags/2.02/Platform/win32-service.mk
===================================================================
--- /tags/2.02/Platform/win32-service.mk	(revision 191)
+++ /tags/2.02/Platform/win32-service.mk	(revision 191)
@@ -0,0 +1,13 @@
+# $Id$
+
+PREFIX = C:/Tewi
+
+CC = i686-w64-mingw32-gcc
+AR = i686-w64-mingw32-ar
+WINDRES = i686-w64-mingw32-windres
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -fPIC -DSERVICE -mwindows
+LDFLAGS = -mwindows
+LIBS = -lws2_32
+EXEC = .exe
+LIB = .dll
+PREOBJS = tewi.res
Index: /tags/2.02/Platform/win32.mk
===================================================================
--- /tags/2.02/Platform/win32.mk	(revision 191)
+++ /tags/2.02/Platform/win32.mk	(revision 191)
@@ -0,0 +1,13 @@
+# $Id$
+
+PREFIX = C:/Tewi
+
+CC = i686-w64-mingw32-gcc
+AR = i686-w64-mingw32-ar
+WINDRES = i686-w64-mingw32-windres
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -fPIC
+LDFLAGS =
+LIBS = -lws2_32
+EXEC = .exe
+LIB = .dll
+PREOBJS = tewi.res
Index: /tags/2.02/Platform/win64-service.mk
===================================================================
--- /tags/2.02/Platform/win64-service.mk	(revision 191)
+++ /tags/2.02/Platform/win64-service.mk	(revision 191)
@@ -0,0 +1,13 @@
+# $Id$
+
+PREFIX = C:/Tewi
+
+CC = x86_64-w64-mingw32-gcc
+AR = x86_64-w64-mingw32-ar
+WINDRES = x86_64-w64-mingw32-windres
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -fPIC -DSERVICE -mwindows
+LDFLAGS = -mwindows
+LIBS = -lws2_32
+EXEC = .exe
+LIB = .dll
+PREOBJS = tewi.res
Index: /tags/2.02/Platform/win64.mk
===================================================================
--- /tags/2.02/Platform/win64.mk	(revision 191)
+++ /tags/2.02/Platform/win64.mk	(revision 191)
@@ -0,0 +1,13 @@
+# $Id$
+
+PREFIX = C:/Tewi
+
+CC = x86_64-w64-mingw32-gcc
+AR = x86_64-w64-mingw32-ar
+WINDRES = x86_64-w64-mingw32-windres
+CFLAGS = -g -std=c99 -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -fPIC
+LDFLAGS =
+LIBS = -lws2_32
+EXEC = .exe
+LIB = .dll
+PREOBJS = tewi.res
Index: /tags/2.02/README
===================================================================
--- /tags/2.02/README	(revision 191)
+++ /tags/2.02/README	(revision 191)
@@ -0,0 +1,49 @@
+
+Tewi HTTPd version 2.02
+
+Original by Nishi <nishi@nishi.boats>
+
+All documentations for Tewi HTTPd will be able to be found on-line at
+https://trac.nishi.boats/tewi
+
+Distribution Contents
+-------------------------------
+You should have received the following with this distribution:
+
+LICENSE      License which applies to this distribution
+README       This file
+Makefile     Top level makefile
+Server/      Directory containing source code for the server
+Module/      Directory containing source code for the module
+Tool/        Directory containing source code for the compile time configs
+Common/      Directory containing source code for the common codes
+Binary/      Directory containing binary stuff
+Icons/       Directory containing icons for directory indexing
+
+Files of note;
+config.h.tmpl     Compile time configuration file template, copy to config.h
+
+Version Number Defined
+--------------------------------
+1.06C
+| | |
+| | +- Bug fixes / security fixes
+| +--- Minor features
++----- Breaking changes
+
+Platform support
+--------------------------
+Currently, these platforms are supported:
+
+Windows 9x                   Experimental port, buggy
+                             TODO: Get it more stable
+                             TODO: Use MinGW not Cygwin
+Windows NT 4.0 and later     Working on NT 4.0 SP6a
+Linux                        Working on Debian GNU/Linux 12 (x86_64)
+NetBSD                       Working on NetBSD/amd64 10.0
+Haiku                        Working
+PlayStation Portable         Working, missing module support
+                             TODO: Get multi-threading working (maybe)
+PlayStation 2                Not working
+PlayStation 3                Working, missing module support
+                             TODO: Get multi-threading working (maybe)
Index: /tags/2.02/README.tmpl
===================================================================
--- /tags/2.02/README.tmpl	(revision 191)
+++ /tags/2.02/README.tmpl	(revision 191)
@@ -0,0 +1,49 @@
+
+Tewi HTTPd version @VERSION@
+
+Original by Nishi <nishi@nishi.boats>
+
+All documentations for Tewi HTTPd will be able to be found on-line at
+https://trac.nishi.boats/tewi
+
+Distribution Contents
+-------------------------------
+You should have received the following with this distribution:
+
+LICENSE      License which applies to this distribution
+README       This file
+Makefile     Top level makefile
+Server/      Directory containing source code for the server
+Module/      Directory containing source code for the module
+Tool/        Directory containing source code for the compile time configs
+Common/      Directory containing source code for the common codes
+Binary/      Directory containing binary stuff
+Icons/       Directory containing icons for directory indexing
+
+Files of note;
+config.h.tmpl     Compile time configuration file template, copy to config.h
+
+Version Number Defined
+--------------------------------
+1.06C
+| | |
+| | +- Bug fixes / security fixes
+| +--- Minor features
++----- Breaking changes
+
+Platform support
+--------------------------
+Currently, these platforms are supported:
+
+Windows 9x                   Experimental port, buggy
+                             TODO: Get it more stable
+                             TODO: Use MinGW not Cygwin
+Windows NT 4.0 and later     Working on NT 4.0 SP6a
+Linux                        Working on Debian GNU/Linux 12 (x86_64)
+NetBSD                       Working on NetBSD/amd64 10.0
+Haiku                        Working
+PlayStation Portable         Working, missing module support
+                             TODO: Get multi-threading working (maybe)
+PlayStation 2                Not working
+PlayStation 3                Working, missing module support
+                             TODO: Get multi-threading working (maybe)
Index: /tags/2.02/Server/Makefile
===================================================================
--- /tags/2.02/Server/Makefile	(revision 191)
+++ /tags/2.02/Server/Makefile	(revision 191)
@@ -0,0 +1,44 @@
+# $Id$
+
+include $(PWD)/Platform/$(PLATFORM).mk
+
+.PHONY: all clean
+.SUFFIXES: .c .o
+
+OBJS = main.o version.o config.o server.o http.o module.o strptime.o font.o $(EXTOBJS) $(PREOBJS)
+
+all: tewi$(EXEC) $(TARGET)
+
+tewi_strip$(EXEC): tewi$(EXEC)
+
+tewi$(EXEC): $(OBJS) ../Common/common.a
+	$(CC) $(LDFLAGS) $(EXTLDFLAGS) -o $@ $(OBJS) $(EXTLIBS) ../Common/common.a $(LIBS)
+	$(SERVADD)
+
+tewi.pbp: tewi_strip$(EXEC) param.sfo
+	pack-pbp $@ param.sfo ../Binary/psp.png NULL NULL NULL NULL tewi_strip$(EXEC) NULL
+
+param.sfo:
+	mksfoex -d MEMSIZE=1 'Tewi HTTPd' $@
+
+tewi.self: tewi_strip$(EXEC)
+	sprxlinker tewi_strip$(EXEC)
+	make_self_npdrm tewi_strip$(EXEC) $@ UP0001-TEWI_00-0000000000000000
+	fself tewi_strip$(EXEC) tewi.fake.self
+
+tewi.pkg: tewi.self
+	mkdir -p pkg/USRDIR
+	cp /usr/local/ps3dev/bin/ICON0.PNG pkg/ICON0.PNG
+	make_self_npdrm tewi.elf pkg/USRDIR/EBOOT.BIN UP0001-TEWI_00-0000000000000000
+	sfo.py --title "Tewi HTTPd" --appid "TEWI" -f /usr/local/ps3dev/bin/sfo.xml pkg/PARAM.SFO
+	pkg.py --contentid UP0001-TEWI_00-0000000000000000 pkg/ $@
+	rm -rf pkg
+
+.c.o:
+	$(CC) $(CFLAGS) $(EXTCFLAGS) -c -o $@ $<
+
+tewi.res: tewi.rc ../Binary/tewi.ico
+	$(WINDRES) tewi.rc -O coff -o $@
+
+clean:
+	rm -f *.o tewi *.exe *.res *.elf *.sfo *.pbp *.self *.pkg
Index: /tags/2.02/Server/config.c
===================================================================
--- /tags/2.02/Server/config.c	(revision 191)
+++ /tags/2.02/Server/config.c	(revision 191)
@@ -0,0 +1,478 @@
+/* $Id$ */
+
+#define SOURCE
+
+#include "tw_config.h"
+#include "tw_module.h"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __MINGW32__
+#include <winsock2.h>
+#endif
+
+#include <cm_string.h>
+#include <cm_log.h>
+
+struct tw_config config;
+
+struct tw_config_entry* tw_vhost_match(const char* name, int port) {
+	int i;
+	for(i = 0; i < config.vhost_count; i++) {
+		if(strcmp(config.vhosts[i].name, name) == 0 && (config.vhosts[i].port == -1 ? 1 : config.vhosts[i].port == port)) {
+			return &config.vhosts[i];
+		}
+	}
+	return &config.root;
+}
+
+bool tw_permission_allowed(const char* path, SOCKADDR addr, struct tw_http_request req, struct tw_config_entry* vhost) {
+	int i;
+	bool found = false;
+	bool pathstart = false;
+	bool perm = false;
+again:
+	for(i = 0; i < vhost->dir_count; i++) {
+		struct tw_dir_entry* e = &vhost->dirs[i];
+		pathstart = false;
+		if(strlen(path) >= strlen(e->dir)) {
+			pathstart = true;
+			int j;
+			for(j = 0; path[j] != 0 && e->dir[j] != 0; j++) {
+				if(path[j] != e->dir[j]) {
+					pathstart = false;
+					break;
+				}
+			}
+		}
+		char* noslash = cm_strdup(e->dir);
+		noslash[strlen(noslash) - 1] = 0;
+		if(strcmp(e->dir, path) == 0 || strcmp(noslash, path) == 0 || pathstart) {
+			found = true;
+			if(strcmp(e->name, "all") == 0) {
+				perm = e->type == TW_DIR_ALLOW;
+			}
+		}
+		free(noslash);
+	}
+	if(!found && vhost != &config.root) {
+		vhost = &config.root;
+		goto again;
+	}
+	return perm;
+}
+
+void tw_config_init(void) {
+	int i;
+	for(i = 0; i < MAX_PORTS + 1; i++) {
+		config.ports[i] = -1;
+	}
+	for(i = 0; i < MAX_VHOSTS; i++) {
+#ifndef NO_SSL
+		config.vhosts[i].sslkey = NULL;
+		config.vhosts[i].sslcert = NULL;
+#endif
+		config.vhosts[i].root = NULL;
+#ifdef HAS_CHROOT
+		config.vhosts[i].chroot_path = NULL;
+#endif
+	}
+#ifndef NO_SSL
+	config.root.sslkey = NULL;
+	config.root.sslcert = NULL;
+#endif
+	config.root.root = NULL;
+	config.root.mime_count = 0;
+	config.root.dir_count = 0;
+	config.root.icon_count = 0;
+	config.root.index_count = 0;
+	config.root.readme_count = 0;
+	config.root.hideport = 0;
+#ifdef HAS_CHROOT
+	config.root.chroot_path = NULL;
+#endif
+	config.vhost_count = 0;
+	config.module_count = 0;
+	config.extension = NULL;
+	config.server_root = cm_strdup(PREFIX);
+	config.server_admin = cm_strdup(SERVER_ADMIN);
+	config.defined[0] = NULL;
+#if defined(_PSP)
+	strcpy(config.hostname, "psp");
+#elif defined(__PPU__)
+	strcpy(config.hostname, "ps3");
+#elif defined(__ps2sdk__)
+	strcpy(config.hostname, "ps2");
+#else
+	gethostname(config.hostname, 1024);
+#endif
+#ifdef HAS_CHROOT
+	tw_add_define("HAS_CHROOT");
+#endif
+#ifndef NO_SSL
+	tw_add_define("HAS_SSL");
+#endif
+}
+
+int tw_config_read(const char* path) {
+	cm_log("Config", "Reading %s", path);
+	char cbuf[2];
+	cbuf[1] = 0;
+	int ln = 0;
+	int ifbr = 0;
+	int ignore = -1;
+	FILE* f = fopen(path, "r");
+	if(f != NULL) {
+		char* line = malloc(1);
+		line[0] = 0;
+		int stop = 0;
+		struct tw_config_entry* current = &config.root;
+		char* vhost = NULL;
+		char* dir = NULL;
+		while(stop == 0) {
+			int c = fread(cbuf, 1, 1, f);
+			if(cbuf[0] == '\n' || c <= 0) {
+				ln++;
+				char* l = cm_trim(line);
+				if(strlen(l) > 0 && l[0] != '#') {
+					char** r = cm_split(l, " \t");
+					int i;
+					if(ignore != -1 && ifbr >= ignore) {
+						if(cm_strcaseequ(r[0], "EndIf")) ifbr--;
+						if(ifbr == 0) {
+							ignore = -1;
+						}
+					} else if(cm_strcaseequ(r[0], "Include") || cm_strcaseequ(r[0], "IncludeOptional")) {
+						for(i = 1; r[i] != NULL; i++) {
+							if(tw_config_read(r[i]) != 0 && cm_strcaseequ(r[0], "Include")) {
+								stop = 1;
+								break;
+							}
+						}
+					} else if(cm_strcaseequ(r[0], "Define")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing name at line %d", ln);
+							stop = 1;
+						} else {
+							tw_add_define(r[1]);
+						}
+					} else if(cm_strcaseequ(r[0], "Undefine")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing name at line %d", ln);
+							stop = 1;
+						} else {
+							tw_delete_define(r[1]);
+						}
+					} else if(cm_strcaseequ(r[0], "BeginDirectory")) {
+						if(dir != NULL) {
+							cm_log("Config", "Already in directory section at line %d", ln);
+							stop = 1;
+						} else {
+							if(r[1] == NULL) {
+								cm_log("Config", "Missing directory at line %d", ln);
+								stop = 1;
+							} else {
+								dir = cm_strcat(r[1], r[1][strlen(r[1]) - 1] == '/' ? "" : "/");
+							}
+						}
+					} else if(cm_strcaseequ(r[0], "EndDirectory")) {
+						if(dir == NULL) {
+							cm_log("Config", "Not in directory section at line %d", ln);
+							stop = 1;
+						} else {
+							free(dir);
+							dir = NULL;
+						}
+					} else if(cm_strcaseequ(r[0], "Allow")) {
+						if(dir == NULL) {
+							cm_log("Config", "Not in directory section at line %d", ln);
+							stop = 1;
+						} else {
+							if(r[1] == NULL) {
+								cm_log("Config", "Missing argument at line %d", ln);
+								stop = 1;
+							} else {
+								struct tw_dir_entry* e = &current->dirs[current->dir_count++];
+								e->name = cm_strdup(r[1]);
+								e->dir = cm_strdup(dir);
+								e->type = TW_DIR_ALLOW;
+							}
+						}
+					} else if(cm_strcaseequ(r[0], "Deny")) {
+						if(dir == NULL) {
+							cm_log("Config", "Not in directory section at line %d", ln);
+							stop = 1;
+						} else {
+							if(r[1] == NULL) {
+								cm_log("Config", "Missing argument at line %d", ln);
+								stop = 1;
+							} else {
+								struct tw_dir_entry* e = &current->dirs[current->dir_count++];
+								e->name = cm_strdup(r[1]);
+								e->dir = cm_strdup(dir);
+								e->type = TW_DIR_DENY;
+							}
+						}
+					} else if(cm_strcaseequ(r[0], "BeginVirtualHost")) {
+						if(vhost != NULL) {
+							cm_log("Config", "Already in virtual host section at line %d", ln);
+							stop = 1;
+						} else {
+							if(r[1] == NULL) {
+								cm_log("Config", "Missing virtual host at line %d", ln);
+								stop = 1;
+							} else {
+								vhost = cm_strdup(r[1]);
+								current = &config.vhosts[config.vhost_count++];
+								current->dir_count = 0;
+								current->mime_count = 0;
+								current->icon_count = 0;
+								current->index_count = 0;
+								current->readme_count = 0;
+								current->hideport = -1;
+								int i;
+								current->name = cm_strdup(vhost);
+								current->port = -1;
+								for(i = 0; vhost[i] != 0; i++) {
+									if(vhost[i] == ':') {
+										current->name[i] = 0;
+										current->port = atoi(current->name + i + 1);
+										break;
+									}
+								}
+							}
+						}
+					} else if(cm_strcaseequ(r[0], "EndVirtualHost")) {
+						if(vhost == NULL) {
+							cm_log("Config", "Not in virtual host section at line %d", ln);
+							stop = 1;
+						} else {
+							free(vhost);
+							vhost = NULL;
+							current = &config.root;
+						}
+					} else if(cm_strcaseequ(r[0], "Listen")
+#ifndef NO_SSL
+						  || cm_strcaseequ(r[0], "ListenSSL")
+#endif
+					) {
+						for(i = 1; r[i] != NULL; i++) {
+							uint64_t port = atoi(r[i]);
+							cm_log("Config", "Going to listen at port %d%s", (int)port, cm_strcaseequ(r[0], "ListenSSL") ? " with SSL" : "");
+							if(cm_strcaseequ(r[0], "ListenSSL")) port |= (1ULL << 32);
+							int j;
+							for(j = 0; config.ports[j] != -1; j++)
+								;
+							config.ports[j] = port;
+						}
+					} else if(cm_strcaseequ(r[0], "HidePort")) {
+						current->hideport = 1;
+					} else if(cm_strcaseequ(r[0], "ShowPort")) {
+						current->hideport = 0;
+#ifndef NO_SSL
+					} else if(cm_strcaseequ(r[0], "SSLKey")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing path at line %d", ln);
+							stop = 1;
+						} else {
+							if(current->sslkey != NULL) free(current->sslkey);
+							current->sslkey = cm_strdup(r[1]);
+						}
+					} else if(cm_strcaseequ(r[0], "SSLCertificate")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing path at line %d", ln);
+							stop = 1;
+						} else {
+							if(current->sslcert != NULL) free(current->sslcert);
+							current->sslcert = cm_strdup(r[1]);
+						}
+#endif
+#ifdef HAS_CHROOT
+					} else if(cm_strcaseequ(r[0], "ChrootDirectory")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing path at line %d", ln);
+							stop = 1;
+						} else {
+							if(current->chroot_path != NULL) free(current->chroot_path);
+							current->chroot_path = cm_strdup(r[1]);
+						}
+#endif
+					} else if(cm_strcaseequ(r[0], "ForceLog")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing log at line %d", ln);
+							stop = 1;
+						} else {
+							cm_force_log(r[1]);
+						}
+					} else if(cm_strcaseequ(r[0], "EndIf")) {
+						if(ifbr == 0) {
+							cm_log("Config", "Missing BeginIf at line %d", ln);
+							stop = 1;
+						}
+						ifbr--;
+					} else if(cm_strcaseequ(r[0], "BeginIf") || cm_strcaseequ(r[0], "BeginIfNot")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing condition type at line %d", ln);
+						} else {
+							ifbr++;
+							bool ign = false;
+							if(cm_strcaseequ(r[1], "False")) {
+								ign = true;
+							} else if(cm_strcaseequ(r[1], "True")) {
+							} else if(cm_strcaseequ(r[1], "Defined")) {
+								if(r[2] == NULL) {
+									cm_log("Config", "Missing name at line %d", ln);
+									stop = 1;
+								} else {
+									int i;
+									bool fndit = false;
+									for(i = 0; config.defined[i] != NULL; i++) {
+										if(strcmp(config.defined[i], r[2]) == 0) {
+											fndit = true;
+											break;
+										}
+									}
+									if(!fndit) {
+										ign = true;
+									}
+								}
+							} else {
+								cm_log("Config", "Unknown condition type at line %d", ln);
+								stop = 1;
+							}
+							if(cm_strcaseequ(r[0], "BeginIfNot")) ign = !ign;
+							if(ign) {
+								ignore = ifbr - 1;
+							}
+						}
+					} else if(cm_strcaseequ(r[0], "ServerRoot")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing path at line %d", ln);
+							stop = 1;
+						} else {
+							chdir(r[1]);
+							free(config.server_root);
+							config.server_root = cm_strdup(r[1]);
+						}
+					} else if(cm_strcaseequ(r[0], "ServerAdmin")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing email at line %d", ln);
+							stop = 1;
+						} else {
+							free(config.server_admin);
+							config.server_admin = cm_strdup(r[1]);
+						}
+					} else if(cm_strcaseequ(r[0], "DocumentRoot")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing path at line %d", ln);
+							stop = 1;
+						} else {
+							if(current->root != NULL) free(current->root);
+							current->root = cm_strdup(strcmp(r[1], "/") == 0 ? "" : r[1]);
+						}
+					} else if(cm_strcaseequ(r[0], "MIMEType")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing extension at line %d", ln);
+							stop = 1;
+						} else if(r[2] == NULL) {
+							cm_log("Config", "Missing MIME at line %d", ln);
+							stop = 1;
+						} else {
+							struct tw_mime_entry* e = &current->mimes[current->mime_count++];
+							e->ext = cm_strdup(r[1]);
+							e->mime = cm_strdup(r[2]);
+						}
+					} else if(cm_strcaseequ(r[0], "Icon")) {
+						if(r[1] == NULL) {
+							cm_log("Config", "Missing MIME at line %d", ln);
+							stop = 1;
+						} else if(r[2] == NULL) {
+							cm_log("Config", "Missing path at line %d", ln);
+							stop = 1;
+						} else {
+							struct tw_icon_entry* e = &current->icons[current->icon_count++];
+							e->mime = cm_strdup(r[1]);
+							e->icon = cm_strdup(r[2]);
+						}
+					} else if(cm_strcaseequ(r[0], "LoadModule")) {
+						for(i = 1; r[i] != NULL; i++) {
+							void* mod = tw_module_load(r[i]);
+							if(mod != NULL) {
+								config.modules[config.module_count++] = mod;
+								if(tw_module_init(mod) != 0) {
+									stop = 1;
+									break;
+								}
+							} else {
+								cm_log("Config", "Could not load the module at line %d", ln);
+								stop = 1;
+								break;
+							}
+						}
+					} else if(cm_strcaseequ(r[0], "DirectoryIndex")) {
+						for(i = 1; r[i] != NULL; i++) {
+							current->indexes[current->index_count++] = cm_strdup(r[i]);
+						}
+					} else if(cm_strcaseequ(r[0], "ReadmeFile") || cm_strcaseequ(r[0], "Readme")) {
+						if(cm_strcaseequ(r[0], "Readme")) {
+							cm_force_log("NOTE: Readme directive is deprecated.");
+						}
+						for(i = 1; r[i] != NULL; i++) {
+							current->readmes[current->readme_count++] = cm_strdup(r[i]);
+						}
+					} else {
+						stop = 1;
+						if(r[0] != NULL) {
+							int argc;
+							for(argc = 0; r[argc] != NULL; argc++)
+								;
+							stop = 0;
+							int i;
+							bool called = false;
+							struct tw_tool tools;
+							tw_init_tools(&tools);
+							for(i = 0; i < config.module_count; i++) {
+								tw_mod_config_t mod_config = (tw_mod_config_t)tw_module_symbol(config.modules[i], "mod_config");
+								int resp;
+								if(mod_config != NULL && (resp = mod_config(&tools, r, argc)) == TW_CONFIG_PARSED) {
+									called = true;
+									break;
+								}
+								if(resp == TW_CONFIG_ERROR) {
+									stop = 1;
+									called = true;
+									break;
+								}
+							}
+							if(!called) {
+								cm_log("Config", "Unknown directive `%s' at line %d", r[0], ln);
+								stop = 1;
+							}
+						}
+					}
+					for(i = 0; r[i] != NULL; i++) free(r[i]);
+					free(r);
+				}
+				free(l);
+				free(line);
+				line = malloc(1);
+				line[0] = 0;
+				if(c <= 0) break;
+			} else if(cbuf[0] != '\r') {
+				char* tmp = line;
+				line = cm_strcat(tmp, cbuf);
+				free(tmp);
+			}
+		}
+		free(line);
+		fclose(f);
+		return stop;
+	} else {
+		cm_log("Config", "Could not open the file");
+		return 1;
+	}
+}
Index: /tags/2.02/Server/font.c
===================================================================
--- /tags/2.02/Server/font.c	(revision 191)
+++ /tags/2.02/Server/font.c	(revision 191)
@@ -0,0 +1,287 @@
+/* $Id$ */
+
+unsigned char font[] = {
+    // 0x20
+    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x21 !
+    0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000, 0b00100000, 0b00000000,
+
+    // 0x22 "
+    0b01001000, 0b01001000, 0b01001000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x23 #
+    0b01010000, 0b11111000, 0b01010000, 0b01010000, 0b01010000, 0b11111000, 0b01010000, 0b00000000,
+
+    // 0x24 $
+    0b00100000, 0b11111000, 0b10100000, 0b11111000, 0b00101000, 0b11111000, 0b00100000, 0b00000000,
+
+    // 0x25 %
+    0b00000000, 0b10001000, 0b10010000, 0b00100000, 0b01001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x25 &
+    0b01000000, 0b10100000, 0b10100000, 0b01011000, 0b10100000, 0b10100000, 0b01011000, 0b00000000,
+
+    // 0x27 '
+    0b00010000, 0b00010000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x28 (
+    0b00001000, 0b00010000, 0b00100000, 0b00100000, 0b00100000, 0b00010000, 0b00001000, 0b00000000,
+
+    // 0x29 )
+    0b01000000, 0b00100000, 0b00010000, 0b00010000, 0b00010000, 0b00100000, 0b01000000, 0b00000000,
+
+    // 0x2a *
+    0b00100000, 0b10101000, 0b01110000, 0b00100000, 0b01110000, 0b10101000, 0b00100000, 0b00000000,
+
+    // 0x2b +
+    0b00100000, 0b00100000, 0b00100000, 0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x2c ,
+    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00010000, 0b00100000, 0b00000000,
+
+    // 0x2d -
+    0b00000000, 0b00000000, 0b00000000, 0b11111000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x2e .
+    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00010000, 0b00010000, 0b00000000,
+
+    // 0x2f /
+    0b00000000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x30 0
+    0b01110000, 0b10001000, 0b10001000, 0b10101000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x31 1
+    0b00100000, 0b01100000, 0b10100000, 0b00100000, 0b00100000, 0b00100000, 0b11111000, 0b00000000,
+
+    // 0x32 2
+    0b01110000, 0b10001000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b11111000, 0b00000000,
+
+    // 0x33 3
+    0b01110000, 0b10001000, 0b00001000, 0b00110000, 0b00001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x34 4
+    0b00011000, 0b00101000, 0b01001000, 0b10001000, 0b11111000, 0b00001000, 0b00001000, 0b00000000,
+
+    // 0x35 5
+    0b11111000, 0b10000000, 0b10000000, 0b11110000, 0b00001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x36 6
+    0b01110000, 0b10001000, 0b10000000, 0b11110000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x37 7
+    0b11111000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x38 8
+    0b01110000, 0b10001000, 0b10001000, 0b01110000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x39 9
+    0b01110000, 0b10001000, 0b10001000, 0b01111000, 0b00001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x3a :
+    0b00010000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00010000, 0b00000000,
+
+    // 0x3b ;
+    0b00010000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00100000, 0b00000000,
+
+    // 0x3c <
+    0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b01000000, 0b00100000, 0b00010000, 0b00000000,
+
+    // 0x3d =
+    0b00000000, 0b00000000, 0b11111000, 0b00000000, 0b11111000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x3e >
+    0b01000000, 0b00100000, 0b00010000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b00000000,
+
+    // 0x3f ?
+    0b01110000, 0b10001000, 0b10001000, 0b00110000, 0b00100000, 0b00000000, 0b00100000, 0b00000000,
+
+    // 0x40 @
+    0b01110000, 0b10001000, 0b10101000, 0b10111000, 0b10000000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x41 A
+    0b00100000, 0b01010000, 0b10001000, 0b11111000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x42 B
+    0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b00000000,
+
+    // 0x43 C
+    0b01110000, 0b10001000, 0b10000000, 0b10000000, 0b10000000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x44 D
+    0b11110000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b11110000, 0b00000000,
+
+    // 0x45 E
+    0b11111000, 0b10000000, 0b10000000, 0b11111000, 0b10000000, 0b10000000, 0b11111000, 0b00000000,
+
+    // 0x46 F
+    0b11111000, 0b10000000, 0b10000000, 0b11111000, 0b10000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x47 G
+    0b01110000, 0b10001000, 0b10000000, 0b10111000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x48 H
+    0b10001000, 0b10001000, 0b10001000, 0b11111000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x49 I
+    0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b11111000, 0b00000000,
+
+    // 0x4a J
+    0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x4b K
+    0b10001000, 0b10010000, 0b10100000, 0b11000000, 0b10100000, 0b10010000, 0b10001000, 0b00000000,
+
+    // 0x4c L
+    0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b11111000, 0b00000000,
+
+    // 0x4d M
+    0b10001000, 0b11011000, 0b10101000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x4e N
+    0b10001000, 0b10001000, 0b11001000, 0b10101000, 0b10011000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x4f O
+    0b01110000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x50 P
+    0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b10000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x51 Q
+    0b01110000, 0b10001000, 0b10001000, 0b10101000, 0b01110000, 0b00010000, 0b00001000, 0b00000000,
+
+    // 0x52 R
+    0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x53 S
+    0b01110000, 0b10001000, 0b10000000, 0b01110000, 0b00001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x54 T
+    0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x55 U
+    0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x56 V
+    0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b01010000, 0b00100000, 0b00000000,
+
+    // 0x57 W
+    0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10101000, 0b11011000, 0b10001000, 0b00000000,
+
+    // 0x58 X
+    0b10001000, 0b10001000, 0b01010000, 0b00100000, 0b01010000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x59 Y
+    0b10001000, 0b01010000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x5a Z
+    0b11111000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b11111000, 0b00000000,
+
+    // 0x5b [
+    0b11110000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b11110000, 0b00000000,
+
+    // 0x5c backslash
+    0b00000000, 0b10000000, 0b01000000, 0b00100000, 0b00010000, 0b00001000, 0b00001000, 0b00000000,
+
+    // 0x5d ]
+    0b01111000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b01111000, 0b00000000,
+
+    // 0x5e ^
+    0b00100000, 0b01010000, 0b10001000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x5f _
+    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b11111000, 0b00000000,
+
+    // 0x60 `
+    0b00100000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x61 a
+    0b00000000, 0b00000000, 0b01110000, 0b00001000, 0b01111000, 0b10001000, 0b01111000, 0b00000000,
+
+    // 0x62 b
+    0b10000000, 0b10000000, 0b10000000, 0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b00000000,
+
+    // 0x63 c
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b10000000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x64 d
+    0b00001000, 0b00001000, 0b00001000, 0b01111000, 0b10001000, 0b10001000, 0b01111000, 0b00000000,
+
+    // 0x65 e
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b11111000, 0b10000000, 0b01111000, 0b00000000,
+
+    // 0x66 f
+    0b00011000, 0b00100000, 0b00100000, 0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x67 g
+    0b00000000, 0b00000000, 0b01111000, 0b10001000, 0b01111000, 0b00001000, 0b11110000, 0b00000000,
+
+    // 0x68 h
+    0b10000000, 0b10000000, 0b11110000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x69 i
+    0b00100000, 0b00000000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x6a j
+    0b00000000, 0b00000000, 0b00100000, 0b00000000, 0b00100000, 0b10100000, 0b01000000, 0b00000000,
+
+    // 0x6b k
+    0b10000000, 0b10000000, 0b10010000, 0b10100000, 0b11000000, 0b10100000, 0b10010000, 0b00000000,
+
+    // 0x6c l
+    0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x6d m
+    0b00000000, 0b00000000, 0b11010000, 0b10101000, 0b10101000, 0b10101000, 0b10101000, 0b00000000,
+
+    // 0x6e n
+    0b00000000, 0b00000000, 0b10110000, 0b11001000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x6f o
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x70 p
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b11110000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x71 q
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b01111000, 0b00001000, 0b00001000, 0b00000000,
+
+    // 0x72 r
+    0b00000000, 0b00000000, 0b10110000, 0b11001000, 0b10000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x73 s
+    0b00000000, 0b00000000, 0b01110000, 0b10000000, 0b01110000, 0b00001000, 0b01110000, 0b00000000,
+
+    // 0x74 t
+    0b00100000, 0b00100000, 0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00011000, 0b00000000,
+
+    // 0x75 u
+    0b00000000, 0b00000000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b01111000, 0b00000000,
+
+    // 0x76 v
+    0b00000000, 0b00000000, 0b10001000, 0b10001000, 0b10001000, 0b01010000, 0b00100000, 0b00000000,
+
+    // 0x77 w
+    0b00000000, 0b00000000, 0b10001000, 0b10001000, 0b10101000, 0b11011000, 0b10001000, 0b00000000,
+
+    // 0x78 x
+    0b00000000, 0b00000000, 0b10001000, 0b01010000, 0b00100000, 0b01010000, 0b10001000, 0b00000000,
+
+    // 0x79 y
+    0b00000000, 0b00000000, 0b10001000, 0b01010000, 0b00100000, 0b01000000, 0b10000000, 0b00000000,
+
+    // 0x7a z
+    0b00000000, 0b00000000, 0b11111000, 0b00010000, 0b00100000, 0b01000000, 0b11111000, 0b00000000,
+
+    // 0x7b {
+    0b00110000, 0b01000000, 0b01000000, 0b10000000, 0b01000000, 0b01000000, 0b00110000, 0b00000000,
+
+    // 0x7c |
+    0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x7d }
+    0b01100000, 0b00010000, 0b00010000, 0b00001000, 0b00010000, 0b00010000, 0b01100000, 0b00000000,
+
+    // 0x7e ~
+    0b00000000, 0b00000000, 0b01000000, 0b10101000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00000000};
Index: /tags/2.02/Server/http.c
===================================================================
--- /tags/2.02/Server/http.c	(revision 191)
+++ /tags/2.02/Server/http.c	(revision 191)
@@ -0,0 +1,367 @@
+/* $Id$ */
+
+#define SOURCE
+
+#include "../config.h"
+
+#include "tw_http.h"
+
+#include "tw_server.h"
+
+#include <cm_log.h>
+#include <cm_string.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __MINGW32__
+#include <winsock2.h>
+#else
+#ifdef USE_POLL
+#ifdef __PPU__
+#include <net/poll.h>
+#else
+#include <poll.h>
+#endif
+#else
+#include <sys/select.h>
+#endif
+#endif
+
+void tw_free_request(struct tw_http_request* req) {
+	if(req->method != NULL) free(req->method);
+	if(req->path != NULL) free(req->path);
+	if(req->query != NULL) free(req->query);
+	if(req->headers != NULL) {
+		int i;
+		for(i = 0; req->headers[i] != NULL; i++) free(req->headers[i]);
+		free(req->headers);
+	}
+	if(req->body != NULL) free(req->body);
+	if(req->version != NULL) free(req->version);
+
+	req->method = NULL;
+	req->path = NULL;
+	req->query = NULL;
+	req->headers = NULL;
+	req->body = NULL;
+	req->version = NULL;
+}
+
+int tw_http_parse(SSL* ssl, int sock, struct tw_http_request* req) {
+	char buffer[512];
+	char cbuf[2];
+	int phase = 0;
+
+#ifdef USE_POLL
+	struct pollfd pollfds[1];
+	pollfds[0].fd = sock;
+	pollfds[0].events = POLLIN | POLLPRI;
+#else
+	fd_set fds;
+#endif
+
+	bool bad = false;
+
+	cbuf[1] = 0;
+
+	req->method = NULL;
+	req->path = NULL;
+	req->query = NULL;
+	req->headers = NULL;
+	req->body = NULL;
+	req->version = NULL;
+
+	char* header = malloc(1);
+	header[0] = 0;
+	int nl = 0;
+
+	while(1) {
+#ifndef USE_POLL
+		FD_ZERO(&fds);
+		FD_SET(sock, &fds);
+		struct timeval tv;
+		tv.tv_sec = 5;
+		tv.tv_usec = 0;
+#endif
+#ifndef NO_SSL
+		if(ssl == NULL || !SSL_has_pending(ssl)) {
+#endif
+#ifdef USE_POLL
+			int n = poll(pollfds, 1, 5000);
+#else
+#ifdef __HAIKU__
+		int n = select(32, &fds, NULL, NULL, &tv);
+#else
+		int n = select(FD_SETSIZE, &fds, NULL, NULL, &tv);
+#endif
+#endif
+			if(n <= 0) {
+				cm_log("HTTP", "Timeout, disconnecting");
+				free(header);
+				tw_free_request(req);
+				return -1;
+			}
+#ifndef NO_SSL
+		}
+#endif
+		int len = tw_read(ssl, sock, buffer, 512);
+		if(len <= 0) {
+			bad = true;
+			break;
+		}
+		int i;
+		for(i = 0; i < len; i++) {
+			char c = buffer[i];
+			if(phase == 0) {
+				if(c == ' ') {
+					if(req->method == NULL) {
+						tw_free_request(req);
+						bad = true;
+						goto getout;
+					} else {
+						phase++;
+					}
+				} else {
+					if(req->method == NULL) {
+						req->method = malloc(1);
+						req->method[0] = 0;
+					}
+					cbuf[0] = c;
+					char* tmp = req->method;
+					req->method = cm_strcat(tmp, cbuf);
+					free(tmp);
+				}
+			} else if(phase == 1) {
+				if(c == ' ') {
+					if(req->path == NULL) {
+						tw_free_request(req);
+						bad = true;
+						goto getout;
+					} else {
+						phase++;
+					}
+				} else {
+					if(req->path == NULL) {
+						req->path = malloc(1);
+						req->path[0] = 0;
+					}
+					cbuf[0] = c;
+					char* tmp = req->path;
+					req->path = cm_strcat(tmp, cbuf);
+					free(tmp);
+				}
+			} else if(phase == 2) {
+				if(c == '\n') {
+					if(req->version == NULL) {
+						tw_free_request(req);
+						bad = true;
+						goto getout;
+					} else {
+						/* We have Method, Path, Version now */
+
+						if(strcmp(req->version, "HTTP/1.1") != 0 && strcmp(req->version, "HTTP/1.0") != 0) {
+							cm_log("HTTP", "Bad HTTP Version");
+							bad = true;
+							goto getout;
+						}
+
+						int j;
+						char* p = malloc(1);
+						p[0] = 0;
+						for(j = 0; req->path[j] != 0; j++) {
+							if(req->path[j] == '/') {
+								cbuf[0] = '/';
+								for(; req->path[j] != 0 && req->path[j] == '/'; j++)
+									;
+								j--;
+							} else {
+								cbuf[0] = req->path[j];
+							}
+							char* tmp = p;
+							p = cm_strcat(tmp, cbuf);
+							free(tmp);
+						}
+						free(req->path);
+						req->path = p;
+
+						int incr = 0;
+						p = malloc(1);
+						p[0] = 0;
+						for(j = 0;; j++) {
+							if(req->path[j] == '/' || req->path[j] == 0) {
+								char oldc = req->path[j];
+								cbuf[0] = oldc;
+								req->path[j] = 0;
+
+								char* pth = req->path + incr;
+
+								if(strcmp(pth, "..") == 0) {
+									int k;
+									if(p[strlen(p) - 1] == '/') p[strlen(p) - 1] = 0;
+									for(k = strlen(p) - 1; k >= 0; k--) {
+										if(p[k] == '/') {
+											p[k + 1] = 0;
+											break;
+										}
+									}
+									if(strlen(p) == 0) {
+										free(p);
+										p = cm_strdup("/");
+									}
+								} else if(strcmp(pth, ".") == 0) {
+								} else {
+									char* tmp = p;
+									p = cm_strcat3(tmp, pth, cbuf);
+									free(tmp);
+								}
+
+								incr = j + 1;
+								if(oldc == 0) break;
+							}
+						}
+						free(req->path);
+						req->path = p;
+
+						cm_log("HTTP", "Request: %s %s %s", req->method, req->path, req->version);
+
+						phase++;
+					}
+				} else if(c != '\r') {
+					if(req->version == NULL) {
+						req->version = malloc(1);
+						req->version[0] = 0;
+					}
+					cbuf[0] = c;
+					char* tmp = req->version;
+					req->version = cm_strcat(tmp, cbuf);
+					free(tmp);
+				}
+			} else if(phase == 3) {
+				if(c == '\n') {
+					nl++;
+					if(nl == 2) {
+						phase++;
+						goto getout;
+					} else {
+						if(req->headers == NULL) {
+							req->headers = malloc(sizeof(*req->headers));
+							req->headers[0] = NULL;
+						}
+						int j;
+						for(j = 0; header[j] != 0; j++) {
+							if(header[j] == ':') {
+								header[j] = 0;
+								j++;
+								for(; header[j] != 0 && (header[j] == ' ' || header[j] == '\t'); j++)
+									;
+								char* kv = header;
+								char* vv = header + j;
+
+								char** old = req->headers;
+								int k;
+								for(k = 0; old[k] != NULL; k++)
+									;
+								req->headers = malloc(sizeof(*req->headers) * (k + 3));
+								for(k = 0; old[k] != NULL; k++) req->headers[k] = old[k];
+								req->headers[k] = cm_strdup(kv);
+								req->headers[k + 1] = cm_strdup(vv);
+								req->headers[k + 2] = NULL;
+								free(old);
+
+								cm_log("HTTP", "Header: %s: %s", kv, vv);
+
+								break;
+							}
+						}
+						free(header);
+						header = malloc(1);
+						header[0] = 0;
+					}
+				} else if(c != '\r') {
+					nl = 0;
+					cbuf[0] = c;
+					char* tmp = header;
+					header = cm_strcat(tmp, cbuf);
+					free(tmp);
+				}
+			}
+		}
+	}
+getout:
+	free(header);
+	if(bad) {
+		tw_free_request(req);
+		return 1;
+	}
+	char* result = malloc(1);
+	result[0] = 0;
+	int i;
+	for(i = 0; req->path[i] != 0; i++) {
+		if(req->path[i] == '?') {
+			req->path[i] = 0;
+			req->query = cm_strdup(req->path + i + 1);
+			break;
+		}
+	}
+	for(i = 0; req->path[i] != 0; i++) {
+		if(req->path[i] == '%') {
+			if(req->path[i + 1] == 0) continue;
+			cbuf[0] = cm_hex(req->path + i + 1, 2);
+			if(cbuf[0] != '\\') {
+				char* tmp = result;
+				result = cm_strcat(tmp, cbuf);
+				free(tmp);
+			}
+			i += 2;
+		} else if(req->path[i] != '\\') {
+			cbuf[0] = req->path[i];
+			char* tmp = result;
+			result = cm_strcat(tmp, cbuf);
+			free(tmp);
+		}
+	}
+	free(req->path);
+	req->path = result;
+
+	int incr = 0;
+	char* p = malloc(1);
+	p[0] = 0;
+	int j;
+	for(j = 0;; j++) {
+		if(req->path[j] == '/' || req->path[j] == 0) {
+			char oldc = req->path[j];
+			cbuf[0] = oldc;
+			req->path[j] = 0;
+
+			char* pth = req->path + incr;
+
+			if(strcmp(pth, "..") == 0) {
+				int k;
+				if(p[strlen(p) - 1] == '/') p[strlen(p) - 1] = 0;
+				for(k = strlen(p) - 1; k >= 0; k--) {
+					if(p[k] == '/') {
+						p[k + 1] = 0;
+						break;
+					}
+				}
+				if(strlen(p) == 0) {
+					free(p);
+					p = cm_strdup("/");
+				}
+			} else if(strcmp(pth, ".") == 0) {
+			} else {
+				char* tmp = p;
+				p = cm_strcat3(tmp, pth, cbuf);
+				free(tmp);
+			}
+
+			incr = j + 1;
+			if(oldc == 0) break;
+		}
+	}
+	free(req->path);
+	req->path = p;
+	return 0;
+}
Index: /tags/2.02/Server/install.nsi
===================================================================
--- /tags/2.02/Server/install.nsi	(revision 191)
+++ /tags/2.02/Server/install.nsi	(revision 191)
@@ -0,0 +1,127 @@
+# $Id$
+
+VIFileVersion "1.0.0.0"
+VIProductVersion "1.0.0.0"
+VIAddVersionKey "FileVersion" "${VERSION}"
+VIAddVersionKey "LegalCopyright" "Public domain. Original by Nishi"
+VIAddVersionKey "FileDescription" "Tewi HTTPd Installer"
+
+LoadLanguageFile "${NSISDIR}\Contrib\Language files\Japanese.nlf"
+LoadLanguageFile "${NSISDIR}\Contrib\Language files\English.nlf"
+
+Name "Tewi HTTPd"
+OutFile "install.exe"
+InstallDir "C:\Tewi"
+Icon "../Binary/tewi-install.ico"
+LicenseData ../LICENSE
+
+LangString EXEC_ONLY ${LANG_ENGLISH} "Install the executable only"
+LangString EXEC_ONLY ${LANG_JAPANESE} "実行機能のみをインストールする"
+LangString SERV_TOO ${LANG_ENGLISH} "Install the service too (NT-only)"
+LangString SERV_TOO ${LANG_JAPANESE} "サービスもインストールする(NTのみ)"
+LangString WAIT_STOP ${LANG_ENGLISH} "Waiting for 1 second so service can stop"
+LangString WAIT_STOP ${LANG_JAPANESE} "サービスが止まるのを待っています"
+
+!include "LogicLib.nsh"
+!include "Sections.nsh"
+
+Page license
+Page components
+Page instfiles
+UninstPage uninstConfirm
+UninstPage instfiles
+Section
+	CreateDirectory "$INSTDIR\etc"
+	CreateDirectory "$INSTDIR\www"
+	CreateDirectory "$INSTDIR\www\icons"
+	CreateDirectory "$INSTDIR\modules"
+	CreateDirectory "$INSTDIR\bin"
+	SetOutPath "$INSTDIR"
+	File /oname=LICENSE.txt "../LICENSE"
+	SetOutPath "$INSTDIR\modules"
+	File "../Module/*.dll"
+	SetOutPath "$INSTDIR\etc"
+	SetOverWrite off
+	File /oname=tewi.conf "../generated.conf"
+	SetOutPath "$INSTDIR\www"
+	File /oname=index.html "../itworks.html"
+	File /oname=pbtewi.gif "../Binary/pbtewi.gif"
+	SetOutPath "$INSTDIR\www\icons"
+	File "../Icons/*.png"
+	SetOverWrite on
+
+	CreateDirectory "$SMPROGRAMS\Tewi HTTPd"
+	CreateShortcut "$SMPROGRAMS\Tewi HTTPd\License.lnk" "$INSTDIR\LICENSE.txt" ""
+	CreateShortcut "$SMPROGRAMS\Tewi HTTPd\Start Tewi HTTPd.lnk" "$INSTDIR\bin\tewi.exe" ""
+	CreateShortcut "$SMPROGRAMS\Tewi HTTPd\Start Tewi HTTPd (verbose).lnk" "$INSTDIR\bin\tewi.exe" "-v"
+	CreateShortcut "$SMPROGRAMS\Tewi HTTPd\Uninstall Tewi HTTPd.lnk" "$INSTDIR\uninstall.exe" ""
+
+	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tewi HTTPd" "DisplayName" "Tewi HTTPd"
+	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tewi HTTPd" "UninstallString" '"$INSTDIR\uninstall.exe"'
+
+	WriteUninstaller "$INSTDIR\uninstall.exe"
+SectionEnd
+
+Section "$(EXEC_ONLY)" SEL_EXEC
+	SetOutPath "$INSTDIR\bin"
+	File "../tewi.exe"
+	WriteINIStr $INSTDIR\install.ini uninstall service false
+SectionEnd
+
+Section /o "$(SERV_TOO)" SEL_SERVICE
+	WriteINIStr $INSTDIR\install.ini uninstall service true
+	FileOpen $9 $INSTDIR\install.bat w
+	FileWrite $9 '"$SYSDIR\sc.exe" stop "TewiHTTPd"$\r$\n'
+	FileClose $9
+	nsExec::Exec '"$INSTDIR\install.bat"'
+	Pop $0
+	DetailPrint "$(WAIT_STOP)"
+	Sleep 1000
+	CreateDirectory "$INSTDIR\logs"
+	SetOutPath "$INSTDIR\bin"
+	File "../tewi.exe"
+	File /oname=tewisrv.exe "../tewi-service.exe"
+	FileOpen $9 $INSTDIR\install.bat w
+	FileWrite $9 '"$SYSDIR\sc.exe" delete "TewiHTTPd"$\r$\n'
+	FileWrite $9 '"$SYSDIR\sc.exe" create "TewiHTTPd" DisplayName= "Tewi HTTPd" binpath= "$INSTDIR\bin\tewisrv.exe" start= "auto"$\r$\n'
+	FileWrite $9 '"$SYSDIR\sc.exe" start "TewiHTTPd"$\r$\n'
+	FileClose $9
+	nsExec::Exec '"$INSTDIR\install.bat"'
+	Pop $0
+	Delete $INSTDIR\install.bat
+SectionEnd
+
+Function .onInit
+	StrCpy $1 ${SEL_EXEC}
+FunctionEnd
+
+Function .onSelChange
+	!insertmacro StartRadioButtons $1
+	!insertmacro RadioButton ${SEL_EXEC}
+	!insertmacro RadioButton ${SEL_SERVICE}
+	!insertmacro EndRadioButtons
+FunctionEnd
+
+Section "Uninstall"
+	ReadINIStr $8 $INSTDIR\install.ini uninstall service
+	${If} $8 == "true"
+		FileOpen $9 $INSTDIR\uninstall.bat w
+		FileWrite $9 '"$SYSDIR\sc.exe" stop "TewiHTTPd"$\r$\n'
+		FileClose $9
+		nsExec::Exec '"$INSTDIR\uninstall.bat"'
+		Pop $0
+		FileOpen $9 $INSTDIR\uninstall.bat w
+		DetailPrint "$(WAIT_STOP)"
+		Sleep 1000
+		FileWrite $9 '"$SYSDIR\sc.exe" delete "TewiHTTPd"$\r$\n'
+		FileClose $9
+		nsExec::Exec '"$INSTDIR\uninstall.bat"'
+		Pop $0
+		Delete $INSTDIR\uninstall.bat
+	${EndIf}
+
+	RMDir /r "$INSTDIR"
+	RMDir /r "$SMPROGRAMS\Tewi HTTPd"
+
+	DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Tewi HTTPd"
+SectionEnd
Index: /tags/2.02/Server/main.c
===================================================================
--- /tags/2.02/Server/main.c	(revision 191)
+++ /tags/2.02/Server/main.c	(revision 191)
@@ -0,0 +1,674 @@
+/* $Id$ */
+
+#define SOURCE
+
+#include "../config.h"
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#ifndef NO_SSL
+#include <openssl/opensslv.h>
+#endif
+
+#include <cm_log.h>
+#include <cm_string.h>
+
+#include "tw_config.h"
+#include "tw_server.h"
+#include "tw_version.h"
+
+#ifdef __MINGW32__
+#include <windows.h>
+#endif
+
+#ifdef _PSP
+#include <pspkernel.h>
+#include <pspdebug.h>
+#include <pspsdk.h>
+#include <psputility.h>
+#include <pspctrl.h>
+#include <pspnet_apctl.h>
+#include <pspwlan.h>
+
+PSP_MODULE_INFO("Tewi HTTPd", PSP_MODULE_USER, 1, 1);
+PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
+
+#define printf(...) pspDebugScreenPrintf(__VA_ARGS__)
+#define STDERR_LOG(...) pspDebugScreenPrintf(__VA_ARGS__)
+#elif defined(__ps2sdk__)
+#include <debug.h>
+#include <sifrpc.h>
+
+#define printf(...) scr_printf(__VA_ARGS__)
+#define STDERR_LOG(...) scr_printf(__VA_ARGS__)
+#elif defined(__PPU__)
+#include <rsx/gcm_sys.h>
+#include <rsx/rsx.h>
+#include <sysutil/video.h>
+#include <malloc.h>
+#include <sys/thread.h>
+#include <stdarg.h>
+
+#define printf(...) tt_printf(__VA_ARGS__)
+#define STDERR_LOG(...) tt_printf(__VA_ARGS__)
+#else
+#define STDERR_LOG(...) fprintf(stderr, __VA_ARGS__)
+#endif
+
+extern bool cm_do_log;
+extern struct tw_config config;
+extern FILE* logfile;
+
+char tw_server[2048];
+
+int startup(int argc, char** argv);
+
+#ifdef SERVICE
+SERVICE_STATUS status;
+SERVICE_STATUS_HANDLE status_handle;
+
+void WINAPI servhandler(DWORD control) {
+	switch(control) {
+	case SERVICE_CONTROL_STOP:
+	case SERVICE_CONTROL_SHUTDOWN:
+		status.dwCurrentState = SERVICE_STOP_PENDING;
+		break;
+	}
+	SetServiceStatus(status_handle, &status);
+}
+
+void WINAPI servmain(DWORD argc, LPSTR* argv) {
+	logfile = fopen(PREFIX "/logs/tewi.log", "a");
+	if(logfile == NULL) logfile = stderr;
+	status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+	status.dwCurrentState = SERVICE_START_PENDING;
+	status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+	status.dwWin32ExitCode = NO_ERROR;
+	status.dwServiceSpecificExitCode = 0;
+	status.dwCheckPoint = 0;
+	status.dwWaitHint = 0;
+	status_handle = RegisterServiceCtrlHandler("Tewi HTTPd", servhandler);
+	if(status_handle == NULL) return;
+	if(SetServiceStatus(status_handle, &status) == 0) return;
+	int st = startup(argc, argv);
+	if(st != -1) {
+		status.dwWin32ExitCode = NO_ERROR;
+		status.dwServiceSpecificExitCode = st;
+		status.dwCurrentState = SERVICE_STOPPED;
+		SetServiceStatus(status_handle, &status);
+		return;
+	}
+	status.dwCurrentState = SERVICE_RUNNING;
+	SetServiceStatus(status_handle, &status);
+	tw_server_loop();
+	status.dwCurrentState = SERVICE_STOPPED;
+	SetServiceStatus(status_handle, &status);
+}
+#endif
+
+int running = 1;
+#ifdef _PSP
+
+int psp_exit_callback(int arg1, int arg2, void* arg3) { running = 0; }
+
+int psp_callback_thread(SceSize args, void* argp) {
+	int cid;
+	cid = sceKernelCreateCallback("Exit Call Back", psp_exit_callback, NULL);
+	sceKernelRegisterExitCallback(cid);
+	sceKernelSleepThreadCB();
+	return 0;
+}
+#endif
+
+#ifdef __PPU__
+uint32_t depth_pitch;
+uint32_t depth_offset;
+uint32_t* depth_buffer;
+
+#define CB_SIZE 0x100000
+#define HOST_SIZE (32 * 1024 * 1024)
+
+struct rsx_buffer {
+	int width, height, id;
+	uint32_t* ptr;
+	uint32_t offset;
+};
+
+void wait_rsx(gcmContextData* ctx, uint32_t label) {
+	rsxSetWriteBackendLabel(ctx, GCM_INDEX_TYPE_32B, label);
+
+	rsxFlushBuffer(ctx);
+
+	while(*(uint32_t*)gcmGetLabelAddress(GCM_INDEX_TYPE_32B) != label) usleep(50);
+
+	label++;
+}
+
+void wait_rsx_until_idle(gcmContextData* ctx) {
+	uint32_t label = 1;
+	rsxSetWriteBackendLabel(ctx, GCM_INDEX_TYPE_32B, label);
+	rsxSetWaitLabel(ctx, GCM_INDEX_TYPE_32B, label);
+	label++;
+	wait_rsx(ctx, label);
+}
+
+void get_resolution(int* width, int* height) {
+	videoState state;
+	videoResolution res;
+	if(videoGetState(0, 0, &state) != 0) {
+		return;
+	}
+
+	if(state.state != 0) {
+		return;
+	}
+
+	if(videoGetResolution(state.displayMode.resolution, &res) != 0) {
+		return;
+	}
+	*width = res.width;
+	*height = res.height;
+}
+
+void make_buffer(struct rsx_buffer* buffer, int id) {
+	int w, h;
+	get_resolution(&w, &h);
+
+	buffer->ptr = (uint32_t*)rsxMemalign(64, 4 * w * h);
+	if(buffer->ptr == NULL) return;
+
+	if(rsxAddressToOffset(buffer->ptr, &buffer->offset) != 0) return;
+
+	if(gcmSetDisplayBuffer(id, buffer->offset, 4 * w, w, h) != 0) return;
+	buffer->width = w;
+	buffer->height = h;
+	buffer->id = id;
+}
+
+gcmContextData* init_screen(void) {
+	void* host = memalign(1024 * 1024, HOST_SIZE);
+	gcmContextData* ctx = NULL;
+	videoState state;
+	videoConfiguration vconfig;
+	videoResolution res;
+	rsxInit(&ctx, CB_SIZE, HOST_SIZE, host);
+	if(ctx == NULL) {
+		free(host);
+		return NULL;
+	}
+
+	if(videoGetState(0, 0, &state) != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+
+	if(state.state != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+
+	if(videoGetResolution(state.displayMode.resolution, &res) != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+
+	memset(&vconfig, 0, sizeof(vconfig));
+	vconfig.resolution = state.displayMode.resolution;
+	vconfig.format = VIDEO_BUFFER_FORMAT_XRGB;
+	vconfig.pitch = res.width * 4;
+	vconfig.aspect = state.displayMode.aspect;
+
+	wait_rsx_until_idle(ctx);
+
+	if(videoConfigure(0, &vconfig, NULL, 0) != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+
+	if(videoGetState(0, 0, &state) != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+	gcmSetFlipMode(GCM_FLIP_VSYNC);
+
+	depth_pitch = res.width * 4;
+	depth_buffer = (uint32_t*)rsxMemalign(64, (res.height * depth_pitch) * 2);
+	rsxAddressToOffset(depth_buffer, &depth_offset);
+
+	gcmResetFlipStatus();
+
+	return ctx;
+}
+
+void set_render_target(gcmContextData* context, struct rsx_buffer* buffer) {
+	gcmSurface sf;
+
+	sf.colorFormat = GCM_SURFACE_X8R8G8B8;
+	sf.colorTarget = GCM_SURFACE_TARGET_0;
+	sf.colorLocation[0] = GCM_LOCATION_RSX;
+	sf.colorOffset[0] = buffer->offset;
+	sf.colorPitch[0] = depth_pitch;
+
+	sf.colorLocation[1] = GCM_LOCATION_RSX;
+	sf.colorLocation[2] = GCM_LOCATION_RSX;
+	sf.colorLocation[3] = GCM_LOCATION_RSX;
+	sf.colorOffset[1] = 0;
+	sf.colorOffset[2] = 0;
+	sf.colorOffset[3] = 0;
+	sf.colorPitch[1] = 64;
+	sf.colorPitch[2] = 64;
+	sf.colorPitch[3] = 64;
+
+	sf.depthFormat = GCM_SURFACE_ZETA_Z16;
+	sf.depthLocation = GCM_LOCATION_RSX;
+	sf.depthOffset = depth_offset;
+	sf.depthPitch = depth_pitch;
+
+	sf.type = GCM_TEXTURE_LINEAR;
+	sf.antiAlias = GCM_SURFACE_CENTER_1;
+
+	sf.width = buffer->width;
+	sf.height = buffer->height;
+	sf.x = 0;
+	sf.y = 0;
+
+	rsxSetSurface(context, &sf);
+}
+
+void wait_flip(void) {
+	while(gcmGetFlipStatus() != 0) usleep(200);
+	gcmResetFlipStatus();
+}
+
+void flip(gcmContextData* ctx, uint32_t buffer) {
+	if(gcmSetFlip(ctx, buffer) == 0) {
+		rsxFlushBuffer(ctx);
+		gcmSetWaitFlip(ctx);
+	}
+}
+
+uint8_t* tvram;
+
+extern uint8_t font[];
+
+int tt_x = 0;
+int tt_y = 0;
+int tt_width;
+int tt_height;
+
+void tt_putstr(const char* str) {
+	int i;
+	for(i = 0; str[i] != 0; i++){
+		tvram[tt_y * tt_width + tt_x] = str[i];
+		if(str[i] == '\n'){
+			tt_x = 0;
+			tt_y++;
+		}else{
+			tt_x++;
+			if(tt_x == tt_width){
+				tt_x = 0;
+				tt_y++;
+			}
+		}
+		if(tt_y == tt_height){
+			tt_y--;
+			int x, y;
+			for(y = 0; y < tt_height - 1; y++){
+				for(x = 0; x < tt_width; x++){
+					tvram[y * tt_width + x] = tvram[(y + 1) * tt_width + x];
+				}
+			}
+			for(x = 0; x < tt_width; x++){
+				tvram[(tt_height - 1) * tt_width + x] = 0;
+			}
+		}
+	}
+}
+
+void tt_putchar(struct rsx_buffer* buffer, int x, int y, uint8_t c){
+	int i, j;
+	if(c < 0x20) c = 0x20;
+	if(c >= 0x7f) c = 0x20;
+	for(i = 0; i < 7; i++){
+		uint8_t l = font[(c - 0x20) * 8 + i];
+		for(j = 0; j < 5; j++){
+			uint32_t c = 0;
+			if(l & (1 << 7)){
+				c = 0xffffff;
+			}
+			l = l << 1;
+			buffer->ptr[(y * 8 + i) * buffer->width + x * 6 + j] = c;
+		}
+	}
+}
+
+void draw(struct rsx_buffer* buffer, int current) {
+	int i, j, c;
+	for(i = 0; i < buffer->height / 8; i++) {
+		for(j = 0; j < buffer->width / 6; j++) {
+			uint8_t c = tvram[i * (buffer->width / 6) + j];
+			tt_putchar(buffer, j, i, c);
+		}
+	}
+}
+
+#define BUFFERS 2
+gcmContextData* ctx;
+struct rsx_buffer buffers[BUFFERS];
+
+void text_thread(void* arg) {
+	int current = 0;
+	while(1) {
+		wait_flip();
+		draw(&buffers[current], current);
+		flip(ctx, buffers[current].id);
+		current++;
+		if(current >= BUFFERS) current = 0;
+	}
+}
+
+void tt_printf(const char* tmpl, ...) {
+	va_list va;
+	va_start(va, tmpl);
+	int i;
+	char cbuf[2];
+	cbuf[1] = 0;
+	char* log = cm_strdup("");
+	for(i = 0; tmpl[i] != 0; i++) {
+		if(tmpl[i] == '%') {
+			i++;
+			if(tmpl[i] == 's'){
+				char* tmp = log;
+				log = cm_strcat(tmp, va_arg(va, char*));
+				free(tmp);
+			}else if(tmpl[i] == 'd'){
+				char buf[513];
+				sprintf(buf, "%d", va_arg(va, int));
+				char* tmp = log;
+				log = cm_strcat(tmp, buf);
+				free(tmp);
+			}else if(tmpl[i] == '%'){
+				char* tmp = log;
+				log = cm_strcat(tmp, "%");
+				free(tmp);
+			}
+		} else {
+			cbuf[0] = tmpl[i];
+			char* tmp = log;
+			log = cm_strcat(tmp, cbuf);
+			free(tmp);
+		}
+	}
+	va_end(va);
+	tt_putstr(log);
+}
+
+#endif
+
+int main(int argc, char** argv) {
+	logfile = stderr;
+#ifdef SERVICE
+	SERVICE_TABLE_ENTRY table[] = {{"Tewi HTTPd", servmain}, {NULL, NULL}};
+	StartServiceCtrlDispatcher(table);
+#else
+#ifdef _PSP
+	pspDebugScreenInit();
+	pspDebugScreenSetXY(0, 0);
+	printf("PSP Bootstrap, Tewi/%s\n", tw_get_version());
+	int thid = sceKernelCreateThread("update_thread", psp_callback_thread, 0x11, 0xfa0, 0, NULL);
+	if(thid >= 0) {
+		sceKernelStartThread(thid, 0, NULL);
+	} else {
+		printf("Failed to start thread\n");
+		while(running) sceKernelDelayThread(50 * 1000);
+		sceKernelExitGame();
+	}
+	sceCtrlSetSamplingCycle(0);
+	sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);
+	sceUtilityLoadNetModule(PSP_NET_MODULE_COMMON);
+	sceUtilityLoadNetModule(PSP_NET_MODULE_INET);
+	if(pspSdkInetInit()) {
+		printf("Could not init the network\n");
+		while(running) sceKernelDelayThread(50 * 1000);
+		sceKernelExitGame();
+	} else {
+		printf("Network initialization successful\n");
+	}
+	if(sceWlanGetSwitchState() != 1) {
+		printf("Turn the Wi-Fi switch on\n");
+		while(sceWlanGetSwitchState() != 1) {
+			sceKernelDelayThread(1000 * 1000);
+		}
+	} else {
+		printf("Wi-Fi is turned on\n");
+	}
+	int i;
+	int choice[100];
+	int incr = 0;
+	int last = 0;
+	int cur = 0;
+	for(i = 1; i < 100; i++) {
+		choice[i - 1] = 0;
+		netData name;
+		netData data;
+		if(sceUtilityCheckNetParam(i) != 0) continue;
+		choice[incr++] = i;
+		pspDebugScreenSetXY(0, 1 + 3 + incr - 1);
+		if(incr == 1) printf("> ");
+		pspDebugScreenSetXY(2, 1 + 3 + incr - 1);
+		sceUtilityGetNetParam(i, 0, &name);
+		sceUtilityGetNetParam(i, 1, &data);
+		printf("SSID=%s", data.asString);
+		sceUtilityGetNetParam(i, 4, &data);
+		if(data.asString[0]) {
+			sceUtilityGetNetParam(i, 5, &data);
+			printf(" IPADDR=%s\n", data.asString);
+		} else {
+			printf(" DHCP\n");
+		}
+	}
+	int press = 0;
+	while(1) {
+		if(!running) {
+			sceKernelExitGame();
+		}
+		SceCtrlData c;
+		sceCtrlReadBufferPositive(&c, 1);
+		press = 0;
+		if(c.Buttons & PSP_CTRL_DOWN) {
+			if(cur < incr - 1) {
+				cur++;
+			}
+			press = 1;
+		} else if(c.Buttons & PSP_CTRL_UP) {
+			if(cur > 0) {
+				cur--;
+			}
+			press = -1;
+		} else if(c.Buttons & PSP_CTRL_START) {
+			break;
+		}
+		if(last != cur) {
+			pspDebugScreenSetXY(0, 1 + 3 + last);
+			printf("  ");
+			pspDebugScreenSetXY(0, 1 + 3 + cur);
+			printf("> ");
+			last = cur;
+		}
+		if(press != 0) {
+			while(1) {
+				SceCtrlData c;
+				sceCtrlReadBufferPositive(&c, 1);
+				if(press == 1) {
+					if(!(c.Buttons & PSP_CTRL_DOWN)) break;
+				} else if(press == -1) {
+					if(!(c.Buttons & PSP_CTRL_UP)) break;
+				}
+			}
+		}
+	}
+	pspDebugScreenSetXY(0, 1 + 3 + incr + 1);
+	int err = sceNetApctlConnect(choice[cur]);
+	if(err != 0) {
+		printf("Apctl initialization failure\n");
+		while(running) sceKernelDelayThread(50 * 1000);
+		sceKernelExitGame();
+	} else {
+		printf("Apctl initialization successful\n");
+	}
+	printf("Apctl connecting\n");
+	while(1) {
+		int state;
+		err = sceNetApctlGetState(&state);
+		if(err != 0) {
+			printf("Apctl getting status failure\n");
+			while(running) sceKernelDelayThread(50 * 1000);
+			sceKernelExitGame();
+		}
+		if(state == 4) {
+			break;
+		}
+		sceKernelDelayThread(50 * 1000);
+	}
+	union SceNetApctlInfo info;
+	if(sceNetApctlGetInfo(8, &info) != 0) {
+		printf("Got an unknown IP\n");
+		while(running) sceKernelDelayThread(50 * 1000);
+		sceKernelExitGame();
+	}
+	printf("Connected, My IP is %s\n", info.ip);
+#elif defined(__PPU__)
+	int i;
+	ctx = init_screen();
+	int w, h;
+	get_resolution(&w, &h);
+	tt_width = w / 6;
+	tt_height = h / 8;
+	tvram = malloc((w / 6) * (h / 8));
+	for(i = 0; i < BUFFERS; i++) make_buffer(&buffers[i], i);
+	flip(ctx, BUFFERS - 1);
+	sys_ppu_thread_t id;
+	sysThreadCreate(&id, text_thread, NULL, 1500, 0x1000, THREAD_JOINABLE, "TextThread");
+	printf("PS3 Bootstrap, Tewi/%s\n", tw_get_version());
+	netInitialize();
+#elif defined(__ps2sdk__)
+	SifInitRpc(0);
+	init_scr();
+	scr_printf("PS2 Bootstrap, Tewi/%s\n", tw_get_version());
+	while(1)
+		;
+#endif
+	int st = startup(argc, argv);
+	if(st != -1) {
+#ifdef _PSP
+		printf("Error code %d\n", st);
+		while(running) sceKernelDelayThread(50 * 1000);
+		sceKernelExitGame();
+#else
+#ifdef __PPU__
+		printf("Error code %d\n", st);
+		while(1)
+			;
+#endif
+		return st;
+#endif
+	}
+	tw_server_loop();
+#endif
+#ifdef _PSP
+	sceKernelExitGame();
+#endif
+	return 0;
+}
+
+int startup(int argc, char** argv) {
+	int i;
+	const char* confpath = PREFIX "/etc/tewi.conf";
+	if(argv != NULL) {
+		for(i = 1; i < argc; i++) {
+			if(argv[i][0] == '-') {
+				if(strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) {
+					if(!cm_do_log) {
+						cm_do_log = true;
+#ifndef NO_SSL
+						cm_log("", "This is Tewi HTTPd, version %s, using %s", tw_get_version(), OPENSSL_VERSION_TEXT);
+#else
+						cm_log("", "This is Tewi HTTPd, version %s", tw_get_version());
+#endif
+					} else {
+						cm_do_log = true;
+					}
+				} else if(strcmp(argv[i], "--config") == 0 || strcmp(argv[i], "-C") == 0) {
+					i++;
+					if(argv[i] == NULL) {
+						STDERR_LOG("Missing argument\n");
+						return 1;
+					}
+					confpath = argv[i];
+#ifndef _PSP
+				} else if(strcmp(argv[i], "--logfile") == 0 || strcmp(argv[i], "-l") == 0) {
+					i++;
+					if(argv[i] == NULL) {
+						STDERR_LOG("Missing argument\n");
+						return 1;
+					}
+					if(logfile != NULL && logfile != stderr) {
+						fclose(logfile);
+					}
+					logfile = fopen(argv[i], "a");
+					if(logfile == NULL) {
+						STDERR_LOG("Failed to open logfile\n");
+						return 1;
+					}
+#endif
+				} else if(strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-V") == 0) {
+					printf("Tewi HTTPd Tewi/%s\n", tw_get_version());
+					printf("Under public domain.\n");
+					printf("Original by 2024 Nishi\n");
+					printf("\n");
+					printf("Usage: %s [--config|-C config] [--verbose|-v] [--version|-V]\n", argv[0]);
+					printf("--config  | -C config      : Specify config\n");
+#ifndef _PSP
+					printf("--logfile | -l logfile     : Specify logfile\n");
+#endif
+					printf("--verbose | -v             : Verbose mode\n");
+					printf("--version | -V             : Version information\n");
+					return 0;
+				} else {
+					STDERR_LOG("Unknown option: %s\n", argv[i]);
+					return 1;
+				}
+			}
+		}
+	}
+	tw_config_init();
+	if(tw_config_read(confpath) != 0) {
+		STDERR_LOG("Could not read the config\n");
+		return 1;
+	}
+	if(tw_server_init() != 0) {
+		STDERR_LOG("Could not initialize the server\n");
+		return 1;
+	}
+	sprintf(tw_server, "Tewi/%s (%s)%s", tw_get_version(), tw_get_platform(), config.extension == NULL ? "" : config.extension);
+	char* r = cm_strcat(tw_server, " running...");
+	cm_force_log(r);
+	free(r);
+#ifndef __MINGW32__
+	signal(SIGCHLD, SIG_IGN);
+	signal(SIGPIPE, SIG_IGN);
+#else
+	SetConsoleTitle(tw_server);
+#endif
+	return -1;
+}
Index: /tags/2.02/Server/module.c
===================================================================
--- /tags/2.02/Server/module.c	(revision 191)
+++ /tags/2.02/Server/module.c	(revision 191)
@@ -0,0 +1,111 @@
+/* $Id$ */
+
+#define SOURCE
+
+#include "tw_module.h"
+
+#include "tw_config.h"
+
+#include <cm_string.h>
+#include <cm_log.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+extern struct tw_config config;
+
+#if defined(_PSP) || defined(__PPU__) || defined(__ps2sdk__)
+void* tw_module_load(const char* path) { return NULL; }
+
+void* tw_module_symbol(void* mod, const char* sym) { return NULL; }
+
+int tw_module_init(void* mod) { return 1; }
+
+#else
+
+#ifdef __MINGW32__
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#endif
+
+void* tw_module_load(const char* path) {
+	char* p = getcwd(NULL, 0);
+	chdir(config.server_root);
+	void* lib;
+#ifdef __MINGW32__
+	lib = LoadLibraryA(path);
+#else
+	lib = dlopen(path, RTLD_LAZY);
+#endif
+	if(lib == NULL) {
+		cm_log("Module", "Could not load %s", path);
+	}
+	chdir(p);
+	free(p);
+	return lib;
+}
+
+void* tw_module_symbol(void* mod, const char* sym) {
+#ifdef __MINGW32__
+	return GetProcAddress(mod, sym);
+#else
+	return dlsym(mod, sym);
+#endif
+}
+
+int tw_module_init(void* mod) {
+	tw_mod_init_t mod_init = (tw_mod_init_t)tw_module_symbol(mod, "mod_init");
+	if(mod_init == NULL) {
+		cm_log("Module", "Could not init a module");
+		return 1;
+	} else {
+		struct tw_tool tools;
+		tw_init_tools(&tools);
+		return mod_init(&config, &tools);
+	}
+}
+#endif
+
+void tw_add_version(const char* string) {
+	if(config.extension == NULL) {
+		config.extension = cm_strcat(" ", string);
+	} else {
+		char* tmp = config.extension;
+		config.extension = cm_strcat3(tmp, " ", string);
+		free(tmp);
+	}
+}
+
+void tw_add_define(const char* string) {
+	int i;
+	for(i = 0; config.defined[i] != NULL; i++) {
+		if(strcmp(config.defined[i], string) == 0) {
+			return;
+		}
+	}
+	for(i = 0; config.defined[i] != NULL; i++)
+		;
+	config.defined[i] = cm_strdup(string);
+	config.defined[i + 1] = NULL;
+}
+
+void tw_delete_define(const char* string) {
+	int i;
+	for(i = 0; config.defined[i] != NULL; i++) {
+		if(strcmp(config.defined[i], string) == 0) {
+			free(config.defined[i]);
+			for(; config.defined[i] != NULL; i++) {
+				config.defined[i] = config.defined[i + 1];
+			}
+			break;
+		}
+	}
+}
+
+void tw_init_tools(struct tw_tool* tools) {
+	tools->log = cm_log;
+	tools->add_version = tw_add_version;
+	tools->add_define = tw_add_define;
+}
Index: /tags/2.02/Server/server.c
===================================================================
--- /tags/2.02/Server/server.c	(revision 191)
+++ /tags/2.02/Server/server.c	(revision 191)
@@ -0,0 +1,995 @@
+/* $Id$ */
+
+#define SOURCE
+
+#include "../config.h"
+
+#include "tw_server.h"
+
+#ifndef NO_SSL
+#include "tw_ssl.h"
+#endif
+
+#include "tw_config.h"
+#include "tw_http.h"
+#include "tw_module.h"
+#include "tw_version.h"
+
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include <cm_string.h>
+#include <cm_log.h>
+#include <cm_dir.h>
+
+#ifdef __MINGW32__
+#ifndef NO_GETADDRINFO
+#include <ws2tcpip.h>
+#include <wspiapi.h>
+#endif
+#include <winsock2.h>
+#include <process.h>
+#include <windows.h>
+
+#include "strptime.h"
+#else
+#ifdef USE_POLL
+#ifdef __PPU__
+#include <net/poll.h>
+#else
+#include <poll.h>
+#endif
+#else
+#include <sys/select.h>
+#endif
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#ifndef __PPU__
+#include <netinet/tcp.h>
+#endif
+#ifndef NO_GETADDRINFO
+#include <netdb.h>
+#endif
+#endif
+
+#if defined(_PSP) || defined(__ps2sdk__)
+#include "strptime.h"
+#endif
+
+#ifdef __HAIKU__
+#include <OS.h>
+#endif
+
+extern struct tw_config config;
+extern char tw_server[];
+
+int sockcount = 0;
+
+SOCKADDR addresses[MAX_PORTS];
+int sockets[MAX_PORTS];
+
+#ifdef __MINGW32__
+const char* reserved_names[] = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"};
+#endif
+
+/* https://qiita.com/gyu-don/items/5a640c6d2252a860c8cd */
+int tw_wildcard_match(const char* wildcard, const char* target) {
+	const char *pw = wildcard, *pt = target;
+
+	while(1) {
+		if(*pt == 0) {
+			while(*pw == '*') pw++;
+			return *pw == 0;
+		} else if(*pw == 0) {
+			return 0;
+		} else if(*pw == '*') {
+			return *(pw + 1) == 0 || tw_wildcard_match(pw, pt + 1) || tw_wildcard_match(pw + 1, pt);
+		} else if(*pw == '?' || (*pw == *pt)) {
+			pw++;
+			pt++;
+			continue;
+		} else {
+			return 0;
+		}
+	}
+}
+
+void close_socket(int sock) {
+#if defined(__MINGW32__)
+	closesocket(sock);
+#else
+	close(sock);
+#endif
+}
+
+int tw_server_init(void) {
+	int i;
+#ifdef __MINGW32__
+	WSADATA wsa;
+	WSAStartup(MAKEWORD(2, 0), &wsa);
+#endif
+	for(i = 0; config.ports[i] != -1; i++)
+		;
+	sockcount = i;
+	for(i = 0; config.ports[i] != -1; i++) {
+#ifdef NO_IPV6
+		int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+#else
+		int sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+#endif
+#ifdef __MINGW32__
+		if(sock == INVALID_SOCKET)
+#else
+		if(sock < 0)
+#endif
+		{
+			cm_log("Server", "Socket creation failure");
+			return 1;
+		}
+		int yes = 1;
+		if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)) < 0) {
+			close_socket(sock);
+			cm_log("Server", "setsockopt failure (reuseaddr)");
+			return 1;
+		}
+#ifndef __PPU__
+		if(setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&yes, sizeof(yes)) < 0) {
+			close_socket(sock);
+			cm_log("Server", "setsockopt failure (nodelay)");
+			return 1;
+		}
+#endif
+#ifndef NO_IPV6
+		int no = 0;
+		if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&no, sizeof(no)) < 0) {
+			close_socket(sock);
+			cm_log("Server", "setsockopt failure (IPv6)");
+			return 1;
+		}
+#endif
+		memset(&addresses[i], 0, sizeof(addresses[i]));
+#ifdef NO_IPV6
+		addresses[i].sin_family = AF_INET;
+		addresses[i].sin_addr.s_addr = INADDR_ANY;
+		addresses[i].sin_port = htons(config.ports[i]);
+#else
+		addresses[i].sin6_family = AF_INET6;
+		addresses[i].sin6_addr = in6addr_any;
+		addresses[i].sin6_port = htons(config.ports[i]);
+#endif
+		if(bind(sock, (struct sockaddr*)&addresses[i], sizeof(addresses[i])) < 0) {
+			close_socket(sock);
+			cm_log("Server", "Bind failure");
+			return 1;
+		}
+		if(listen(sock, 128) < 0) {
+			close_socket(sock);
+			cm_log("Server", "Listen failure");
+			return 1;
+		}
+		sockets[i] = sock;
+	}
+	return 0;
+}
+
+size_t tw_read(SSL* ssl, int s, void* data, size_t len) {
+#ifndef NO_SSL
+	if(ssl == NULL) {
+		return recv(s, data, len, 0);
+	} else {
+		return SSL_read(ssl, data, len);
+	}
+#else
+	return recv(s, data, len, 0);
+#endif
+}
+
+size_t tw_write(SSL* ssl, int s, void* data, size_t len) {
+#ifndef NO_SSL
+	if(ssl == NULL) {
+		return send(s, data, len, 0);
+	} else {
+		return SSL_write(ssl, data, len);
+	}
+#else
+	return send(s, data, len, 0);
+#endif
+}
+
+#define ERROR_HTML \
+	"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n" \
+	"<html>\n" \
+	"	<head>\n" \
+	"		<title>%s</title>\n" \
+	"	</head>\n" \
+	"	<body>\n" \
+	"		<h1>%s</h1>\n" \
+	"		<hr>\n" \
+	"		", \
+	    address, \
+	    "\n" \
+	    "	</body>\n" \
+	    "</html>\n"
+
+void _tw_process_page(SSL* ssl, int sock, const char* status, const char* type, FILE* f, const unsigned char* doc, size_t size, char** headers, time_t mtime, time_t cmtime) {
+	char construct[512];
+	if(mtime != 0 && cmtime != 0 && mtime <= cmtime) {
+		status = "304 Not Modified";
+		type = NULL;
+		size = 0;
+		headers = NULL;
+		f = NULL;
+		doc = NULL;
+	}
+	sprintf(construct, "%llu", (unsigned long long)size);
+	tw_write(ssl, sock, "HTTP/1.1 ", 9);
+	tw_write(ssl, sock, (char*)status, strlen(status));
+	tw_write(ssl, sock, "\r\n", 2);
+	if(type != NULL) {
+		tw_write(ssl, sock, "Content-Type: ", 7 + 5 + 2);
+		tw_write(ssl, sock, (char*)type, strlen(type));
+		tw_write(ssl, sock, "\r\n", 2);
+	}
+	tw_write(ssl, sock, "Server: ", 6 + 2);
+	tw_write(ssl, sock, tw_server, strlen(tw_server));
+	tw_write(ssl, sock, "\r\n", 2);
+	if(size != 0) {
+		tw_write(ssl, sock, "Content-Length: ", 7 + 7 + 2);
+		tw_write(ssl, sock, construct, strlen(construct));
+		tw_write(ssl, sock, "\r\n", 2);
+		if(mtime != 0) {
+			struct tm* tm = gmtime(&mtime);
+			char date[513];
+			strftime(date, 512, "%a, %d %b %Y %H:%M:%S GMT", tm);
+			tw_write(ssl, sock, "Last-Modified: ", 5 + 8 + 2);
+			tw_write(ssl, sock, date, strlen(date));
+			tw_write(ssl, sock, "\r\n", 2);
+		}
+	}
+	int i;
+	if(headers != NULL) {
+		for(i = 0; headers[i] != NULL; i += 2) {
+			tw_write(ssl, sock, headers[i], strlen(headers[i]));
+			tw_write(ssl, sock, ": ", 2);
+			tw_write(ssl, sock, headers[i + 1], strlen(headers[i + 1]));
+			tw_write(ssl, sock, "\r\n", 2);
+		}
+	}
+	tw_write(ssl, sock, "\r\n", 2);
+	if(doc == NULL && f == NULL) return;
+	size_t incr = 0;
+	while(1) {
+		if(f != NULL) {
+			char buffer[128];
+			fread(buffer, size < 128 ? size : 128, 1, f);
+			tw_write(ssl, sock, buffer, size < 128 ? size : 128);
+		} else {
+			tw_write(ssl, sock, (unsigned char*)doc + incr, size < 128 ? size : 128);
+		}
+		incr += 128;
+		if(size <= 128) break;
+		size -= 128;
+	}
+}
+
+void tw_process_page(SSL* ssl, int sock, const char* status, const char* type, FILE* f, const unsigned char* doc, size_t size, time_t mtime, time_t cmtime) { _tw_process_page(ssl, sock, status, type, f, doc, size, NULL, mtime, cmtime); }
+
+const char* tw_http_status(int code) {
+	if(code == 200) {
+		return "200 OK";
+	} else if(code == 301) {
+		return "301 Moved Permanently";
+	} else if(code == 308) {
+		return "308 Permanent Redirect";
+	} else if(code == 400) {
+		return "400 Bad Request";
+	} else if(code == 401) {
+		return "401 Unauthorized";
+	} else if(code == 403) {
+		return "403 Forbidden";
+	} else if(code == 404) {
+		return "404 Not Found";
+	} else if(code == 500) {
+		return "500 Internal Server Error";
+	} else {
+		return "400 Bad Request";
+	}
+}
+
+char* tw_http_default_error(int code, char* name, int port, struct tw_config_entry* vhost) {
+	char address[1024];
+
+	if((vhost->hideport == -1 ? config.root.hideport : vhost->hideport) == 1) {
+		sprintf(address, "<address>%s Server at %s</address>", tw_server, name, port);
+	} else {
+		sprintf(address, "<address>%s Server at %s Port %d</address>", tw_server, name, port);
+	}
+
+	char* st = cm_strdup(tw_http_status(code));
+	char* st2;
+	int i;
+	for(i = 0; st[i] != 0; i++) {
+		if(st[i] == ' ') {
+			st2 = cm_strdup(st + i + 1);
+			break;
+		}
+	}
+	char* buffer = malloc(4096);
+	char* str = cm_strcat3(ERROR_HTML);
+	sprintf(buffer, str, st, st2);
+	free(str);
+	free(st);
+	return buffer;
+}
+
+void tw_http_error(SSL* ssl, int sock, int error, char* name, int port, struct tw_config_entry* vhost) {
+	char* str = tw_http_default_error(error, name, port, vhost);
+	tw_process_page(ssl, sock, tw_http_status(error), "text/html", NULL, str, strlen(str), 0, 0);
+	free(str);
+}
+
+void addstring(char** str, const char* add, ...) {
+	int i;
+	char cbuf[2];
+	cbuf[1] = 0;
+	va_list va;
+	va_start(va, add);
+	for(i = 0; add[i] != 0; i++) {
+		cbuf[0] = add[i];
+		if(add[i] == '%') {
+			i++;
+			if(add[i] == 's') {
+				char* tmp = *str;
+				*str = cm_strcat(tmp, va_arg(va, const char*));
+				free(tmp);
+			} else if(add[i] == 'h') {
+				char* h = cm_html_escape(va_arg(va, const char*));
+				char* tmp = *str;
+				*str = cm_strcat(tmp, h);
+				free(tmp);
+				free(h);
+			} else if(add[i] == 'l') {
+				char* h = cm_url_escape(va_arg(va, const char*));
+				char* tmp = *str;
+				*str = cm_strcat(tmp, h);
+				free(tmp);
+				free(h);
+			} else if(add[i] == 'd') {
+				int n = va_arg(va, int);
+				char* h = malloc(512);
+				sprintf(h, "%d", n);
+				char* tmp = *str;
+				*str = cm_strcat(tmp, h);
+				free(tmp);
+				free(h);
+			} else if(add[i] == '%') {
+				char* tmp = *str;
+				*str = cm_strcat(tmp, "%");
+				free(tmp);
+			}
+		} else {
+			char* tmp = *str;
+			*str = cm_strcat(tmp, cbuf);
+			free(tmp);
+		}
+	}
+	va_end(va);
+}
+
+char* tw_get_mime(const char* ext, struct tw_config_entry* vhost_entry) {
+	char* mime = "application/octet-stream";
+	if(ext == NULL) return mime;
+	bool set = false;
+	int i;
+	for(i = 0; i < vhost_entry->mime_count; i++) {
+		if(strcmp(vhost_entry->mimes[i].ext, "all") == 0 || (ext != NULL && tw_wildcard_match(vhost_entry->mimes[i].ext, ext))) {
+			mime = vhost_entry->mimes[i].mime;
+			set = true;
+		}
+	}
+	if(!set) {
+		for(i = 0; i < config.root.mime_count; i++) {
+			if(strcmp(config.root.mimes[i].ext, "all") == 0 || (ext != NULL && tw_wildcard_match(config.root.mimes[i].ext, ext))) {
+				mime = config.root.mimes[i].mime;
+			}
+		}
+	}
+	return mime;
+}
+
+char* tw_get_icon(const char* mime, struct tw_config_entry* vhost_entry) {
+	char* icon = "";
+	if(mime == NULL) return "";
+	bool set = false;
+	int i;
+	for(i = 0; i < vhost_entry->icon_count; i++) {
+		if(strcmp(vhost_entry->icons[i].mime, "all") == 0 || (mime != NULL && tw_wildcard_match(vhost_entry->icons[i].mime, mime))) {
+			icon = vhost_entry->icons[i].icon;
+			set = true;
+		}
+	}
+	if(!set) {
+		for(i = 0; i < config.root.icon_count; i++) {
+			if(strcmp(config.root.icons[i].mime, "all") == 0 || (mime != NULL && tw_wildcard_match(config.root.icons[i].mime, mime))) {
+				icon = config.root.icons[i].icon;
+			}
+		}
+	}
+	return icon;
+}
+
+struct pass_entry {
+	int sock;
+	int port;
+	bool ssl;
+	SOCKADDR addr;
+};
+
+#ifdef __MINGW32__
+unsigned int WINAPI tw_server_pass(void* ptr) {
+#elif defined(__HAIKU__)
+int32_t tw_server_pass(void* ptr) {
+#elif defined(_PSP) || defined(__PPU__)
+int tw_server_pass(void* ptr) {
+#endif
+#if defined(__HAIKU__) || defined(__MINGW32__) || defined(_PSP) || defined(__PPU__)
+	int sock = ((struct pass_entry*)ptr)->sock;
+	bool ssl = ((struct pass_entry*)ptr)->ssl;
+	int port = ((struct pass_entry*)ptr)->port;
+	SOCKADDR addr = ((struct pass_entry*)ptr)->addr;
+	free(ptr);
+#else
+	void tw_server_pass(int sock, bool ssl, int port, SOCKADDR addr) {
+#endif
+	char* name = config.hostname;
+
+#ifndef NO_SSL
+	SSL_CTX* ctx = NULL;
+	SSL* s = NULL;
+	bool sslworks = false;
+	if(ssl) {
+		ctx = tw_create_ssl_ctx(port);
+		s = SSL_new(ctx);
+		SSL_set_fd(s, sock);
+		if(SSL_accept(s) <= 0) goto cleanup;
+		sslworks = true;
+	}
+#else
+		void* s = NULL;
+#endif
+
+	char address[513];
+	address[0] = 0;
+#ifndef NO_GETADDRINFO
+	struct sockaddr* sa = (struct sockaddr*)&addr;
+	getnameinfo(sa, sizeof(addr), address, 512, NULL, 0, NI_NUMERICHOST);
+#endif
+
+	struct tw_http_request req;
+	struct tw_http_response res;
+	struct tw_tool tools;
+	res._processed = false;
+	tw_init_tools(&tools);
+	int ret = tw_http_parse(s, sock, &req);
+	if(ret == 0) {
+		char date[513];
+		time_t t = time(NULL);
+		struct tm* tm = localtime(&t);
+		strftime(date, 512, "%a, %d %b %Y %H:%M:%S %Z", tm);
+
+		char* useragent = cm_strdup("");
+
+		int i;
+		for(i = 0; req.headers[i] != NULL; i += 2) {
+			if(cm_strcaseequ(req.headers[i], "User-Agent")) {
+				free(useragent);
+				useragent = cm_strdup(req.headers[i + 1]);
+			}
+		}
+
+		char* tmp = cm_strcat3(address, " - [", date);
+		char* tmp2 = cm_strcat3(tmp, "] \"", req.method);
+		char* tmp3 = cm_strcat3(tmp2, " ", req.path);
+		char* tmp4 = cm_strcat3(tmp3, " ", req.version);
+		char* tmp5 = cm_strcat3(tmp4, "\" \"", useragent);
+		char* log = cm_strcat(tmp5, "\"");
+		free(tmp);
+		free(tmp2);
+		free(tmp3);
+		free(tmp4);
+		free(tmp5);
+		free(useragent);
+		cm_force_log(log);
+		free(log);
+
+		char* vhost = cm_strdup(config.hostname);
+		time_t cmtime = 0;
+		if(req.headers != NULL) {
+			for(i = 0; req.headers[i] != NULL; i += 2) {
+				if(cm_strcaseequ(req.headers[i], "Host")) {
+					free(vhost);
+					vhost = cm_strdup(req.headers[i + 1]);
+				} else if(cm_strcaseequ(req.headers[i], "If-Modified-Since")) {
+					struct tm tm;
+					strptime(req.headers[i + 1], "%a, %d %b %Y %H:%M:%S GMT", &tm);
+#if defined(__MINGW32__) || defined(_PSP) || defined(__PPU__) || defined(__ps2sdk__)
+					time_t t = 0;
+					struct tm* btm = localtime(&t);
+					cmtime = mktime(&tm);
+					cmtime -= (btm->tm_hour * 60 + btm->tm_min) * 60;
+#else
+						cmtime = timegm(&tm);
+#endif
+				}
+			}
+		}
+		bool rej = false;
+		cm_log("Server", "Host is %s", vhost);
+		int port = s == NULL ? 80 : 443;
+		char* host = cm_strdup(vhost);
+		for(i = 0; vhost[i] != 0; i++) {
+			if(vhost[i] == ':') {
+				host[i] = 0;
+				port = atoi(host + i + 1);
+				break;
+			}
+		}
+		name = host;
+		cm_log("Server", "Hostname is `%s', port is `%d'", host, port);
+		struct tw_config_entry* vhost_entry = tw_vhost_match(host, port);
+#ifdef HAS_CHROOT
+		char* chrootpath = vhost_entry->chroot_path != NULL ? vhost_entry->chroot_path : config.root.chroot_path;
+		if(chrootpath != NULL) {
+			if(chdir(chrootpath) == 0) {
+				if(chroot(".") == 0) {
+					cm_log("Server", "Chroot successful");
+				}
+			} else {
+				cm_log("Server", "chdir() failed, cannot chroot");
+				tw_http_error(s, sock, 500, name, port, vhost_entry);
+				rej = true;
+			}
+		}
+#endif
+		for(i = 0; i < config.module_count; i++) {
+			tw_mod_request_t mod_req = (tw_mod_request_t)tw_module_symbol(config.modules[i], "mod_request");
+			if(mod_req != NULL) {
+				int ret = mod_req(&tools, &req, &res);
+				int co = ret & 0xff;
+				if(co == _TW_MODULE_PASS) {
+					continue;
+				} else if(co == _TW_MODULE_STOP) {
+					/* Handle response here ... */
+					res._processed = true;
+					break;
+				} else if(co == _TW_MODULE_STOP2) {
+					res._processed = true;
+					break;
+				} else if(co == _TW_MODULE_ERROR) {
+					tw_http_error(s, sock, (ret & 0xffff00) >> 8, name, port, vhost_entry);
+					break;
+				}
+			}
+		}
+		if(!res._processed) {
+			cm_log("Server", "Document root is %s", vhost_entry->root == NULL ? "not set" : vhost_entry->root);
+			char* path = cm_strcat(vhost_entry->root == NULL ? "" : vhost_entry->root, req.path);
+			cm_log("Server", "Filesystem path is %s", path);
+#ifdef __MINGW32__
+			char* rpath = cm_strdup(path);
+			for(i = strlen(rpath) - 1; i >= 0; i--) {
+				if(rpath[i] == '/') {
+					int j;
+					for(j = i + 1; rpath[j] != 0; j++) {
+						if(rpath[j] == ':' || rpath[j] == '.') {
+							rpath[j] = 0;
+							break;
+						}
+					}
+					break;
+				}
+			}
+			for(i = 0; i < sizeof(reserved_names) / sizeof(reserved_names[0]); i++) {
+				char* n = cm_strcat("/", reserved_names[i]);
+				if(cm_nocase_endswith(rpath, n)) {
+					tw_http_error(s, sock, 403, name, port, vhost_entry);
+					free(n);
+					rej = true;
+					cm_log("Server", "XP Patch ; rejecting access to device");
+					break;
+				}
+				free(n);
+			}
+			free(rpath);
+#endif
+			struct stat st;
+			if(!rej && stat(path, &st) == 0) {
+				if(!tw_permission_allowed(path, addr, req, vhost_entry)) {
+					tw_http_error(s, sock, 403, name, port, vhost_entry);
+				} else if(S_ISDIR(st.st_mode)) {
+					if(req.path[strlen(req.path) - 1] != '/') {
+						cm_log("Server", "Accessing directory without the slash at the end");
+						char* headers[3] = {"Location", cm_strcat(req.path, "/"), NULL};
+						_tw_process_page(s, sock, tw_http_status(301), NULL, NULL, NULL, 0, headers, 0, 0);
+						free(headers[1]);
+					} else {
+						char** indexes = vhost_entry->index_count == 0 ? config.root.indexes : vhost_entry->indexes;
+						int index_count = vhost_entry->index_count == 0 ? config.root.index_count : vhost_entry->index_count;
+						bool found = false;
+						for(i = 0; i < index_count; i++) {
+							char* p = cm_strcat3(path, "/", indexes[i]);
+							FILE* f = fopen(p, "rb");
+							if(f != NULL) {
+								char* ext = NULL;
+								int j;
+								for(j = strlen(p) - 1; j >= 0; j--) {
+									if(p[j] == '.') {
+										ext = cm_strdup(p + j);
+										break;
+									} else if(p[j] == '/') {
+										break;
+									}
+								}
+								struct stat st;
+								stat(p, &st);
+								char* mime = tw_get_mime(ext, vhost_entry);
+								tw_process_page(s, sock, tw_http_status(200), mime, f, NULL, st.st_size, 0, 0);
+								fclose(f);
+								if(ext != NULL) free(ext);
+								free(p);
+								found = true;
+								break;
+							}
+							free(p);
+						}
+						if(!found) {
+							char* str = malloc(1);
+							str[0] = 0;
+							char** items = cm_scandir(path);
+							addstring(&str, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n");
+							addstring(&str, "<html>\n");
+							addstring(&str, "	<head>\n");
+							addstring(&str, "		<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
+							addstring(&str, "		<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n");
+							addstring(&str, "		<title>Index of %h</title>\n", req.path);
+							addstring(&str, "	</head>\n");
+							addstring(&str, "	<body>\n");
+							addstring(&str, "		<h1>Index of %h</h1>\n", req.path);
+							addstring(&str, "		<hr>\n");
+							addstring(&str, "		<table border=\"0\">\n");
+							addstring(&str, "			<tr>\n");
+							addstring(&str, "				<th></th>\n");
+							addstring(&str, "				<th>Filename</th>\n");
+							addstring(&str, "				<th>MIME</th>\n");
+							addstring(&str, "				<th>Size</th>\n");
+							addstring(&str, "			</tr>\n");
+							int readme = -1;
+							char** readmes = vhost_entry->readme_count == 0 ? config.root.readmes : vhost_entry->readmes;
+							int readme_count = vhost_entry->readme_count == 0 ? config.root.readme_count : vhost_entry->readme_count;
+							if(items != NULL) {
+								int phase = 0;
+							doit:
+								for(i = 0; items[i] != NULL; i++) {
+									int j;
+									char* fpth = cm_strcat3(path, "/", items[i]);
+									struct stat s;
+									char size[512];
+									size[0] = 0;
+									stat(fpth, &s);
+									if(phase == 0 && !S_ISDIR(s.st_mode)) {
+										free(fpth);
+										continue;
+									} else if(phase == 1 && S_ISDIR(s.st_mode)) {
+										free(fpth);
+										continue;
+									}
+									if(readme == -1) {
+										for(j = 0; j < readme_count; j++) {
+											if(strcmp(items[i], readmes[j]) == 0) {
+												readme = j;
+												break;
+											}
+										}
+										if(readme != -1) {
+											free(fpth);
+											continue;
+										}
+									}
+									if(s.st_size < 1024ULL) {
+										sprintf(size, "%d", (int)s.st_size);
+									} else if(s.st_size < 1024ULL * 1024) {
+										sprintf(size, "%.1fK", (double)s.st_size / 1024);
+									} else if(s.st_size < 1024ULL * 1024 * 1024) {
+										sprintf(size, "%.1fM", (double)s.st_size / 1024 / 1024);
+									} else if(s.st_size < 1024ULL * 1024 * 1024 * 1024) {
+										sprintf(size, "%.1fG", (double)s.st_size / 1024 / 1024 / 1024);
+									} else if(s.st_size < 1024ULL * 1024 * 1024 * 1024 * 1024) {
+										sprintf(size, "%.1fT", (double)s.st_size / 1024 / 1024 / 1024 / 1024);
+									}
+
+									free(fpth);
+
+									char* ext = NULL;
+									for(j = strlen(items[i]) - 1; j >= 0; j--) {
+										if(items[i][j] == '.') {
+											ext = cm_strdup(items[i] + j);
+											break;
+										} else if(items[i][j] == '/') {
+											break;
+										}
+									}
+									char* showmime = "";
+									char* mime = tw_get_mime(ext, vhost_entry);
+									if(strcmp(items[i], "../") == 0) {
+										mime = "misc/parent";
+										size[0] = 0;
+									} else if(items[i][strlen(items[i]) - 1] == '/') {
+										mime = "misc/dir";
+										size[0] = 0;
+									} else {
+										showmime = mime;
+									}
+									char* icon = tw_get_icon(mime, vhost_entry);
+									if(ext != NULL) free(ext);
+									char* itm = cm_strdup(items[i]);
+									if(strlen(itm) >= 32) {
+										if(itm[strlen(itm) - 1] == '/') {
+											itm[31] = 0;
+											itm[30] = '/';
+											itm[29] = '.';
+											itm[28] = '.';
+											itm[27] = '.';
+										} else {
+											itm[31] = 0;
+											itm[30] = '.';
+											itm[29] = '.';
+											itm[28] = '.';
+										}
+									}
+									addstring(&str, "<tr>\n");
+									addstring(&str, "	<td><img src=\"%s\" alt=\"icon\"></td>\n", icon);
+									addstring(&str, "	<td><a href=\"%l\"><code>%h</code></a></td>\n", items[i], itm);
+									addstring(&str, "	<td><code>  %h  </code></td>\n", showmime);
+									addstring(&str, "	<td><code>  %s  </code></td>\n", size);
+									addstring(&str, "</tr>\n");
+									free(itm);
+								}
+								phase++;
+								if(phase != 2) goto doit;
+								for(i = 0; items[i] != NULL; i++) free(items[i]);
+								free(items);
+							}
+							addstring(&str, "		</table>\n");
+							if(readme != -1) {
+								addstring(&str, "<hr>\n");
+								char* fpth = cm_strcat3(path, "/", readmes[readme]);
+								struct stat s;
+								stat(fpth, &s);
+								FILE* fr = fopen(fpth, "r");
+								if(fr != NULL) {
+									char* rmbuf = malloc(s.st_size + 1);
+									rmbuf[s.st_size] = 0;
+									fread(rmbuf, s.st_size, 1, fr);
+									addstring(&str, "<pre><code>%h</code></pre>\n", rmbuf);
+									fclose(fr);
+									free(rmbuf);
+								}
+								free(fpth);
+							}
+							addstring(&str, "		<hr>\n");
+							int hp = vhost_entry->hideport == -1 ? config.root.hideport : vhost_entry->hideport;
+							if(hp == 0) {
+								addstring(&str, "		<address>%s Server at %s Port %d</address>\n", tw_server, name, port);
+							} else {
+								addstring(&str, "		<address>%s Server at %s</address>\n", tw_server, name, port);
+							}
+							addstring(&str, "	</body>\n");
+							addstring(&str, "</html>\n");
+							tw_process_page(s, sock, tw_http_status(200), "text/html", NULL, str, strlen(str), 0, 0);
+							free(str);
+						}
+					}
+				} else {
+					char* ext = NULL;
+					for(i = strlen(req.path) - 1; i >= 0; i--) {
+						if(req.path[i] == '.') {
+							ext = cm_strdup(req.path + i);
+							break;
+						} else if(req.path[i] == '/') {
+							break;
+						}
+					}
+					char* mime = tw_get_mime(ext, vhost_entry);
+					if(ext != NULL) free(ext);
+					FILE* f = fopen(path, "rb");
+					if(f == NULL) {
+						tw_http_error(s, sock, 403, name, port, vhost_entry);
+					} else {
+						tw_process_page(s, sock, tw_http_status(200), mime, f, NULL, st.st_size, st.st_mtime, cmtime);
+						fclose(f);
+					}
+				}
+			} else {
+				if(!rej) {
+					tw_http_error(s, sock, 404, name, port, vhost_entry);
+				}
+			}
+			free(path);
+		}
+		free(vhost);
+		free(host);
+	} else if(ret == -1) {
+	} else {
+		tw_http_error(s, sock, 400, name, port, &config.root);
+	}
+	tw_free_request(&req);
+cleanup:
+#ifndef NO_SSL
+	if(sslworks) {
+		SSL_shutdown(s);
+	}
+	SSL_free(s);
+#endif
+	close_socket(sock);
+#ifdef __MINGW32__
+	_endthreadex(0);
+#elif defined(__HAIKU__)
+		exit_thread(0);
+#endif
+	;
+}
+
+#ifdef SERVICE
+extern SERVICE_STATUS status;
+extern SERVICE_STATUS_HANDLE status_handle;
+#endif
+
+#if defined(__MINGW32__) || defined(__HAIKU__)
+struct thread_entry {
+#ifdef __HAIKU__
+	thread_id thread;
+#else
+	HANDLE handle;
+#endif
+	bool used;
+};
+#endif
+
+extern int running;
+
+void tw_server_loop(void) {
+	int i;
+#if defined(__MINGW32__) || defined(__HAIKU__)
+	struct thread_entry threads[2048];
+	for(i = 0; i < sizeof(threads) / sizeof(threads[0]); i++) {
+		threads[i].used = false;
+	}
+#endif
+#ifdef USE_POLL
+	struct pollfd pollfds[sockcount];
+	for(i = 0; i < sockcount; i++) {
+		pollfds[i].fd = sockets[i];
+		pollfds[i].events = POLLIN | POLLPRI;
+	}
+#else
+		fd_set fdset;
+		struct timeval tv;
+#endif
+	while(running) {
+#ifdef USE_POLL
+		int ret = poll(pollfds, sockcount, 1000);
+#else
+			FD_ZERO(&fdset);
+			for(i = 0; i < sockcount; i++) {
+				FD_SET(sockets[i], &fdset);
+			}
+			tv.tv_sec = 1;
+			tv.tv_usec = 0;
+#ifdef __HAIKU__
+			int ret = select(32, &fdset, NULL, NULL, &tv);
+#else
+			int ret = select(FD_SETSIZE, &fdset, NULL, NULL, &tv);
+#endif
+#endif
+		if(ret == -1) {
+#ifndef __MINGW32__
+			cm_log("Server", "Select failure: %s", strerror(errno));
+#endif
+			break;
+		} else if(ret == 0) {
+#ifdef SERVICE
+			if(status.dwCurrentState == SERVICE_STOP_PENDING) {
+				break;
+			}
+#endif
+		} else if(ret > 0) {
+			/* connection */
+			int i;
+			for(i = 0; i < sockcount; i++) {
+				bool cond;
+#ifdef USE_POLL
+				cond = pollfds[i].revents & POLLIN;
+#else
+					cond = FD_ISSET(sockets[i], &fdset);
+#endif
+				if(cond) {
+					SOCKADDR claddr;
+					socklen_t clen = sizeof(claddr);
+					int sock = accept(sockets[i], (struct sockaddr*)&claddr, &clen);
+					cm_log("Server", "New connection accepted");
+#if defined(__MINGW32__) || defined(__HAIKU__) || defined(_PSP) || defined(__PPU__)
+					struct pass_entry* e = malloc(sizeof(*e));
+					e->sock = sock;
+					e->ssl = config.ports[i] & (1ULL << 32);
+					e->port = config.ports[i];
+					e->addr = claddr;
+#endif
+#ifdef __MINGW32__
+					int j;
+					for(j = 0; j < sizeof(threads) / sizeof(threads[0]); j++) {
+						if(threads[j].used) {
+							DWORD ex;
+							GetExitCodeThread(threads[j].handle, &ex);
+							if(ex != STILL_ACTIVE) {
+								CloseHandle(threads[j].handle);
+								threads[j].used = false;
+							}
+						}
+					}
+					for(j = 0; j < sizeof(threads) / sizeof(threads[0]); j++) {
+						if(!threads[j].used) {
+							threads[j].handle = (HANDLE)_beginthreadex(NULL, 0, tw_server_pass, e, 0, NULL);
+							threads[j].used = true;
+							break;
+						}
+					}
+#elif defined(_PSP) || defined(__PPU__)
+						tw_server_pass(e);
+#elif defined(__HAIKU__)
+					int j;
+					for(j = 0; j < sizeof(threads) / sizeof(threads[0]); j++) {
+						if(threads[j].used) {
+							thread_info info;
+							bool kill = false;
+							if(get_thread_info(threads[j].thread, &info) == B_OK) {
+							} else {
+								kill = true;
+							}
+							if(kill) {
+								threads[j].used = false;
+							}
+						}
+					}
+					for(j = 0; j < sizeof(threads) / sizeof(threads[0]); j++) {
+						if(!threads[j].used) {
+							threads[j].thread = spawn_thread(tw_server_pass, "Tewi HTTPd", 60, e);
+							threads[j].used = true;
+							resume_thread(threads[j].thread);
+							break;
+						}
+					}
+#else
+					pid_t pid = fork();
+					if(pid == 0) {
+						int j;
+						for(j = 0; j < sockcount; j++) close_socket(sockets[j]);
+						tw_server_pass(sock, config.ports[i] & (1ULL << 32), config.ports[i], claddr);
+						_exit(0);
+					} else {
+						close_socket(sock);
+					}
+#endif
+				}
+			}
+		}
+	}
+}
Index: /tags/2.02/Server/ssl.c
===================================================================
--- /tags/2.02/Server/ssl.c	(revision 191)
+++ /tags/2.02/Server/ssl.c	(revision 191)
@@ -0,0 +1,41 @@
+/* $Id$ */
+
+#define SOURCE
+
+#include "tw_ssl.h"
+
+#include "tw_config.h"
+
+#include <stdio.h>
+
+#include <cm_log.h>
+
+extern struct tw_config config;
+
+int tw_ssl_cert_cb(SSL* ssl, void* arg) {
+	const char* s = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+	if(s != NULL) {
+		cm_log("SSL", "Certificate request for %s", s);
+	} else {
+		s = config.hostname;
+		cm_log("SSL", "Could not get the servername, defaulting to the hostname: %s", s);
+	}
+	struct tw_config_entry* e = tw_vhost_match(s, (__UINTPTR_TYPE__)arg);
+	if(e != NULL && e->sslkey != NULL && e->sslcert != NULL) {
+		SSL_use_PrivateKey_file(ssl, e->sslkey, SSL_FILETYPE_PEM);
+		SSL_use_certificate_file(ssl, e->sslcert, SSL_FILETYPE_PEM);
+		return 1;
+	} else if(config.root.sslkey != NULL && config.root.sslcert != NULL) {
+		SSL_use_PrivateKey_file(ssl, config.root.sslkey, SSL_FILETYPE_PEM);
+		SSL_use_certificate_file(ssl, config.root.sslcert, SSL_FILETYPE_PEM);
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+SSL_CTX* tw_create_ssl_ctx(__UINTPTR_TYPE__ port) {
+	SSL_CTX* ctx = SSL_CTX_new(TLS_server_method());
+	SSL_CTX_set_cert_cb(ctx, tw_ssl_cert_cb, (void*)port);
+	return ctx;
+}
Index: /tags/2.02/Server/strptime.c
===================================================================
--- /tags/2.02/Server/strptime.c	(revision 191)
+++ /tags/2.02/Server/strptime.c	(revision 191)
@@ -0,0 +1,838 @@
+/*	$NetBSD: strptime.c,v 1.62 2017/08/24 01:01:09 ginsbach Exp $	*/
+/* http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/lib/libc/time/strptime.c?only_with_tag=HEAD
+ * NetBSD implementation strptime().
+ * Format description: https://netbsd.gw.com/cgi-bin/man-cgi?strptime+3+NetBSD-current
+*/
+/*-
+ * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code was contributed to The NetBSD Foundation by Klaus Klein.
+ * Heavily optimised by David Laight
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+//#include <sys/cdefs.h>
+//__RCSID("$NetBSD: strptime.c,v 1.62 2017/08/24 01:01:09 ginsbach Exp $");
+
+#if defined(__MINGW32__)
+
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdint.h>
+
+static const unsigned char *conv_num(const unsigned char *, int *, unsigned int, unsigned int);
+static const unsigned char *find_string(const unsigned char *, int *, const char * const *, const char * const *, int);
+
+/*
+ * We do not implement alternate representations. However, we always
+ * check whether a given modifier is allowed for a certain conversion.
+ */
+#define ALT_E			0x01
+#define ALT_O			0x02
+#define LEGAL_ALT(x)	{ if (alt_format & ~(x)) return NULL; }
+
+#define TM_YEAR_BASE	1900
+
+#define TM_SUNDAY       0
+#define TM_MONDAY       1
+#define TM_TUESDAY      2
+#define TM_WEDNESDAY    3
+#define TM_THURSDAY     4
+#define TM_FRIDAY       5
+#define TM_SATURDAY     6
+
+#define S_YEAR			(1 << 0)
+#define S_MON			(1 << 1)
+#define S_YDAY			(1 << 2)
+#define S_MDAY			(1 << 3)
+#define S_WDAY			(1 << 4)
+#define S_HOUR			(1 << 5)
+
+#define HAVE_MDAY(s)	(s & S_MDAY)
+#define HAVE_MON(s)		(s & S_MON)
+#define HAVE_WDAY(s)	(s & S_WDAY)
+#define HAVE_YDAY(s)	(s & S_YDAY)
+#define HAVE_YEAR(s)	(s & S_YEAR)
+#define HAVE_HOUR(s)	(s & S_HOUR)
+
+#define SECSPERMIN      60
+#define MINSPERHOUR     60
+#define SECSPERHOUR     (SECSPERMIN * MINSPERHOUR)
+#define HOURSPERDAY     24
+
+#define HERE_D_T_FMT    "%a %b %e %H:%M:%S %Y"
+#define HERE_D_FMT      "%y/%m/%d"
+#define HERE_T_FMT_AMPM "%I:%M:%S %p"
+#define HERE_T_FMT      "%H:%M:%S"
+
+#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
+
+/*
+** Since everything in isleap is modulo 400 (or a factor of 400), we know that
+**	isleap(y) == isleap(y % 400)
+** and so
+**	isleap(a + b) == isleap((a + b) % 400)
+** or
+**	isleap(a + b) == isleap(a % 400 + b % 400)
+** This is true even if % means modulo rather than Fortran remainder
+** (which is allowed by C89 but not by C99 or later).
+** We use this to avoid addition overflow problems.
+*/
+
+#define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
+
+#ifdef _MSC_VER
+#define tzname              _tzname
+#define strncasecmp         _strnicmp
+#endif
+
+#ifdef TM_ZONE
+static char* utc = "UTC";
+#endif
+/* RFC-822/RFC-2822 */
+static const char* const nast[] = {
+       "EST",    "CST",    "MST",    "PST",    "\0\0\0"
+};
+static const char* const nadt[] = {
+       "EDT",    "CDT",    "MDT",    "PDT",    "\0\0\0"
+};
+static const char* weekday_name[] =
+{
+    "Sunday", "Monday", "Tuesday", "Wednesday",
+    "Thursday", "Friday", "Saturday"
+};
+static const char* ab_weekday_name[] =
+{
+    "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+static const char* month_name[] =
+{
+    "January", "February", "March", "April", "May", "June",
+    "July", "August", "September", "October", "November", "December"
+};
+static const char* ab_month_name[] =
+{
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+static const char* am_pm[] = {"AM", "PM"};
+
+
+/*
+ * Table to determine the ordinal date for the start of a month.
+ * Ref: http://en.wikipedia.org/wiki/ISO_week_date
+ */
+static const int start_of_month[2][13] = {
+    /* non-leap year */
+    { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
+    /* leap year */
+    { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
+};
+
+/*
+ * Calculate the week day of the first day of a year. Valid for
+ * the Gregorian calendar, which began Sept 14, 1752 in the UK
+ * and its colonies. Ref:
+ * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
+ */
+
+static int
+first_wday_of(int yr)
+{
+    return ((2 * (3 - (yr / 100) % 4)) + (yr % 100) + ((yr % 100) /  4) +
+        (isleap(yr) ? 6 : 0) + 1) % 7;
+}
+
+#define delim(p)	((p) == '\0' || isspace((unsigned char)(p)))
+
+static int
+fromzone(const unsigned char **bp, struct tm *tm, int mandatory)
+{
+//    timezone_t tz;
+    char buf[512], *p;
+    const unsigned char *rp;
+
+    for (p = buf, rp = *bp; !delim(*rp) && p < &buf[sizeof(buf) - 1]; rp++)
+        *p++ = *rp;
+    *p = '\0';
+
+    if (mandatory)
+        *bp = rp;
+    if (!isalnum((unsigned char)*buf))
+        return 0;
+//    tz = tzalloc(buf);
+//    if (tz == NULL)
+//        return 0;
+
+    *bp = rp;
+    tm->tm_isdst = 0;	/* XXX */
+#ifdef TM_GMTOFF
+    tm->TM_GMTOFF = tzgetgmtoff(tz, tm->tm_isdst);
+#endif
+#ifdef TM_ZONE
+    // Can't use tzgetname() here because we are going to free()
+    tm->TM_ZONE = NULL; /* XXX */
+#endif
+//    tzfree(tz);
+    return 1;
+}
+
+char* strptime(const char *buf, const char *fmt, struct tm *tm)
+{
+    unsigned char c;
+    const unsigned char *bp, *ep, *zname;
+    int alt_format, i, split_year = 0, neg = 0, state = 0,
+        day_offset = -1, week_offset = 0, offs, mandatory;
+    const char *new_fmt;
+
+    bp = (const unsigned char *)buf;
+
+    while (bp != NULL && (c = *fmt++) != '\0') {
+        /* Clear `alternate' modifier prior to new conversion. */
+        alt_format = 0;
+        i = 0;
+
+        /* Eat up white-space. */
+        if (isspace(c)) {
+            while (isspace(*bp))
+                bp++;
+            continue;
+        }
+
+        if (c != '%')
+            goto literal;
+
+
+again:		switch (c = *fmt++) {
+        case '%':	/* "%%" is converted to "%". */
+literal:
+            if (c != *bp++)
+                return NULL;
+            LEGAL_ALT(0);
+            continue;
+
+        /*
+         * "Alternative" modifiers. Just set the appropriate flag
+         * and start over again.
+         */
+        case 'E':	/* "%E?" alternative conversion modifier. */
+            LEGAL_ALT(0);
+            alt_format |= ALT_E;
+            goto again;
+
+        case 'O':	/* "%O?" alternative conversion modifier. */
+            LEGAL_ALT(0);
+            alt_format |= ALT_O;
+            goto again;
+
+        /*
+         * "Complex" conversion rules, implemented through recursion.
+         */
+        case 'c':	/* Date and time, using the locale's format. */
+//            new_fmt = _TIME_LOCALE(loc)->d_t_fmt;
+            new_fmt = HERE_D_T_FMT;
+            state |= S_WDAY | S_MON | S_MDAY | S_YEAR;
+            goto recurse;
+
+        case 'F':	/* The date as "%Y-%m-%d". */
+            new_fmt = "%Y-%m-%d";
+            LEGAL_ALT(0);
+            state |= S_MON | S_MDAY | S_YEAR;
+            goto recurse;
+
+        case 'R':	/* The time as "%H:%M". */
+            new_fmt = "%H:%M";
+            LEGAL_ALT(0);
+            goto recurse;
+
+        case 'r':	/* The time in 12-hour clock representation. */
+//            new_fmt = _TIME_LOCALE(loc)->t_fmt_ampm;
+            new_fmt = HERE_T_FMT_AMPM;
+            LEGAL_ALT(0);
+            goto recurse;
+
+        case 'X':	/* The time, using the locale's format. */
+            /* fall through */
+
+        case 'T':	/* The time as "%H:%M:%S". */
+            new_fmt = HERE_T_FMT;
+            LEGAL_ALT(0);
+
+recurse:
+            bp = (const unsigned char *)strptime((const char *)bp,
+                                new_fmt, tm);
+            LEGAL_ALT(ALT_E);
+            continue;
+
+        case 'x':	/* The date, using the locale's format. */
+            /* fall throug */
+
+        case 'D':	/* The date as "%y/%m/%d". */
+        {
+            new_fmt = HERE_D_FMT;
+            LEGAL_ALT(0);
+            state |= S_MON | S_MDAY | S_YEAR;
+            const int year = split_year ? tm->tm_year : 0;
+
+            bp = (const unsigned char *)strptime((const char *)bp,
+                                new_fmt, tm);
+            LEGAL_ALT(ALT_E);
+            tm->tm_year += year;
+            if (split_year && tm->tm_year % (2000 - TM_YEAR_BASE) <= 68)
+                tm->tm_year -= 2000 - TM_YEAR_BASE;
+            split_year = 1;
+            continue;
+        }
+        /*
+         * "Elementary" conversion rules.
+         */
+        case 'A':	/* The day of week, using the locale's form. */
+        case 'a':
+            bp = find_string(bp, &tm->tm_wday, weekday_name, ab_weekday_name, 7);
+            LEGAL_ALT(0);
+            state |= S_WDAY;
+            continue;
+
+        case 'B':	/* The month, using the locale's form. */
+        case 'b':
+        case 'h':
+            bp = find_string(bp, &tm->tm_mon, month_name, ab_month_name, 12);
+            LEGAL_ALT(0);
+            state |= S_MON;
+            continue;
+
+        case 'C':	/* The century number. */
+            i = 20;
+            bp = conv_num(bp, &i, 0, 99);
+
+            i = i * 100 - TM_YEAR_BASE;
+            if (split_year)
+                i += tm->tm_year % 100;
+            split_year = 1;
+            tm->tm_year = i;
+            LEGAL_ALT(ALT_E);
+            state |= S_YEAR;
+            continue;
+
+        case 'd':	/* The day of month. */
+        case 'e':
+            bp = conv_num(bp, &tm->tm_mday, 1, 31);
+            LEGAL_ALT(ALT_O);
+            state |= S_MDAY;
+            continue;
+
+        case 'k':	/* The hour (24-hour clock representation). */
+            LEGAL_ALT(0);
+            /* FALLTHROUGH */
+        case 'H':
+            bp = conv_num(bp, &tm->tm_hour, 0, 23);
+            LEGAL_ALT(ALT_O);
+            state |= S_HOUR;
+            continue;
+
+        case 'l':	/* The hour (12-hour clock representation). */
+            LEGAL_ALT(0);
+            /* FALLTHROUGH */
+        case 'I':
+            bp = conv_num(bp, &tm->tm_hour, 1, 12);
+            if (tm->tm_hour == 12)
+                tm->tm_hour = 0;
+            LEGAL_ALT(ALT_O);
+            state |= S_HOUR;
+            continue;
+
+        case 'j':	/* The day of year. */
+            i = 1;
+            bp = conv_num(bp, &i, 1, 366);
+            tm->tm_yday = i - 1;
+            LEGAL_ALT(0);
+            state |= S_YDAY;
+            continue;
+
+        case 'M':	/* The minute. */
+            bp = conv_num(bp, &tm->tm_min, 0, 59);
+            LEGAL_ALT(ALT_O);
+            continue;
+
+        case 'm':	/* The month. */
+            i = 1;
+            bp = conv_num(bp, &i, 1, 12);
+            tm->tm_mon = i - 1;
+            LEGAL_ALT(ALT_O);
+            state |= S_MON;
+            continue;
+
+        case 'p':	/* The locale's equivalent of AM/PM. */
+            bp = find_string(bp, &i, am_pm, NULL, 2);
+            if (HAVE_HOUR(state) && tm->tm_hour > 11)
+                return NULL;
+            tm->tm_hour += i * 12;
+            LEGAL_ALT(0);
+            continue;
+
+        case 'S':	/* The seconds. */
+            bp = conv_num(bp, &tm->tm_sec, 0, 61);
+            LEGAL_ALT(ALT_O);
+            continue;
+
+#ifndef TIME_MAX
+#define TIME_MAX	INT64_MAX
+#endif
+        case 's':	/* seconds since the epoch */
+            {
+                time_t sse = 0;
+                uint64_t rulim = TIME_MAX;
+
+                if (*bp < '0' || *bp > '9') {
+                    bp = NULL;
+                    continue;
+                }
+
+                do {
+                    sse *= 10;
+                    sse += *bp++ - '0';
+                    rulim /= 10;
+                } while ((sse * 10 <= TIME_MAX) &&
+                     rulim && *bp >= '0' && *bp <= '9');
+
+                if (sse < 0 || (uint64_t)sse > TIME_MAX) {
+                    bp = NULL;
+                    continue;
+                }
+#ifdef _WIN32
+                if (localtime_s(tm, &sse) == 0)
+#else
+                if (localtime_r(&sse, tm))
+#endif
+                    state |= S_YDAY | S_WDAY | S_MON | S_MDAY | S_YEAR;
+                else
+                    bp = NULL;
+            }
+            continue;
+
+        case 'U':	/* The week of year, beginning on sunday. */
+        case 'W':	/* The week of year, beginning on monday. */
+            /*
+             * This is bogus, as we can not assume any valid
+             * information present in the tm structure at this
+             * point to calculate a real value, so save the
+             * week for now in case it can be used later.
+             */
+            bp = conv_num(bp, &i, 0, 53);
+            LEGAL_ALT(ALT_O);
+            if (c == 'U')
+                day_offset = TM_SUNDAY;
+            else
+                day_offset = TM_MONDAY;
+            week_offset = i;
+            continue;
+
+        case 'w':	/* The day of week, beginning on sunday. */
+            bp = conv_num(bp, &tm->tm_wday, 0, 6);
+            LEGAL_ALT(ALT_O);
+            state |= S_WDAY;
+            continue;
+
+        case 'u':	/* The day of week, monday = 1. */
+            bp = conv_num(bp, &i, 1, 7);
+            tm->tm_wday = i % 7;
+            LEGAL_ALT(ALT_O);
+            state |= S_WDAY;
+            continue;
+
+        case 'g':	/* The year corresponding to the ISO week
+                 * number but without the century.
+                 */
+            bp = conv_num(bp, &i, 0, 99);
+            continue;
+
+        case 'G':	/* The year corresponding to the ISO week
+                 * number with century.
+                 */
+            do
+                bp++;
+            while (isdigit(*bp));
+            continue;
+
+        case 'V':	/* The ISO 8601:1988 week number as decimal */
+            bp = conv_num(bp, &i, 0, 53);
+            continue;
+
+        case 'Y':	/* The year. */
+            i = TM_YEAR_BASE;	/* just for data sanity... */
+            bp = conv_num(bp, &i, 0, 9999);
+            tm->tm_year = i - TM_YEAR_BASE;
+            LEGAL_ALT(ALT_E);
+            state |= S_YEAR;
+            continue;
+
+        case 'y':	/* The year within 100 years of the epoch. */
+            /* LEGAL_ALT(ALT_E | ALT_O); */
+            bp = conv_num(bp, &i, 0, 99);
+
+            if (split_year)
+                /* preserve century */
+                i += (tm->tm_year / 100) * 100;
+            else {
+                split_year = 1;
+                if (i <= 68)
+                    i = i + 2000 - TM_YEAR_BASE;
+            }
+            tm->tm_year = i;
+            state |= S_YEAR;
+            continue;
+
+        case 'Z':       // time zone name
+        case 'z':       //
+#ifdef _WIN32
+            _tzset();
+#else
+            tzset();
+#endif
+            mandatory = c == 'z';
+            /*
+             * We recognize all ISO 8601 formats:
+             * Z	= Zulu time/UTC
+             * [+-]hhmm
+             * [+-]hh:mm
+             * [+-]hh
+             * We recognize all RFC-822/RFC-2822 formats:
+             * UT|GMT
+             *          North American : UTC offsets
+             * E[DS]T = Eastern : -4 | -5
+             * C[DS]T = Central : -5 | -6
+             * M[DS]T = Mountain: -6 | -7
+             * P[DS]T = Pacific : -7 | -8
+             *          Nautical/Military
+             * [A-IL-M] = -1 ... -9 (J not used)
+             * [N-Y]  = +1 ... +12
+             * Note: J maybe used to denote non-nautical
+             *       local time
+             */
+            if (mandatory)
+                while (isspace(*bp))
+                    bp++;
+
+            zname = bp;
+            switch (*bp++) {
+            case 'G':
+                if (*bp++ != 'M')
+                    goto namedzone;
+                /*FALLTHROUGH*/
+            case 'U':
+                if (*bp++ != 'T')
+                    goto namedzone;
+                else if (!delim(*bp) && *bp++ != 'C')
+                    goto namedzone;
+                /*FALLTHROUGH*/
+            case 'Z':
+                if (!delim(*bp))
+                    goto namedzone;
+                tm->tm_isdst = 0;
+#ifdef TM_GMTOFF
+                tm->TM_GMTOFF = 0;
+#endif
+#ifdef TM_ZONE
+                tm->TM_ZONE = utc;
+#endif
+                continue;
+            case '+':
+                neg = 0;
+                break;
+            case '-':
+                neg = 1;
+                break;
+            default:
+namedzone:
+                bp = zname;
+
+                /* Nautical / Military style */
+                if (delim(bp[1]) &&
+                    ((*bp >= 'A' && *bp <= 'I') ||
+                     (*bp >= 'L' && *bp <= 'Y'))) {
+#ifdef TM_GMTOFF
+                    /* Argh! No 'J'! */
+                    if (*bp >= 'A' && *bp <= 'I')
+                        tm->TM_GMTOFF =
+                            (int)*bp - ('A' - 1);
+                    else if (*bp >= 'L' && *bp <= 'M')
+                        tm->TM_GMTOFF = (int)*bp - 'A';
+                    else if (*bp >= 'N' && *bp <= 'Y')
+                        tm->TM_GMTOFF = 'M' - (int)*bp;
+                    tm->TM_GMTOFF *= SECSPERHOUR;
+#endif
+#ifdef TM_ZONE
+                    tm->TM_ZONE = NULL; /* XXX */
+#endif
+                    bp++;
+                    continue;
+                }
+                /* 'J' is local time */
+                if (delim(bp[1]) && *bp == 'J') {
+#ifdef TM_GMTOFF
+                    tm->TM_GMTOFF = -timezone;
+#endif
+#ifdef TM_ZONE
+                    tm->TM_ZONE = NULL; /* XXX */
+#endif
+                    bp++;
+                    continue;
+                }
+
+                /*
+                 * From our 3 letter hard-coded table
+                 * XXX: Can be removed, handled by tzload()
+                 */
+                if (delim(bp[0]) || delim(bp[1]) ||
+                    delim(bp[2]) || !delim(bp[3]))
+                    goto loadzone;
+                ep = find_string(bp, &i, nast, NULL, 4);
+                if (ep != NULL) {
+#ifdef TM_GMTOFF
+                    tm->TM_GMTOFF = (-5 - i) * SECSPERHOUR;
+#endif
+#ifdef TM_ZONE
+                    tm->TM_ZONE = __UNCONST(nast[i]);
+#endif
+                    bp = ep;
+                    continue;
+                }
+                ep = find_string(bp, &i, nadt, NULL, 4);
+                if (ep != NULL) {
+                    tm->tm_isdst = 1;
+#ifdef TM_GMTOFF
+                    tm->TM_GMTOFF = (-4 - i) * SECSPERHOUR;
+#endif
+#ifdef TM_ZONE
+                    tm->TM_ZONE = __UNCONST(nadt[i]);
+#endif
+                    bp = ep;
+                    continue;
+                }
+                /*
+                 * Our current timezone
+                 */
+                ep = find_string(bp, &i,
+                             (const char * const *)NULL,
+                              NULL, 2);
+                if (ep != NULL) {
+                    tm->tm_isdst = i;
+#ifdef TM_GMTOFF
+                    tm->TM_GMTOFF = -timezone;
+#endif
+#ifdef TM_ZONE
+                    tm->TM_ZONE = tzname[i];
+#endif
+                    bp = ep;
+                    continue;
+                }
+loadzone:
+                /*
+                 * The hard way, load the zone!
+                 */
+                if (fromzone(&bp, tm, mandatory))
+                    continue;
+                goto out;
+            }
+            offs = 0;
+            for (i = 0; i < 4; ) {
+                if (isdigit(*bp)) {
+                    offs = offs * 10 + (*bp++ - '0');
+                    i++;
+                    continue;
+                }
+                if (i == 2 && *bp == ':') {
+                    bp++;
+                    continue;
+                }
+                break;
+            }
+            if (isdigit(*bp))
+                goto out;
+            switch (i) {
+            case 2:
+                offs *= SECSPERHOUR;
+                break;
+            case 4:
+                i = offs % 100;
+                offs /= 100;
+                if (i >= SECSPERMIN)
+                    goto out;
+                /* Convert minutes into decimal */
+                offs = offs * SECSPERHOUR + i * SECSPERMIN;
+                break;
+            default:
+out:
+                if (mandatory)
+                    return NULL;
+                bp = zname;
+                continue;
+            }
+            /* ISO 8601 & RFC 3339 limit to 23:59 max */
+            if (offs >= (HOURSPERDAY * SECSPERHOUR))
+                goto out;
+            if (neg)
+                offs = -offs;
+            tm->tm_isdst = 0;	/* XXX */
+#ifdef TM_GMTOFF
+            tm->TM_GMTOFF = offs;
+#endif
+#ifdef TM_ZONE
+            tm->TM_ZONE = NULL;	/* XXX */
+#endif
+            continue;
+
+        /*
+         * Miscellaneous conversions.
+         */
+        case 'n':	/* Any kind of white-space. */
+        case 't':
+            while (isspace(*bp))
+                bp++;
+            LEGAL_ALT(0);
+            continue;
+
+
+        default:	/* Unknown/unsupported conversion. */
+            return NULL;
+        }
+    }
+
+    if (!HAVE_YDAY(state) && HAVE_YEAR(state)) {
+        if (HAVE_MON(state) && HAVE_MDAY(state)) {
+            /* calculate day of year (ordinal date) */
+            tm->tm_yday =  start_of_month[isleap_sum(tm->tm_year,
+                TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1);
+            state |= S_YDAY;
+        } else if (day_offset != -1) {
+            /*
+             * Set the date to the first Sunday (or Monday)
+             * of the specified week of the year.
+             */
+            if (!HAVE_WDAY(state)) {
+                tm->tm_wday = day_offset;
+                state |= S_WDAY;
+            }
+            tm->tm_yday = (7 -
+                first_wday_of(tm->tm_year + TM_YEAR_BASE) +
+                day_offset) % 7 + (week_offset - 1) * 7 +
+                tm->tm_wday  - day_offset;
+            state |= S_YDAY;
+        }
+    }
+
+    if (HAVE_YDAY(state) && HAVE_YEAR(state)) {
+        int isleap;
+
+        if (!HAVE_MON(state)) {
+            /* calculate month of day of year */
+            i = 0;
+            isleap = isleap_sum(tm->tm_year, TM_YEAR_BASE);
+            while (tm->tm_yday >= start_of_month[isleap][i])
+                i++;
+            if (i > 12) {
+                i = 1;
+                tm->tm_yday -= start_of_month[isleap][12];
+                tm->tm_year++;
+            }
+            tm->tm_mon = i - 1;
+            state |= S_MON;
+        }
+
+        if (!HAVE_MDAY(state)) {
+            /* calculate day of month */
+            isleap = isleap_sum(tm->tm_year, TM_YEAR_BASE);
+            tm->tm_mday = tm->tm_yday -
+                start_of_month[isleap][tm->tm_mon] + 1;
+            state |= S_MDAY;
+        }
+
+        if (!HAVE_WDAY(state)) {
+            /* calculate day of week */
+            i = 0;
+            week_offset = first_wday_of(tm->tm_year);
+            while (i++ <= tm->tm_yday) {
+                if (week_offset++ >= 6)
+                    week_offset = 0;
+            }
+            tm->tm_wday = week_offset;
+            state |= S_WDAY;
+        }
+    }
+
+    return (char*)bp;
+}
+
+
+static const unsigned char *
+conv_num(const unsigned char *buf, int *dest, unsigned int llim, unsigned int ulim)
+{
+    unsigned int result = 0;
+    unsigned char ch;
+
+    /* The limit also determines the number of valid digits. */
+    unsigned int rulim = ulim;
+
+    ch = *buf;
+    if (ch < '0' || ch > '9')
+        return NULL;
+
+    do {
+        result *= 10;
+        result += ch - '0';
+        rulim /= 10;
+        ch = *++buf;
+    } while ((result <= ulim) && rulim && ch >= '0' && ch <= '9');
+
+    if (result < llim || result > ulim)
+        return NULL;
+
+    *dest = result;
+    return buf;
+}
+
+static const unsigned char *
+find_string(const unsigned char *bp, int *tgt, const char * const *n1,
+        const char * const *n2, int c)
+{
+    int i;
+    size_t len;
+
+    /* check full name - then abbreviated ones */
+    for (; n1 != NULL; n1 = n2, n2 = NULL) {
+        for (i = 0; i < c; i++, n1++) {
+            len = strlen(*n1);
+            if (strncasecmp(*n1, (const char *)bp, len) == 0) {
+                *tgt = i;
+                return bp + len;
+            }
+        }
+    }
+
+    /* Nothing matched */
+    return NULL;
+}
+
+#endif
Index: /tags/2.02/Server/strptime.h
===================================================================
--- /tags/2.02/Server/strptime.h	(revision 191)
+++ /tags/2.02/Server/strptime.h	(revision 191)
@@ -0,0 +1,18 @@
+#ifndef STRPTIME_H
+#define STRPTIME_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <time.h>
+
+#if defined(_WIN32) || defined(_PSP) || defined(__ps2sdk__)
+char* strptime(const char *buf, const char *fmt, struct tm *tm);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // STRPTIME_H
Index: /tags/2.02/Server/tewi.rc
===================================================================
--- /tags/2.02/Server/tewi.rc	(revision 191)
+++ /tags/2.02/Server/tewi.rc	(revision 191)
@@ -0,0 +1,33 @@
+#include <winver.h>
+#include "tw_version.h"
+#include "../config.h"
+
+id ICON "../Binary/tewi.ico"
+VS_VERSION_INFO VERSIONINFO
+	FILEVERSION 1,0,0,0
+	PRODUCTVERSION 1,0,0,0
+BEGIN
+	BLOCK "StringFileInfo"
+	BEGIN
+		BLOCK "040904E4"
+		BEGIN
+			VALUE "CompanyName", "Nishi\0"
+#ifdef NO_SSL
+			VALUE "FileDescription", "Simple HTTP Daemon\0"
+#else
+			VALUE "FileDescription", "Simple HTTP Daemon with TLS/SSL\0"
+#endif
+			VALUE "LegalCopyright", "Public domain, original by Nishi\0"
+			VALUE "FileVersion", TW_VERSION
+			VALUE "ProductVersion", TW_VERSION
+			VALUE "ProductName", "Tewi HTTPd\0"
+			VALUE "License", "Unlicense\0"
+			VALUE "Info", "http://nishi.boats/tewi\0"
+		END
+	END
+	
+	BLOCK "VarFileInfo"
+	BEGIN
+		VALUE "Translation", 0x409, 65001
+	END
+END
Index: /tags/2.02/Server/tw_config.h
===================================================================
--- /tags/2.02/Server/tw_config.h	(revision 191)
+++ /tags/2.02/Server/tw_config.h	(revision 191)
@@ -0,0 +1,113 @@
+/* $Id$ */
+
+#ifndef __TW_CONFIG_H__
+#define __TW_CONFIG_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../config.h"
+
+#include "tw_http.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __MINGW32__
+#include <winsock2.h>
+#define NO_IPV6
+#else
+#ifdef __PPU__
+#include <net/net.h>
+#endif
+#include <netinet/in.h>
+#ifdef __HAIKU__
+#define NO_IPV6
+#endif
+#endif
+
+#ifdef NO_IPV6
+#define SOCKADDR struct sockaddr_in
+#else
+#define SOCKADDR struct sockaddr_in6
+#endif
+
+#define MAX_PORTS 1024
+#define MAX_VHOSTS 1024
+#define MAX_MODULES 1024
+#define MAX_DIRS 1024
+#define MAX_MIME 1024
+#define MAX_ICON 1024
+#define MAX_INDEX 1024
+#define MAX_README 8
+
+enum TW_DIR_TYPE {
+	TW_DIR_ALLOW = 0,
+	TW_DIR_DENY
+};
+
+struct tw_dir_entry {
+	char* name;
+	char* dir;
+	int type;
+};
+
+struct tw_mime_entry {
+	char* ext;
+	char* mime;
+};
+
+struct tw_icon_entry {
+	char* mime;
+	char* icon;
+};
+
+struct tw_config_entry {
+	char* name;
+	int port;
+#ifndef NO_SSL
+	char* sslkey;
+	char* sslcert;
+#endif
+	char* root;
+	int hideport;
+	struct tw_dir_entry dirs[MAX_DIRS];
+	int dir_count;
+	struct tw_mime_entry mimes[MAX_DIRS];
+	int mime_count;
+	struct tw_icon_entry icons[MAX_DIRS];
+	int icon_count;
+	char* indexes[MAX_INDEX];
+	int index_count;
+	char* readmes[MAX_README];
+	int readme_count;
+#ifdef HAS_CHROOT
+	char* chroot_path;
+#endif
+};
+
+struct tw_config {
+	uint64_t ports[MAX_PORTS + 1]; /* If port & (1 << 32) is non-zero, it is SSL */
+	char hostname[1025];
+	char* defined[1025];
+	struct tw_config_entry root;
+	struct tw_config_entry vhosts[MAX_VHOSTS];
+	void* modules[MAX_MODULES];
+	int module_count;
+	int vhost_count;
+	char* server_admin;
+	char* server_root;
+	char* extension;
+};
+
+void tw_config_init(void);
+int tw_config_read(const char* path);
+struct tw_config_entry* tw_vhost_match(const char* name, int port);
+bool tw_permission_allowed(const char* path, SOCKADDR addr, struct tw_http_request req, struct tw_config_entry* vhost);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: /tags/2.02/Server/tw_http.h
===================================================================
--- /tags/2.02/Server/tw_http.h	(revision 191)
+++ /tags/2.02/Server/tw_http.h	(revision 191)
@@ -0,0 +1,45 @@
+/* $Id$ */
+
+#ifndef __TW_HTTP_H__
+#define __TW_HTTP_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+
+#include "../config.h"
+
+struct tw_http_request {
+	char* method;
+	char* path;
+	char* query;
+	char* version;
+	char** headers;
+	char* body;
+};
+
+struct tw_http_response {
+	char** headers;
+	int status;
+	bool _processed; /* Internal parameter */
+};
+
+#ifdef SOURCE
+#ifndef NO_SSL
+#include <openssl/ssl.h>
+#endif
+void tw_free_request(struct tw_http_request* req);
+#ifndef NO_SSL
+int tw_http_parse(SSL* ssl, int sock, struct tw_http_request* req);
+#else
+int tw_http_parse(void* ssl, int sock, struct tw_http_request* req);
+#endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: /tags/2.02/Server/tw_module.h
===================================================================
--- /tags/2.02/Server/tw_module.h	(revision 191)
+++ /tags/2.02/Server/tw_module.h	(revision 191)
@@ -0,0 +1,59 @@
+/* $Id$ */
+
+#ifndef __TW_MODULE_H__
+#define __TW_MODULE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "tw_config.h"
+#include "tw_http.h"
+
+struct tw_tool {
+	void (*log)(const char* name, const char* log, ...);
+	void (*add_version)(const char* string);
+	void (*add_define)(const char* string);
+	void (*delete_define)(const char* string);
+};
+
+enum TW_MODULE_RETURN {
+	_TW_MODULE_PASS = 0, /* Pass to the next module. */
+	_TW_MODULE_STOP,     /* Do not pass to the next module. */
+	_TW_MODULE_STOP2,    /* Do not pass to the next module, and do not handle response. */
+	_TW_MODULE_ERROR,    /* Error, and do not pass to the next module. */
+
+	_TW_CONFIG_PARSED, /* Got parsed */
+	_TW_CONFIG_NOTME,  /* Did not parse */
+	_TW_CONFIG_ERROR   /* Error */
+};
+
+#define TW_MODULE_PASS _TW_MODULE_PASS
+#define TW_MODULE_STOP _TW_MODULE_STOP
+#define TW_MODULE_STOP2 _TW_MODULE_STOP2
+#define TW_MODULE_ERROR(x) (_TW_MODULE_ERROR | ((x) << 8))
+
+#define TW_CONFIG_PARSED _TW_CONFIG_PARSED
+#define TW_CONFIG_NOTME _TW_CONFIG_NOTME
+#define TW_CONFIG_ERROR _TW_CONFIG_ERROR
+
+typedef int (*tw_mod_init_t)(struct tw_config* config, struct tw_tool* tools);
+typedef int (*tw_mod_request_t)(struct tw_tool* tools, struct tw_http_request* req, struct tw_http_response* res);
+typedef int (*tw_mod_config_t)(struct tw_tool* tools, char** argv, int argc);
+
+#ifdef SOURCE
+void* tw_module_load(const char* path);
+void* tw_module_symbol(void* mod, const char* sym);
+void tw_init_tools(struct tw_tool* tools);
+int tw_module_init(void* mod);
+
+void tw_add_version(const char* string);
+void tw_add_define(const char* string);
+void tw_delete_define(const char* string);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: /tags/2.02/Server/tw_server.h
===================================================================
--- /tags/2.02/Server/tw_server.h	(revision 191)
+++ /tags/2.02/Server/tw_server.h	(revision 191)
@@ -0,0 +1,33 @@
+/* $Id$ */
+
+#ifndef __TW_SERVER_H__
+#define __TW_SERVER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../config.h"
+
+#include <stddef.h>
+
+#ifndef NO_SSL
+#include <openssl/ssl.h>
+#endif
+
+int tw_server_init(void);
+void tw_server_loop(void);
+
+#ifndef NO_SSL
+size_t tw_read(SSL* ssl, int s, void* data, size_t len);
+size_t tw_write(SSL* ssl, int s, void* data, size_t len);
+#else
+size_t tw_read(void* ssl, int s, void* data, size_t len);
+size_t tw_write(void* ssl, int s, void* data, size_t len);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: /tags/2.02/Server/tw_ssl.h
===================================================================
--- /tags/2.02/Server/tw_ssl.h	(revision 191)
+++ /tags/2.02/Server/tw_ssl.h	(revision 191)
@@ -0,0 +1,19 @@
+/* $Id$ */
+
+#ifndef __TW_SSL_H__
+#define __TW_SSL_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <openssl/ssl.h>
+#include <stdint.h>
+
+SSL_CTX* tw_create_ssl_ctx(__UINTPTR_TYPE__ port);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: /tags/2.02/Server/tw_version.h
===================================================================
--- /tags/2.02/Server/tw_version.h	(revision 191)
+++ /tags/2.02/Server/tw_version.h	(revision 191)
@@ -0,0 +1,19 @@
+/* $Id$ */
+
+#ifndef __TW_VERSION_H__
+#define __TW_VERSION_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TW_VERSION "2.02\0"
+
+const char* tw_get_version(void);
+const char* tw_get_platform(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
Index: /tags/2.02/Server/version.c
===================================================================
--- /tags/2.02/Server/version.c	(revision 191)
+++ /tags/2.02/Server/version.c	(revision 191)
@@ -0,0 +1,32 @@
+/* $Id$ */
+
+#define SOURCE
+
+#include "tw_version.h"
+
+const char* tw_version = TW_VERSION;
+
+const char* tw_platform =
+#if defined(PLATFORM)
+    PLATFORM
+#elif defined(__NetBSD__)
+    "NetBSD"
+#elif defined(__linux__)
+    "Linux"
+#elif defined(__MINGW32__)
+    "Windows"
+#elif defined(__HAIKU__)
+    "Haiku"
+#elif defined(__CYGWIN__)
+    "Cygwin"
+#elif defined(_PSP)
+    "PSP"
+#elif defined(__PPU__)
+    "PS3"
+#else
+    "Unix"
+#endif
+    ;
+
+const char* tw_get_version(void) { return tw_version; }
+const char* tw_get_platform(void) { return tw_platform; }
Index: /tags/2.02/Tool/genconf.c
===================================================================
--- /tags/2.02/Tool/genconf.c	(revision 191)
+++ /tags/2.02/Tool/genconf.c	(revision 191)
@@ -0,0 +1,49 @@
+/* $Id$ */
+
+#include "../config.h"
+
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+	if(argc < 4) {
+		return 1;
+	}
+	printf("##\n");
+	printf("## tewi.conf -- Tewi HTTPd configuration file\n");
+	printf("##\n");
+	printf("\n");
+	printf("# This configuration file is auto-generated.\n");
+	printf("\n");
+	printf("ServerRoot %s\n", argv[1]);
+	printf("\n");
+	printf("ServerAdmin %s\n", SERVER_ADMIN);
+	printf("\n");
+	printf("#LoadModule %s/mod_cgi.%s\n", argv[2], argv[3]);
+	printf("#LoadModule %s/mod_proxy.%s\n", argv[2], argv[3]);
+	printf("\n");
+	printf("Listen 80\n");
+	printf("#ListenSSL 443\n");
+	printf("\n");
+	printf("#SSLKey key.pem\n");
+	printf("#SSLCertificate cert.pem\n");
+	printf("\n");
+	printf("MIMEType all application/octet-stream\n");
+	printf("MIMEType .html text/html\n");
+	printf("MIMEType .txt text/plain\n");
+	printf("MIMEType .png image/png\n");
+	printf("\n");
+	printf("Icon all /icons/unknown.png\n");
+	printf("Icon text/* /icons/text.png\n");
+	printf("Icon image/* /icons/image.png\n");
+	printf("Icon misc/dir /icons/folder.png\n");
+	printf("Icon misc/parent /icons/parent.png\n");
+	printf("DirectoryIndex index.html\n");
+	printf("\n");
+	printf("ReadmeFile README\n");
+	printf("\n");
+	printf("DocumentRoot %s/www\n", argv[1]);
+	printf("BeginDirectory %s/www\n", argv[1]);
+	printf("	Allow all\n");
+	printf("EndDirectory\n", argv[1]);
+	return 0;
+}
Index: /tags/2.02/Tool/itworks.c
===================================================================
--- /tags/2.02/Tool/itworks.c	(revision 191)
+++ /tags/2.02/Tool/itworks.c	(revision 191)
@@ -0,0 +1,36 @@
+/* $Id$ */
+
+#include "../config.h"
+
+#include <stdio.h>
+
+int main() {
+	printf("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n");
+	printf("<html>\n");
+	printf("	<head>\n");
+	printf("		<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
+	printf("		<title>Test Page for Tewi HTTPd Installation</title>\n");
+	printf("	</head>\n");
+	printf("	<body vlink=\"#000080\" text=\"#000000\" link=\"#0000FF\" bgcolor=\"#FFFFFF\" alink=\"#FF0000\">\n");
+	printf("		<h1 align=\"center\">It works! - Tewi HTTPd is installed on this website!</h1>\n");
+	printf("		<p>\n");
+	printf("			If you can see this page, then the people who own this domain have just installed <a href=\"http://nishi.boats/tewi\">Tewi HTTPd</a> successfully. They now have to add content to this directoryt and replace this placeholder page, or else point the server at their real content.\n");
+	printf("		</p>\n");
+	printf("		<hr>\n");
+	printf("		<blockquote>\n");
+	printf("			If you are seeing this page instead of the website you expected, please <strong>contact the administrator of the website involved.</strong> (Try sending email to <samp>&lt;%s&gt;</samp>.) Although this site is running Tewi HTTPd it almost certainly has no other connection to the developers of Tewi HTTPd, so please do not send email about this website or its contents to the developers of Tewi HTTPd. If you do, your message will be <strong><big>ignored</big></strong>.\n", SERVER_ADMIN);
+	printf("		</blockquote>\n");
+	printf("		<hr>\n");
+	printf("		<p>\n");
+	printf("			For the document, take a look at <a href=\"https://trac.nishi.boats/tewi\">Tewi HTTPd Trac</a>.\n");
+	printf("		</p>\n");
+	printf("		<p>\n");
+	printf("			The Webmaster of this site is free to use the image below on the Tewi HTTPd-powered Web server. Thanks for using Tewi HTTPd!\n");
+	printf("		</p>\n");
+	printf("		<div align=\"center\">\n");
+	printf("			<img src=\"/pbtewi.gif\">\n");
+	printf("		</div>\n");
+	printf("	</body>\n");
+	printf("</html>\n");
+	return 0;
+}
Index: /tags/2.02/Tool/option.c
===================================================================
--- /tags/2.02/Tool/option.c	(revision 191)
+++ /tags/2.02/Tool/option.c	(revision 191)
@@ -0,0 +1,32 @@
+/* $Id$ */
+/* This file is not intended to be in the server. */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "../config.h"
+
+int main(int argc, char** argv) {
+	if(argc < 3) {
+		return 1;
+	}
+	if(strcmp(argv[1], "cflags") == 0) {
+#ifndef NO_SSL
+		printf("-I %s/openssl/include", argv[2]);
+#endif
+	} else if(strcmp(argv[1], "ldflags") == 0) {
+#ifndef NO_SSL
+		printf("-L %s/openssl/lib", argv[2]);
+#endif
+	} else if(strcmp(argv[1], "objs") == 0) {
+#ifndef NO_SSL
+		printf("ssl.o");
+#endif
+	} else if(strcmp(argv[1], "libs") == 0) {
+#ifndef NO_SSL
+		printf("-lssl -lcrypto");
+#endif
+	}
+	printf("\n");
+	return 0;
+}
Index: /tags/2.02/config.h.tmpl
===================================================================
--- /tags/2.02/config.h.tmpl	(revision 191)
+++ /tags/2.02/config.h.tmpl	(revision 191)
@@ -0,0 +1,54 @@
+/* $Id$ */
+
+#ifndef __CONFIG_H__
+#define __CONFIG_H__
+
+#define SERVER_ADMIN "webmaster@domain"
+
+#undef NO_IPV6
+#undef NO_SSL
+#define USE_POLL
+#define HAS_CHROOT
+#undef NO_GETADDRINFO
+
+/* DO NOT EDIT BELOW THIS LINE */
+
+#ifdef NO_SSL
+#define SSL void
+#endif
+
+#if defined(__MINGW32__) && defined(USE_POLL)
+#undef USE_POLL
+/* Force select(2) for Windows */
+#endif
+
+#if defined(__MINGW32__) && defined(HAS_CHROOT)
+#undef HAS_CHROOT
+/* Windows should not have chroot */
+#endif
+
+#if (defined(_PSP) || defined(__PPU__) || defined(__ps2sdk__)) && !defined(NO_IPV6)
+#define NO_IPV6
+/* PSP/PS2/PS3 does not have IPv6 */
+#endif
+
+#if (defined(_PSP) || defined(__ps2sdk__)) && defined(USE_POLL)
+#undef USE_POLL
+/* Force select(2) for PSP/PS2 */
+#endif
+
+#if (defined(_PSP) || defined(__PPU__) || defined(__ps2sdk__)) && defined(HAS_CHROOT)
+#undef HAS_CHROOT
+/* PSP/PS2/PS3 should not have chroot */
+#endif
+
+#if (defined(_PSP) || defined(__PPU__) || defined(__ps2sdk__)) && !defined(NO_GETADDRINFO)
+#define NO_GETADDRINFO
+/* PSP/PS2/PS3 should not have getaddrinfo */
+#endif
+
+#endif
+
+/*
+vim: syntax=c
+*/
Index: /tags/2.02/cygwin-wine.sh
===================================================================
--- /tags/2.02/cygwin-wine.sh	(revision 191)
+++ /tags/2.02/cygwin-wine.sh	(revision 191)
@@ -0,0 +1,6 @@
+#!/bin/sh
+# $Id$
+
+export WINEPATH='C:\cygwin\bin'
+cat config.h.tmpl | sed -E 's/#undef (NO_IPV6|NO_SSL|NO_GETADDRINFO)/#define \1/g' > config.h
+wine bash cygwin.sh
Index: /tags/2.02/cygwin.sh
===================================================================
--- /tags/2.02/cygwin.sh	(revision 191)
+++ /tags/2.02/cygwin.sh	(revision 191)
@@ -0,0 +1,19 @@
+#!/bin/sh
+# $Id$
+rm -rf tewidist
+make PREFIX=/cygdrive/c/Tewi DESTDIR=tewidist PLATFORM=cygwin install
+cp -rfv tewidist/cygdrive/c/Tewi tewidist/Tewi
+cp /usr/bin/cygwin1.dll tewidist/Tewi/bin/
+rm -rf tewidist/cygdrive
+echo "@echo off" > tewidist/install.bat
+echo "echo This installer will attempt to overwrite files." >> tewidist/install.bat
+echo "echo Make sure you make backups." >> tewidist/install.bat
+echo "pause" >> tewidist/install.bat
+echo "xcopy /e Tewi C:\\Tewi" >> tewidist/install.bat
+echo "echo Tewi HTTPd has been installed." >> tewidist/install.bat
+echo "pause" >> tewidist/install.bat
+echo "exit" >> tewidist/install.bat
+unix2dos tewidist/install.bat
+rm -f tewidist.zip
+zip -rv tewidist.zip tewidist
+rm -rf tewidist
Index: /tags/2.02/example.conf
===================================================================
--- /tags/2.02/example.conf	(revision 191)
+++ /tags/2.02/example.conf	(revision 191)
@@ -0,0 +1,40 @@
+# $Id$
+# This is an example config
+
+ServerRoot /home/nishi/SVN/tewi/trunk
+
+#LoadModule Module/mod_cgi.so
+#LoadModule Module/mod_proxy.so
+
+Listen 80
+ListenSSL 443
+
+SSLKey key.pem
+SSLCertificate cert.pem
+
+MIMEType all application/octet-stream
+MIMEType .html text/html
+MIMEType .txt text/plain
+MIMEType .png image/png
+
+Icon all /icons/unknown.png
+Icon text/* /icons/text.png
+Icon image/* /icons/image.png
+Icon misc/dir /icons/folder.png
+Icon misc/parent /icons/parent.png
+
+DirectoryIndex index.html
+
+ReadmeFile README
+
+DocumentRoot /var/www
+
+Define test
+Undefine test
+
+BeginIf Defined test
+	ForceLog "defined"
+EndIf
+BeginIfNot Defined test
+	ForceLog "not defined"
+EndIf
Index: /tags/2.02/installer.sh
===================================================================
--- /tags/2.02/installer.sh	(revision 191)
+++ /tags/2.02/installer.sh	(revision 191)
@@ -0,0 +1,55 @@
+#!/bin/sh
+# $Id$
+
+if [ "x$1" = "x" ]; then
+	echo "Usage: $0 win32"
+	echo "       $0 win64"
+	exit 1
+fi
+
+fail() {
+	rm -f tewi-service.exe
+	rm -f tewi.exe
+	rm -f generated.conf
+	rm -f itworks.html
+	exit 1
+}
+
+rm -f install-nossl.exe install-ssl.exe
+
+VERSION=`make get-version`
+
+sed "s/undef NO_SSL/define NO_SSL/g" config.h.tmpl > config.h
+
+make clean || fail
+make PLATFORM=$1 -j4 || fail
+cp Server/tewi.exe tewi.exe
+make clean || fail
+make PLATFORM=$1-service -j4 || fail
+cp Server/tewi.exe tewi-service.exe
+cd Server
+../Tool/genconf "C:/Tewi" modules dll > ../generated.conf
+../Tool/itworks > ../itworks.html
+makensis -DVERSION=$VERSION install.nsi
+cp install.exe ../install-nossl.exe
+rm -f tewi.exe tewi-service.exe
+cd ..
+
+sed "s/define NO_SSL/undef NO_SSL/g" config.h.tmpl > config.h
+
+make clean || fail
+make PLATFORM=$1 -j4 || fail
+cp Server/tewi.exe tewi.exe
+make clean || fail
+make PLATFORM=$1-service -j4 || fail
+cp Server/tewi.exe tewi-service.exe
+cd Server
+../Tool/genconf "C:/Tewi" modules dll > ../generated.conf
+../Tool/itworks > ../itworks.html
+makensis -DVERSION=$VERSION install.nsi
+cp install.exe ../install-ssl.exe
+rm -f tewi.exe tewi-service.exe
+cd ..
+
+rm itworks.html
+rm generated.conf
Index: /tags/2.02/psp.sh
===================================================================
--- /tags/2.02/psp.sh	(revision 191)
+++ /tags/2.02/psp.sh	(revision 191)
@@ -0,0 +1,18 @@
+#!/bin/sh
+# $Id$
+export PSPDEV=/usr/local/pspdev
+export PATH=$PATH:/usr/local/pspdev/bin
+cat config.h.tmpl | sed -E 's/#undef (NO_SSL)/#define \1/g' > config.h
+rm -rf httpd
+make PLATFORM=psp DESTDIR=httpd/ install
+mv httpd/ms0:/PSP/GAME/httpd/* httpd/
+rm -rf httpd/lib httpd/bin
+rm -rf httpd/ms0:
+cp Server/tewi.pbp httpd/EBOOT.PBP
+echo "Tewi HTTPd $(make get-version) for PSP" > httpd/README
+echo "========================" >> httpd/README
+echo "To install, just copy this \`httpd' folder into your /PSP/GAME of the PSP memorystick." >> httpd/README
+cat httpd/readme
+rm -f tewidist.zip
+zip -rv tewidist.zip httpd
+rm -rf httpd
Index: /trunk/Common/log.c
===================================================================
--- /trunk/Common/log.c	(revision 190)
+++ /trunk/Common/log.c	(revision 191)
@@ -16,4 +16,8 @@
 #endif
 
+#ifdef __PPU__
+extern void tt_printf(const char* tmpl, ...);
+#endif
+
 FILE* logfile;
 
@@ -29,4 +33,6 @@
 #ifdef _PSP
 	pspDebugScreenPrintf("[%s] %s\n", date, log);
+#elif defined(__PPU__)
+	tt_printf("[%s] %s\n", date, log);
 #else
 	fprintf(logfile, "[%s] %s\n", date, log);
@@ -79,4 +85,6 @@
 #ifdef _PSP
 	pspDebugScreenPrintf("%s %s\n", namebuf, result);
+#elif defined(__PPU__)
+	tt_printf("%s %s\n", namebuf, result);
 #else
 	fprintf(logfile, "%s %s\n", namebuf, result);
Index: /trunk/Platform/ps3.mk
===================================================================
--- /trunk/Platform/ps3.mk	(revision 190)
+++ /trunk/Platform/ps3.mk	(revision 191)
@@ -7,5 +7,5 @@
 CFLAGS = -g -DPREFIX=\"$(PREFIX)\" -I $(PWD)/Common -I /usr/local/ps3dev/ppu/include
 LDFLAGS = -L /usr/local/ps3dev/ppu/lib
-LIBS = -lnet -lsysmodule -lsysutil -lrt -llv2
+LIBS = -lnet -lsysmodule -lsysutil -lrt -llv2 -lrsx -lgcm_sys
 EXEC = .elf
 LIB = .so
Index: /trunk/Server/Makefile
===================================================================
--- /trunk/Server/Makefile	(revision 190)
+++ /trunk/Server/Makefile	(revision 191)
@@ -6,5 +6,5 @@
 .SUFFIXES: .c .o
 
-OBJS = main.o version.o config.o server.o http.o module.o strptime.o $(EXTOBJS) $(PREOBJS)
+OBJS = main.o version.o config.o server.o http.o module.o strptime.o font.o $(EXTOBJS) $(PREOBJS)
 
 all: tewi$(EXEC) $(TARGET)
Index: /trunk/Server/font.c
===================================================================
--- /trunk/Server/font.c	(revision 191)
+++ /trunk/Server/font.c	(revision 191)
@@ -0,0 +1,287 @@
+/* $Id$ */
+
+unsigned char font[] = {
+    // 0x20
+    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x21 !
+    0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000, 0b00100000, 0b00000000,
+
+    // 0x22 "
+    0b01001000, 0b01001000, 0b01001000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x23 #
+    0b01010000, 0b11111000, 0b01010000, 0b01010000, 0b01010000, 0b11111000, 0b01010000, 0b00000000,
+
+    // 0x24 $
+    0b00100000, 0b11111000, 0b10100000, 0b11111000, 0b00101000, 0b11111000, 0b00100000, 0b00000000,
+
+    // 0x25 %
+    0b00000000, 0b10001000, 0b10010000, 0b00100000, 0b01001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x25 &
+    0b01000000, 0b10100000, 0b10100000, 0b01011000, 0b10100000, 0b10100000, 0b01011000, 0b00000000,
+
+    // 0x27 '
+    0b00010000, 0b00010000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x28 (
+    0b00001000, 0b00010000, 0b00100000, 0b00100000, 0b00100000, 0b00010000, 0b00001000, 0b00000000,
+
+    // 0x29 )
+    0b01000000, 0b00100000, 0b00010000, 0b00010000, 0b00010000, 0b00100000, 0b01000000, 0b00000000,
+
+    // 0x2a *
+    0b00100000, 0b10101000, 0b01110000, 0b00100000, 0b01110000, 0b10101000, 0b00100000, 0b00000000,
+
+    // 0x2b +
+    0b00100000, 0b00100000, 0b00100000, 0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x2c ,
+    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00010000, 0b00100000, 0b00000000,
+
+    // 0x2d -
+    0b00000000, 0b00000000, 0b00000000, 0b11111000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x2e .
+    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00010000, 0b00010000, 0b00000000,
+
+    // 0x2f /
+    0b00000000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x30 0
+    0b01110000, 0b10001000, 0b10001000, 0b10101000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x31 1
+    0b00100000, 0b01100000, 0b10100000, 0b00100000, 0b00100000, 0b00100000, 0b11111000, 0b00000000,
+
+    // 0x32 2
+    0b01110000, 0b10001000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b11111000, 0b00000000,
+
+    // 0x33 3
+    0b01110000, 0b10001000, 0b00001000, 0b00110000, 0b00001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x34 4
+    0b00011000, 0b00101000, 0b01001000, 0b10001000, 0b11111000, 0b00001000, 0b00001000, 0b00000000,
+
+    // 0x35 5
+    0b11111000, 0b10000000, 0b10000000, 0b11110000, 0b00001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x36 6
+    0b01110000, 0b10001000, 0b10000000, 0b11110000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x37 7
+    0b11111000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x38 8
+    0b01110000, 0b10001000, 0b10001000, 0b01110000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x39 9
+    0b01110000, 0b10001000, 0b10001000, 0b01111000, 0b00001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x3a :
+    0b00010000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00010000, 0b00000000,
+
+    // 0x3b ;
+    0b00010000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00100000, 0b00000000,
+
+    // 0x3c <
+    0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b01000000, 0b00100000, 0b00010000, 0b00000000,
+
+    // 0x3d =
+    0b00000000, 0b00000000, 0b11111000, 0b00000000, 0b11111000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x3e >
+    0b01000000, 0b00100000, 0b00010000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b00000000,
+
+    // 0x3f ?
+    0b01110000, 0b10001000, 0b10001000, 0b00110000, 0b00100000, 0b00000000, 0b00100000, 0b00000000,
+
+    // 0x40 @
+    0b01110000, 0b10001000, 0b10101000, 0b10111000, 0b10000000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x41 A
+    0b00100000, 0b01010000, 0b10001000, 0b11111000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x42 B
+    0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b00000000,
+
+    // 0x43 C
+    0b01110000, 0b10001000, 0b10000000, 0b10000000, 0b10000000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x44 D
+    0b11110000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b11110000, 0b00000000,
+
+    // 0x45 E
+    0b11111000, 0b10000000, 0b10000000, 0b11111000, 0b10000000, 0b10000000, 0b11111000, 0b00000000,
+
+    // 0x46 F
+    0b11111000, 0b10000000, 0b10000000, 0b11111000, 0b10000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x47 G
+    0b01110000, 0b10001000, 0b10000000, 0b10111000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x48 H
+    0b10001000, 0b10001000, 0b10001000, 0b11111000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x49 I
+    0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b11111000, 0b00000000,
+
+    // 0x4a J
+    0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x4b K
+    0b10001000, 0b10010000, 0b10100000, 0b11000000, 0b10100000, 0b10010000, 0b10001000, 0b00000000,
+
+    // 0x4c L
+    0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b11111000, 0b00000000,
+
+    // 0x4d M
+    0b10001000, 0b11011000, 0b10101000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x4e N
+    0b10001000, 0b10001000, 0b11001000, 0b10101000, 0b10011000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x4f O
+    0b01110000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x50 P
+    0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b10000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x51 Q
+    0b01110000, 0b10001000, 0b10001000, 0b10101000, 0b01110000, 0b00010000, 0b00001000, 0b00000000,
+
+    // 0x52 R
+    0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x53 S
+    0b01110000, 0b10001000, 0b10000000, 0b01110000, 0b00001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x54 T
+    0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x55 U
+    0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x56 V
+    0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b01010000, 0b00100000, 0b00000000,
+
+    // 0x57 W
+    0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b10101000, 0b11011000, 0b10001000, 0b00000000,
+
+    // 0x58 X
+    0b10001000, 0b10001000, 0b01010000, 0b00100000, 0b01010000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x59 Y
+    0b10001000, 0b01010000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x5a Z
+    0b11111000, 0b00001000, 0b00010000, 0b00100000, 0b01000000, 0b10000000, 0b11111000, 0b00000000,
+
+    // 0x5b [
+    0b11110000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b10000000, 0b11110000, 0b00000000,
+
+    // 0x5c backslash
+    0b00000000, 0b10000000, 0b01000000, 0b00100000, 0b00010000, 0b00001000, 0b00001000, 0b00000000,
+
+    // 0x5d ]
+    0b01111000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b01111000, 0b00000000,
+
+    // 0x5e ^
+    0b00100000, 0b01010000, 0b10001000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x5f _
+    0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b11111000, 0b00000000,
+
+    // 0x60 `
+    0b00100000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
+
+    // 0x61 a
+    0b00000000, 0b00000000, 0b01110000, 0b00001000, 0b01111000, 0b10001000, 0b01111000, 0b00000000,
+
+    // 0x62 b
+    0b10000000, 0b10000000, 0b10000000, 0b11110000, 0b10001000, 0b10001000, 0b11110000, 0b00000000,
+
+    // 0x63 c
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b10000000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x64 d
+    0b00001000, 0b00001000, 0b00001000, 0b01111000, 0b10001000, 0b10001000, 0b01111000, 0b00000000,
+
+    // 0x65 e
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b11111000, 0b10000000, 0b01111000, 0b00000000,
+
+    // 0x66 f
+    0b00011000, 0b00100000, 0b00100000, 0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x67 g
+    0b00000000, 0b00000000, 0b01111000, 0b10001000, 0b01111000, 0b00001000, 0b11110000, 0b00000000,
+
+    // 0x68 h
+    0b10000000, 0b10000000, 0b11110000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x69 i
+    0b00100000, 0b00000000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x6a j
+    0b00000000, 0b00000000, 0b00100000, 0b00000000, 0b00100000, 0b10100000, 0b01000000, 0b00000000,
+
+    // 0x6b k
+    0b10000000, 0b10000000, 0b10010000, 0b10100000, 0b11000000, 0b10100000, 0b10010000, 0b00000000,
+
+    // 0x6c l
+    0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x6d m
+    0b00000000, 0b00000000, 0b11010000, 0b10101000, 0b10101000, 0b10101000, 0b10101000, 0b00000000,
+
+    // 0x6e n
+    0b00000000, 0b00000000, 0b10110000, 0b11001000, 0b10001000, 0b10001000, 0b10001000, 0b00000000,
+
+    // 0x6f o
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b10001000, 0b10001000, 0b01110000, 0b00000000,
+
+    // 0x70 p
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b11110000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x71 q
+    0b00000000, 0b00000000, 0b01110000, 0b10001000, 0b01111000, 0b00001000, 0b00001000, 0b00000000,
+
+    // 0x72 r
+    0b00000000, 0b00000000, 0b10110000, 0b11001000, 0b10000000, 0b10000000, 0b10000000, 0b00000000,
+
+    // 0x73 s
+    0b00000000, 0b00000000, 0b01110000, 0b10000000, 0b01110000, 0b00001000, 0b01110000, 0b00000000,
+
+    // 0x74 t
+    0b00100000, 0b00100000, 0b11111000, 0b00100000, 0b00100000, 0b00100000, 0b00011000, 0b00000000,
+
+    // 0x75 u
+    0b00000000, 0b00000000, 0b10001000, 0b10001000, 0b10001000, 0b10001000, 0b01111000, 0b00000000,
+
+    // 0x76 v
+    0b00000000, 0b00000000, 0b10001000, 0b10001000, 0b10001000, 0b01010000, 0b00100000, 0b00000000,
+
+    // 0x77 w
+    0b00000000, 0b00000000, 0b10001000, 0b10001000, 0b10101000, 0b11011000, 0b10001000, 0b00000000,
+
+    // 0x78 x
+    0b00000000, 0b00000000, 0b10001000, 0b01010000, 0b00100000, 0b01010000, 0b10001000, 0b00000000,
+
+    // 0x79 y
+    0b00000000, 0b00000000, 0b10001000, 0b01010000, 0b00100000, 0b01000000, 0b10000000, 0b00000000,
+
+    // 0x7a z
+    0b00000000, 0b00000000, 0b11111000, 0b00010000, 0b00100000, 0b01000000, 0b11111000, 0b00000000,
+
+    // 0x7b {
+    0b00110000, 0b01000000, 0b01000000, 0b10000000, 0b01000000, 0b01000000, 0b00110000, 0b00000000,
+
+    // 0x7c |
+    0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00100000, 0b00000000,
+
+    // 0x7d }
+    0b01100000, 0b00010000, 0b00010000, 0b00001000, 0b00010000, 0b00010000, 0b01100000, 0b00000000,
+
+    // 0x7e ~
+    0b00000000, 0b00000000, 0b01000000, 0b10101000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, 0b00000000};
Index: /trunk/Server/main.c
===================================================================
--- /trunk/Server/main.c	(revision 190)
+++ /trunk/Server/main.c	(revision 191)
@@ -47,4 +47,14 @@
 #define printf(...) scr_printf(__VA_ARGS__)
 #define STDERR_LOG(...) scr_printf(__VA_ARGS__)
+#elif defined(__PPU__)
+#include <rsx/gcm_sys.h>
+#include <rsx/rsx.h>
+#include <sysutil/video.h>
+#include <malloc.h>
+#include <sys/thread.h>
+#include <stdarg.h>
+
+#define printf(...) tt_printf(__VA_ARGS__)
+#define STDERR_LOG(...) tt_printf(__VA_ARGS__)
 #else
 #define STDERR_LOG(...) fprintf(stderr, __VA_ARGS__)
@@ -114,4 +124,294 @@
 	return 0;
 }
+#endif
+
+#ifdef __PPU__
+uint32_t depth_pitch;
+uint32_t depth_offset;
+uint32_t* depth_buffer;
+
+#define CB_SIZE 0x100000
+#define HOST_SIZE (32 * 1024 * 1024)
+
+struct rsx_buffer {
+	int width, height, id;
+	uint32_t* ptr;
+	uint32_t offset;
+};
+
+void wait_rsx(gcmContextData* ctx, uint32_t label) {
+	rsxSetWriteBackendLabel(ctx, GCM_INDEX_TYPE_32B, label);
+
+	rsxFlushBuffer(ctx);
+
+	while(*(uint32_t*)gcmGetLabelAddress(GCM_INDEX_TYPE_32B) != label) usleep(50);
+
+	label++;
+}
+
+void wait_rsx_until_idle(gcmContextData* ctx) {
+	uint32_t label = 1;
+	rsxSetWriteBackendLabel(ctx, GCM_INDEX_TYPE_32B, label);
+	rsxSetWaitLabel(ctx, GCM_INDEX_TYPE_32B, label);
+	label++;
+	wait_rsx(ctx, label);
+}
+
+void get_resolution(int* width, int* height) {
+	videoState state;
+	videoResolution res;
+	if(videoGetState(0, 0, &state) != 0) {
+		return;
+	}
+
+	if(state.state != 0) {
+		return;
+	}
+
+	if(videoGetResolution(state.displayMode.resolution, &res) != 0) {
+		return;
+	}
+	*width = res.width;
+	*height = res.height;
+}
+
+void make_buffer(struct rsx_buffer* buffer, int id) {
+	int w, h;
+	get_resolution(&w, &h);
+
+	buffer->ptr = (uint32_t*)rsxMemalign(64, 4 * w * h);
+	if(buffer->ptr == NULL) return;
+
+	if(rsxAddressToOffset(buffer->ptr, &buffer->offset) != 0) return;
+
+	if(gcmSetDisplayBuffer(id, buffer->offset, 4 * w, w, h) != 0) return;
+	buffer->width = w;
+	buffer->height = h;
+	buffer->id = id;
+}
+
+gcmContextData* init_screen(void) {
+	void* host = memalign(1024 * 1024, HOST_SIZE);
+	gcmContextData* ctx = NULL;
+	videoState state;
+	videoConfiguration vconfig;
+	videoResolution res;
+	rsxInit(&ctx, CB_SIZE, HOST_SIZE, host);
+	if(ctx == NULL) {
+		free(host);
+		return NULL;
+	}
+
+	if(videoGetState(0, 0, &state) != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+
+	if(state.state != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+
+	if(videoGetResolution(state.displayMode.resolution, &res) != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+
+	memset(&vconfig, 0, sizeof(vconfig));
+	vconfig.resolution = state.displayMode.resolution;
+	vconfig.format = VIDEO_BUFFER_FORMAT_XRGB;
+	vconfig.pitch = res.width * 4;
+	vconfig.aspect = state.displayMode.aspect;
+
+	wait_rsx_until_idle(ctx);
+
+	if(videoConfigure(0, &vconfig, NULL, 0) != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+
+	if(videoGetState(0, 0, &state) != 0) {
+		rsxFinish(ctx, 0);
+		free(host);
+		return NULL;
+	}
+	gcmSetFlipMode(GCM_FLIP_VSYNC);
+
+	depth_pitch = res.width * 4;
+	depth_buffer = (uint32_t*)rsxMemalign(64, (res.height * depth_pitch) * 2);
+	rsxAddressToOffset(depth_buffer, &depth_offset);
+
+	gcmResetFlipStatus();
+
+	return ctx;
+}
+
+void set_render_target(gcmContextData* context, struct rsx_buffer* buffer) {
+	gcmSurface sf;
+
+	sf.colorFormat = GCM_SURFACE_X8R8G8B8;
+	sf.colorTarget = GCM_SURFACE_TARGET_0;
+	sf.colorLocation[0] = GCM_LOCATION_RSX;
+	sf.colorOffset[0] = buffer->offset;
+	sf.colorPitch[0] = depth_pitch;
+
+	sf.colorLocation[1] = GCM_LOCATION_RSX;
+	sf.colorLocation[2] = GCM_LOCATION_RSX;
+	sf.colorLocation[3] = GCM_LOCATION_RSX;
+	sf.colorOffset[1] = 0;
+	sf.colorOffset[2] = 0;
+	sf.colorOffset[3] = 0;
+	sf.colorPitch[1] = 64;
+	sf.colorPitch[2] = 64;
+	sf.colorPitch[3] = 64;
+
+	sf.depthFormat = GCM_SURFACE_ZETA_Z16;
+	sf.depthLocation = GCM_LOCATION_RSX;
+	sf.depthOffset = depth_offset;
+	sf.depthPitch = depth_pitch;
+
+	sf.type = GCM_TEXTURE_LINEAR;
+	sf.antiAlias = GCM_SURFACE_CENTER_1;
+
+	sf.width = buffer->width;
+	sf.height = buffer->height;
+	sf.x = 0;
+	sf.y = 0;
+
+	rsxSetSurface(context, &sf);
+}
+
+void wait_flip(void) {
+	while(gcmGetFlipStatus() != 0) usleep(200);
+	gcmResetFlipStatus();
+}
+
+void flip(gcmContextData* ctx, uint32_t buffer) {
+	if(gcmSetFlip(ctx, buffer) == 0) {
+		rsxFlushBuffer(ctx);
+		gcmSetWaitFlip(ctx);
+	}
+}
+
+uint8_t* tvram;
+
+extern uint8_t font[];
+
+int tt_x = 0;
+int tt_y = 0;
+int tt_width;
+int tt_height;
+
+void tt_putstr(const char* str) {
+	int i;
+	for(i = 0; str[i] != 0; i++){
+		tvram[tt_y * tt_width + tt_x] = str[i];
+		if(str[i] == '\n'){
+			tt_x = 0;
+			tt_y++;
+		}else{
+			tt_x++;
+			if(tt_x == tt_width){
+				tt_x = 0;
+				tt_y++;
+			}
+		}
+		if(tt_y == tt_height){
+			tt_y--;
+			int x, y;
+			for(y = 0; y < tt_height - 1; y++){
+				for(x = 0; x < tt_width; x++){
+					tvram[y * tt_width + x] = tvram[(y + 1) * tt_width + x];
+				}
+			}
+			for(x = 0; x < tt_width; x++){
+				tvram[(tt_height - 1) * tt_width + x] = 0;
+			}
+		}
+	}
+}
+
+void tt_putchar(struct rsx_buffer* buffer, int x, int y, uint8_t c){
+	int i, j;
+	if(c < 0x20) c = 0x20;
+	if(c >= 0x7f) c = 0x20;
+	for(i = 0; i < 7; i++){
+		uint8_t l = font[(c - 0x20) * 8 + i];
+		for(j = 0; j < 5; j++){
+			uint32_t c = 0;
+			if(l & (1 << 7)){
+				c = 0xffffff;
+			}
+			l = l << 1;
+			buffer->ptr[(y * 8 + i) * buffer->width + x * 6 + j] = c;
+		}
+	}
+}
+
+void draw(struct rsx_buffer* buffer, int current) {
+	int i, j, c;
+	for(i = 0; i < buffer->height / 8; i++) {
+		for(j = 0; j < buffer->width / 6; j++) {
+			uint8_t c = tvram[i * (buffer->width / 6) + j];
+			tt_putchar(buffer, j, i, c);
+		}
+	}
+}
+
+#define BUFFERS 2
+gcmContextData* ctx;
+struct rsx_buffer buffers[BUFFERS];
+
+void text_thread(void* arg) {
+	int current = 0;
+	while(1) {
+		wait_flip();
+		draw(&buffers[current], current);
+		flip(ctx, buffers[current].id);
+		current++;
+		if(current >= BUFFERS) current = 0;
+	}
+}
+
+void tt_printf(const char* tmpl, ...) {
+	va_list va;
+	va_start(va, tmpl);
+	int i;
+	char cbuf[2];
+	cbuf[1] = 0;
+	char* log = cm_strdup("");
+	for(i = 0; tmpl[i] != 0; i++) {
+		if(tmpl[i] == '%') {
+			i++;
+			if(tmpl[i] == 's'){
+				char* tmp = log;
+				log = cm_strcat(tmp, va_arg(va, char*));
+				free(tmp);
+			}else if(tmpl[i] == 'd'){
+				char buf[513];
+				sprintf(buf, "%d", va_arg(va, int));
+				char* tmp = log;
+				log = cm_strcat(tmp, buf);
+				free(tmp);
+			}else if(tmpl[i] == '%'){
+				char* tmp = log;
+				log = cm_strcat(tmp, "%");
+				free(tmp);
+			}
+		} else {
+			cbuf[0] = tmpl[i];
+			char* tmp = log;
+			log = cm_strcat(tmp, cbuf);
+			free(tmp);
+		}
+	}
+	va_end(va);
+	tt_putstr(log);
+}
+
 #endif
 
@@ -249,4 +549,15 @@
 	printf("Connected, My IP is %s\n", info.ip);
 #elif defined(__PPU__)
+	int i;
+	ctx = init_screen();
+	int w, h;
+	get_resolution(&w, &h);
+	tt_width = w / 6;
+	tt_height = h / 8;
+	tvram = malloc((w / 6) * (h / 8));
+	for(i = 0; i < BUFFERS; i++) make_buffer(&buffers[i], i);
+	flip(ctx, BUFFERS - 1);
+	sys_ppu_thread_t id;
+	sysThreadCreate(&id, text_thread, NULL, 1500, 0x1000, THREAD_JOINABLE, "TextThread");
 	printf("PS3 Bootstrap, Tewi/%s\n", tw_get_version());
 	netInitialize();
@@ -265,4 +576,9 @@
 		sceKernelExitGame();
 #else
+#ifdef __PPU__
+		printf("Error code %d\n", st);
+		while(1)
+			;
+#endif
 		return st;
 #endif
