open System open System.IO open System.IO.Compression open System.Security.Cryptography open System.Text open System.Text.Json open System.Text.Json.Nodes let args = fsi.CommandLineArgs |> Array.skip 1 if args.Length <> 3 then failwith "Usage: pack.fsx SOURCE.mrpack CLIENT_DIR OUTPUT_DIR" let source, client, output = args[0], args[1], args[2] let digest algorithm path = use stream = File.OpenRead path (match algorithm with | "sha512" -> SHA512.HashData stream | "sha1" -> SHA1.HashData stream | _ -> invalidArg "algorithm" algorithm) |> Convert.ToHexString |> _.ToLowerInvariant() let bytes (entry: ZipArchiveEntry) = use stream = entry.Open() use memory = new MemoryStream() stream.CopyTo memory memory.ToArray() let text value = Encoding.UTF8.GetBytes(value: string) let json value = JsonValue.Create(value: string) :> JsonNode let shaderMods = set [ "iris-neoforge-1.8.12+mc1.21.1.jar" "monocle-0.2.3.ms.jar" "colorwheel-neoforge-1.3.1-mc1.21.1.jar" ] let shaderConfig = set [ "overrides/config/iris.properties" "overrides/config/iris-excluded.json" "overrides/config/monocle.properties" "overrides/config/colorwheel-client.toml" ] let restores = [ "ftb-essentials-neoforge-2101.1.10.jar", "https://edge.forgecdn.net/files/8442/866/ftb-essentials-neoforge-2101.1.10.jar" "AdvancedBackups-neoforge-1.21-3.7.1.jar", "https://cdn.modrinth.com/data/Jrmoreqs/versions/ufIaRDFo/AdvancedBackups-neoforge-1.21-3.7.1.jar" ] let patcher = "EuphoriaPatcher-1.10.5-r5.9.3-neoforge.jar" let shader = "ComplementaryReimagined_r5.9.3.zip" let patched = "ComplementaryReimagined_r5.9.3 + EuphoriaPatches_1.10.5" let file path url = let local = Path.Combine(client, path) let hashes = JsonObject() hashes["sha512"] <- json (digest "sha512" local) hashes["sha1"] <- json (digest "sha1" local) let env = JsonObject() env["client"] <- json "required" env["server"] <- json "unsupported" let item = JsonObject() item["path"] <- json path item["hashes"] <- hashes item["env"] <- env item["downloads"] <- JsonArray(json url) item["fileSize"] <- JsonValue.Create(FileInfo(local).Length) item :> JsonNode let build shaders = use original = ZipFile.OpenRead source let index = original.GetEntry("modrinth.index.json") |> bytes |> Encoding.UTF8.GetString |> JsonNode.Parse let files = index["files"].AsArray() for item in files do let path = item["path"].GetValue() let hashes = item["hashes"] if digest "sha512" (Path.Combine(client, path)) <> hashes["sha512"].GetValue() then failwithf "Source mod differs: %s" path let replaced = restores |> List.map (fun (name, _) -> "mods/" + name) |> Set.ofList |> Set.add ("mods/" + patcher) let selected = JsonArray() for item in files do let path = item["path"].GetValue() if not (replaced.Contains path) && (shaders || not (shaderMods.Contains(Path.GetFileName path))) then selected.Add(item.DeepClone()) for name, url in restores do selected.Add(file ("mods/" + name) url) if shaders then selected.Add(file ("mods/" + patcher) "https://cdn.modrinth.com/data/4H6sumDB/versions/QMsRXtSJ/EuphoriaPatcher-1.10.5-r5.9.3-neoforge.jar") selected.Add(file ("shaderpacks/" + shader) "https://cdn.modrinth.com/data/HVnmMxH1/versions/Bqen1mJX/ComplementaryReimagined_r5.9.3.zip") index["files"] <- selected index["name"] <- json (if shaders then "Enigmatica 10 - Custom (Shaders)" else "Enigmatica 10 - Custom (Minimal)") index["versionId"] <- json (if shaders then "1.33.0-shaders.2" else "1.33.0-minimal.2") index["summary"] <- json (if shaders then "Multiplayer pack with Complementary Reimagined and Euphoria Patches." else "Multiplayer pack without the shader renderer or administrative tools.") let entries = System.Collections.Generic.SortedDictionary(StringComparer.Ordinal) for entry in original.Entries do if [".jar"; ".zip"; ".mrpack"; ".glsl"; ".fsh"; ".vsh"; ".csh"] |> List.exists (fun suffix -> entry.FullName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) then failwithf "Embedded payload must be a manifest download: %s" entry.FullName if entry.FullName <> "modrinth.index.json" && not (shaderConfig.Contains entry.FullName) then entries.Add(entry.FullName, bytes entry) entries.Add("modrinth.index.json", text (index.ToJsonString(JsonSerializerOptions(WriteIndented = true)))) if shaders then entries["overrides/config/iris.properties"] <- text ("shaderPack=" + patched + "\n") let name = if shaders then "e10c-shaders.mrpack" else "e10c.mrpack" let path = Path.Combine(output, name) use destination = File.Open(path, FileMode.CreateNew) use archive = new ZipArchive(destination, ZipArchiveMode.Create) for KeyValue(name, data) in entries do let entry = archive.CreateEntry(name, CompressionLevel.Optimal) entry.LastWriteTime <- DateTimeOffset(1980, 1, 1, 0, 0, 0, TimeSpan.Zero) use stream = entry.Open() stream.Write data printfn "%s files=%d mods=%d" path selected.Count (selected |> Seq.filter (fun item -> item["path"].GetValue().StartsWith("mods/")) |> Seq.length) Directory.CreateDirectory output |> ignore build false build true