aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--LICENCE21
-rw-r--r--README.md3
-rw-r--r--auth.json7
-rw-r--r--info.json15
-rwxr-xr-xmcsh.sh58
-rw-r--r--meta.jq64
7 files changed, 174 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..93ad9bc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+assets
+libraries
+logs
+natives
+run
+versions
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..24dbe72
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Caroline Larimore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3ec0235
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# mcsh
+
+a minecraft launcher written in bash (and jq), for some reason
diff --git a/auth.json b/auth.json
new file mode 100644
index 0000000..2f3a7c6
--- /dev/null
+++ b/auth.json
@@ -0,0 +1,7 @@
+{
+ "player_name": "mcsh",
+ "uuid": "00000000-0000-0000-0000-000000000000",
+ "xuid": "00000000-0000-0000-0000-000000000000",
+ "access_token": "0",
+ "type": "mojang"
+}
diff --git a/info.json b/info.json
new file mode 100644
index 0000000..6e3bd0e
--- /dev/null
+++ b/info.json
@@ -0,0 +1,15 @@
+{
+ "os": {
+ "name": "linux",
+ "arch": "x64"
+ },
+ "features": {
+ "is_demo_user": false,
+ "has_custom_resolution": false,
+ "has_quick_plays_support": false,
+
+ "is_quick_play_singleplayer": false,
+ "is_quick_play_multiplayer": false,
+ "is_quick_play_realms": false
+ }
+}
diff --git a/mcsh.sh b/mcsh.sh
new file mode 100755
index 0000000..9d9938f
--- /dev/null
+++ b/mcsh.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+set -euo pipefail
+IFS=$'\n\t'
+
+VERSION="1.21.8"
+
+function download_library {
+ if ! [[ -e "${2:-}" ]]; then
+ printf '%s\n' "${1:-}"
+ mkdir -p "$(dirname "${2:-}")"
+ curl --silent -o "${2:-}" "${3:-}"
+ fi
+}
+export -f download_library
+
+function download_asset {
+ if ! [[ -e "assets/objects/${2:-}" ]]; then
+ printf '%s\n' "${1:-}"
+ mkdir -p "$(dirname "assets/objects/${2:-}")"
+ curl --silent -o "assets/objects/${2:-}" "https://resources.download.minecraft.net/${2:-}"
+ fi
+}
+export -f download_asset
+
+printf 'updating version manifest\n'
+mkdir -p versions
+curl --silent "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" | jq '{"latest": .latest, "versions": [.versions[] | {"key": .id, "value": .url}] | from_entries}' > versions/manifest.json
+
+
+printf 'updating %s meta %s\n' "$VERSION"
+mkdir -p "versions/$VERSION"
+curl --silent -o "versions/$VERSION/meta.json" "$(jq -r --arg version "$VERSION" '.versions[$version]' versions/manifest.json)"
+meta="$(jq -c --slurpfile info info.json --slurpfile auth auth.json -f meta.jq "versions/$VERSION/meta.json")"
+
+printf 'updating %s asset index...\n' "$VERSION"
+mkdir -p assets/indexes
+assets_url="$(<<<"$meta" jq -r '.asset_index')"
+curl --silent -o "assets/indexes/$(basename "$assets_url")" "$assets_url"
+
+#TODO: check hash
+if ! [[ -e "versions/$VERSION/client.jar" ]]; then
+ printf 'downloading %s client jar...\n' "$VERSION"
+ <<<"$meta" jq -r '.client_url' | xargs curl --silent -o "versions/$VERSION/client.jar"
+fi
+
+printf 'downloading libraries...\n'
+<<<"$meta" jq -r '.libraries[] | "\(.name) libraries/\(.path) \(.url)"' | xargs -I {} "$SHELL" -c "download_library {}"
+
+printf 'downloading assets...\n'
+jq -r '.objects | to_entries[] | "\(.key) \(.value.hash[:2])/\(.value.hash)"' "assets/indexes/$(basename "$assets_url")" | xargs -P 10 -I {} "$SHELL" -c "download_asset {}"
+
+# if ! [[ -e "versions/$VERSION/log4j.xml" ]]; then
+# printf 'downloading %s log4j config...\n' "$VERSION"
+# <<<"$meta" jq -r '.logging_config' | xargs curl --silent -o "versions/$VERSION/log4j.xml"
+# fi
+
+printf 'starting game :3\n'
+<<<"$meta" jq -r '.args.jvm + [.main_class] + .args.game | join(" ")' | xargs java
diff --git a/meta.jq b/meta.jq
new file mode 100644
index 0000000..2dd6a54
--- /dev/null
+++ b/meta.jq
@@ -0,0 +1,64 @@
+def apply_optional: if has("rules") then
+ {
+ "rules": [.rules[] | {
+ "action": .action,
+ "match": del(.action)
+ }],
+ "value": del(.rules)
+ } as $optional
+ | if $optional.rules[].match | inside($info[0]) then
+ $optional.value
+ else
+ empty
+ end
+end;
+
+def replace_placeholders: {
+ "auth_player_name": $auth[0].player_name,
+ "auth_uuid": $auth[0].uuid,
+ "auth_xuid": $auth[0].xuid,
+ "auth_access_token": $auth[0].access_token,
+ "user_type": $auth[0].type,
+
+ "version_name": .id,
+ "version_type": .type,
+
+ "assets_index_name": .assets,
+ "assets_root": "assets",
+ "game_directory": "run",
+ "natives_directory": "natives",
+
+ "classpath": (. as $root | [
+ .libraries[] | apply_optional | "libraries/" + .downloads.artifact.path
+ ] + ["versions/\($root.id)/client.jar"] | join(":")),
+ "clientid": "idfk",
+
+ # log4j config path
+ "path": "versions/\(.id)/log4j.xml",
+
+ "launcher_name": "mcsh",
+ "launcher_version": "v0.1.0"
+} as $strings | walk(if type == "string" then
+ gsub("\\${(?<key>.*)}"; "\($strings[.key] // "${\(.key)}")")
+end);
+
+replace_placeholders | {
+ "args": .arguments | to_entries | [.[] | {
+ "key": .key,
+ "value": .value | [
+ .[] | if type == "object" then
+ apply_optional | .value
+ end
+ ] | flatten
+ }] | from_entries, # as $args | $args + {"jvm": [.logging.client.argument] + $args.jvm}),
+ "libraries": [.libraries[] | apply_optional | {
+ "name": .name,
+ "path": .downloads.artifact.path,
+ "url": .downloads.artifact.url
+ }],
+ "asset_index": .assetIndex.url,
+ "java_version": .javaVersion.majorVersion,
+ "client_url": .downloads.client.url,
+ "main_class": .mainClass,
+ "logging_config": .logging.client.file.url
+}