Merge branch 'main' of git.colon-three.com:kodi/amethyst
All checks were successful
Build & Test / nix-build (push) Successful in 1m12s

This commit is contained in:
Kodi Craft 2024-08-04 07:44:13 +02:00
commit a285e31175
Signed by: kodi
GPG Key ID: 69D9EED60B242822
6 changed files with 218 additions and 30 deletions

View File

@ -18,21 +18,55 @@ defmodule Amethyst.Minecraft.Write do
import Bitwise import Bitwise
@moduledoc """ @moduledoc """
This module contains functions for writing certain Minecraft data types which are more complex This module contains functions for writing Minecraft data.
than simple binary 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) UUID.string_to_binary!(uuid)
end end
def bool(value) do def bool(value) when is_boolean(value) do
case value do case value do
true -> <<0x01::8>> true -> <<0x01::8>>
false -> <<0x00::8>> false -> <<0x00::8>>
end end
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 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 <<value::32-unsigned>> = <<value::32-signed>> # This is a trick to allow the arithmetic shift to act as a logical shift
varnum("", value) varnum("", value)
@ -63,19 +97,44 @@ defmodule Amethyst.Minecraft.Write do
def string(value) do def string(value) do
<<varint(byte_size(value))::binary, value::binary>> <<varint(byte_size(value))::binary, value::binary>>
end 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 end
defmodule Amethyst.Minecraft.Read do defmodule Amethyst.Minecraft.Read do
import Bitwise import Bitwise
@moduledoc """ @moduledoc """
This module contains functions for reading Minecraft data. Unlike Amethyst.Minecraft.Write, this This module contains functions for reading Minecraft data.
includes all supported data types in order to make the interface more consistent.
These functions allow you to chain them into eachother, at the end they will produce a list of all the These functions allow you to chain them into eachother, at the end they will produce a list of all the
values they have read. 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. The return value of the chain is a tuple containing the list of values and the remaining binary buffer.
iex> alias Amethyst.Minecraft.Read iex> alias Amethyst.Minecraft.Read
@ -83,9 +142,15 @@ defmodule Amethyst.Minecraft.Read do
{[true, 999, 64], ""} {[true, 999, 64], ""}
""" """
@doc """
This function structures an input binary to be used by the functions in `Amethyst.Minecraft.Read`.
"""
def start(binary) do def start(binary) do
{[], binary, :reversed} {[], binary, :reversed}
end 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 def stop({acc, rest, :reversed}) do
{Enum.reverse(acc), rest} {Enum.reverse(acc), rest}
end end
@ -135,6 +200,9 @@ defmodule Amethyst.Minecraft.Read do
{[data | acc], rest, :reversed} {[data | acc], rest, :reversed}
end 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(tuple, read \\ 0, nacc \\ 0)
def varint({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 5 do 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))) 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!" raise RuntimeError, "Got an incomplete varint!"
end 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(tuple, read \\ 0, nacc \\ 0)
def varlong({acc, <<1::1, value::7, rest::binary>>, :reversed}, read, nacc) when read < 10 do 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))) varlong({acc, rest, :reversed}, read + 1, nacc + (value <<< (7 * read)))
@ -161,10 +232,10 @@ defmodule Amethyst.Minecraft.Read do
{[value | acc], rest, :reversed} {[value | acc], rest, :reversed}
end end
def varlong(_, read, _) when read >= 10 do 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 end
def varlong({_, ""}, _, _) do def varlong({_, ""}, _, _) do
raise RuntimeError, "Got an incomplete varint!" raise RuntimeError, "Got an incomplete varlong!"
end end
def string({acc, data, :reversed}) do def string({acc, data, :reversed}) do

View File

