Merge branch 'main' of git.colon-three.com:kodi/amethyst
All checks were successful
Build & Test / nix-build (push) Successful in 1m12s
All checks were successful
Build & Test / nix-build (push) Successful in 1m12s
This commit is contained in:
commit
a285e31175
89
lib/data.ex
89
lib/data.ex
@ -18,21 +18,55 @@ defmodule Amethyst.Minecraft.Write do
|
||||
import Bitwise
|
||||
|
||||
@moduledoc """
|
||||
This module contains functions for writing certain Minecraft data types which are more complex
|
||||
than simple binary data.
|
||||
This module contains functions for writing Minecraft data.
|
||||
|
||||
Each function in this module takes in an input of the proper type and returns a binary
|
||||
of the encoded data.
|
||||
"""
|
||||
|
||||
def uuid(uuid) do
|
||||
def uuid(uuid) when is_binary(uuid) do
|
||||
UUID.string_to_binary!(uuid)
|
||||
end
|
||||
|
||||
def bool(value) do
|
||||
def bool(value) when is_boolean(value) do
|
||||
case value do
|
||||
true -> <<0x01::8>>
|
||||
false -> <<0x00::8>>
|
||||
end
|
||||
end
|
||||
|
||||
def byte(value) when value in -128..127 do
|
||||
<<value::8-signed-big>>
|
||||
end
|
||||
|
||||
def ubyte(value) when value in 0..255 do
|
||||
<<value::8-unsigned-big>>
|
||||
end
|
||||
|
||||
def short(value) when value in -32_768..32_767 do
|
||||
<<value::16-signed-big>>
|
||||
end
|
||||
|
||||
def ushort(value) when value in 0..65_535 do
|
||||
<<value::16-unsigned-big>>
|
||||
end
|
||||
|
||||
def int(value) when value in -2_147_483_648..2_147_483_647 do
|
||||
<<value::32-signed-big>>
|
||||
end
|
||||
|
||||
def long(value) when value in -9_223_372_036_854_775_808..9_223_372_036_854_775_807 do
|
||||
<<value::64-signed-big>>
|
||||
end
|
||||
|
||||
def float(value) when is_number(value) do
|
||||
<<value::32-float>>
|
||||
end
|
||||
|
||||
def double(value) when is_number(value) do
|
||||
<<value::64-float>>
|
||||
end
|
||||
|
||||
def varint(value) when value in -2_147_483_648..2_147_483_647 do
|
||||
<<value::32-unsigned>> = <<value::32-signed>> # This is a trick to allow the arithmetic shift to act as a logical shift
|
||||
varnum("", value)
|
||||
@ -63,19 +97,44 @@ defmodule Amethyst.Minecraft.Write do
|
||||
def string(value) do
|
||||
<<varint(byte_size(value))::binary, value::binary>>
|
||||
end
|
||||
|
||||
def position({x, y, z}) do
|
||||
<<x::signed-big-26, z::signed-big-26, y::signed-big-12>>
|
||||
end
|
||||
|
||||
@doc """
|
||||
Writes a list of elements with the given `callback` function. This does not
|
||||
prefix the list with a length, remember to do that yourself if needed.
|
||||
|
||||
iex> Amethyst.Minecraft.Write.list([1, 2, 3, 4], &Amethyst.Minecraft.Write.byte/1)
|
||||
<<1, 2, 3, 4>>
|
||||
"""
|
||||
def list(list, callback) do
|
||||
Enum.reduce(list, "", &(&2 <> callback.(&1)))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Shorthand function for writing a value which may not be present. If `value` is `nil`,
|
||||
writes `false`, otherwise writes `true` followed by the value using `callback`.
|
||||
"""
|
||||
def option(value, callback) do
|
||||
case value do
|
||||
nil -> bool(false)
|
||||
v -> bool(true) <> callback.(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Amethyst.Minecraft.Read do
|
||||
import Bitwise
|
||||
|
||||
@moduledoc """
|
||||
This module contains functions for reading Minecraft data. Unlike Amethyst.Minecraft.Write, this
|
||||
includes all supported data types in order to make the interface more consistent.
|
||||
This module contains functions for reading Minecraft data.
|
||||
|
||||
These functions allow you to chain them into eachother, at the end they will produce a list of all the
|
||||
values they have read.
|
||||
|
||||
You may use the helper function Amethyst.Minecraft.Read.start/1 to start the chain with a binary buffer.
|
||||
You may use the helper function `Amethyst.Minecraft.Read.start/1` to start the chain with a binary buffer.
|
||||
The return value of the chain is a tuple containing the list of values and the remaining binary buffer.
|
||||
|
||||
iex> alias Amethyst.Minecraft.Read
|
||||
@ -83,9 +142,15 @@ defmodule Amethyst.Minecraft.Read do
|
||||
{[true, 999, 64], ""}
|
||||
"""
|
||||
|
||||
@doc """
|
||||
This function structures an input binary to be used by the functions in `Amethyst.Minecraft.Read`.
|
||||
"""
|
||||
def start(binary) do
|
||||
{[], binary, :reversed}
|
||||
end
|
||||
@doc """
|
||||
This function structures the result of the functions in `Amethyst.Minecraft.Read` to be used in the same order they were read.
|
||||
"""
|
||||
def stop({acc, rest, :reversed}) do
|
||||
{Enum.reverse(acc), rest}
|
||||
end
|
||||
@ -135,6 +200,9 @@ defmodule Amethyst.Minecraft.Read do
|
||||
{[data | acc], rest, :reversed}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a varint. `read` tracks the number of bytes read and `nacc` tracks the number being read.
|
||||
"""
|
||||
def varint(tuple, read \\ 0, nacc \\ 0)
|
||||
def varint({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 5 do
|
||||
varint({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))
|
||||
@ -151,6 +219,9 @@ defmodule Amethyst.Minecraft.Read do
|
||||
raise RuntimeError, "Got an incomplete varint!"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a varlong. `read` tracks the number of bytes read and `nacc` tracks the number being read.
|
||||
"""
|
||||
def varlong(tuple, read \\ 0, nacc \\ 0)
|
||||
def varlong({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 10 do
|
||||
varlong({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))
|
||||
@ -161,10 +232,10 @@ defmodule Amethyst.Minecraft.Read do
|
||||
{[value | acc], rest, :reversed}
|
||||
end
|
||||
def varlong(_, read, _) when read >= 10 do
|
||||
raise RuntimeError, "Got a varint which is too big!"
|
||||
raise RuntimeError, "Got a varlong which is too big!"
|
||||
end
|
||||
def varlong({_, ""}, _, _) do
|
||||
raise RuntimeError, "Got an incomplete varint!"
|
||||
raise RuntimeError, "Got an incomplete varlong!"
|
||||
end
|
||||
|
||||
def string({acc, data, :reversed}) do
|
||||
|
@ -97,11 +97,11 @@ defmodule Amethyst.Server.Configuration do
|
||||
# Serverbound Known Packs https://wiki.vg/Protocol#Serverbound_Known_Packs
|
||||
def deserialize(0x07, data) do
|
||||
{[count], rest} = Read.start(data) |> Read.varint |> Read.stop
|
||||
{packs, _} = Enum.reduce(0..count, {[], rest}, fn _, {acc, rest} ->
|
||||
{packs, _} = Enum.reduce(1..count, {[], rest}, fn _, {acc, rest} ->
|
||||
{[namespace, id, version], rest} = Read.start(rest) |> Read.string |> Read.string |> Read.string |> Read.stop
|
||||
{[{namespace, id, version} | acc], rest}
|
||||
end)
|
||||
{:resource_pack_stack, packs}
|
||||
{:serverbound_known_packs, packs}
|
||||
end
|
||||
def deserialize(type, _) do
|
||||
raise RuntimeError, "Got unknown packet type #{type}!"
|
||||
@ -143,16 +143,12 @@ defmodule Amethyst.Server.Configuration do
|
||||
end
|
||||
# Remove Resource Pack https://wiki.vg/Protocol#Remove_Resource_Pack_(configuration)
|
||||
def serialize({:remove_resource_pack, id}) do
|
||||
if id == nil do
|
||||
Write.varint(0x08) <> <<0x00>>
|
||||
else
|
||||
Write.varint(0x08) <> <<0x01>> <> Write.string(id)
|
||||
end
|
||||
Write.option(id, &Write.string/1)
|
||||
end
|
||||
# Add Resource Pack https://wiki.vg/Protocol#Add_Resource_Pack_(configuration)
|
||||
def serialize({:add_resource_pack, id, url, hash, forced, msg}) do
|
||||
Write.varint(0x09) <> Write.string(id) <> Write.string(url) <> Write.string(hash) <>
|
||||
if(forced, do: <<0x01>>, else: <<0x00>>) <> if(msg == nil, do: <<0x00>>, else: <<0x01>> <> Write.string(msg))
|
||||
Write.bool(forced) <> Write.option(msg, &Write.string/1)
|
||||
end
|
||||
# Store Cookie https://wiki.vg/Protocol#Store_Cookie_(configuration)
|
||||
def serialize({:store_cookie, id, data}) do
|
||||
@ -164,7 +160,7 @@ defmodule Amethyst.Server.Configuration do
|
||||
end
|
||||
# Feature Flags https://wiki.vg/Protocol#Feature_Flags
|
||||
def serialize({:feature_flags, flags}) do
|
||||
Write.varint(0x0C) <> Write.varint(length(flags)) <> Enum.reduce(flags, "", fn id, acc -> acc <> Write.string(id) end)
|
||||
Write.varint(0x0C) <> Write.varint(length(flags)) <> Write.list(flags, &Write.string/1)
|
||||
end
|
||||
# Update Tags https://wiki.vg/Protocol#Update_Tags
|
||||
def serialize({:update_tags, tags}) do
|
||||
@ -174,19 +170,17 @@ defmodule Amethyst.Server.Configuration do
|
||||
# Clientbound Known Packs https://wiki.vg/Protocol#Clientbound_Known_Packs
|
||||
def serialize({:clientbound_known_packs, packs}) do
|
||||
Write.varint(0x0E) <> Write.varint(length(packs)) <>
|
||||
Enum.reduce(packs, "", fn {namespace, id, version}, acc -> acc <> Write.string(namespace) <> Write.string(id) <> Write.string(version) end)
|
||||
Write.list(packs, fn {namespace, id, version} -> Write.string(namespace) <> Write.string(id) <> Write.string(version) end)
|
||||
end
|
||||
# Custom Report Details https://wiki.vg/Protocol#Custom_Report_Details
|
||||
def serialize({:custom_report_details, details}) do
|
||||
Write.varint(0x0F) <> Write.varint(length(details)) <>
|
||||
Enum.reduce(details, "", fn {id, data}, acc -> acc <> Write.string(id) <> Write.string(data) end)
|
||||
Write.list(details, fn {id, data} -> Write.string(id) <> Write.string(data) end)
|
||||
end
|
||||
# Server Links https://wiki.vg/Protocol#Server_Links_(configuration)
|
||||
def serialize({:server_links, links}) do
|
||||
Write.varint(0x10) <> Write.varint(length(links)) <>
|
||||
Enum.reduce(links, "", fn {label, url}, acc ->
|
||||
acc <> serialize_link_label(label) <> Write.string(url)
|
||||
end)
|
||||
Write.list(links, fn {label, url} -> serialize_link_label(label) <> Write.string(url) end)
|
||||
end
|
||||
def serialize(packet) do
|
||||
raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}"
|
||||
@ -196,8 +190,10 @@ defmodule Amethyst.Server.Configuration do
|
||||
acc <> Write.string(id) <> Write.varint(length(elements)) <> serialize_elements(elements)
|
||||
end
|
||||
defp serialize_elements(elements) do
|
||||
Enum.reduce(elements, "", fn {id, ids}, acc -> acc <> Write.string(id) <> Write.varint(length(ids)) <>
|
||||
Enum.reduce(ids, "", fn id, acc -> acc <> Write.varint(id) end) end)
|
||||
Write.list(elements, fn {id, ids} ->
|
||||
Write.string(id) <> Write.varint(length(ids)) <>
|
||||
Write.list(ids, &Write.varint/1)
|
||||
end)
|
||||
end
|
||||
defp serialize_link_label(:bug_report) do
|
||||
<<0x01>> <> Write.varint(0x00)
|
||||
@ -235,12 +231,40 @@ defmodule Amethyst.Server.Configuration do
|
||||
|
||||
## HANDLING
|
||||
@impl true
|
||||
# Client Information https://wiki.vg/Protocol#Client_Information
|
||||
def handle({:client_information, locale, v_dist, chat_mode, chat_colors, displayed_skin_parts, main_hand, text_filtering, allow_listing}, client, state) do
|
||||
state = state |> Keyword.put(:locale, locale) |> Keyword.put(:view_dist, v_dist) |> Keyword.put(:chat_mode, chat_mode) |>
|
||||
Keyword.put(:chat_colors, chat_colors) |> Keyword.put(:displayed_skin_parts, displayed_skin_parts) |> Keyword.put(:main_hand, main_hand) |>
|
||||
Keyword.put(:text_filtering, text_filtering) |> Keyword.put(:allow_listing, allow_listing)
|
||||
# TODO: Here we should create the game handling task for this player and give it
|
||||
# this data.
|
||||
transmit({:clientbound_plugin_message, "minecraft:brand", Write.string("amethyst")}, client)
|
||||
transmit({:clientbound_known_packs, [{"minecraft", "core", "1.21"}]}, client)
|
||||
{:ok, state}
|
||||
end
|
||||
# Serverbound Known Packs https://wiki.vg/Protocol#Serverbound_Known_Packs
|
||||
def handle({:serverbound_known_packs, _packs}, client, state) do
|
||||
# L + ratio + don't care + didn't ask + finish configuration
|
||||
# TODO: we should send registries i think? does amethyst need to deal with vanilla registries at all? god only knows
|
||||
transmit({:finish_configuration}, client)
|
||||
{:ok, state}
|
||||
end
|
||||
# Acknowledge Finish Configuration https://wiki.vg/Protocol#Acknowledge_Finish_Configuration
|
||||
def handle({:acknowledge_finish_configuration}, client, state) do
|
||||
# TODO: All of this stuff should obviously not be hardcoded here
|
||||
Amethyst.Server.Play.transmit({:login,
|
||||
0, false, ["minecraft:overworld"], 0, 16, 16, false, true, true, 0,
|
||||
"minecraft:overworld", <<0::64>>, :spectator, nil, false, true, nil, 0, false
|
||||
}, client)
|
||||
Amethyst.Server.Play.serve(client, state)
|
||||
end
|
||||
# Serverbound Plugin Message https://wiki.vg/Protocol#Serverbound_Plugin_Message_(configuration)
|
||||
def handle({:serverbound_plugin_message, channel, data}, client, state) do
|
||||
handle_plugin_message(channel, data, client, state)
|
||||
end
|
||||
def handle(tuple, _) do
|
||||
def handle(tuple, state) do
|
||||
Logger.error("Unhandled but known packet #{elem(tuple, 0)}")
|
||||
{:unhandled, state}
|
||||
end
|
||||
defp handle_plugin_message("minecraft:brand", data, client, state) do
|
||||
{[brand], ""} = Read.start(data) |> Read.string |> Read.stop
|
||||
|
79
lib/servers/play.ex
Normal file
79
lib/servers/play.ex
Normal file
@ -0,0 +1,79 @@
|
||||
# Amethyst - An experimental Minecraft server written in Elixir.
|
||||
# Copyright (C) 2024 KodiCraft
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
defmodule Amethyst.Server.Play do
|
||||
@moduledoc """
|
||||
This module contains the logic for the Play stage of the server.
|
||||
"""
|
||||
require Logger
|
||||
use Amethyst.Server
|
||||
|
||||
alias Amethyst.Minecraft.Read
|
||||
alias Amethyst.Minecraft.Write
|
||||
|
||||
@impl true
|
||||
def init(state) do
|
||||
state
|
||||
end
|
||||
|
||||
## DESERIALIZATION
|
||||
@impl true
|
||||
|
||||
def deserialize(type, _) do
|
||||
raise RuntimeError, "Got unknown packet type #{type}!"
|
||||
end
|
||||
|
||||
## SERIALIZATION
|
||||
@impl true
|
||||
# Login https://wiki.vg/Protocol#Login_(play)
|
||||
def serialize({:login, eid, hardcore, dimensions,
|
||||
max_players, view_distance, simulation_distance,
|
||||
reduce_debug, enable_respawn_screen, limited_crafting,
|
||||
dim_type, dim_name, hashed_seed, gamemode, prev_gm,
|
||||
is_debug, is_flat, death_loc, portal_cooldown, enforce_chat}) when byte_size(hashed_seed) == 8 do
|
||||
# TODO: This is a big unreadable slab of serialization which makes bugs really hard to catch, it needs a proper rework at some point
|
||||
Write.varint(0x2B) <>
|
||||
Write.int(eid) <> Write.bool(hardcore) <>
|
||||
Write.varint(length(dimensions)) <> Write.list(dimensions, &Write.string/1) <>
|
||||
Write.varint(max_players) <> Write.varint(view_distance) <> Write.varint(simulation_distance) <> Write.bool(reduce_debug) <>
|
||||
Write.bool(enable_respawn_screen) <>
|
||||
Write.bool(limited_crafting) <> Write.varint(dim_type) <> Write.string(dim_name) <>
|
||||
hashed_seed <> Write.ubyte(gamemode_id(gamemode)) <> Write.byte(gamemode_id(prev_gm)) <>
|
||||
Write.bool(is_debug) <> Write.bool(is_flat) <>
|
||||
if(death_loc == nil, do: <<0::big-8>>, else: <<1::big-8>> <> Write.string(elem(death_loc, 0)) <> Write.position(elem(death_loc, 1))) <>
|
||||
Write.varint(portal_cooldown) <> Write.bool(enforce_chat)
|
||||
end
|
||||
def serialize(packet) do
|
||||
raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}"
|
||||
end
|
||||
|
||||
## HANDLING
|
||||
@impl true
|
||||
def handle(tuple, _, state) do
|
||||
Logger.error("Unhandled but known packet #{elem(tuple, 0)}")
|
||||
{:unhandled, state}
|
||||
end
|
||||
|
||||
defp gamemode_id(gm) do
|
||||
case gm do
|
||||
nil -> -1
|
||||
:survival -> 0
|
||||
:creative -> 1
|
||||
:adventure -> 2
|
||||
:spectator -> 3
|
||||
end
|
||||
end
|
||||
end
|
@ -51,7 +51,7 @@ defmodule Amethyst.Server.Status do
|
||||
Write.varint(0x00) <> Write.string(data)
|
||||
end
|
||||
def serialize({:ping_response, payload}) do
|
||||
Write.varint(0x01) <> <<payload::64-big-signed>>
|
||||
Write.varint(0x01) <> Write.long(payload)
|
||||
end
|
||||
def serialize(packet) do
|
||||
raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}"
|
||||
|
14
mix.exs
14
mix.exs
@ -7,14 +7,21 @@ defmodule Amethyst.MixProject do
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.17",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
deps: deps(),
|
||||
|
||||
name: "Amethyst",
|
||||
source_url: "https://git.colon-three.com/kodi/amethyst",
|
||||
docs: [
|
||||
main: "readme",
|
||||
extras: ["README.md", "LICENSE.md"]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger],
|
||||
extra_applications: [:logger, :public_key],
|
||||
mod: {Amethyst.Application, []}
|
||||
]
|
||||
end
|
||||
@ -23,7 +30,8 @@ defmodule Amethyst.MixProject do
|
||||
defp deps do
|
||||
[
|
||||
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
||||
{:uuid, "~> 1.1"}
|
||||
{:uuid, "~> 1.1"},
|
||||
{:ex_doc, "~> 0.22", only: :dev, runtime: false}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
6
mix.lock
6
mix.lock
@ -1,7 +1,13 @@
|
||||
%{
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
|
||||
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
|
||||
"jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"},
|
||||
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
||||
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user