commit 68c8e3cfb868e49a0deef1188af406e910b9d615 Author: Failure Date: Sat Nov 16 03:38:44 2024 -0800 initial diff --git a/config.nims b/config.nims new file mode 100644 index 0000000..feacfdf --- /dev/null +++ b/config.nims @@ -0,0 +1 @@ +setCommand "cpp" \ No newline at end of file diff --git a/magicmissile.nimble b/magicmissile.nimble new file mode 100644 index 0000000..31f031f --- /dev/null +++ b/magicmissile.nimble @@ -0,0 +1,16 @@ +# Package + +version = "0.1.0" +author = "Failure" +description = "foul-magics launcher" +license = "GPL-3.0-or-later" +srcDir = "src" +bin = @["magicmissile"] + + +# Dependencies + +requires "nim >= 2.2.0" + +requires "nimgl >= 1.3.2" +requires "zippy >= 0.10.16" \ No newline at end of file diff --git a/src/magicmissile.nim b/src/magicmissile.nim new file mode 100644 index 0000000..42bb53a --- /dev/null +++ b/src/magicmissile.nim @@ -0,0 +1,277 @@ +# Copyright 2018, NimGL contributors. + +import nimgl/imgui, nimgl/imgui/[impl_opengl, impl_glfw] +import nimgl/[opengl, glfw] +import std/[times, os, math, asyncdispatch, httpclient, strformat, strutils, paths, dirs, osproc] +from zippy/ziparchives import nil +from zippy/tarballs import nil + + +const zeroPos* = ImVec2(x: 0, y: 0) + +proc `or`(a, b: ImGuiWindowFlags): ImGuiWindowFlags = + (a.int or b.int).ImGuiWindowFlags + +removeDir("cache") +createdir("cache") +var cache = unixToNativePath("cache") + +createDir("user") +var userDir = unixToNativePath("user") +var clientDir = unixToNativePath("client") +var javaDir = unixToNativePath("java") + + +var javaExists = dirExists("java") +var clientExists = dirExists("client") + +proc superUnsafeSet(variable: var string, content: string, internalLen: int) = + zeroMem(variable[0].addr, internalLen) + copyMem(variable[0].addr, content[0].addr, content.len) + +var javaStatus = newString(255) +var clientStatus = newString(255) + +if javaExists: superUnsafeSet(javaStatus, "Java: good to go", 255) +else: superUnsafeSet(javaStatus, "Java: needs download", 255) + +if clientExists: superUnsafeSet(clientStatus, "Client: checking for update...", 255) +else: superUnsafeSet(clientStatus, "Client: needs download", 255) + +var downloadingClient = false +var downloadingJava = false +var checkingForUpdate = true +var clientVersion = "???" + +var headers = newHttpHeaders() +headers["User-Agent"] = "MAGIC MISSILE/1.0" + +proc getFoulUrl(): Future[string] {.async.} = + var client = newAsyncHttpClient(headers=headers, maxRedirects=0) + var redir = await client.head("https://git.cef.icu/Failure/foul-magics/actions/runs/latest") + return redir.headers["location"] + +proc getFoulVersion(): Future[string] {.async.} = + result = (await getFoulUrl()).split("/")[^1] + +proc onClientChange(total, progress, speed: BiggestInt) {.async.} = + {.cast(gcsafe).}: + var status = &"{(progress / 1000).int}K/{(total / 1000).int}k ({speed div 1000} kbps)" + superUnsafeSet(clientStatus, status, 255) + +proc mergeDirectory(source, dest: string) = + createDir(dest) + for file in walkDirRec(source, {PathComponent.pcDir}, relative=true): + createDir(dest.Path / file.Path) + + for file in walkDirRec(source, {PathComponent.pcFile}, relative=true): + copyFile($(source.Path / file.Path), $(dest.Path / file.Path)) + + +proc getClient() {.async.} = + removeDir("client") + # clean up versioned files + removeDir(unixToNativePath("client") / "lib") + downloadingClient = true + var client = newAsyncHttpClient(headers=headers, maxRedirects=0) + var baseUrl = await getFoulUrl() + + var url = baseUrl & "/artifacts/client.zip" + var path: string = cache / "baseclient.zip" + + client.onProgressChanged = onClientChange + + try: + await client.downloadFile(url, path) + finally: + client.close() + + + superUnsafeSet(clientStatus, "Client: extracting...", 255) + ziparchives.extractAll(path, cache / "client") + ziparchives.extractAll(cache / "client" / "mage-client.zip", clientDir) + superUnsafeSet(clientStatus, "Client: good to go", 255) + + mergeDirectory(clientDir / "sounds", userDir / "sounds") + mergeDirectory(clientDir / "sample-decks", userDir / "sample-decks") + mergeDirectory(clientDir / "backgrounds", userDir / "backgrounds") + + clientExists = true + writeFile("version", baseUrl.split("/")[^1]) + + +mergeDirectory(clientDir / "sounds", userDir / "sounds") +mergeDirectory(clientDir / "sample-decks", userDir / "sample-decks") +mergeDirectory(clientDir / "backgrounds", userDir / "backgrounds") + +proc onJavaChange(total, progress, speed: BiggestInt) {.async.} = + {.cast(gcsafe).}: + var status = &"{(progress / 1000).int}K/{(total / 1000).int}k ({speed div 1000} kbps)" + superUnsafeSet(javaStatus, status, 255) + +proc getJava() {.async.} = + removeDir("java") + downloadingJava = true + var client = newAsyncHttpClient(headers=headers) + + client.onProgressChanged = onJavaChange + var url: string + var filename: string + + when defined(windows): + url = "https://api.adoptium.net/v3/binary/latest/11/ga/windows/x64/jre/hotspot/normal/adoptium" + filename = "java.zip" + elif defined(linux): + url = "https://api.adoptium.net/v3/binary/latest/11/ga/linux/x64/jre/hotspot/normal/adoptium" + filename = "java.tar.gz" + var path = unixToNativePath("cache") / filename + + try: + await client.downloadFile(url, path) + finally: + client.close() + + superUnsafeSet(javaStatus, "Java: extracting...", 255) + when defined(windows): + ziparchives.extractAll(path, cache / "java") + else: + tarballs.extractAll(path, cache / "java") + for item in walkDir(cache / "java", true, false, true): + if item.kind == pcDir: + moveDir(cache / "java" / item.path, "java") + break + superUnsafeSet(javaStatus, "Java: good to go", 255) + javaExists = true + + +proc checkForUpdates() {.async.} = + defer: + checkingForUpdate = false + if fileExists("version"): + clientVersion = await getFoulVersion() + + if readFile("version") == clientVersion: + superUnsafeSet(clientStatus, "Client: good to go", 255) + else: + superUnsafeSet(clientStatus, "Client: needs update", 255) + clientExists = false + else: + superUnsafeSet(clientStatus, "Client: needs download", 255) + clientExists = false + +proc getJarPath(): string = + for item in walkDir(clientDir / "lib", true, false, true): + if item.path.startswith("mage-client"): + return clientDir / "lib" / item.path + +proc main() = + asyncCheck checkForUpdates() + doAssert glfwInit() + + glfwWindowHint(GLFWContextVersionMajor, 3) + glfwWindowHint(GLFWContextVersionMinor, 3) + glfwWindowHint(GLFWOpenglForwardCompat, GLFW_TRUE) + glfwWindowHint(GLFWOpenglProfile, GLFW_OPENGL_CORE_PROFILE) + glfwWindowHint(GLFWResizable, GLFW_FALSE) + + var w: GLFWWindow = glfwCreateWindow(300, 120, "MAGIC MISSILE") + if w == nil: + quit(-1) + + w.makeContextCurrent() + + doAssert glInit() + + let context = igCreateContext() + let io = igGetIO() + io[].iniFilename = nil + + doAssert igGlfwInitForOpenGL(w, true) + doAssert igOpenGL3Init() + + igStyleColorsCherry() + + var frameTimer = cpuTime() + 0.016666 + var javaBuf {.global.} = newString(2048) + if fileExists("javaArgs"): + superUnsafeSet(javaBuf, readFile("javaArgs"), 2048) + else: + superUnsafeSet(javaBuf, "-Xmx1024m -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true", 2048) + + while not w.windowShouldClose: + frameTimer = cpuTime() + 0.016 + glfwPollEvents() + + igOpenGL3NewFrame() + igGlfwNewFrame() + igNewFrame() + + igSetNextWindowPos(zeroPos) + + igBegin("MAGIC MISSILE", flags=ImGuiWindowFlags.NoResize or ImGuiWindowFlags.NoBringToFrontOnFocus or ImGuiWindowFlags.NoCollapse) + var mainWinX, mainWinY: int32 + w.getFramebufferSize(addr mainWinX, addr mainWinY) + igSetWindowSize(ImVec2(x: mainWinX.float32, y: mainwinY.float32)) + + igText(javaStatus.cstring) + if not javaExists: + igPushId(1) + if not downloadingJava: + igSameLine(igGetWindowWidth() - 70) + if igButton("Download", ImVec2(x: 0, y: 0)): + asyncCheck getJava() + igPopId() + + igPushId(2) + if not clientExists: + igText(clientStatus.cstring) + if not downloadingClient: + igSameLine(igGetWindowWidth() - 70) + if igButton("Download", ImVec2(x: 0, y: 0)): + asyncCheck getClient() + else: + igText(clientStatus.cstring) + igPopId() + + igText("Client version:") + igSameLine() + igText(clientVersion) + + if clientExists and javaExists: + igText("Java args:") + igSameLine() + igPushItemWidth(211) + igInputText("", javaBuf, 2048) + + if igButton("Run", ImVec2(x: 288, y: 0)): + w.hideWindow() + var command = absolutePath(javaDir / "bin" / "java") & " " & (javaBuf.strip(chars={'\x00'}).split(" ") & @["-jar", absolutePath(getJarPath())]).join(" ") + var process = execCmdEx(command, workingDir=userDir) + w.showWindow() + igEnd() + # End simple window + + igRender() + + glClearColor(0.45f, 0.55f, 0.60f, 1.00f) + glClear(GL_COLOR_BUFFER_BIT) + + igOpenGL3RenderDrawData(igGetDrawData()) + + w.swapBuffers() + + try: + while frameTimer - cpuTime() > 0: + poll(((max(0, frameTimer - cpuTime())) * 1000).int) + + except: + sleep(((max(0, frameTimer - cpuTime())) * 1000).int) + + igOpenGL3Shutdown() + igGlfwShutdown() + context.igDestroyContext() + + w.destroyWindow() + glfwTerminate() + +main()