diff options
Diffstat (limited to 'src/main.zig')
-rw-r--r-- | src/main.zig | 251 |
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 +} |