commit 07180ce371375c6d2fa85981f815ed3d422d17cc Author: ceikry Date: Sat Jun 26 13:41:11 2021 -0500 Fixed up the launcher project structure and naming diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2516d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.gradle/ +build diff --git a/AppImage/2009scape.AppImage b/AppImage/2009scape.AppImage new file mode 100755 index 0000000..44e82e8 Binary files /dev/null and b/AppImage/2009scape.AppImage differ diff --git a/AppImage/2009scape.jar b/AppImage/2009scape.jar new file mode 100644 index 0000000..8e0e957 Binary files /dev/null and b/AppImage/2009scape.jar differ diff --git a/AppImage/LICENSE b/AppImage/LICENSE new file mode 100644 index 0000000..93914fb --- /dev/null +++ b/AppImage/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2016, Adam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. diff --git a/AppImage/appimage/2009scape.desktop b/AppImage/appimage/2009scape.desktop new file mode 100644 index 0000000..863c1aa --- /dev/null +++ b/AppImage/appimage/2009scape.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=2009scape +Comment=2009scape - Your Adventure Awaits! +Exec=@finalName@ +Icon=2009scape +Terminal=false +Type=Application +Categories=Game; diff --git a/AppImage/appimage/2009scape.png b/AppImage/appimage/2009scape.png new file mode 100644 index 0000000..b236856 Binary files /dev/null and b/AppImage/appimage/2009scape.png differ diff --git a/AppImage/build-linux.sh b/AppImage/build-linux.sh new file mode 100755 index 0000000..6cdae75 --- /dev/null +++ b/AppImage/build-linux.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +set -e + +PACKR_VERSION="runelite-1.0" + +rm -f 2009scape.AppImage + +# Check if there's a client jar file - If there's no file the AppImage will not work but will still be built. +if ! [ -e 2009scape.jar ] +then + echo "2009scape.jar not found, exiting" + exit 1 +fi + +if ! [ -f OpenJDK8U-jre_x64_linux_hotspot_8u275b01.tar.gz ] ; then + curl -Lo OpenJDK8U-jre_x64_linux_hotspot_8u275b01.tar.gz \ + https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u275-b01/OpenJDK8U-jre_x64_linux_hotspot_8u275b01.tar.gz +fi + +rm -f packr.jar +curl -o packr.jar https://libgdx.badlogicgames.com/ci/packr/packr.jar + +# packr requires a "jdk" and pulls the jre from it - so we have to place it inside +# the jdk folder at jre/ +if ! [ -d linux-jdk ] ; then + tar zxf OpenJDK8U-jre_x64_linux_hotspot_8u275b01.tar.gz + mkdir linux-jdk + mv jdk8u275-b01-jre linux-jdk/jre +fi + +if ! [ -f packr_${PACKR_VERSION}.jar ] ; then + curl -Lo packr_${PACKR_VERSION}.jar \ + https://github.com/runelite/packr/releases/download/${PACKR_VERSION}/packr.jar +fi + +echo "18b7cbaab4c3f9ea556f621ca42fbd0dc745a4d11e2a08f496e2c3196580cd53 packr_${PACKR_VERSION}.jar" | sha256sum -c + +java -jar packr_${PACKR_VERSION}.jar \ + --platform \ + linux64 \ + --jdk \ + linux-jdk \ + --executable \ + 2009scape \ + --classpath \ + 2009scape.jar \ + --mainclass \ + fox.Launcher \ + --output \ + native-linux/2009scape.AppDir/ \ + --resources \ + appimage/2009scape.desktop \ + appimage/2009scape.png + +cp appimage/2009scape.png native-linux/2009scape.AppDir + +# Symlink AppRun -> 2009scape +pushd native-linux/2009scape.AppDir/ +ln -s 2009scape AppRun +popd + +curl -Lo appimagetool-x86_64.AppImage https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage +chmod 755 appimagetool-x86_64.AppImage + +./appimagetool-x86_64.AppImage \ + native-linux/2009scape.AppDir/ \ + native-linux/2009scape.AppImage + +echo "Cleaning up.." + +mv native-linux/2009scape.AppImage . +rm -rf packr packr.jar packr_runelite-1.0.jar native-linux linux-jdk OpenJDK8U-jre_x64_linux_hotspot_8u275b01.tar.gz appimagetool-x86_64.AppImage diff --git a/AppImage/build-osx.sh b/AppImage/build-osx.sh new file mode 100755 index 0000000..7a7f8f5 --- /dev/null +++ b/AppImage/build-osx.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +echo "NOTE: THIS DOES NOT WORK. IT HAS NEVER BEEN MODIFIED SINCE COPIED FROM https://github.com/open-osrs/launcher/blob/master/build-osx.sh" + +set -e + +JDK_VER="11.0.4" +JDK_BUILD="11" +PACKR_VERSION="runelite-1.0" + +SIGNING_IDENTITY="Developer ID Application" +ALTOOL_USER="user@icloud.com" +ALTOOL_PASS="@keychain:altool-password" + +if ! [ -f OpenJDK11U-jre_x64_mac_hotspot_${JDK_VER}_${JDK_BUILD}.tar.gz ] ; then + curl -Lo OpenJDK11U-jre_x64_mac_hotspot_${JDK_VER}_${JDK_BUILD}.tar.gz \ + https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-${JDK_VER}%2B${JDK_BUILD}/OpenJDK11U-jre_x64_mac_hotspot_${JDK_VER}_${JDK_BUILD}.tar.gz +fi + +rm -f packr.jar +curl -o packr.jar https://libgdx.badlogicgames.com/ci/packr/packr.jar + +echo "1647fded28d25e562811f7bce2092eb9c21d30608843b04250c023b40604ff26 OpenJDK11U-jre_x64_mac_hotspot_${JDK_VER}_${JDK_BUILD}.tar.gz" | shasum -c + +# packr requires a "jdk" and pulls the jre from it - so we have to place it inside +# the jdk folder at jre/ +if ! [ -d osx-jdk ] ; then + tar zxf OpenJDK11U-jre_x64_mac_hotspot_${JDK_VER}_${JDK_BUILD}.tar.gz + mkdir osx-jdk + mv jdk-11.0.4+11-jre osx-jdk/jre + + # Move JRE out of Contents/Home/ + pushd osx-jdk/jre + cp -r Contents/Home/* . + popd +fi + +if ! [ -f packr_${PACKR_VERSION}.jar ] ; then + curl -Lo packr_${PACKR_VERSION}.jar \ + https://github.com/runelite/packr/releases/download/${PACKR_VERSION}/packr.jar +fi + +echo "18b7cbaab4c3f9ea556f621ca42fbd0dc745a4d11e2a08f496e2c3196580cd53 packr_${PACKR_VERSION}.jar" | shasum -c + +java -jar packr_${PACKR_VERSION}.jar \ + --platform \ + mac \ + --icon \ + packr/openosrs.icns \ + --jdk \ + osx-jdk \ + --executable \ + OpenOSRS \ + --classpath \ + build/libs/OpenOSRS-shaded.jar \ + --mainclass \ + net.runelite.launcher.Launcher \ + --vmargs \ + Drunelite.launcher.nojvm=true \ + Xmx512m \ + Xss2m \ + XX:CompileThreshold=1500 \ + Djna.nosys=true \ + --output \ + native-osx/OpenOSRS.app + +cp build/filtered-resources/Info.plist native-osx/OpenOSRS.app/Contents + +echo Setting world execute permissions on OpenOSRS +pushd native-osx/OpenOSRS.app +chmod g+x,o+x Contents/MacOS/OpenOSRS +popd + +codesign -f -s "${SIGNING_IDENTITY}" --entitlements osx/signing.entitlements --options runtime native-osx/RuneLite.app || true + +# create-dmg exits with an error code due to no code signing, but is still okay +# note we use Adam-/create-dmg as upstream does not support UDBZ +create-dmg --format UDBZ native-osx/OpenOSRS.app.app native-osx/ || true + +mv native-osx/OpenOSRS\ *.dmg native-osx/OpenOSRS.dmg + +xcrun altool --notarize-app --username "${ALTOOL_USER}" --password "${ALTOOL_PASS}" --primary-bundle-id openosrs --file native-osx/OpenOSRS.dmg || true diff --git a/README.md b/README.md new file mode 100644 index 0000000..24fd129 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# 2009scape Launcher + +The official launcher for 2009scape/2009scape-compatible sources + +# Running + +1. Start the 2009scape Management Server & Server on localhost (found [here](https://github.com/2009scape/2009scape)) +2. Run `src/com/rs09/Launcher.java` + +Note: To be able to download client and cache through the launcher you need to have a web server running and you need to have the cache + client jar in the web root of the web server. + +## Troubleshooting + +* No audio (Linux) + * Install Pipewire (and Pipewire-alsa) as a replacement to Pulseaudio. This is a deep Java issue that will hopefully disappear with Pulseaudio's phasing out over the 2021/2022 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c7b00c5 --- /dev/null +++ b/build.gradle @@ -0,0 +1,38 @@ +plugins { + id "java" + id "application" +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +mainClassName = 'rs09.Launcher' + + +repositories { + flatDir { + dirs 'libs' + } + mavenCentral() +} + +dependencies { +} + + +jar { + archiveBaseName = '2009scapeLauncher' + archiveVersion = '1.0.0' + manifest { + attributes 'Main-Class': 'rs09.Launcher', + 'Class-Path': configurations.runtime.files.collect { "lib/$it.name" }.join(' ') + } + from('src') { + include 'data/**' + } +} + +sourceSets { + main.java.srcDirs = ['src/main/java'] +} + diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..3605492 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: rs09.Launcher + diff --git a/src/main/java/rs09/Launcher.java b/src/main/java/rs09/Launcher.java new file mode 100644 index 0000000..1639134 --- /dev/null +++ b/src/main/java/rs09/Launcher.java @@ -0,0 +1,30 @@ +package rs09; + +import java.awt.Color; + +import javax.swing.UIManager; + +import rs09.audio.Audio; +import rs09.components.AppFrame; +//import fox.threads.ServerTime; + +public class Launcher { + + public static Audio audio = new Audio("/data/audio/welcomehome.mid"); + public static AppFrame app; + + public static void main(String[] main) { + UIManager.put("Button.select", new Color(1.0f,1.0f, 1.0f, 0.05f)); + System.setProperty("awt.useSystemAAFontSettings","on"); + System.setProperty("swing.aatext", "true"); + + app = new AppFrame(); + app.setVisible(true); + app.setLocationRelativeTo(null); + if (Settings.enableMusicPlayer) + audio.playAudio(); + //new Thread(new ServerTime()).start(); + } + + +} \ No newline at end of file diff --git a/src/main/java/rs09/Settings.java b/src/main/java/rs09/Settings.java new file mode 100644 index 0000000..a5842f1 --- /dev/null +++ b/src/main/java/rs09/Settings.java @@ -0,0 +1,43 @@ +package rs09; + +import java.awt.Color; +import java.awt.Dimension; +import java.io.File; + +public class Settings { + + public static final String SERVER_NAME = "2009scape"; + public static final String DOWNLOAD_URL = "http://play.2009scape.org/2009scape.jar"; + public static final String CACHE_URL = "http://play.2009scape.org/"; + + public static final String SAVE_NAME = "2009scape.jar"; + public static final String SAVE_DIR = System.getProperty("user.home") + File.separator; + public static final String CACHE_DIR = System.getProperty("user.home") + File.separator + ".runite_rs" + File.separator + "runescape" + File.separator; + + public static final String SERVER_IP = "play.2009scape.org"; + public static final int SERVER_PORT = 43595; + + public static final boolean enableMusicPlayer = false; + + // Frame Settings + public static final Dimension frameSize = new Dimension(600, 350); + public static final Color borderColor = new Color(0, 0, 0); + public static final Color backgroundColor = new Color(158,134,94); + public static final Color primaryColor = new Color(255,204,54); + public static final Color iconShadow = new Color(0, 0, 0); + public static final Color buttonDefaultColor = new Color( + 97,76,35 + ); + + // link settings + //public static final String youtube = ""; + //public static final String twitter = ""; + //public static final String facebook = ""; + + public static final String community = "https://discord.gg/UVtqkDhxVD"; + public static final String leaders = "https://2009scape.org/services/m%3dhiscore/hiscores.html"; + //public static final String store = ""; + //public static final String vote = ""; + public static final String bugs = "https://2009scape.org/services/m=bugtracker_v4/index.html?j"; + +} diff --git a/src/main/java/rs09/audio/Audio.java b/src/main/java/rs09/audio/Audio.java new file mode 100644 index 0000000..9aae3ae --- /dev/null +++ b/src/main/java/rs09/audio/Audio.java @@ -0,0 +1,42 @@ +package rs09.audio; + +import javax.sound.midi.*; + +public class Audio { + + private static Sequencer player; + private String midiFile; + + public Audio(String file) { + this.midiFile = file; + try { + player = MidiSystem.getSequencer(); + player.open(); + } catch (MidiUnavailableException e) { + e.printStackTrace(); + } + } + + public void playAudio() { + if (player == null || !player.isOpen() || player.isRunning()) + return; + try { + player.setSequence(getClass().getResourceAsStream(midiFile)); + player.setLoopCount(3); + player.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void stopAudio() { + if (player == null) + return; + player.stop(); + } + + public static void main(String[] args) { + Audio audio = new Audio("/data/audio/welcomehome.mid"); + audio.playAudio(); + } +} \ No newline at end of file diff --git a/src/main/java/rs09/components/AppFrame.java b/src/main/java/rs09/components/AppFrame.java new file mode 100644 index 0000000..de18d65 --- /dev/null +++ b/src/main/java/rs09/components/AppFrame.java @@ -0,0 +1,293 @@ +package rs09.components; + +import java.awt.Color; +import java.awt.Point; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JProgressBar; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import javax.swing.plaf.basic.BasicProgressBarUI; + +import rs09.Settings; +import rs09.listeners.ButtonListener; +import rs09.utils.Utils; + +public class AppFrame extends JFrame { + private static final long serialVersionUID = -3130053101521666537L; + private static Point initialClick; + + public static int appWidth, appHeight; + + public AppFrame() { + setPreferredSize(Settings.frameSize); + + appWidth = (int) getPreferredSize().getWidth(); + appHeight = (int) getPreferredSize().getHeight(); + + setUndecorated(true); + setResizable(false); + setTitle(Settings.SERVER_NAME); + setLayout(null); + getRootPane().setBorder(new LineBorder(Color.BLACK)); + getContentPane().setBackground(Settings.backgroundColor); + + //addMenuBar(); + addNewsBox(); + addWorldSelect(); + addLinks(); + addHeader(); + addPlayButton(); + addProgressBar(); + + setIconImage(Utils.getImage("favicon_large.png").getImage()); + addMouseListener(); + pack(); + } + + public static JProgressBar pbar; + + private void addProgressBar() { + pbar = new JProgressBar(); + + pbar.setUI(new BasicProgressBarUI() { + protected Color getSelectionBackground() { + return Settings.primaryColor; + } + protected Color getSelectionForeground() { + return Settings.primaryColor; + } + }); + + pbar.setBounds(0, appHeight - 35, appWidth, 35); + pbar.setBackground(Settings.backgroundColor.darker()); + pbar.setBorderPainted(false); + pbar.setStringPainted(true); + pbar.setString("Click Launch to play "+Settings.SERVER_NAME+"!"); + pbar.setBorder(new EmptyBorder(0, 0, 0, 0)); + pbar.setForeground(new Color(25, 25, 25)); + Utils.setFont(pbar, "OpenSans-Regular.ttf", 13); + add(pbar); + } + + + public static Control playButton = new Control("Play"); + private void addPlayButton() { + + playButton.setActionCommand("play1"); + playButton.setBackground(Settings.primaryColor); + playButton.addActionListener(new ButtonListener()); + //(appWidth / 2) - (167 / 2) + playButton.setBounds(151, appHeight - 88, 100, 35); + Utils.setFont(playButton, "OpenSans-Regular.ttf", 16); + add(playButton); + } + + public static Control playButton2 = new Control("Play"); + private void addPlayButton2() { + playButton2.setActionCommand("play2"); + playButton2.setBackground(Settings.primaryColor); + playButton2.addActionListener(new ButtonListener()); + //(appWidth / 2) - (167 / 2) + playButton2.setBounds(appWidth - 254, appHeight - 88, 100, 35); + Utils.setFont(playButton2, "OpenSans-Regular.ttf", 16); + playButton2.setEnabled(true); + add(playButton2); + } + + private void addWorldSelect() { + JLabel world1box = new JLabel(""); + JLabel world1text = new JLabel("World 1 - Authentic"); + world1text.setBounds(20, appHeight - 80, 145,20); + world1text.setForeground(Color.WHITE); + Utils.setFont(world1text,"OpenSans-Regular.ttf",12); + world1box.setBounds(5, appHeight - 90,250,40); + world1box.setOpaque(true); + world1box.setBackground(new Color(0.0f,0.0f,0.0f,0.2f)); + add(world1box); + add(world1text); + + IconLabel globeIcon = new IconLabel("\uf0ac",50); + globeIcon.setBounds(275,appHeight - 98,64,64); + add(globeIcon); + + JLabel world2box = new JLabel(""); + JLabel world2Text = new JLabel("World 2 - Custom/QoL"); + world2Text.setBounds(appWidth - 145,appHeight - 80,145,20); + world2Text.setForeground(Color.WHITE); + Utils.setFont(world2Text,"OpenSans-Regular.ttf",12); + world2box.setBounds(appWidth - 258,appHeight - 90, 250, 40); + world2box.setOpaque(true); + world2box.setBackground(new Color(0.0f,0.0f,0.0f,0.2f)); + add(world2box); + add(world2Text); + addPlayButton2(); + } + + public static JLabel tooltip; + public static IconLabel serverTime; + public static IconLabel playerCount; + + private void addLinks() { + tooltip = new JLabel(""); + tooltip.setBounds(135, appHeight - 140, 335, 35); + tooltip.setHorizontalAlignment(SwingConstants.CENTER); + Utils.setFont(tooltip, "OpenSans-Light.ttf", 30); + tooltip.setForeground(Color.white); + add(tooltip); + + IconLabel forum = new IconLabel("\uf086", "Community", 50); + forum.setBounds(275, 150, 64, 64);//forum.setBounds(135, 170, 64, 64); + add(forum); + + IconLabel hs = new IconLabel("\uf080", "Leaderboards", 50); + hs.setBounds(205, 150, 64, 64); + add(hs); + + /*IconLabel shop = new IconLabel("\uf07a", "Shopping Center", 50); + shop.setBounds(275, 170, 64, 64); + add(shop);*/ + + /*IconLabel vote = new IconLabel("\uf046", "Vote for Us", 50); + vote.setBounds(345, 172, 64, 64); + add(vote);*/ + + IconLabel web = new IconLabel("\uf188", "Report an Issue", 50); + web.setBounds(345, 152, 64, 64);//web.setBounds(415, 172, 64, 64); + add(web); + + if (Settings.enableMusicPlayer) { + IconLabel music = new IconLabel("\uf205", "Music", "FontAwesome.ttf", 16); + music.setBounds(appWidth - 32, appHeight - 60, 24, 18); + add(music); + + IconLabel musicLabel = new IconLabel("Music", 12); + musicLabel.setBounds(appWidth - 75, appHeight - 60, 36, 18); + add(musicLabel); + } + +// //serverTime = new IconLabel("Server Time: 1:50 PM", 12); +// serverTime.setBounds(15, appHeight - 60, 175, 18); +// add(serverTime); + + /*playerCount = new IconLabel("There are 10 player(s) Online!", 12); + playerCount.setForeground(Color.white); + playerCount.setBounds(65, 105, 200, 18); + add(playerCount);*/ + + /*JButton player = new JButton("Stop Music"); + player.setBounds(520, 378, 75, 16); + add(player);*/ + } + + String serverStatus = "..."; + + + private void addNewsBox() { + + +// IconLabel facebook = new IconLabel("\uf082", "Facebook", 32); +// facebook.setBounds(appWidth - 35, 30, 36, 36); + +// if (Settings.facebook.length() > 1) { +// add(facebook); +// } +// +// IconLabel twitter = new IconLabel("\uf099", "Twitter", 32); +// twitter.setBounds(appWidth - 35, 65, 36, 36); +// +// if (Settings.twitter.length() > 1) { +// add(twitter); +// } +// +// IconLabel youtube = new IconLabel("\uf167", "Youtube", 32); +// youtube.setBounds(appWidth - 35, 100, 36, 36); +// +// if (Settings.youtube.length() > 1) { +// add(youtube); +// } + + final int red = Settings.primaryColor.getRed(); + final int green = Settings.primaryColor.getGreen(); + final int blue = Settings.primaryColor.getBlue(); + + JLabel status1 = new JLabel("Welcome to "+Settings.SERVER_NAME+"!"); + status1.setForeground(Settings.primaryColor); + status1.setHorizontalAlignment(SwingConstants.CENTER); + status1.setBounds(0, 75, appWidth, 95); + Utils.setFont(status1, "OpenSans-Light.ttf", 40); + //add(status1); + + /*final JLabel status2 = new JLabel("We are currently "+serverStatus+"!"); + + new Thread() { + public void run() { + serverStatus = Utils.hostAvailabilityCheck() ? + "Online" : + "Offline"; + status2.setText("We are currently "+serverStatus+"!"); + } + }.start(); + + status2.setForeground(Color.WHITE); + status2.setHorizontalAlignment(SwingConstants.CENTER); + status2.setBounds(0, 25, appWidth, 25); + Utils.setFont(status2, "OpenSans-Light.ttf", 14); + add(status2);*/ + } + + private void addMenuBar() { + MenuBar bar = new MenuBar(this); + bar.setBounds(0, 0, appWidth, 25); + add(bar); + } + + @SuppressWarnings("unused") + private void addHeader() { + JLabel logo = new JLabel(Utils.getImage("logo.png")); + logo.setBounds(125, 55, 350, 60); + add(logo); + +// JLabel head = new JLabel(" "); +// head.setOpaque(true); +// head.setBackground(new Color(0.0f, 0.0f, 0.0f, 0.2f)); +// head.setBounds(-1, 25, appWidth, 114); +// add(head); + } + + private void addMouseListener() { + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + initialClick = e.getPoint(); + getComponentAt(initialClick); + } + }); + + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + + int iX = initialClick.x; + int iY = initialClick.y; + + if (iX >= 0 && iX <= getPreferredSize().getWidth() && iY >= 0 && iY <= 25) { + int thisX = getLocation().x; + int thisY = getLocation().y; + + int xMoved = (thisX + e.getX()) - (thisX + initialClick.x); + int yMoved = (thisY + e.getY()) - (thisY + initialClick.y); + + int X = thisX + xMoved; + int Y = thisY + yMoved; + setLocation(X, Y); + } + } + }); + } + +} diff --git a/src/main/java/rs09/components/Control.java b/src/main/java/rs09/components/Control.java new file mode 100644 index 0000000..e9eef19 --- /dev/null +++ b/src/main/java/rs09/components/Control.java @@ -0,0 +1,58 @@ +package rs09.components; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.Icon; +import javax.swing.JButton; + +import rs09.Settings; + +@SuppressWarnings("serial") +public class Control extends JButton implements MouseListener { + + public Control(Icon image) { + super(image); + + setBorderPainted(false); + setFocusable(false); + addMouseListener(this); + } + + public Control(String name) { + super(name); + setForeground(Settings.buttonDefaultColor); + setBorderPainted(false); + setFocusable(false); + addMouseListener(this); + } + + @Override + public void mouseClicked(MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseEntered(MouseEvent me) { + setBackground(this.getBackground().darker()); + } + + @Override + public void mouseExited(MouseEvent me) { + setBackground(this.getBackground().brighter()); + + } + + @Override + public void mousePressed(MouseEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void mouseReleased(MouseEvent e) { + // TODO Auto-generated method stub + + } +} diff --git a/src/main/java/rs09/components/Header.java b/src/main/java/rs09/components/Header.java new file mode 100644 index 0000000..7bd1922 --- /dev/null +++ b/src/main/java/rs09/components/Header.java @@ -0,0 +1,20 @@ +package rs09.components; + +import java.awt.Color; + +import javax.swing.JLabel; +import javax.swing.SwingConstants; + +import rs09.utils.Utils; + +@SuppressWarnings("serial") +public class Header extends JLabel { + + public Header(String text) { + super(text); + setForeground(Color.WHITE); + setHorizontalAlignment(SwingConstants.CENTER); + Utils.setFont(this, "OpenSans-Light.ttf", 32); + } + +} diff --git a/src/main/java/rs09/components/IconLabel.java b/src/main/java/rs09/components/IconLabel.java new file mode 100644 index 0000000..624fe7a --- /dev/null +++ b/src/main/java/rs09/components/IconLabel.java @@ -0,0 +1,180 @@ +package rs09.components; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.font.LineMetrics; + +import javax.swing.JLabel; + +import rs09.Launcher; +import rs09.Settings; +import rs09.utils.Utils; + +@SuppressWarnings("serial") +public class IconLabel extends JLabel implements MouseListener { + + private int tracking; + private String action; + + public IconLabel(String text, int fontSize) { + super(text); + this.tracking = 1; + setForeground(Settings.primaryColor); + this.setRightShadow(1, 1, Settings.iconShadow); + Utils.setFont(this, "FontAwesome.ttf", fontSize); + } + + public IconLabel(String text, String action, int fontSize) { + super(text); + this.tracking = 1; + this.action = action; + setForeground(Settings.primaryColor); + addMouseListener(this); + this.setRightShadow(1, 1, Settings.iconShadow); + Utils.setFont(this, "FontAwesome.ttf", fontSize); + } + + public IconLabel(String text, String action, String font, int fontSize) { + super(text); + this.tracking = 1; + this.action = action; + setForeground(Settings.primaryColor); + addMouseListener(this); + this.setRightShadow(1, 1, Settings.iconShadow); + Utils.setFont(this, font, fontSize); + } + + private int left_x, left_y, right_x, right_y; + private Color left_color, right_color; + public void setLeftShadow(int x, int y, Color color) { + left_x = x; + left_y = y; + left_color = color; + } + + public void setRightShadow(int x, int y, Color color) { + right_x = x; + right_y = y; + right_color = color; + } + + public Dimension getPreferredSize() { + String text = getText(); + FontMetrics fm = this.getFontMetrics(getFont()); + + int w = fm.stringWidth(text); + w += (text.length()-1)*tracking; + w += left_x + right_x; + + int h = fm.getHeight(); + h += left_y + right_y; + + return new Dimension(w,h); + } + + public void paintComponent(Graphics g) { + ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + char[] chars = getText().toCharArray(); + + FontMetrics fm = this.getFontMetrics(getFont()); + int h = fm.getAscent(); + @SuppressWarnings("unused") + LineMetrics lm = fm.getLineMetrics(getText(),g); + g.setFont(getFont()); + + int x = 0; + + for(int i=0; i MAX_BUFFER_SIZE) { + buffer = new byte[MAX_BUFFER_SIZE]; + } else { + buffer = new byte[size - downloaded]; + } + + int read = stream.read(buffer); + + if (read == -1) + break; + + int progress = (int) getProgress(); + + if (progress > lastNum) { + AppFrame.pbar.setValue(progress); + lastNum = progress; + AppFrame.pbar.setString("Downloading Update: "+progress+"%"); + } + + file.write(buffer, 0, read); + downloaded += read; + if(contentLength == downloaded){ + status = COMPLETE; + } + } + } catch (Exception e) { + error(); + } finally { + if (file != null) + try { file.close(); } catch (Exception e) { } + if (stream != null) + try { stream.close(); } catch (Exception e) { } + } + } + private void stateChanged() { + setChanged(); + notifyObservers(); + } +} diff --git a/src/main/java/rs09/net/Checksum.java b/src/main/java/rs09/net/Checksum.java new file mode 100644 index 0000000..9346f8c --- /dev/null +++ b/src/main/java/rs09/net/Checksum.java @@ -0,0 +1,105 @@ +package rs09.net; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import rs09.Settings; +import rs09.components.AppFrame; + +public class Checksum { + + public static String getLocalChecksum() { + File local = new File(Settings.SAVE_DIR + Settings.SAVE_NAME); + try (FileInputStream fis = new FileInputStream(local)) { + return Checksum.calculateMd5(fis); + } catch (Exception e) { + e.printStackTrace(); + AppFrame.pbar.setString(e.getMessage()); + } + return null; + } + + public static String getLocalChecksum(String file) { + File local = new File(file); + try (FileInputStream fis = new FileInputStream(local)) { + return Checksum.calculateMd5(fis); + } catch (Exception e) { + e.printStackTrace(); + AppFrame.pbar.setString(e.getMessage()); + } + return null; + } + + public static String getRemoteChecksum() { + try (InputStream stream = new URL(Settings.DOWNLOAD_URL).openStream()) { + return Checksum.calculateMd5(stream); + } catch (Exception e) { + e.printStackTrace(); + AppFrame.pbar.setString(e.getMessage()); + return null; + } + } + + public static String getRemoteChecksum(String url) { + try (InputStream stream = new URL(url).openStream()) { + return Checksum.calculateMd5(stream); + } catch (Exception e) { + e.printStackTrace(); + AppFrame.pbar.setString(e.getMessage()); + return null; + } + } + + public static String calculateMd5(final InputStream instream) { + return calculateDigest(instream, "MD5"); + } + + private static String calculateDigest(final InputStream instream, final String algorithm) { + final byte[] buffer = new byte[4096]; + final MessageDigest messageDigest = getMessageDigest(algorithm); + messageDigest.reset(); + int bytesRead; + try { + while ((bytesRead = instream.read(buffer)) != -1) { + messageDigest.update(buffer, 0, bytesRead); + } + } catch (IOException e) { + System.err.println("Error making a '" + algorithm + "' digest on the inputstream"); + } + return toHex(messageDigest.digest()); + } + + public static String toHex(final byte[] ba) { + int baLen = ba.length; + char[] hexchars = new char[baLen * 2]; + int cIdx = 0; + for (int i = 0; i < baLen; ++i) { + hexchars[cIdx++] = hexdigit[(ba[i] >> 4) & 0x0F]; + hexchars[cIdx++] = hexdigit[ba[i] & 0x0F]; + } + return new String(hexchars); + } + + public static MessageDigest getMessageDigest(final String algorithm) { + MessageDigest messageDigest = null; + try { + messageDigest = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + System.err.println("The '" + algorithm + "' algorithm is not available"); + } + return messageDigest; + } + + private static final char[] hexdigit = { + '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f' + }; + + +} diff --git a/src/main/java/rs09/net/Download.java b/src/main/java/rs09/net/Download.java new file mode 100644 index 0000000..7a08509 --- /dev/null +++ b/src/main/java/rs09/net/Download.java @@ -0,0 +1,182 @@ +package rs09.net; +import java.io.File; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Observable; + +import rs09.Settings; +import rs09.components.AppFrame; +import rs09.listeners.ButtonListener; + +public class Download extends Observable implements Runnable { + + private static final int MAX_BUFFER_SIZE = 1024; + public static final String STATUSES[] = { "Downloading", "Paused", "Complete", "Cancelled", "Error" }; + + public static final int DOWNLOADING = 0; + public static final int PAUSED = 1; + public static final int COMPLETE = 2; + public static final int CANCELLED = 3; + public static final int ERROR = 4; + + private URL url; + private int size; + private int downloaded; + private int status; + + public Download(String url) { + try { + this.url = new URL(url); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + size = -1; + downloaded = 0; + status = DOWNLOADING; + } + + public String getUrl() { + return url.toString(); + } + + public int getSize() { + return size; + } + + public float getProgress() { + return ((float) downloaded / size) * 100; + } + + public int getStatus() { + return status; + } + + public void pause() { + status = PAUSED; + stateChanged(); + } + + public void resume() { + status = DOWNLOADING; + stateChanged(); + download(); + } + + public void cancel() { + status = CANCELLED; + stateChanged(); + } + + private void error() { + status = ERROR; + stateChanged(); + } + + public void download() { + Thread thread = new Thread(this); + thread.start(); + } + + @SuppressWarnings("unused") + private String getFileName(URL url) { + String fileName = url.getFile(); + return fileName.substring(fileName.lastIndexOf('/') + 1); + } + + public void run() { + AppFrame.playButton.setEnabled(false); + RandomAccessFile file = null; + InputStream stream = null; + + try { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestProperty("Range", "bytes=" + downloaded + "-"); + connection.connect(); + + if (connection.getResponseCode() / 100 != 2) { + error(); + } + + int contentLength = connection.getContentLength(); + + if (contentLength < 1) { + error(); + } + + if (size == -1) { + size = contentLength; + stateChanged(); + } + + System.out.println("Trying to create file " + Settings.SAVE_DIR + Settings.SAVE_NAME); + if(!new File(Settings.SAVE_DIR).exists()){ + new File(Settings.SAVE_DIR).mkdir(); + } + if (new File(Settings.SAVE_DIR + Settings.SAVE_NAME).exists()) { + new File(Settings.SAVE_DIR + Settings.SAVE_NAME).delete(); + } + new File(Settings.SAVE_DIR + Settings.SAVE_NAME).createNewFile(); + file = new RandomAccessFile(Settings.SAVE_DIR + Settings.SAVE_NAME, "rw"); + file.seek(downloaded); + + stream = connection.getInputStream(); + + int lastNum = 0; + + while (status == DOWNLOADING) { + byte buffer[]; + + if (size - downloaded > MAX_BUFFER_SIZE) { + buffer = new byte[MAX_BUFFER_SIZE]; + } else { + buffer = new byte[size - downloaded]; + } + + int read = stream.read(buffer); + + if (read == -1) + break; + + int progress = (int) getProgress(); + + if (progress > lastNum) { + AppFrame.pbar.setValue(progress); + lastNum = progress; + AppFrame.pbar.setString("Downloading Update: "+progress+"%"); + } + + file.write(buffer, 0, read); + downloaded += read; + if(downloaded == contentLength){ + if(ButtonListener.checkRun()){ + AppFrame.playButton.setEnabled(true); + AppFrame.playButton2.setEnabled(true); + AppFrame.pbar.setValue(0); + AppFrame.pbar.setString("Click Launch to play "+Settings.SERVER_NAME+"!"); + stateChanged(); + } + status = COMPLETE; + } + } + + if (status == COMPLETE) { + } + } catch (Exception e) { + error(); + } finally { + if (file != null) + try { file.close(); } catch (Exception e) { } + if (stream != null) + try { stream.close(); } catch (Exception e) { } + } + } + + private void stateChanged() { + setChanged(); + notifyObservers(); + } + +} \ No newline at end of file diff --git a/src/main/java/rs09/net/Update.java b/src/main/java/rs09/net/Update.java new file mode 100644 index 0000000..0770b10 --- /dev/null +++ b/src/main/java/rs09/net/Update.java @@ -0,0 +1,46 @@ +package rs09.net; + +import java.io.File; + +import rs09.Settings; + +public class Update { + + public static byte updateExists(int type, String filename) { + if(type == 0) { + File file = new File(Settings.SAVE_DIR + Settings.SAVE_NAME); + if (!file.exists()) + return 1; + + String localCheck = Checksum.getLocalChecksum(); + String remoteCheck = Checksum.getRemoteChecksum(); + + if (remoteCheck == null || localCheck == null) + return 2; + + if (!remoteCheck.equalsIgnoreCase(localCheck)) + return 3; + + return 0; + } else if (type == 1){ + System.out.println("Type 1 detected, filename " + filename); + File file = new File(Settings.CACHE_DIR + filename); + System.out.println("Checking in " + Settings.CACHE_DIR); + if (!file.exists()) + return 1; + + String localCheck = Checksum.getLocalChecksum(Settings.CACHE_DIR + filename); + String remoteCheck = Checksum.getRemoteChecksum(Settings.CACHE_URL + filename); + + if (remoteCheck == null || localCheck == null) + return 2; + + if (!remoteCheck.equalsIgnoreCase(localCheck)) + return 3; + + return 0; + } + return 0; + } + +} diff --git a/src/main/java/rs09/threads/PlayersOnline.java b/src/main/java/rs09/threads/PlayersOnline.java new file mode 100644 index 0000000..fd051ad --- /dev/null +++ b/src/main/java/rs09/threads/PlayersOnline.java @@ -0,0 +1,37 @@ +package rs09.threads; + +import java.net.URL; +import java.util.Scanner; + +import rs09.components.AppFrame; + +public class PlayersOnline implements Runnable { + + @Override + public void run() { + while(true) { + try { + readPlayersOnline(); + System.gc(); + Thread.sleep(30000L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public void readPlayersOnline() { + try (Scanner scanner = new Scanner(new URL("http://as-pk.net/players.txt").openStream())) { + scanner.useDelimiter("/r/n"); + if (!scanner.hasNextInt()) { + AppFrame.playerCount.setText("Error fetching Players Online"); + return; + } + int count = scanner.nextInt(); + AppFrame.playerCount.setText("There are " + count + " player(s) Online!"); + } catch (Exception e) { + AppFrame.playerCount.setText("Error fetching Players Online"); + } + } + +} diff --git a/src/main/java/rs09/threads/ServerTime.java b/src/main/java/rs09/threads/ServerTime.java new file mode 100644 index 0000000..646e83c --- /dev/null +++ b/src/main/java/rs09/threads/ServerTime.java @@ -0,0 +1,23 @@ +//package fox.threads; +// +//import fox.components.AppFrame; +//import fox.utils.Utils; +// +//public class ServerTime implements Runnable { +// +// @Override +// public void run() { +// +// while (true) { +// AppFrame.serverTime.setText("Server Time: "+Utils.getServerTime()); +// try { +// Thread.sleep(1000L); +// } catch (InterruptedException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } +// +// } +// +//} diff --git a/src/main/java/rs09/utils/ProcessTest.java b/src/main/java/rs09/utils/ProcessTest.java new file mode 100644 index 0000000..65ee2c5 --- /dev/null +++ b/src/main/java/rs09/utils/ProcessTest.java @@ -0,0 +1,36 @@ +package rs09.utils; + +import rs09.Settings; +import rs09.components.AppFrame; + +public class ProcessTest { + + private Process proc; + + public ProcessTest(Process proc) { + this.proc = proc; + } + + public void startTesting() { + new Thread() { + @Override + public void run() { + while(true) { + try { + proc.exitValue(); + AppFrame.pbar.setString("Click Launch to play "+Settings.SERVER_NAME+"!"); + AppFrame.playButton.setEnabled(true); + break; + } catch (Exception e) { + try { + AppFrame.pbar.setString(""+Settings.SERVER_NAME+" is currently running."); + Thread.sleep(600L); + } catch (InterruptedException ex) { + e.printStackTrace(); + } + } + } + } + }.start(); + } +} diff --git a/src/main/java/rs09/utils/Utils.java b/src/main/java/rs09/utils/Utils.java new file mode 100644 index 0000000..a25d729 --- /dev/null +++ b/src/main/java/rs09/utils/Utils.java @@ -0,0 +1,116 @@ +package rs09.utils; + +import java.awt.Component; +import java.awt.Desktop; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.GraphicsEnvironment; +import java.io.File; +import java.io.IOException; +import java.net.Socket; +import java.net.URL; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import javax.swing.ImageIcon; + +import rs09.Settings; +import rs09.components.AppFrame; + +public class Utils { + + public static void main(String[] args) { + System.out.println(getServerTime()); + } + + public static DateFormat df; + + public static String getServerTime() { + if (df == null) { + df = new SimpleDateFormat("h:mm:ss a"); + df.setTimeZone(TimeZone.getTimeZone("Australia/Brisbane")); + } + return df.format(new Date()); + } + + /** + * Uses the ProcessBuilder class to launch the client.jar from the specified + * folder + */ + public static void launchClient(int which) { + AppFrame.playButton.setEnabled(true); + try { + Runtime.getRuntime().exec("java -jar " + Settings.SAVE_DIR + Settings.SAVE_NAME + (which == 1 ? " world=2" : ""),null, new File(System.getProperty("user.home"))); + System.exit(0); + } catch (Exception e) { + System.out.println("Failed to launch client! " + e); + } + } + + /** + * Checks if the server is online or offline by attempting to make a TCP + * connection + * + * @return true if it connects (ie. it is online) + */ + public static boolean hostAvailabilityCheck() { + try (Socket s = new Socket(Settings.SERVER_IP, Settings.SERVER_PORT)) { + s.setTcpNoDelay(true); + return true; + } catch (IOException ex) { + /* ignore */ + } + return false; + } + + /** + * Loads a custom font from the data/font folder. Font must be either otf or + * ttf. + * + * @param fontName + * @param size + */ + public static void setFont(Component c, String fontName, float size) { + try { + Font font = Font.createFont(0, Utils.class.getResource("/data/fonts/" + fontName).openStream()); + GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment(); + genv.registerFont(font); + font = font.deriveFont(size); + c.setFont(font); + } catch (FontFormatException | IOException e) { + e.printStackTrace(); + } + } + + /** + * Opens the users browser and goes to the specified URL + * @param url + */ + public static void openWebpage(String url) { + Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + desktop.browse(new URL(url).toURI()); + } catch (Exception e) { + // e.printStackTrace(); + } + } + } + + private static long timeCorrection; + private static long lastTimeUpdate; + + public static synchronized long currentTimeMillis() { + long l = System.currentTimeMillis(); + if (l < lastTimeUpdate) + timeCorrection += lastTimeUpdate - l; + lastTimeUpdate = l; + return l + timeCorrection; + } + + public static ImageIcon getImage(String name) { + return new ImageIcon(Utils.class.getResource("/data/img/" + name)); + } +} diff --git a/src/main/java/rs09/utils/encrypt/MD5.java b/src/main/java/rs09/utils/encrypt/MD5.java new file mode 100644 index 0000000..8fba98a --- /dev/null +++ b/src/main/java/rs09/utils/encrypt/MD5.java @@ -0,0 +1,34 @@ +package rs09.utils.encrypt; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class MD5 { + + private static String convertToHex(byte[] data) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < data.length; i++) { + int halfbyte = (data[i] >>> 4) & 0x0F; + int two_halfs = 0; + do { + if ((0 <= halfbyte) && (halfbyte <= 9)) + buf.append((char) ('0' + halfbyte)); + else + buf.append((char) ('a' + (halfbyte - 10))); + halfbyte = data[i] & 0x0F; + } while (two_halfs++ < 1); + } + return buf.toString(); + } + + public static String encrypt(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest md; + md = MessageDigest.getInstance("MD5"); + byte[] md5hash = new byte[32]; + md.update(text.getBytes("iso-8859-1"), 0, text.length()); + md5hash = md.digest(); + return convertToHex(md5hash); + } + +} diff --git a/src/main/resources/data/audio/welcomehome.mid b/src/main/resources/data/audio/welcomehome.mid new file mode 100644 index 0000000..60c7c43 Binary files /dev/null and b/src/main/resources/data/audio/welcomehome.mid differ diff --git a/src/main/resources/data/fonts/FontAwesome.ttf b/src/main/resources/data/fonts/FontAwesome.ttf new file mode 100644 index 0000000..96a3639 Binary files /dev/null and b/src/main/resources/data/fonts/FontAwesome.ttf differ diff --git a/src/main/resources/data/fonts/OpenSans-Light.ttf b/src/main/resources/data/fonts/OpenSans-Light.ttf new file mode 100644 index 0000000..0d38189 Binary files /dev/null and b/src/main/resources/data/fonts/OpenSans-Light.ttf differ diff --git a/src/main/resources/data/fonts/OpenSans-Regular.ttf b/src/main/resources/data/fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000..db43334 Binary files /dev/null and b/src/main/resources/data/fonts/OpenSans-Regular.ttf differ diff --git a/src/main/resources/data/img/favicon.png b/src/main/resources/data/img/favicon.png new file mode 100644 index 0000000..6551680 Binary files /dev/null and b/src/main/resources/data/img/favicon.png differ diff --git a/src/main/resources/data/img/favicon_large.png b/src/main/resources/data/img/favicon_large.png new file mode 100644 index 0000000..21b2eba Binary files /dev/null and b/src/main/resources/data/img/favicon_large.png differ diff --git a/src/main/resources/data/img/logo.png b/src/main/resources/data/img/logo.png new file mode 100644 index 0000000..2b3e054 Binary files /dev/null and b/src/main/resources/data/img/logo.png differ