@ -97,11 +97,11 @@ defmodule Amethyst.Server.Configuration do
# Serverbound Known Packs https://wiki.vg/Protocol#Serverbound_Known_Packs # Serverbound Known Packs https://wiki.vg/Protocol#Serverbound_Known_Packs
def deserialize(0x07, data) do def deserialize(0x07, data) do
{[count], rest} = Read.start(data) |> Read.varint |> Read.stop {[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], rest} = Read.start(rest) |> Read.string |> Read.string |> Read.string |> Read.stop
{[{namespace, id, version} | acc], rest} {[{namespace, id, version} | acc], rest}
end) end)
{:resource_pack_stack, packs} {:serverbound_known_packs, packs}
end end
def deserialize(type, _) do def deserialize(type, _) do
raise RuntimeError, "Got unknown packet type #{type}!" raise RuntimeError, "Got unknown packet type #{type}!"
@ -143,16 +143,12 @@ defmodule Amethyst.Server.Configuration do
end end
# Remove Resource Pack https://wiki.vg/Protocol#Remove_Resource_Pack_(configuration) # Remove Resource Pack https://wiki.vg/Protocol#Remove_Resource_Pack_(configuration)
def serialize({:remove_resource_pack, id}) do def serialize({:remove_resource_pack, id}) do
if id == nil do Write.option(id, &Write.string/1)
Write.varint(0x08) <> <<0x00>>
else
Write.varint(0x08) <> <<0x01>> <> Write.string(id)
end
end end
# Add Resource Pack https://wiki.vg/Protocol#Add_Resource_Pack_(configuration) # Add Resource Pack https://wiki.vg/Protocol#Add_Resource_Pack_(configuration)
def serialize({:add_resource_pack, id, url, hash, forced, msg}) do def serialize({:add_resource_pack, id, url, hash, forced, msg}) do
Write.varint(0x09) <> Write.string(id) <> Write.string(url) <> Write.string(hash) <> 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 end
# Store Cookie https://wiki.vg/Protocol#Store_Cookie_(configuration) # Store Cookie https://wiki.vg/Protocol#Store_Cookie_(configuration)
def serialize({:store_cookie, id, data}) do def serialize({:store_cookie, id, data}) do
@ -164,7 +160,7 @@ defmodule Amethyst.Server.Configuration do
end end
# Feature Flags https://wiki.vg/Protocol#Feature_Flags # Feature Flags https://wiki.vg/Protocol#Feature_Flags
def serialize({:feature_flags, flags}) do 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 end
# Update Tags https://wiki.vg/Protocol#Update_Tags # Update Tags https://wiki.vg/Protocol#Update_Tags
def serialize({:update_tags, tags}) do 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 # Clientbound Known Packs https://wiki.vg/Protocol#Clientbound_Known_Packs
def serialize({:clientbound_known_packs, packs}) do def serialize({:clientbound_known_packs, packs}) do
Write.varint(0x0E) <> Write.varint(length(packs)) <> 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 end
# Custom Report Details https://wiki.vg/Protocol#Custom_Report_Details # Custom Report Details https://wiki.vg/Protocol#Custom_Report_Details
def serialize({:custom_report_details, details}) do def serialize({:custom_report_details, details}) do
Write.varint(0x0F) <> Write.varint(length(details)) <> 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 end
# Server Links https://wiki.vg/Protocol#Server_Links_(configuration) # Server Links https://wiki.vg/Protocol#Server_Links_(configuration)
def serialize({:server_links, links}) do def serialize({:server_links, links}) do
Write.varint(0x10) <> Write.varint(length(links)) <> Write.varint(0x10) <> Write.varint(length(links)) <>
Enum.reduce(links, "", fn {label, url}, acc -> Write.list(links, fn {label, url} -> serialize_link_label(label) <> Write.string(url) end)
acc <> serialize_link_label(label) <> Write.string(url)
end)
end end
def serialize(packet) do def serialize(packet) do
raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}" 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) acc <> Write.string(id) <> Write.varint(length(elements)) <> serialize_elements(elements)
end end
defp serialize_elements(elements) do defp serialize_elements(elements) do
Enum.reduce(elements, "", fn {id, ids}, acc -> acc <> Write.string(id) <> Write.varint(length(ids)) <> Write.list(elements, fn {id, ids} ->
Enum.reduce(ids, "", fn id, acc -> acc <> Write.varint(id) end) end) Write.string(id) <> Write.varint(length(ids)) <>
Write.list(ids, &Write.varint/1)
end)
end end
defp serialize_link_label(:bug_report) do defp serialize_link_label(:bug_report) do
<<0x01>> <> Write.varint(0x00) <<0x01>> <> Write.varint(0x00)
@ -235,12 +231,40 @@ defmodule Amethyst.Server.Configuration do
## HANDLING ## HANDLING
@impl true @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) # Serverbound Plugin Message https://wiki.vg/Protocol#Serverbound_Plugin_Message_(configuration)
def handle({:serverbound_plugin_message, channel, data}, client, state) do def handle({:serverbound_plugin_message, channel, data}, client, state) do
handle_plugin_message(channel, data, client, state) handle_plugin_message(channel, data, client, state)
end end
def handle(tuple, _) do def handle(tuple, state) do
Logger.error("Unhandled but known packet #{elem(tuple, 0)}") Logger.error("Unhandled but known packet #{elem(tuple, 0)}")
{:unhandled, state}
end end
defp handle_plugin_message("minecraft:brand", data, client, state) do defp handle_plugin_message("minecraft:brand", data, client, state) do
{[brand], ""} = Read.start(data) |> Read.string |> Read.stop {[brand], ""} = Read.start(data) |> Read.string |> Read.stop

79
lib/servers/play.ex Normal file
View 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

View File

@ -51,7 +51,7 @@ defmodule Amethyst.Server.Status do
Write.varint(0x00) <> Write.string(data) Write.varint(0x00) <> Write.string(data)
end end
def serialize({:ping_response, payload}) do def serialize({:ping_response, payload}) do
Write.varint(0x01) <> <<payload::64-big-signed>> Write.varint(0x01) <> Write.long(payload)
end end
def serialize(packet) do def serialize(packet) do
raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}" raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}"

14
mix.exs
View File

@ -7,14 +7,21 @@ defmodule Amethyst.MixProject do
version: "0.1.0", version: "0.1.0",
elixir: "~> 1.17", elixir: "~> 1.17",
start_permanent: Mix.env() == :prod, 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 end
# Run "mix help compile.app" to learn about applications. # Run "mix help compile.app" to learn about applications.
def application do def application do
[ [
extra_applications: [:logger], extra_applications: [:logger, :public_key],
mod: {Amethyst.Application, []} mod: {Amethyst.Application, []}
] ]
end end
@ -23,7 +30,8 @@ defmodule Amethyst.MixProject do
defp deps do defp deps do
[ [
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:uuid, "~> 1.1"} {:uuid, "~> 1.1"},
{:ex_doc, "~> 0.22", only: :dev, runtime: false}
] ]
end end
end end

View File

@ -1,7 +1,13 @@
%{ %{
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "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"}, "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"}, "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"}, "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"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
} }