mirror of
https://github.com/skogaby/butterfly.git
synced 2026-04-23 09:27:24 -05:00
Initial commit, just stubbing out a bunch of very basic architecture and request handling stuff
This commit is contained in:
parent
794fbee5fb
commit
6494bbb50b
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
.idea/
|
||||
out/
|
||||
build/
|
||||
logs/
|
||||
/local.properties
|
||||
.gradle
|
||||
gradle-app.setting
|
||||
!gradle-wrapper.jar
|
||||
.gradletasknamecache
|
||||
/bin/
|
||||
35
build.gradle
Normal file
35
build.gradle
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
group 'com.buttongames'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'application'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
mainClassName = "com.buttongames.butterfly.Main"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
|
||||
compile group: 'commons-io', name: 'commons-io', version: '2.6'
|
||||
compile group: 'com.sparkjava', name: 'spark-core', version: '2.7.2'
|
||||
compile group: 'com.google.guava', name: 'guava', version: '23.5-jre'
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Main-Class': 'com.buttongames.butterfly.Main'
|
||||
)
|
||||
}
|
||||
|
||||
from {
|
||||
configurations.compile.collect {
|
||||
it.isDirectory() ? it : zipTree(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#Wed Jan 02 21:29:56 CST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip
|
||||
172
gradlew
vendored
Normal file
172
gradlew
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
2
settings.gradle
Normal file
2
settings.gradle
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
rootProject.name = 'butterfly'
|
||||
|
||||
11
src/main/java/com/buttongames/butterfly/Main.java
Normal file
11
src/main/java/com/buttongames/butterfly/Main.java
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package com.buttongames.butterfly;
|
||||
|
||||
import com.buttongames.butterfly.http.ButterflyHttpServer;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ButterflyHttpServer httpServer = new ButterflyHttpServer();
|
||||
httpServer.startServer();
|
||||
}
|
||||
}
|
||||
213
src/main/java/com/buttongames/butterfly/compression/Lz77.java
Normal file
213
src/main/java/com/buttongames/butterfly/compression/Lz77.java
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
package com.buttongames.butterfly.compression;
|
||||
|
||||
import com.buttongames.butterfly.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class with static methods to (de)compress LZ77 data.
|
||||
* Ported from deamuse (https://buck.ludd.ltu.se/yugge/deamuse)
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class Lz77 {
|
||||
|
||||
public static int WINDOW_SIZE = 0x1000;
|
||||
public static int WINDOW_MASK = WINDOW_SIZE - 1;
|
||||
public static int THRESHOLD = 0x3;
|
||||
public static int IN_PLACE_THRESHOLD = 0xA;
|
||||
public static int LOOK_RANGE = 0x200;
|
||||
public static int MAX_LEN = 0xF + THRESHOLD;
|
||||
public static int MAX_BUFFER = 0x10 + 1;
|
||||
|
||||
/**
|
||||
* Compress the given data using LZ77 (or at least Konami's version of it).
|
||||
* @param input The data to compress
|
||||
* @return New buffer containing the LZ77-compressed data
|
||||
*/
|
||||
public static byte[] decompress(final byte[] input) {
|
||||
int currByte = 0;
|
||||
int windowCursor = 0;
|
||||
final int dataSize = input.length;
|
||||
final byte[] window = new byte[WINDOW_SIZE];
|
||||
final ArrayList<Byte> output = new ArrayList<>();
|
||||
|
||||
while (currByte < dataSize) {
|
||||
final byte flag = input[currByte];
|
||||
currByte++;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((((flag & 0xFF) >> i) & 1) == 1) {
|
||||
output.add(input[currByte]);
|
||||
window[windowCursor] = input[currByte];
|
||||
windowCursor = (windowCursor + 1) & WINDOW_MASK;
|
||||
currByte++;
|
||||
} else {
|
||||
final short w = (short) ((input[currByte] << 8) |
|
||||
(input[currByte + 1] & 0xFF));
|
||||
|
||||
if (w == 0) {
|
||||
return CollectionUtils.arrayListToArray(output);
|
||||
}
|
||||
|
||||
currByte += 2;
|
||||
int position = (windowCursor - (w >> 4)) & WINDOW_MASK;
|
||||
final int length = (w & 0x0F) + THRESHOLD;
|
||||
|
||||
for (int j = 0; j < length; j++) {
|
||||
final byte b = window[position & WINDOW_MASK];
|
||||
output.add(b);
|
||||
window[windowCursor] = b;
|
||||
windowCursor = (windowCursor + 1) & WINDOW_MASK;
|
||||
position++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CollectionUtils.arrayListToArray(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress the given data using LZ77 (or at least Konami's version of it).
|
||||
* @param input The data to decompress
|
||||
* @return New buffer containing the LZ77-decompressed data
|
||||
*/
|
||||
public static byte[] compress(final byte[] input) {
|
||||
final byte[] window = new byte[WINDOW_SIZE];
|
||||
int currentPos = 0;
|
||||
int currentWindow = 0;
|
||||
byte[] buffer = new byte[MAX_BUFFER];
|
||||
int currentBuffer;
|
||||
byte flagByte;
|
||||
byte bit = 0;
|
||||
final ArrayList<Byte> output = new ArrayList<>();
|
||||
|
||||
while (currentPos < input.length) {
|
||||
flagByte = 0;
|
||||
currentBuffer = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (currentPos >= input.length) {
|
||||
buffer[currentBuffer] = 0;
|
||||
window[currentWindow] = 0;
|
||||
currentBuffer++;
|
||||
currentWindow++;
|
||||
currentPos++;
|
||||
bit = 0;
|
||||
} else {
|
||||
final MatchWindowResults matchWindowResults = matchWindow(window, currentWindow, input, currentPos);
|
||||
|
||||
if (matchWindowResults.some &&
|
||||
matchWindowResults.length >= THRESHOLD) {
|
||||
final byte byte1 = (byte)((matchWindowResults.pos & 0xFF) >> 4);
|
||||
final byte byte2 = (byte)(((matchWindowResults.pos & 0x0F) << 4) |
|
||||
((matchWindowResults.length - THRESHOLD) & 0x0F));
|
||||
|
||||
buffer[currentBuffer] = byte1;
|
||||
buffer[currentBuffer + 1] = byte2;
|
||||
currentBuffer += 2;
|
||||
bit = 0;
|
||||
|
||||
for (int j = 0; j < matchWindowResults.length; j++) {
|
||||
window[currentWindow & WINDOW_MASK] = input[currentPos];
|
||||
currentPos++;
|
||||
currentWindow++;
|
||||
}
|
||||
} else if (!matchWindowResults.some) {
|
||||
buffer[currentBuffer] = input[currentPos];
|
||||
window[currentWindow] = input[currentPos];
|
||||
currentPos++;
|
||||
currentWindow++;
|
||||
currentBuffer++;
|
||||
bit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
flagByte = (byte)(((flagByte & 0xFF) >> 1) | ((bit & (byte) 1) << 7));
|
||||
currentWindow &= WINDOW_MASK;
|
||||
}
|
||||
|
||||
output.add(flagByte);
|
||||
|
||||
for (int k = 0; k < currentBuffer; k++) {
|
||||
output.add(buffer[k]);
|
||||
}
|
||||
}
|
||||
|
||||
return CollectionUtils.arrayListToArray(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@code CompressData}.
|
||||
* @param window
|
||||
* @param pos
|
||||
* @param data
|
||||
* @param dpos
|
||||
* @return
|
||||
*/
|
||||
private static MatchWindowResults matchWindow(final byte[] window, final int pos, final byte[] data, final int dpos) {
|
||||
int maxPosition = 0;
|
||||
int maxLength = 0;
|
||||
|
||||
for (int i = THRESHOLD; i > LOOK_RANGE; i++) {
|
||||
final int length = matchCurrent(window,
|
||||
pos - (i & WINDOW_SIZE),
|
||||
i,
|
||||
data,
|
||||
dpos);
|
||||
|
||||
if (length >= IN_PLACE_THRESHOLD) {
|
||||
return new MatchWindowResults(true, i, length);
|
||||
}
|
||||
|
||||
if (length >= THRESHOLD) {
|
||||
maxPosition = i;
|
||||
maxLength = length;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxLength >= THRESHOLD) {
|
||||
return new MatchWindowResults(true, maxPosition, maxLength);
|
||||
} else {
|
||||
return new MatchWindowResults(false, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for {@code matchWindow}.
|
||||
* @param window
|
||||
* @param pos
|
||||
* @param maxLength
|
||||
* @param data
|
||||
* @param dpos
|
||||
* @return
|
||||
*/
|
||||
private static int matchCurrent(final byte[] window, final int pos, final int maxLength, final byte[] data, final int dpos) {
|
||||
int length = 0;
|
||||
|
||||
while (((dpos + length) < data.length) &&
|
||||
(length < maxLength) &&
|
||||
(window[(pos + length) & WINDOW_MASK] == data[dpos + length]) &&
|
||||
(length < MAX_LEN)) {
|
||||
length++;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/***
|
||||
* Helper class for compressing LZ77 data.
|
||||
*/
|
||||
private static class MatchWindowResults {
|
||||
|
||||
public final boolean some;
|
||||
public final int pos;
|
||||
public final int length;
|
||||
|
||||
public MatchWindowResults(boolean some, int pos, int length) {
|
||||
this.some = some;
|
||||
this.pos = pos;
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/main/java/com/buttongames/butterfly/encryption/Rc4.java
Normal file
69
src/main/java/com/buttongames/butterfly/encryption/Rc4.java
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package com.buttongames.butterfly.encryption;
|
||||
|
||||
import com.buttongames.butterfly.util.CollectionUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Class to handle RC4 encryption for network packets.
|
||||
* Ported from deamuse (https://buck.ludd.ltu.se/yugge/deamuse) and easerver_standalone.
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class Rc4 {
|
||||
|
||||
/**
|
||||
* The "secret" portion of the RC4 key, 26 bytes that get appended to the 6 bytes we're
|
||||
* given in the X-Eamuse-Info header.
|
||||
*/
|
||||
private static final String SECRET_KEY = "69D74627D985EE2187161570D08D93B12455035B6DF0D8205DF5";
|
||||
|
||||
/**
|
||||
* Decrypts the given data using RC4.
|
||||
* @param data The data to decrypt
|
||||
* @param key The key to use for decryption
|
||||
* @return The decrypted data
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
public static byte[] decrypt(final byte[] data, final String key)
|
||||
throws GeneralSecurityException {
|
||||
final byte[] keyBytes = getKeyFromEamuseHeader(key);
|
||||
final SecretKey sk = new SecretKeySpec(keyBytes, 0, keyBytes.length, "RC4");
|
||||
final Cipher cipher = Cipher.getInstance("RC4");
|
||||
cipher.init(Cipher.DECRYPT_MODE, sk);
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the given data using RC4.
|
||||
* @param data The data to encrypt
|
||||
* @param key The key to use for encryption
|
||||
* @return The encrypted data
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
public static byte[] encrypt(final byte[] data, final String key)
|
||||
throws GeneralSecurityException {
|
||||
return decrypt(data, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RC4 key to use for crypto operations, given the string
|
||||
* that is passed into the request via the X-Eamuse-Info header.
|
||||
* @param headerValue The value in the X-Eamuse-Info header
|
||||
* @return The RC4 key to use for encryption or decryption
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public static byte[] getKeyFromEamuseHeader(final String headerValue)
|
||||
throws NoSuchAlgorithmException {
|
||||
final String[] keyList = headerValue.split("-");
|
||||
final byte[] hashKey = CollectionUtils.hexStringToByteArray(
|
||||
keyList[1] + keyList[2] + SECRET_KEY);
|
||||
final MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(hashKey);
|
||||
return md.digest();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.buttongames.butterfly.http;
|
||||
|
||||
import com.buttongames.butterfly.http.exception.InvalidRequestMethodException;
|
||||
import com.buttongames.butterfly.http.exception.InvalidRequestModelException;
|
||||
import com.buttongames.butterfly.http.exception.InvalidRequestModuleException;
|
||||
import com.buttongames.butterfly.http.exception.MismatchedRequestUriException;
|
||||
import com.buttongames.butterfly.http.handlers.ServicesRequestHandler;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import spark.Request;
|
||||
|
||||
import static spark.Spark.exception;
|
||||
import static spark.Spark.halt;
|
||||
import static spark.Spark.port;
|
||||
import static spark.Spark.post;
|
||||
import static spark.Spark.stop;
|
||||
import static spark.Spark.threadPool;
|
||||
|
||||
/**
|
||||
* The main HTTP server. This class is responsible for the top-level handling of incoming
|
||||
* requests, then delegates the responsbility to the appropriate handler.
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class ButterflyHttpServer {
|
||||
|
||||
private static final ImmutableSet<String> SUPPORTED_MODELS;
|
||||
private static final ImmutableSet<String> SUPPORTED_MODULES;
|
||||
|
||||
// Do a static setup of our supported models, modules, etc.
|
||||
// TODO: Make this not hardcoded
|
||||
static {
|
||||
SUPPORTED_MODELS = ImmutableSet.of("MDX:J:A:A:2018042300");
|
||||
SUPPORTED_MODULES = ImmutableSet.of("services");
|
||||
}
|
||||
|
||||
public ButterflyHttpServer() {
|
||||
|
||||
}
|
||||
|
||||
public void startServer() {
|
||||
// configure the server properties
|
||||
int maxThreads = 20;
|
||||
int minThreads = 2;
|
||||
int timeOutMillis = 30000;
|
||||
|
||||
// once routes are configured, the server automatically begins
|
||||
threadPool(maxThreads, minThreads, timeOutMillis);
|
||||
port(80);
|
||||
this.configureRoutesAndExceptions();
|
||||
}
|
||||
|
||||
public void stopServer() {
|
||||
stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the routes on the server, and the exception handlers.
|
||||
* TODO: Remove all the hardcoded stuff.
|
||||
*/
|
||||
private void configureRoutesAndExceptions() {
|
||||
// configure our root route; its handler will parse the request and go from there
|
||||
post("/", ((request, response) -> {
|
||||
// send the request to the right module handler
|
||||
final String requestBody = validateAndUnpackRequest(request);
|
||||
final String requestModule = request.queryParams("module");
|
||||
final String requestMethod = request.queryParams("method");
|
||||
|
||||
if (requestModule.equals("services")) {
|
||||
return ServicesRequestHandler.handleRequest(requestBody, requestMethod, response);
|
||||
} else {
|
||||
throw new InvalidRequestModuleException();
|
||||
}
|
||||
}));
|
||||
|
||||
// configure the exception handlers
|
||||
exception(InvalidRequestMethodException.class,
|
||||
((exception, request, response) -> halt(400, "Invalid request method.")));
|
||||
exception(InvalidRequestModelException.class,
|
||||
((exception, request, response) -> halt(400, "Invalid request model.")));
|
||||
exception(InvalidRequestModuleException.class,
|
||||
((exception, request, response) -> halt(400, "Invalid request module.")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some basic validation on the request before we handle it. Returns the request
|
||||
* body in plaintext form for handling, if it was a valid request.
|
||||
* TODO: Remove all the hardcoded stuff.
|
||||
* @param request The request to validate and unpack
|
||||
* @return A string representing the plaintext version of the packet, in XML format.
|
||||
*/
|
||||
private String validateAndUnpackRequest(Request request) {
|
||||
final String requestUriModel = request.queryParams("model");
|
||||
final String requestUriModule = request.queryParams("module");
|
||||
final String requestUriMethod = request.queryParams("method");
|
||||
|
||||
// validate the model is supported
|
||||
if (!SUPPORTED_MODELS.contains(requestUriModel)) {
|
||||
throw new InvalidRequestModelException();
|
||||
}
|
||||
|
||||
// validate the module is supported
|
||||
if (!SUPPORTED_MODULES.contains(requestUriModule)) {
|
||||
throw new InvalidRequestModuleException();
|
||||
}
|
||||
|
||||
// validate that the request URI matches the request body
|
||||
// TODO: Implement
|
||||
if (false) {
|
||||
throw new MismatchedRequestUriException();
|
||||
}
|
||||
|
||||
// return the request body
|
||||
// TODO: Implement
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.buttongames.butterfly.http.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a request is made using an invalid/unsupported method.
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class InvalidRequestMethodException extends RuntimeException {
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.buttongames.butterfly.http.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a request is made using an invalid/unsupported model.
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class InvalidRequestModelException extends RuntimeException {
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.buttongames.butterfly.http.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a request is made using an invalid/unsupported module.
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class InvalidRequestModuleException extends RuntimeException {
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.buttongames.butterfly.http.exception;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when a request URI doesn't match the methods and modules
|
||||
* present in its request body.
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class MismatchedRequestUriException extends RuntimeException {
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.buttongames.butterfly.http.handlers;
|
||||
|
||||
/**
|
||||
* Base request handler that the others inherit from.
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class BaseRequestHandler {
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.buttongames.butterfly.http.handlers;
|
||||
|
||||
import com.buttongames.butterfly.http.exception.InvalidRequestMethodException;
|
||||
import spark.Response;
|
||||
|
||||
/**
|
||||
* Handler for any requests that come to the <code>services</code> module.
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class ServicesRequestHandler extends BaseRequestHandler {
|
||||
|
||||
/**
|
||||
* Handles all requests to the <code>services/code> module.
|
||||
* @param request
|
||||
* @param requestMethod
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
public static Object handleRequest(String request, String requestMethod, Response response) {
|
||||
if (requestMethod.equals("get")) {
|
||||
return handleGetRequest(request, response);
|
||||
} else {
|
||||
throw new InvalidRequestMethodException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles <code>services.get</code> requests.
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
private static Object handleGetRequest(String request, Response response) {
|
||||
// TODO: Implement
|
||||
return "Womp womp";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.buttongames.butterfly.util;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Simple class with methods to interact with collections (mainly bytes).
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class CollectionUtils {
|
||||
|
||||
/**
|
||||
* Convert an {@code ArrayList<Byte>} to a {@code byte[]}
|
||||
* @param list
|
||||
* @return
|
||||
*/
|
||||
public static byte[] arrayListToArray(final ArrayList<Byte> list) {
|
||||
final byte[] ret = new byte[list.size()];
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ret[i] = list.get(i);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a hex string into a byte array.
|
||||
* @param s
|
||||
* @return
|
||||
*/
|
||||
public static byte[] hexStringToByteArray(final String s) {
|
||||
final int len = s.length();
|
||||
final byte[] data = new byte[len / 2];
|
||||
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i + 1), 16));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all the bytes in an input stream into a new byte array.
|
||||
* @param is The {@code InputStream} to read
|
||||
* @return A byte array containing everything read from the stream
|
||||
* @throws IOException
|
||||
*/
|
||||
public static byte[] readInputStream(final InputStream is) throws IOException {
|
||||
return IOUtils.toByteArray(is);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.buttongames.butterfly.xml;
|
||||
|
||||
import com.buttongames.butterfly.util.CollectionUtils;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Class to help translating between binary and plaintext XML. Right now it's a very dumb
|
||||
* class and just uses mon's Python implementation until I can make a native Java one. This does
|
||||
* depend on you having done a <code>pip install kbinxml</code> to make <code>kbinxml</code>
|
||||
* a valid command.
|
||||
* See: https://github.com/mon/kbinxml
|
||||
* @author skogaby (skogabyskogaby@gmail.com)
|
||||
*/
|
||||
public class BinaryXmlUtils {
|
||||
|
||||
public static byte[] binaryToXml(final byte[] input) throws IOException {
|
||||
final String tmpPath = System.getProperty("user.home") + "\\tmpkbin";
|
||||
final DataOutputStream dos = new DataOutputStream(new FileOutputStream(tmpPath));
|
||||
dos.write(input, 0, input.length);
|
||||
dos.flush();
|
||||
dos.close();
|
||||
|
||||
// shell out to mon's implementation for now
|
||||
final Process child = Runtime.getRuntime().exec("kbinxml " + tmpPath);
|
||||
final byte[] output = CollectionUtils.readInputStream(child.getInputStream());
|
||||
return output;
|
||||
}
|
||||
|
||||
public static byte[] xmlToBinary(final byte[] input) throws IOException {
|
||||
return binaryToXml(input);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user