Initial push

This commit is contained in:
ceikry 2021-07-15 19:28:03 -05:00
commit dee36e793b
27 changed files with 554 additions and 0 deletions

View file

@ -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("<html><div style='text-align: center;'>$text</div></html")
init {
isVisible = false
layout = GridBagLayout()
textLabel.foreground = Color(255,255,255)
add(textLabel)
}
}

View file

@ -0,0 +1,40 @@
import java.awt.Dimension
import java.awt.Graphics
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import javax.swing.JPanel
/**
* Won't lie, I stole this from some nice person on StackOverflow so I didn't have to figure it out myself.
* Credit: https://stackoverflow.com/questions/24746354/java-jpanel-tiled-background-image
*/
open class BackgroundPanel(var tileImage: BufferedImage?, url: String = "") : JPanel() {
constructor(url: String) : this(null, url)
init {
if(url.isNotBlank()) tileImage = ImageIO.read(javaClass.getResource(url))
layout = null
}
override fun paintComponent(g: Graphics) {
val width = width
val height = height
run {
var x = 0
while (x < width) {
run {
var y = 0
while (y < height) {
g.drawImage(tileImage, x, y, this)
y += tileImage!!.height
}
}
x += tileImage!!.width
}
}
}
override fun getPreferredSize(): Dimension {
return Dimension(240, 240)
}
}

View file

@ -0,0 +1,98 @@
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 kotlin.experimental.and
object Checksum {
val localChecksum: String?
get() {
val local: File = File(Settings.SAVE_DIR + Settings.SAVE_NAME)
try {
FileInputStream(local).use { fis -> 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'
)
}

View file

@ -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)
}

View file

@ -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))
}
}

View file

@ -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++
}
}
}
}

View file

@ -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<br/>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<br/>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<br/>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<br/>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<String>) {
}
}

View file

@ -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"
}

View file

@ -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
}
}