commit dee36e793b0ce433e73245ec14cd51b520d63ffa Author: ceikry Date: Thu Jul 15 19:28:03 2021 -0500 Initial push diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..f45880c --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.4.32' +} + +group 'org.rs09' +version '1.0' + +repositories { + mavenCentral() +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib" + implementation 'org.jsoup:jsoup:1.14.1' +} + +jar { + manifest { + attributes 'Main-Class': 'MainWindow' + } + from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..b8d02f1 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'rs09launcher' + diff --git a/src/main/kotlin/BackgroundLabel.kt b/src/main/kotlin/BackgroundLabel.kt new file mode 100644 index 0000000..555ddb8 --- /dev/null +++ b/src/main/kotlin/BackgroundLabel.kt @@ -0,0 +1,15 @@ +import java.awt.BorderLayout +import java.awt.Color +import java.awt.GridBagLayout +import javax.swing.JLabel + +class BackgroundLabel(url: String, text: String) : BackgroundPanel(url) { + val textLabel = JLabel("
$text
return calculateMd5(fis) } + } catch (e: Exception) { + e.printStackTrace() + MainWindow.loadingLabel.text = e.message + MainWindow.loadingLabel.repaint() + } + return null + } + + fun getLocalChecksum(file: String?): String { + val local = File(file!!) + if(!local.exists()) return "" + else FileInputStream(local).use { fis -> return calculateMd5(fis) } + } + + val remoteChecksum: String? + get() { + try { + URL(Settings.DOWNLOAD_URL).openStream().use { stream -> return calculateMd5(stream) } + } catch (e: Exception) { + e.printStackTrace() + MainWindow.loadingLabel.text = e.message + MainWindow.loadingLabel.repaint() + return null + } + } + + fun getRemoteChecksum(url: String?): String? { + try { + URL(url).openStream().use { stream -> return calculateMd5(stream) } + } catch (e: Exception) { + e.printStackTrace() + MainWindow.loadingLabel.text = e.message + MainWindow.loadingLabel.repaint() + return null + } + } + + fun calculateMd5(instream: InputStream): String { + return calculateDigest(instream, "MD5") + } + + private fun calculateDigest(instream: InputStream, algorithm: String): String { + val buffer = ByteArray(4096) + val messageDigest = getMessageDigest(algorithm) + messageDigest!!.reset() + var bytesRead: Int + try { + while (instream.read(buffer).also { bytesRead = it } != -1) { + messageDigest.update(buffer, 0, bytesRead) + } + } catch (e: IOException) { + System.err.println("Error making a '$algorithm' digest on the inputstream") + } + return toHex(messageDigest.digest()) + } + + fun toHex(ba: ByteArray): String { + val baLen = ba.size + val hexchars = CharArray(baLen * 2) + var cIdx = 0 + for (i in 0 until baLen) { + hexchars[cIdx++] = hexdigit[(ba[i].toInt() shr 4) and 0x0F] + hexchars[cIdx++] = hexdigit[(ba[i] and 0x0F).toInt()] + } + return String(hexchars) + } + + fun getMessageDigest(algorithm: String): MessageDigest? { + var messageDigest: MessageDigest? = null + try { + messageDigest = MessageDigest.getInstance(algorithm) + } catch (e: NoSuchAlgorithmException) { + System.err.println("The '$algorithm' algorithm is not available") + } + return messageDigest + } + + private val hexdigit = charArrayOf( + '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f' + ) +} \ No newline at end of file diff --git a/src/main/kotlin/Extensions.kt b/src/main/kotlin/Extensions.kt new file mode 100644 index 0000000..bcbb469 --- /dev/null +++ b/src/main/kotlin/Extensions.kt @@ -0,0 +1,6 @@ +import java.awt.Component + +fun Component.placeAt(x: Int, y: Int, width: Int, height: Int){ + this.setBounds(x,y,width,height) + if(this is ImgButton) this.scale(width,height) +} \ No newline at end of file diff --git a/src/main/kotlin/ImgButton.kt b/src/main/kotlin/ImgButton.kt new file mode 100644 index 0000000..c049094 --- /dev/null +++ b/src/main/kotlin/ImgButton.kt @@ -0,0 +1,55 @@ +import java.awt.Image +import java.awt.event.MouseEvent +import java.awt.event.MouseListener +import javax.swing.Icon +import javax.swing.ImageIcon +import javax.swing.JLabel + +class ImgButton(enabledURL: String, disabledURL: String = enabledURL, val autoHandleMouse: Boolean = true) : JLabel() { + + private var hoverMethod: (MouseEvent) -> Unit = {} + private var mouseLeaveMethod: (MouseEvent) -> Unit = {} + private var onClickMethod: (MouseEvent) -> Unit = {} + + init { + isEnabled = false + icon = ImageIcon(javaClass.getResource(enabledURL)) + disabledIcon = ImageIcon(javaClass.getResource(disabledURL)) + addMouseListener(object : MouseListener { + override fun mouseClicked(p0: MouseEvent) { + onClickMethod.invoke(p0) + } + + override fun mousePressed(p0: MouseEvent?) {} + + override fun mouseReleased(p0: MouseEvent?) {} + + override fun mouseEntered(p0: MouseEvent) { + if(autoHandleMouse) isEnabled = true + hoverMethod.invoke(p0) + } + + override fun mouseExited(p0: MouseEvent) { + if(autoHandleMouse) isEnabled = false + mouseLeaveMethod.invoke(p0) + } + }) + } + + fun onClick(handler: (event: MouseEvent) -> Unit){ + onClickMethod = handler + } + + fun onMouseEnter(handler: (event: MouseEvent) -> Unit){ + hoverMethod = handler + } + + fun onMouseExit(handler: (event: MouseEvent) -> Unit){ + mouseLeaveMethod = handler + } + + fun scale(width: Int, height: Int){ + icon = ImageIcon((icon as ImageIcon).image.getScaledInstance(width,height, Image.SCALE_SMOOTH)) + disabledIcon = ImageIcon((disabledIcon as ImageIcon).image.getScaledInstance(width, height, Image.SCALE_SMOOTH)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/LatestUpdatePane.kt b/src/main/kotlin/LatestUpdatePane.kt new file mode 100644 index 0000000..8073551 --- /dev/null +++ b/src/main/kotlin/LatestUpdatePane.kt @@ -0,0 +1,71 @@ +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.jsoup.nodes.Node +import java.awt.Color +import java.awt.Dimension +import java.awt.Graphics +import java.awt.Rectangle +import javax.swing.JButton +import javax.swing.JComponent +import javax.swing.JScrollPane +import javax.swing.JTextPane +import javax.swing.plaf.basic.BasicScrollBarUI + +class LatestUpdatePane(url: String) : JScrollPane() { + val texPane = JTextPane() + init { + texPane.contentType = "text/html" + + texPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true) + border = null + + val doc = Jsoup.connect(url).get() + doc.getElementsByTag("img").remove() + removeComments(doc) + val postBody = doc.select(".rightpanel").select(".msgcontents") + texPane.text = postBody.toString() + setViewportView(texPane) + + getViewport().background = Color(153,132,104) + texPane.background = Color(153,132,104) + background = Color(153,132,104) + + getVerticalScrollBar().ui = object : BasicScrollBarUI() { + override fun configureScrollBarColors() { + this.thumbColor = Color(75,69,53) + } + + override fun paintTrack(p0: Graphics?, p1: JComponent?, p2: Rectangle?) {} + + override fun createDecreaseButton(p0: Int): JButton { + return object : JButton() { + override fun getPreferredSize(): Dimension { + return Dimension() + } + } + } + + override fun createIncreaseButton(p0: Int): JButton { + return object : JButton() { + override fun getPreferredSize(): Dimension { + return Dimension() + } + } + } + } + + getVerticalScrollBar().background = Color(49,45,37) + } + + private fun removeComments(node: Node) { + var i = 0 + while (i < node.childNodeSize()) { + val child = node.childNode(i) + if (child.nodeName() == "#comment") child.remove() + else { + removeComments(child) + i++ + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/MainWindow.kt b/src/main/kotlin/MainWindow.kt new file mode 100644 index 0000000..076e7a9 --- /dev/null +++ b/src/main/kotlin/MainWindow.kt @@ -0,0 +1,153 @@ +import java.awt.Desktop +import java.awt.Dimension +import java.awt.Image +import java.io.File +import java.net.URI +import javax.imageio.ImageIO +import javax.swing.* +import kotlin.system.exitProcess + + +object MainWindow : JFrame("2009scape Launcher") { + + val loadingLabel = JLabel("Press Launch to play 2009scape!") + val loadingBar = JLabel(ImageIcon(javaClass.getResource("/loadingBar.png"))) + val playButton = ImgButton("/playButton.png","/playButtonDisabled.png", false) + + + init { + defaultCloseOperation = EXIT_ON_CLOSE + preferredSize = Dimension(1366,768) + size = Dimension(800,500) + val tileBG = BackgroundPanel(ImageIO.read(javaClass.getResource("/tile.png"))) + contentPane = tileBG + layout = null + + constructTopBar() + val updatePane = LatestUpdatePane("https://2009scape.org/services/m=news/archives/2021-07-12.html") + updatePane.placeAt(4, 45, MainWindow.width - 8, MainWindow.height - 85) + add(updatePane) + addPlayButton() + + setLocationRelativeTo(null) + isUndecorated = true + isVisible = true + } + + private fun constructTopBar(){ + val panel = BackgroundPanel(ImageIO.read(javaClass.getResource("/topBar.png"))) + panel.placeAt(0,0,MainWindow.width, 40) + addCloseButton(panel) + addNewsButton(panel) + addBugReportButton(panel) + addHighscoreButton(panel) + addDiscordButton(panel) + addLogo(panel) + add(panel) + } + + private fun addPlayButton(){ + val loadingFrame = JLabel(ImageIcon(javaClass.getResource("/loadingFrame.png"))) + playButton.isEnabled = true + playButton.onClick { + if(Updater.checkUpdate()) Updater.runUpdate() + else Runtime.getRuntime().exec("java -jar " + Settings.SAVE_DIR + File.separator + Settings.SAVE_NAME, null, File(System.getProperty("user.home"))).also { exitProcess(0) } + } + loadingFrame.placeAt(96,MainWindow.height - 35, 704, 35) + loadingBar.placeAt(103, MainWindow.height - 33, 695, 31) + playButton.placeAt(0,MainWindow.height - 35,100,35) + loadingLabel.placeAt(300, MainWindow.height - 24, 300, 15) + add(loadingLabel) + add(loadingBar) + add(loadingFrame) + add(playButton) + } + + private fun addNewsButton(panel: JPanel){ + val label = BackgroundLabel("/messageBox.png", "Latest
Update") + label.placeAt(0, 40, 90, 56) + add(label) + + val button = ImgButton("/news.png") + button.onMouseEnter { label.isVisible = true } + button.onMouseExit { label.isVisible = false } + button.onClick { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(URI("https://2009scape.org/services/m=news/archives/2021-07-12.html")); + } + } + button.placeAt(5,4,35,35) + panel.add(button) + } + + private fun addBugReportButton(panel: JPanel){ + val label = BackgroundLabel("/messageBox.png", "Report
Bug") + label.placeAt(25, 40, 90, 56) + add(label) + + val button = ImgButton("/reportBug.png") + button.onMouseEnter { label.isVisible = true } + button.onMouseExit { label.isVisible = false } + button.onClick { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(URI("https://gitlab.com/2009scape/2009scape/-/issues")); + } + } + button.placeAt(50, 4, 35, 35) + panel.add(button) + } + + private fun addHighscoreButton(panel: JPanel){ + val label = BackgroundLabel("/messageBox.png", "Leader
Boards") + label.placeAt(70, 40, 90, 56) + add(label) + + val button = ImgButton("/highScores.png") + button.onMouseEnter { label.isVisible = true } + button.onMouseExit { label.isVisible = false } + button.onClick { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(URI("https://2009scape.org/services/m=hiscore/hiscores.html?world=2")); + } + } + button.placeAt(90, 2, 38, 38) + panel.add(button) + } + + private fun addDiscordButton(panel: JPanel){ + val label = BackgroundLabel("/messageBox.png", "Join
Discord") + label.placeAt(110, 40, 90, 56) + add(label) + + val button = ImgButton("/joinDiscord.png") + button.onMouseEnter { label.isVisible = true } + button.onMouseExit { label.isVisible = false } + button.onClick { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(URI("https://discord.gg/YY7WSttN7H")); + } + } + button.placeAt(135, 4, 35, 35) + panel.add(button) + } + + private fun addCloseButton(panel: BackgroundPanel){ + val button = ImgButton("/close_hi.png","/close_dark.png") + button.onClick { exitProcess(0) } + button.placeAt(panel.width - 35, 7, 25, 25) + button.isOpaque = false + panel.add(button) + } + + private fun addLogo(panel: JPanel){ + val logo = JLabel(ImageIcon(javaClass.getResource("/logo.png"))) + logo.icon = ImageIcon((logo.icon as ImageIcon).image.getScaledInstance(179,40, Image.SCALE_SMOOTH)) + logo.placeAt(311, 0, 179, 40) + panel.add(logo) + } + + @JvmStatic + fun main(args: Array) { + + } +} \ No newline at end of file diff --git a/src/main/kotlin/Settings.kt b/src/main/kotlin/Settings.kt new file mode 100644 index 0000000..4633f68 --- /dev/null +++ b/src/main/kotlin/Settings.kt @@ -0,0 +1,5 @@ +object Settings { + val SAVE_DIR = System.getProperty("user.home") + val SAVE_NAME = "2009scape.jar" + val DOWNLOAD_URL = "http://play.2009scape.org/2009scape.jar" +} \ No newline at end of file diff --git a/src/main/kotlin/Updater.kt b/src/main/kotlin/Updater.kt new file mode 100644 index 0000000..f036681 --- /dev/null +++ b/src/main/kotlin/Updater.kt @@ -0,0 +1,86 @@ +import java.awt.Image +import java.io.File +import java.io.RandomAccessFile +import java.net.URL +import java.util.concurrent.Executors +import javax.swing.ImageIcon + +object Updater { + + private var status = UpdateStatus.DOWNLOADING + + fun checkUpdate(): Boolean{ + val localMD5 = Checksum.getLocalChecksum(Settings.SAVE_DIR + File.separator + Settings.SAVE_NAME) + val remoteMD5 = Checksum.getRemoteChecksum(Settings.DOWNLOAD_URL) + + return localMD5 != remoteMD5 + } + + fun runUpdate(){ + val t = Thread() { + var oldText = MainWindow.loadingLabel.text + MainWindow.loadingLabel.text = "Updating client..." + MainWindow.playButton.isEnabled = false + var downloaded = 0 + var lastBarUpdate = 0 + + val connection = URL(Settings.DOWNLOAD_URL).openConnection() + connection.connect() + + val length = connection.contentLength + + if (!File(Settings.SAVE_DIR).exists()) { + File(Settings.SAVE_DIR).mkdir() + } + if (File(Settings.SAVE_DIR + File.separator + Settings.SAVE_NAME).exists()) { + File(Settings.SAVE_DIR + File.separator + Settings.SAVE_NAME).delete() + } + + File(Settings.SAVE_DIR + File.separator + Settings.SAVE_NAME).createNewFile() + + val file = RandomAccessFile(Settings.SAVE_DIR + File.separator + Settings.SAVE_NAME, "rw") + + val stream = connection.getInputStream() + + while(status == UpdateStatus.DOWNLOADING){ + val buffer = if(length - downloaded > 1024){ + ByteArray(1024) + } else { + ByteArray(length - downloaded) + } + + val read = stream.read(buffer) + + if(read == -1) break + + val progress = ((downloaded / length.toFloat()) * 100).toInt() + val barLength = 1 + ((progress / 100.0) * 695.0).toInt() + + MainWindow.loadingLabel.text = "Updating client... $progress%" + + if(barLength - lastBarUpdate > 5){ + MainWindow.loadingBar.icon = ImageIcon((MainWindow.loadingBar.icon as ImageIcon).image.getScaledInstance(barLength, 31, Image.SCALE_FAST)) + MainWindow.loadingBar.placeAt(103, MainWindow.height - 33, barLength, 31) + lastBarUpdate = barLength + } + + file.write(buffer, 0, read) + downloaded += read + + if(downloaded >= length){ + MainWindow.loadingLabel.text = oldText + MainWindow.playButton.isEnabled = true + file.close() + status = UpdateStatus.COMPLETE + } + } + MainWindow.loadingBar.icon = ImageIcon((MainWindow.loadingBar.icon as ImageIcon).image.getScaledInstance(695, 31, Image.SCALE_FAST)) + MainWindow.loadingBar.placeAt(103, MainWindow.height - 33, 695, 31) + }.start() + } + + internal enum class UpdateStatus{ + DOWNLOADING, + COMPLETE + } +} \ No newline at end of file diff --git a/src/main/resources/close_dark.png b/src/main/resources/close_dark.png new file mode 100644 index 0000000..470ce74 Binary files /dev/null and b/src/main/resources/close_dark.png differ diff --git a/src/main/resources/close_hi.png b/src/main/resources/close_hi.png new file mode 100644 index 0000000..1b80f89 Binary files /dev/null and b/src/main/resources/close_hi.png differ diff --git a/src/main/resources/highScores.png b/src/main/resources/highScores.png new file mode 100644 index 0000000..13333fe Binary files /dev/null and b/src/main/resources/highScores.png differ diff --git a/src/main/resources/joinDiscord.png b/src/main/resources/joinDiscord.png new file mode 100644 index 0000000..7db79ea Binary files /dev/null and b/src/main/resources/joinDiscord.png differ diff --git a/src/main/resources/loadingBar.png b/src/main/resources/loadingBar.png new file mode 100644 index 0000000..c2f0b90 Binary files /dev/null and b/src/main/resources/loadingBar.png differ diff --git a/src/main/resources/loadingFrame.png b/src/main/resources/loadingFrame.png new file mode 100644 index 0000000..c11f1ea Binary files /dev/null and b/src/main/resources/loadingFrame.png differ diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png new file mode 100644 index 0000000..2b3e054 Binary files /dev/null and b/src/main/resources/logo.png differ diff --git a/src/main/resources/messageBox.png b/src/main/resources/messageBox.png new file mode 100644 index 0000000..17e6869 Binary files /dev/null and b/src/main/resources/messageBox.png differ diff --git a/src/main/resources/news.png b/src/main/resources/news.png new file mode 100644 index 0000000..f7cd39e Binary files /dev/null and b/src/main/resources/news.png differ diff --git a/src/main/resources/playButton.png b/src/main/resources/playButton.png new file mode 100644 index 0000000..a4bdc29 Binary files /dev/null and b/src/main/resources/playButton.png differ diff --git a/src/main/resources/playButtonDisabled.png b/src/main/resources/playButtonDisabled.png new file mode 100644 index 0000000..f50b960 Binary files /dev/null and b/src/main/resources/playButtonDisabled.png differ diff --git a/src/main/resources/reportBug.png b/src/main/resources/reportBug.png new file mode 100644 index 0000000..d4fae6c Binary files /dev/null and b/src/main/resources/reportBug.png differ diff --git a/src/main/resources/tile.png b/src/main/resources/tile.png new file mode 100644 index 0000000..97d621c Binary files /dev/null and b/src/main/resources/tile.png differ diff --git a/src/main/resources/topBar.png b/src/main/resources/topBar.png new file mode 100644 index 0000000..6d88295 Binary files /dev/null and b/src/main/resources/topBar.png differ