aboutsummaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2023-09-09 23:10:49 +0100
committerMartin Ashby <martin@ashbysoft.com>2023-09-09 23:10:49 +0100
commit3157633c35da97dbc60bfed1b12c4a93bcc543a2 (patch)
tree1fc7214cf6bc06ccb5fe0b78209d7d9cffa25b73 /src/main.zig
downloadmcl-3157633c35da97dbc60bfed1b12c4a93bcc543a2.tar.gz
mcl-3157633c35da97dbc60bfed1b12c4a93bcc543a2.tar.bz2
mcl-3157633c35da97dbc60bfed1b12c4a93bcc543a2.tar.xz
mcl-3157633c35da97dbc60bfed1b12c4a93bcc543a2.zip
Initial
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig251
1 files changed, 251 insertions, 0 deletions
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..8ca780b
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,251 @@
+const std = @import("std");
+
+const Manifest = struct {
+ const uri = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
+
+ latest: struct {
+ release: []const u8,
+ snapshot: []const u8,
+ },
+ versions: []struct {
+ id: []const u8,
+ type: []const u8,
+ url: []const u8,
+ releaseTime: []const u8,
+ sha1: []const u8,
+ complianceLevel: i64,
+ },
+};
+
+const Version = struct {
+ const Rule = struct {
+ const Feature = struct {
+ is_demo_user: bool = false,
+ has_custom_resolution: bool = false,
+ is_quick_play_multiplayer: bool = false,
+ is_quick_play_realms: bool = false,
+ };
+ const Os = struct {
+ name: ?[]const u8 = null,
+ arch: ?[]const u8 = null,
+ };
+ action: enum{allow},
+ features: ?Feature = null,
+ os: ?Os = null,
+ };
+
+ const Arg = union(enum) {
+ const Extended = struct {
+ const Value = union(enum) {
+ single: []const u8,
+ many: []const []const u8,
+ pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !Value {
+ switch (try source.peekNextTokenType()) {
+ .array_begin => {
+ return .{ .many = try std.json.innerParse([]const []const u8, allocator, source, options) };
+ },
+ .string => {
+ return .{ .single = try std.json.innerParse([]const u8, allocator, source, options) };
+ },
+ else => return error.UnexpectedToken,
+ }
+ }
+ };
+
+ rules: []Rule,
+ value: Value,
+ };
+
+ plain: []const u8,
+ extended: Extended,
+
+ pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !Arg {
+ switch (try source.peekNextTokenType()) {
+ .object_begin => {
+ return .{
+ .extended = try std.json.innerParse(Extended, allocator, source, options),
+ };
+ },
+ .string => {
+ return .{
+ .plain = try std.json.innerParse([]const u8, allocator, source, options),
+ };
+ },
+ else => return error.UnexpectedToken,
+ }
+ }
+ };
+
+ arguments: struct {
+ game: []Arg,
+ jvm: []Arg,
+ },
+};
+
+/// Minecraft launcher!
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = false }){}; // TODO turn safety back on
+ defer _ = gpa.deinit();
+ const a = gpa.allocator();
+ var client = std.http.Client{ .allocator = a };
+ defer client.deinit();
+
+ const m_wrap = try download_json(&client, Manifest, Manifest.uri);
+ defer m_wrap.deinit();
+ const m = m_wrap.value;
+ std.log.info("release: {s} snapshot: {s}", .{ m.latest.release, m.latest.snapshot });
+
+ // Find the release version
+ const rvu = for (m.versions) |v| {
+ if (std.mem.eql(u8, v.id, m.latest.release)) {
+ break v.url;
+ }
+ } else {
+ return error.NoReleaseVersionFound;
+ };
+ std.log.info("found release version url {s}", .{rvu});
+ var rv_wrap = try download_json(&client, Version, rvu);
+ defer rv_wrap.deinit();
+ const rv = rv_wrap.value;
+ for (rv.arguments.game) |ga| {
+ std.log.info("game arg: {}", .{ga});
+ }
+ // Download the manifest for that
+
+ // std.mem.replace(comptime T: type, input: []const T, needle: []const T, replacement: []const T, output: []T)
+
+ // check the latest.release
+ // find that in the versions[] by key id
+ // get the download URL for that https://piston-meta.mojang.com/v1/packages/1f376e6cb981265af345076889f9ef325bb6b9e8/1.20.1.json
+
+ // get arguments.game and arguments.jvm ready to supply ...
+ // probably just use the system java version for now
+ //
+}
+
+/// Caller is responsible for calling deinit on the resulting Parsed struct.
+fn download_json(client: *std.http.Client, comptime T: type, url_maybe_https: []const u8) !std.json.Parsed(T) {
+ // TODO use https once zig supports TLSv1.2 or mojang support TLSv1.3
+ const a = client.allocator;
+ const sz = std.mem.replacementSize(u8, url_maybe_https, "https://", "http://");
+ const url = try a.alloc(u8, sz);
+ defer a.free(url);
+ _ = std.mem.replace(u8, url_maybe_https, "https://", "http://", url);
+
+ var h = std.http.Headers.init(a);
+ defer h.deinit();
+ try h.append("Accept", "application/json");
+ const uri = try std.Uri.parse(url);
+ var r = try client.request(.GET, uri, h, .{});
+ try r.start();
+ try r.wait();
+ if (r.response.status != .ok) {
+ std.log.err("request failed {}:{s}", .{ r.response.status, r.response.reason });
+ return error.HttpStatusError;
+ }
+ var r2 = std.json.reader(a, r.reader());
+ var diags = std.json.Diagnostics{};
+ r2.enableDiagnostics(&diags);
+ defer r2.deinit();
+ var res = std.json.parseFromTokenSource(T, a, &r2, .{ .ignore_unknown_fields = true, .allocate = .alloc_always }) catch |e| {
+ std.log.err("parse failed {} line {} col {}", .{ e, diags.getLine(), diags.getColumn() });
+ return e;
+ };
+ return res;
+}
+
+test "heterogenous array" {
+ const x =
+ \\ {
+ \\ "y": [
+ \\ { "z": "yay" },
+ \\ "nay"
+ \\ ]
+ \\ }
+ ;
+ const IX = union(enum) {
+ // What to name union tags?
+ const P = struct { z: []const u8 };
+ p: P,
+ q: []const u8,
+ pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !@This() {
+ switch (try source.peekNextTokenType()) {
+ .object_begin => {
+ return .{
+ .p = try std.json.innerParse(P, allocator, source, options),
+ };
+ },
+ .string => {
+ return .{
+ .q = try std.json.innerParse([]const u8, allocator, source, options),
+ };
+ },
+ else => return error.UnexpectedToken,
+ }
+ }
+ };
+ const X = struct { y: []const IX };
+ const xx = try std.json.parseFromSlice(X, std.testing.allocator, x, .{});
+ defer xx.deinit();
+ try std.testing.expectEqualDeep(X{
+ .y = &[_]IX{
+ .{ .p = .{ .z = "yay" } },
+ .{ .q = "nay" },
+ },
+ }, xx.value);
+}
+
+test "string list" {
+ const x =
+ \\["ond", "two", "three", "four"]
+ ;
+ const v_w = try std.json.parseFromSlice([]const []const u8, std.testing.allocator, x, .{});
+ defer v_w.deinit();
+ const expect: []const []const u8 = &[_][]const u8{ "ond", "two", "three", "four" };
+ try std.testing.expectEqualDeep(expect, v_w.value);
+}
+
+test "string or string list" {
+ const x1 =
+ \\ "testing"
+ ;
+ const x2 =
+ \\ ["one", "two"]
+ ;
+ const X = union(enum) {
+ one: []const u8,
+ many: []const []const u8,
+ pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !@This() {
+ switch (try source.peekNextTokenType()) {
+ .array_begin => {
+ return .{ .many = try std.json.innerParse([]const []const u8, allocator, source, options) };
+ },
+ .string => {
+ return .{ .one = try std.json.innerParse([]const u8, allocator, source, options) };
+ },
+ else => return error.UnexpectedToken,
+ }
+ }
+ };
+ var ax1_w = try std.json.parseFromSlice(X, std.testing.allocator, x1, .{});
+ defer ax1_w.deinit();
+ var ax2_w = try std.json.parseFromSlice(X, std.testing.allocator, x2, .{});
+ defer ax2_w.deinit();
+}
+
+test "Version" {
+ const x = @embedFile("version_test.json");
+ var f = std.io.fixedBufferStream(x);
+ var jr = std.json.reader(std.testing.allocator, f.reader());
+ defer jr.deinit();
+ var d = std.json.Diagnostics{};
+ jr.enableDiagnostics(&d);
+ var v_w = std.json.parseFromTokenSource(Version, std.testing.allocator, &jr, .{.ignore_unknown_fields = true}) catch |e| {
+ std.log.err("parse failed {} line {} col {}", .{ e, d.getLine(), d.getColumn() });
+ return e;
+ };
+ defer v_w.deinit();
+ const v = v_w.value;
+ _ = v;
+ // v.arguments.game
+}