Compare commits

..

No commits in common. "main" and "first-attempt" have entirely different histories.

19 changed files with 157 additions and 614 deletions

View File

@ -86,8 +86,6 @@
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
{Credo.Check.Readability.VariableNames, []},
{Credo.Check.Readability.WithSingleClause, []},
{Credo.Check.Readability.BlockPipe, []},
{Credo.Check.Readability.SinglePipe, []},
#
## Refactoring Opportunities
@ -108,8 +106,6 @@
{Credo.Check.Refactor.RejectReject, []},
{Credo.Check.Refactor.UnlessWithElse, []},
{Credo.Check.Refactor.WithClauses, []},
{Credo.Check.Refactor.MapMap, []},
{Credo.Check.Refactor.FilterReject, []},
#
## Warnings
@ -150,6 +146,7 @@
{Credo.Check.Design.DuplicatedCode, []},
{Credo.Check.Design.SkipTestWithoutComment, []},
{Credo.Check.Readability.AliasAs, []},
{Credo.Check.Readability.BlockPipe, []},
{Credo.Check.Readability.ImplTrue, []},
{Credo.Check.Readability.MultiAlias, []},
{Credo.Check.Readability.NestedFunctionCalls, []},
@ -157,13 +154,16 @@
{Credo.Check.Readability.OnePipePerLine, []},
{Credo.Check.Readability.SeparateAliasRequire, []},
{Credo.Check.Readability.SingleFunctionToBlockPipe, []},
{Credo.Check.Readability.SinglePipe, []},
{Credo.Check.Readability.Specs, []},
{Credo.Check.Readability.StrictModuleLayout, []},
{Credo.Check.Readability.WithCustomTaggedTuple, []},
{Credo.Check.Refactor.ABCSize, []},
{Credo.Check.Refactor.AppendSingleItem, []},
{Credo.Check.Refactor.DoubleBooleanNegation, []},
{Credo.Check.Refactor.FilterReject, []},
{Credo.Check.Refactor.IoPuts, []},
{Credo.Check.Refactor.MapMap, []},
{Credo.Check.Refactor.ModuleDependencies, []},
{Credo.Check.Refactor.NegatedIsNil, []},
{Credo.Check.Refactor.PassAsyncInTestCases, []},
@ -174,8 +174,9 @@
{Credo.Check.Warning.LeakyEnvironment, []},
{Credo.Check.Warning.MapGetUnsafePass, []},
{Credo.Check.Warning.MixEnv, []},
{Credo.Check.Warning.UnsafeToAtom, []},
{Credo.Check.Refactor.MapInto, []},
{Credo.Check.Warning.UnsafeToAtom, []}
# {Credo.Check.Refactor.MapInto, []},
#
# Custom checks can be created using `mix credo.gen.check`.

View File

@ -19,7 +19,6 @@ defmodule Amethyst.ConnectionHandler do
This module is responsible for handling incoming packets and sending outgoing packets. It keeps track of what state the connection is in and which game should
receive the packets.
"""
alias Amethyst.Minecraft.Write
require Logger
@spec child_spec(:gen_tcp.socket()) :: Supervisor.child_spec()
@ -49,8 +48,11 @@ defmodule Amethyst.ConnectionHandler do
@spec loop(:gen_tcp.socket(), atom(), integer(), map()) :: no_return()
defp loop(socket, connstate, version, state) do
receive do
:closed ->
Logger.info("Connection #{inspect(socket)} closed.")
Process.exit(self(), :normal)
{:disconnect, reason} ->
disconnect(socket, reason, connstate, version, state)
disconnect(socket, reason, connstate, version)
Process.exit(self(), :normal)
{:set_state, newstate} ->
Logger.debug("Switching to state #{newstate} from #{connstate}")
@ -58,16 +60,6 @@ defmodule Amethyst.ConnectionHandler do
{:set_version, newversion} ->
Logger.debug("Switching to version #{newversion} from #{version}")
loop(socket, connstate, newversion, state)
{:set_encryption, secret} ->
Logger.debug("Enabling encryption with shared secret #{inspect(secret)}")
encryption_state = :crypto.crypto_init(:aes_128_cfb8, secret, secret, true)
decryption_state = :crypto.crypto_init(:aes_128_cfb8, secret, secret, false)
state = state |> Map.put(:encryption_state, encryption_state) |> Map.put(:decryption_state, decryption_state)
loop(socket, connstate, version, state)
{:set_compression, threshold} ->
Logger.debug("Enabling comrpession with threshold #{threshold}")
state = Map.put(state, :compression, threshold)
loop(socket, connstate, version, state)
{:set_position, position} ->
prev_position = Map.get(state, :position)
state = Map.put(state, :position, position)
@ -88,9 +80,9 @@ defmodule Amethyst.ConnectionHandler do
if prev_cp == nil do
MapSet.new([])
else
MapSet.new(visible_chunks_from(elem(prev_cp, 0), elem(prev_cp, 1), 16)) # This 16 would be the server view distance!!
MapSet.new(visible_chunks_from(elem(prev_cp, 0), elem(prev_cp, 1), Map.get(state, :view_distance, 16)))
end
chunks = MapSet.new(visible_chunks_from(elem(cp, 0), elem(cp, 1), 16))
chunks = MapSet.new(visible_chunks_from(elem(cp, 0), elem(cp, 1), Map.get(state, :view_distance, 16)))
new_chunks = MapSet.difference(chunks, prev_chunks)
Logger.debug("Sending #{MapSet.size(new_chunks)} chunks...")
# We can process all chunks in parallel
@ -106,20 +98,10 @@ defmodule Amethyst.ConnectionHandler do
loop(socket, connstate, version, state)
{:send_packet, packet} ->
# Logger.debug("Sending packet #{inspect(packet)}")
send_packet(socket, connstate, packet, version, state)
send_packet(socket, connstate, packet, version)
loop(socket, connstate, version, state)
after 0 ->
# Received stuff from the connection receiver is lower priority
receive do
:closed ->
Logger.info("Connection #{inspect(socket)} closed.")
Process.exit(self(), :normal)
{:get_encryption, from} ->
send(from, Map.get(state, :decryption_state))
loop(socket, connstate, version, state)
{:get_compression, from} ->
send(from, Map.get(state, :compression))
loop(socket, connstate, version, state)
{:packet, id, data} ->
state = handle_packet(id, data, connstate, version, state)
loop(socket, connstate, version, state)
@ -153,94 +135,69 @@ defmodule Amethyst.ConnectionHandler do
heightmaps = compound(%{})
data = Enum.chunk_every(chunk_array, 16, 16, 0) # 0 -> air
|> Enum.reduce("", fn chunk_section, acc ->
blocks_and_lights = chunk_section |> List.flatten()
block_count = blocks_and_lights |> Enum.filter(fn {bs, _, _} -> bs != 0 end) |> length
|> Enum.reduce("", fn chunk_section, acc ->
blocks = chunk_section |> List.flatten()
block_count = blocks |> Enum.filter(&(&1 != 0)) |> length
# Put together the palette
unique_blocks = MapSet.new(blocks_and_lights |> Enum.map(fn {bs, _, _} -> bs end))
min_bpe = MapSet.size(unique_blocks) |> :math.log2() |> ceil()
# Put together the palette
unique_blocks = MapSet.new(blocks)
min_bpe = MapSet.size(unique_blocks) |> :math.log2() |> ceil()
paletted_container_data = case min_bpe do
0 ->
# SINGLE VALUED
Write.ubyte(0) <>
Write.varint(MapSet.to_list(unique_blocks) |> List.first()) <>
Write.varint(0) # No data, empty pallette
min_bpe when min_bpe in 1..8 ->
# INDIRECT
# Minimum bpe accepted by minecraft is 4
bpe = max(min_bpe, 4)
palette = MapSet.to_list(unique_blocks) |>
Enum.with_index() |>
Map.new(fn {i, v} -> {i, v} end)
paletted_blocks = blocks |>
Enum.map(&(Map.get(palette, &1)))
paletted_data = long_aligned_bit_string_reduce(paletted_blocks, bpe)
paletted_container_data = case min_bpe do
0 ->
# SINGLE VALUED
Write.ubyte(0) <>
Write.varint(MapSet.to_list(unique_blocks) |> List.first()) <>
Write.varint(0) # No data, empty pallette
Write.ubyte(bpe) <>
Write.varint(map_size(palette)) <>
Enum.reduce(palette, "", fn {_k, v}, acc ->
acc <> Write.varint(v)
end) <>
Write.varint(floor(bit_size(paletted_data) / 64)) <>
paletted_data
_ ->
# DIRECT
data = long_aligned_bit_string_reduce(blocks, 15)
Write.ubyte(15) <>
Write.varint(floor(bit_size(data) / 64)) <>
data
end
min_bpe when min_bpe in 1..8 ->
# INDIRECT
# Minimum bpe accepted by minecraft is 4
bpe = max(min_bpe, 4)
palette = MapSet.to_list(unique_blocks) |>
Enum.with_index() |>
Map.new(fn {v, i} -> {v, i} end)
paletted_blocks = blocks_and_lights |>
Enum.map(fn {bs, _, _} -> Map.get(palette, bs) end)
paletted_data = long_aligned_bit_string_reduce(paletted_blocks, bpe,
fn bs -> <<bs::signed-integer-big-size(bpe)>> end)
Write.ubyte(bpe) <>
Write.varint(map_size(palette)) <>
(Enum.sort(palette, fn {_k1, v1}, {_k2, v2} -> v1 < v2 end) |>
Enum.reduce("", fn {k, _v}, acc -> acc <> Write.varint(k) end)) <>
Write.varint(floor(bit_size(paletted_data) / 64)) <>
paletted_data
acc <> Write.short(block_count) <> paletted_container_data <>
<<0::8, 0::8, 0::8>> # TODO: This should be biome data
end)
_ ->
# DIRECT
data = long_aligned_bit_string_reduce(blocks_and_lights, 15,
fn {bs, _sl, _bl} -> <<bs::signed-integer-big-size(15)>> end)
Write.ubyte(15) <>
Write.varint(floor(bit_size(data) / 64)) <>
data
end
acc <> Write.short(block_count) <> paletted_container_data <>
<<0::8, 0::8, 0::8>> # TODO: This should be biome data
end)
sky_light_sections = Enum.chunk_every(chunk_array, 16, 16, 0)
|> Enum.map(fn chunk_section ->
chunk_section |> List.flatten() |> Enum.map(fn {_bs, sl, _bl} -> sl end)
end)
non_empty_sky_light_sections = sky_light_sections |> Enum.filter(fn section -> !Enum.all?(section, &(&1 == 0)) end)
has_sky_light = [false] ++ (sky_light_sections |> Enum.map(fn section -> !Enum.all?(section, &(&1 == 0)) end)) ++ [false]
empty_sky_light = has_sky_light |> Enum.map(&not/1)
sky_light_array = non_empty_sky_light_sections |> Enum.map(&(Enum.reduce(&1, <<>>, fn light, acc ->
<<acc::bitstring, light::size(4)>>
end)))
block_light_sections = Enum.chunk_every(chunk_array, 16, 16, 0)
|> Enum.map(fn chunk_section ->
chunk_section |> List.flatten() |> Enum.map(fn {_bs, _sl, bl} -> bl end)
end)
non_empty_block_light_sections = block_light_sections |> Enum.filter(fn section -> !Enum.all?(section, &(&1 == 0)) end)
has_block_light = [false] ++ (block_light_sections |> Enum.map(fn section -> !Enum.all?(section, &(&1 == 0)) end)) ++ [false]
empty_block_light = has_block_light |> Enum.map(&not/1)
block_light_array = non_empty_block_light_sections |> Enum.map(&(Enum.reduce(&1, <<>>, fn light, acc ->
<<acc::bitstring, light::size(4)>>
end)))
send(to, {:send_packet, %{
packet_type: :chunk_data_and_update_light,
chunk_x: cx, chunk_z: cz,
heightmaps: heightmaps,
data: data,
block_entities: [],
sky_light_mask: has_sky_light,
block_light_mask: has_block_light,
empty_sky_light_mask: empty_sky_light,
empty_block_light_mask: empty_block_light,
sky_light_arrays: sky_light_array |> Enum.map(&(%{sky_light_array: &1})),
block_light_arrays: block_light_array |> Enum.map(&(%{block_light_array: &1}))
}})
:ok
send(to, {:send_packet, %{
packet_type: :chunk_data_and_update_light,
chunk_x: cx, chunk_z: cz,
heightmaps: heightmaps,
data: data,
block_entities: [],
# TODO: Light
sky_light_mask: Write.varint(0),
block_light_mask: Write.varint(0),
empty_sky_light_mask: Write.varint(0),
empty_block_light_mask: Write.varint(0),
sky_light_arrays: [],
block_light_arrays: []
}})
:ok
end
defp long_aligned_bit_string_reduce(values, bpe, func) do
defp long_aligned_bit_string_reduce(values, bpe) do
values |> Enum.reduce(<<>>, fn value, acc ->
ret = func.(value)
next = <<acc::bitstring, ret::bitstring-size(bpe)>>
next = <<acc::bitstring, value::big-size(bpe)>>
# man i hope they dont suddenly change the size of a long
if rem(bit_size(next), 64) + bpe > 64 do
# gotta pad it
@ -270,39 +227,21 @@ defmodule Amethyst.ConnectionHandler do
end
rescue
e ->
if Application.get_env(:amethyst, :release, false) do
send(self(), {:disconnect, "§cError handling packet #{inspect(id, base: :hex)}:\n#{Exception.format(:error, e, __STACKTRACE__)}"})
if Mix.env() == :dev do
Logger.error("Error handling packet with ID #{inspect(id, base: :hex)} in state #{connstate}: #{Exception.format(:error, e, __STACKTRACE__)}")
else
send(self(), {:disconnect, "§cError handling packet #{inspect(id, base: :hex)}:\n#{Exception.format(:error, e, __STACKTRACE__)}"})
Logger.error("Error handling packet with ID #{inspect(id, base: :hex)} in state #{connstate}: #{Exception.format(:error, e, __STACKTRACE__)}")
end
state
end
end
defp send_packet(socket, connstate, packet, version, state) do
defp send_packet(socket, connstate, packet, version) do
try do
data = connstate.serialize(packet, version)
container = if Map.get(state, :compression) == nil do
# Packet ID is included in data
Write.varint(byte_size(data)) <> data
else
threshold = Map.get(state, :compression, 0)
data_length = byte_size(data)
if data_length >= threshold do
compressed = Write.varint(data_length) <> :zlib.compress(data)
Write.varint(byte_size(compressed)) <> compressed
else
compressed = Write.varint(0) <> data
Write.varint(byte_size(compressed)) <> compressed
end
end
encrypted = if Map.get(state, :encryption_state) == nil do
container
else
Map.get(state, :encryption_state) |> :crypto.crypto_update(container)
end
:gen_tcp.send(socket, encrypted)
length = byte_size(data) |> Amethyst.Minecraft.Write.varint()
:gen_tcp.send(socket, length <> data)
rescue
e ->
Logger.error("Error sending packet #{inspect(packet)} in state #{connstate}: #{Exception.format(:error, e, __STACKTRACE__)}")
@ -310,12 +249,12 @@ defmodule Amethyst.ConnectionHandler do
end
end
defp disconnect(socket, reason, connstate, version, state) do
defp disconnect(socket, reason, connstate, version) do
Logger.info("Disconnecting connection #{inspect(socket)}")
Logger.debug("Disconnecting connection #{inspect(socket)}: #{reason}")
case connstate.disconnect(reason) do
nil -> nil
packet -> send_packet(socket, connstate, packet, version, state)
packet -> send_packet(socket, connstate, packet, version)
end
:gen_tcp.close(socket)
end

View File

@ -35,7 +35,7 @@ defmodule Amethyst.ConnectionReceiver do
{:ok, spawn(fn ->
Process.set_label("ConnectionReceiver for #{inspect(socket)}")
{:ok, pid} = Amethyst.ConnectionHandler.start_link(socket, Amethyst.ConnectionState.Handshake, 0)
receive(socket, pid, nil, nil)
receive(socket, pid)
end)}
end
@ -44,89 +44,42 @@ defmodule Amethyst.ConnectionReceiver do
{:ok, spawn_link(fn ->
Process.set_label("ConnectionReceiver for #{inspect(socket)}")
{:ok, pid} = Amethyst.ConnectionHandler.start_link(socket, Amethyst.ConnectionState.Handshake, 0)
receive(socket, pid, nil, nil)
receive(socket, pid)
end)}
end
@spec receive(:gen_tcp.socket(), pid(), nil | :crypto.crypto_state(), nil | pos_integer()) :: no_return()
def receive(socket, sender, estate, cstate) do
case get_packet(socket, estate, cstate) do
@spec receive(:gen_tcp.socket(), pid()) :: no_return()
def receive(socket, sender) do
case get_packet(socket) do
:closed -> send(sender, :closed)
Process.exit(self(), :normal)
{:error, error} -> Logger.error("Error reading packet: #{error}")
{id, data} -> send(sender, {:packet, id, data})
end
estate = if estate == nil do
# Ask the handler if we have encryption now
send(sender, {:get_encryption, self()})
receive do
nil -> nil
some -> some
end
else estate end
cstate = if cstate == nil do
# Ask the handler if we have encryption now
send(sender, {:get_compression, self()})
receive do
nil -> nil
some -> some
end
else cstate end
receive(socket, sender, estate, cstate)
receive(socket, sender)
end
def get_packet(client, estate, cstate) do
case get_varint(client, "", estate) do
def get_packet(client) do
case get_varint(client, "") do
:closed -> :closed
{:error, error} -> {:error, error}
{[length], ""} ->
data = :gen_tcp.recv(client, length)
case data do
{:ok, data} ->
# Perform decryption and decompression
decrypted = if estate == nil do
data
else
:crypto.crypto_update(estate, data)
end
full_packet = if cstate == nil do
# Using "without compression" container
decrypted
else
# Using "with compression" container
{[dlength], rest} = Read.start(decrypted) |> Read.varint |> Read.stop
if dlength == 0 do
# Uncompressed data
rest
else
# Compressed data
rest |> :zlib.uncompress()
end
end
{[id], data} = Read.start(full_packet) |> Read.varint |> Read.stop
{id, data}
recv = :gen_tcp.recv(client, length)
case recv do
{:ok, full_packet} -> ({[id], data} = Read.start(full_packet) |> Read.varint() |> Read.stop()
{id, data})
{:error, :closed} -> :closed
{:error, error} -> {:error, error}
end
end
end
defp get_varint(client, acc, estate) do
defp get_varint(client, acc) do
case :gen_tcp.recv(client, 1) do
{:ok, byte} ->
byte = case estate do
nil -> byte
ds -> :crypto.crypto_update(ds, byte)
end
case byte do
<<0::1, _::7>> -> Read.start(acc <> byte) |> Read.varint() |> Read.stop()
<<1::1, _::7>> -> get_varint(client, acc <> byte, estate)
end
{:ok, byte} -> case byte do
<<0::1, _::7>> -> Read.start(acc <> byte) |> Read.varint() |> Read.stop()
<<1::1, _::7>> -> get_varint(client, acc <> byte)
end
{:error, :closed} -> :closed
{:error, error} -> {:error, error}
end

View File

@ -16,7 +16,6 @@
defmodule Amethyst.Minecraft.Write do
import Bitwise
require Logger
@moduledoc """
This module contains functions for writing Minecraft data.
@ -140,28 +139,6 @@ defmodule Amethyst.Minecraft.Write do
def nbt(value) do
Amethyst.NBT.Write.write_net(value)
end
def bitset(list) do
unaligned = Enum.reduce(list, <<>>, &(if &1 do <<1::1, &2::bitstring>> else <<0::1, &2::bitstring>> end))
aligned = if rem(bit_size(unaligned), 64) == 0 do
unaligned
else
<<0::size(64 - rem(bit_size(unaligned), 64)), unaligned::bitstring>>
end
varint(div(byte_size(aligned), 8)) <> aligned
end
def fixed_bitset(list, length) do
unaligned = Enum.reduce(list, <<>>, &(if &1 do <<1::1, &2::bitstring>> else <<0::1, &2::bitstring>> end))
if bit_size(unaligned) != length do
raise ArgumentError, "Fixed bitset is not the correct length"
end
aligned = if rem(bit_size(unaligned), 64) == 0 do
unaligned
else
<<0::size(64 - rem(bit_size(unaligned), 64)), unaligned::bitstring>>
end
aligned
end
end
defmodule Amethyst.Minecraft.Read do
@ -239,11 +216,6 @@ defmodule Amethyst.Minecraft.Read do
{[data | acc], rest, :reversed}
end
def byte_array({acc, data, :reversed}) do
{[length], rest} = start(data) |> varint |> stop
raw({acc, rest, :reversed}, length)
end
@doc """
Reads a varint. `read` tracks the number of bytes read and `nacc` tracks the number being read.
"""
@ -294,10 +266,4 @@ defmodule Amethyst.Minecraft.Read do
def raw({acc, data, :reversed}) do
{[data | acc], "", :reversed}
end
def fixed_bitset({acc, data, :reversed}, length) do
bytes = ceil(length / 8)
<<value::binary-size(bytes), rest::binary>> = data
value = :binary.bin_to_list(value, 0, 1) |> Enum.reverse() |> Enum.take(length) |> Enum.map(& &1 != 0)
{[value | acc], rest, :reversed}
end
end

View File

@ -30,6 +30,10 @@ defmodule Amethyst.Keys do
GenServer.start_link(__MODULE__, bits, name: __MODULE__)
end
def get_priv do
GenServer.call(__MODULE__, :get_priv)
end
def get_pub do
GenServer.call(__MODULE__, :get_pub)
end
@ -48,19 +52,22 @@ defmodule Amethyst.Keys do
rsa_public_key = {:RSAPublicKey, modulus, public_exponent}
Logger.info("Generated RSA keys")
{:ok, {rsa_public_key, rsa_private_key, bits}}
{:ok, {rsa_public_key, rsa_private_key}}
end
@impl true
def handle_call(:get_pub, _from, {pubkey, privkey, bits}) do
{:SubjectPublicKeyInfo, pk, :not_encrypted} = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, pubkey)
# Logger.debug("#{inspect(pem_encoded, limit: :infinity)}")
{:reply, pk, {pubkey, privkey, bits}}
def handle_call(:get_priv, _from, {pubkey, privkey}) do
{:reply, :public_key.der_encode(:RSAPrivateKey, privkey), {pubkey, privkey}}
end
@impl true
def handle_call({:decrypt, encrypted}, _from, {pubkey, privkey, bits}) do
def handle_call(:get_pub, _from, {pubkey, privkey}) do
{:reply, :public_key.der_encode(:RSAPublicKey, pubkey), {pubkey, privkey}}
end
@impl true
def handle_call({:decrypt, encrypted}, _from, {pubkey, privkey}) do
plaintext = :public_key.decrypt_private(encrypted, privkey)
{:reply, plaintext, {pubkey, privkey, bits}}
{:reply, plaintext, {pubkey, privkey}}
end
end

View File

@ -88,22 +88,11 @@ defmodule Amethyst.Game do
mod.accept_teleport(self(), id, refs)
end
@doc """
`chat/3` is called when a player sends a chat message. Note that it is not how slash commands should be handled.
- 'from' is the PID of the player's connection process (see `login/3`).
- 'message' is the chat message sent by the player.
- `state_refs` are your references (see `instantiate/1`)
"""
@callback chat(from :: pid(), message :: binary(), state_refs :: map()) :: :ok
def chat(%{:mod => mod, :refs => refs}, message) do
mod.chat(self(), message, refs)
end
@doc """
The terrain of a specific chunk column. This is automatically used to load chunks for a player.
For now, this data must be formatted as a 3D list, indexed as [y][z][x].
"""
@callback chunk(from :: pid(), {x :: integer(), z :: integer()}, state_refs :: map()) :: [[[{block_state :: pos_integer(), sky_light :: 0..15, block_light :: 0..15}]]]
@callback chunk(from :: pid(), {x :: integer(), z :: integer()}, state_refs :: map()) :: [[[pos_integer()]]]
def chunk(%{:mod => mod, :refs => refs}, pos) do
mod.chunk(self(), pos, refs)
end

View File

@ -1,26 +0,0 @@
# 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.Minecraft.Sha1 do
@moduledoc """
This module implements the awkward non-standard hex digest hash that Minecraft uses
"""
def hash(input) do
# this sucks but so does the fact that mojang makes me do this so it fits
<<number::integer-signed-size(20*8)>> = :crypto.hash(:sha, input)
inspect(number, base: :hex) |> String.replace("0x", "") |> String.downcase()
end
end

View File

@ -203,10 +203,10 @@ defmodule Amethyst.ConnectionState.Configuration do
# https://gist.github.com/WinX64/ab8c7a8df797c273b32d3a3b66522906 minecraft:plains
basic_biome = compound(%{
"effects" => compound(%{
"sky_color" => int(7_907_327),
"water_fog_color" => int(329_011),
"fog_color" => int(12_638_463),
"water_color" => int(4_159_204),
"sky_color" => int(7907327),
"water_fog_color" => int(329011),
"fog_color" => int(12638463),
"water_color" => int(4159204),
"mood_sound" => compound(%{
"tick_delay" => int(6000),
"offset" => float(2.0),
@ -310,16 +310,6 @@ defmodule Amethyst.ConnectionState.Configuration do
chunk_z: div(floor(z), 16)
}})
send(self(), {:set_position, {x, y, z}})
send(self(), {:send_packet, %{packet_type: :player_info_update_add_player,
players: [
%{
uuid: Map.get(state, :uuid),
name: Map.get(state, :name),
properties: Map.get(state, :properties) |>
Enum.map(fn prop -> %{name: prop["name"], value: prop["value"], signature: Map.get(prop, "signature")} end)
}
]
}})
# Begin keepalive loop
# TODO: Put it under some supervisor
me = self()

View File

@ -60,20 +60,10 @@ defmodule Amethyst.ConnectionState.Login do
Macros.defpacket_serverbound :login_acknowledged, 0x03, 767, []
Macros.defpacket_serverbound :cookie_response, 0x04, 767, [identifier: :string, payload: {:optional, :byte_array}]
def handle(%{packet_type: :login_start, name: name, player_uuid: player_uuid}, 767, state) do
def handle(%{packet_type: :login_start, name: name, player_uuid: player_uuid}, 767, _state) do
Logger.debug("Received login start for #{name} with UUID #{player_uuid}")
if Application.get_env(:amethyst, :encryption, true) do
verify_token = :crypto.strong_rand_bytes(4)
public_key = Amethyst.Keys.get_pub()
Logger.debug("Public key: #{inspect(public_key, limit: :infinity)}")
send(self(), {:send_packet, %{
packet_type: :encryption_request,
server_id: "",
public_key: public_key,
verify_token: verify_token,
should_authenticate: Application.get_env(:amethyst, :auth, false)
}})
state |> Map.put(:verify_token, verify_token) |> Map.put(:name, name) |> Map.put(:uuid, player_uuid)
if Application.fetch_env!(:amethyst, :encryption) do
raise RuntimeError, "Encryption is not currently supported"
else
send(self(), {:send_packet, %{
packet_type: :login_success,
@ -85,57 +75,7 @@ defmodule Amethyst.ConnectionState.Login do
:ok
end
end
def handle(%{packet_type: :encryption_response, shared_secret: secret, verify_token: verify_token}, 767, state) do
secret = Amethyst.Keys.decrypt(secret)
verify_token = Amethyst.Keys.decrypt(verify_token)
if verify_token == Map.get(state, :verify_token, :never) do
send(self(), {:set_encryption, secret})
if Application.get_env(:amethyst, :compression, nil) != nil do
threshold = Application.get_env(:amethyst, :compression, 0)
send(self(), {:send_packet, %{packet_type: :set_compression, threshold: threshold}})
send(self(), {:set_compression, threshold})
end
if Application.get_env(:amethyst, :auth, false) == false do
# Don't check authentication
send(self(), {:send_packet, %{
packet_type: :login_success,
uuid: Map.get(state, :uuid),
username: Map.get(state, :name),
properties: [],
strict_error_handling: true
}})
Map.put(state, :authenticated, false)
else
# Check authentication
pubkey = Amethyst.Keys.get_pub()
hash = Amethyst.Minecraft.Sha1.hash(secret <> pubkey)
url = Application.get_env(:amethyst, :session_server, "https://sessionserver.mojang.com") <> "/session/minecraft/hasJoined?username=" <>
Map.get(state, :name) <> "&serverId=" <> hash # I don't think we need to verify the IP in the use case of Amethyst...
response = Req.get!(url,
headers: [
{"user-agent", "Amethyst/1.0"}
]).body
<<c1::binary-size(8), c2::binary-size(4), c3::binary-size(4), c4::binary-size(4), c5::binary>> = response["id"]
uuid = [c1, c2, c3, c4, c5] |> Enum.join("-")
send(self(), {:send_packet, %{
packet_type: :login_success,
uuid: uuid,
username: response["name"],
properties: response["properties"] |>
Enum.map(fn prop -> %{name: prop["name"], value: prop["value"], signature: Map.get(prop, "signature")} end),
strict_error_handling: true
}})
Map.put(state, :authenticated, true) |> Map.put(:uuid, uuid) |> Map.put(:name, response["name"]) |> Map.put(:properties, response["properties"])
end
else
raise RuntimeError, "Invalid verify token. Broken encryption?"
end
end
def handle(%{packet_type: :login_acknowledged}, 767, _state) do
Logger.debug("Received login acknowledged")
send(self(), {:set_state, Amethyst.ConnectionState.Configuration})

View File

@ -14,12 +14,7 @@
# 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/>.
# TODO!!!: REDO THIS WHOLE THING AGAIN IT'S A MESS
defmodule Amethyst.ConnectionState.Macros do
@moduledoc """
Useful macros for defining packets.
"""
require Logger
defmacro defpacket_serverbound(name, id, version, signature, where \\ true) do
quote do
@ -33,8 +28,7 @@ defmodule Amethyst.ConnectionState.Macros do
defmacro defpacket_clientbound(name, id, version, signature, where \\ true) do
quote do
def serialize(%{packet_type: unquote(name)} = packet, unquote(version)) when unquote(where) do
# Don't check types if we are in release mode
if Application.get_env(:amethyst, :release, false) || Amethyst.ConnectionState.Macros.check_type(packet, unquote(signature)) do
if Amethyst.ConnectionState.Macros.check_type(packet, unquote(signature)) do
Amethyst.Minecraft.Write.varint(unquote(id)) <> Amethyst.ConnectionState.Macros.write_signature(packet, unquote(signature))
else
raise "Invalid packet type for #{unquote(name)}! Got #{inspect(packet)}"
@ -58,13 +52,6 @@ defmodule Amethyst.ConnectionState.Macros do
else
{[nil | acc], rest, :reversed}
end
{:optional, {:raw, length}} ->
{[exists], rest} = Read.start(rest) |> Read.bool() |> Read.stop()
if exists do
Read.raw({acc, rest, :reversed}, length)
else
{[nil | acc], rest, :reversed}
end
{:optional, t} ->
{[exists], rest} = Read.start(rest) |> Read.bool() |> Read.stop()
if exists do
@ -72,10 +59,6 @@ defmodule Amethyst.ConnectionState.Macros do
else
{[nil | acc], rest, :reversed}
end
{:fixed_bitset, length} ->
Read.fixed_bitset({acc, rest, :reversed}, length)
{:raw, length} ->
Read.raw({acc, rest, :reversed}, length)
{:array, signature} ->
{[count], rest} = Read.start(rest) |> Read.varint() |> Read.stop()
if count == 0 do
@ -112,7 +95,7 @@ defmodule Amethyst.ConnectionState.Macros do
Enum.reduce(Map.get(packet, name), "", fn item, acc ->
acc <> write_signature(item, signature)
end)
{:literal, type, value} -> acc <> apply(Write, type, [value])
{:literal, {type, value}} -> acc <> apply(Write, type, [value])
t -> acc <> apply(Write, t, [Map.get(packet, name)])
end
end)
@ -123,7 +106,7 @@ defmodule Amethyst.ConnectionState.Macros do
Enum.all?(signature, fn {name, type} ->
case Map.get(packet, name, :missing) do
:missing ->
if is_tuple(type) && elem(type, 0) == :literal do
if elem(type, 0) == :literal do
true
else
throw {:missing, name}
@ -144,22 +127,22 @@ defmodule Amethyst.ConnectionState.Macros do
def type_matches(value, :bool) when is_boolean(value), do: true
def type_matches(value, :byte) when is_integer(value) and value in -128..127, do: true
def type_matches(value, :ubyte) when is_integer(value) and value in 0..255, do: true
def type_matches(value, :short) when is_integer(value) and value in -32_768..32_767, do: true
def type_matches(value, :ushort) when is_integer(value) and value in 0..65_535, do: true
def type_matches(value, :int) when is_integer(value) and value in -2_147_483_648..2_147_483_647, do: true
def type_matches(value, :long) when is_integer(value) and value in -9_223_372_036_854_775_808..9_223_372_036_854_775_807, do: true
def type_matches(value, :short) when is_integer(value) and value in -32768..32767, do: true
def type_matches(value, :ushort) when is_integer(value) and value in 0..65535, do: true
def type_matches(value, :int) when is_integer(value) and value in -2147483648..2147483647, do: true
def type_matches(value, :long) when is_integer(value) and value in -9223372036854775808..9223372036854775807, do: true
def type_matches(value, :float) when is_number(value), do: true
def type_matches(value, :double) when is_number(value), do: true
def type_matches(value, :varint) when is_integer(value) and value in -2_147_483_648..2_147_483_647, do: true
def type_matches(value, :varlong) when is_integer(value) and value in -9_223_372_036_854_775_808..9_223_372_036_854_775_807, do: true
def type_matches(value, :varint) when is_integer(value) and value in -2147483648..2147483647, do: true
def type_matches(value, :varlong) when is_integer(value) and value in -9223372036854775808..9223372036854775807, do: true
def type_matches(value, :uuid) when is_binary(value) and byte_size(value) == 36, do: true
def type_matches(value, :string) when is_binary(value), do: true
def type_matches(value, :raw) when is_binary(value), do: true
def type_matches(value, :byte_array) when is_binary(value), do: true
def type_matches({x, y, z}, :position) when
is_integer(x) and x in -33_554_432..33_554_431 and
is_integer(x) and x in -33554432..33554431 and
is_integer(y) and y in -2048..2047 and
is_integer(z) and z in -33_554_432..33_554_431, do: true
is_integer(z) and z in -33554432..33554431, do: true
def type_matches(value, :nbt), do: Amethyst.NBT.Write.check_type(value)
def type_matches(value, :json) do
case Jason.encode(value) do
@ -170,7 +153,6 @@ defmodule Amethyst.ConnectionState.Macros do
def type_matches(value, {:optional, _type}) when is_nil(value), do: true
def type_matches(value, {:optional, type}), do: type_matches(value, type)
def type_matches(value, {:array, signature}) when is_list(value), do: Enum.all?(value, fn item -> check_type(item, signature) end)
def type_matches(value, :bitset) when is_list(value), do: Enum.all?(value, fn item -> is_boolean(item) end)
def type_matches(value, {:compound, signature}) when is_map(value), do: check_type(value, signature)
def type_matches(_, _) do
false

View File

@ -36,10 +36,10 @@ defmodule Amethyst.ConnectionState.Play do
type: :varint,
data: :nbt
]},
sky_light_mask: :bitset,
block_light_mask: :bitset,
empty_sky_light_mask: :bitset,
empty_block_light_mask: :bitset,
sky_light_mask: :raw,
block_light_mask: :raw,
empty_sky_light_mask: :raw,
empty_block_light_mask: :raw,
sky_light_arrays: {:array, [
sky_light_array: :byte_array
]},
@ -71,8 +71,8 @@ defmodule Amethyst.ConnectionState.Play do
portal_cooldown: :varint,
enforces_secure_chat: :bool,
]
Macros.defpacket_clientbound :player_info_update_add_player, 0x3E, 767, [
actions: {:literal, :byte, 0x01},
Macros.defpacket_clientbound :player_info_update_add_player, 0x2E, 767, [
actions: {:literal, 0x01},
players: {:array, [
uuid: :uuid,
name: :string,
@ -83,8 +83,8 @@ defmodule Amethyst.ConnectionState.Play do
]}
]}
]
Macros.defpacket_clientbound :player_info_update_initialize_chat, 0x3E, 767, [
actions: {:literal, :byte, 0x02},
Macros.defpacket_clientbound :player_info_update_initialize_chat, 0x2E, 767, [
actions: {:literal, 0x02},
players: {:array, [
uuid: :uuid,
data: {:optional, {:compound, [
@ -95,29 +95,29 @@ defmodule Amethyst.ConnectionState.Play do
]}}
]}
]
Macros.defpacket_clientbound :player_info_update_update_game_mode, 0x3E, 767, [
actions: {:literal, :byte, 0x04},
Macros.defpacket_clientbound :player_info_update_update_game_mode, 0x2E, 767, [
actions: {:literal, 0x04},
players: {:array, [
uuid: :uuid,
gamemode: :varint
]}
]
Macros.defpacket_clientbound :player_info_update_update_listed, 0x3E, 767, [
actions: {:literal, :byte, 0x08},
Macros.defpacket_clientbound :player_info_update_update_listed, 0x2E, 767, [
actions: {:literal, 0x08},
players: {:array, [
uuid: :uuid,
listed: :bool
]}
]
Macros.defpacket_clientbound :player_info_update_update_latency, 0x3E, 767, [
actions: {:literal, :byte, 0x10},
Macros.defpacket_clientbound :player_info_update_update_latency, 0x2E, 767, [
actions: {:literal, 0x10},
players: {:array, [
uuid: :uuid,
ping: :varint, # Milliseconds
]}
]
Macros.defpacket_clientbound :player_info_update_update_display_name, 0x3E, 767, [
actions: {:literal, :byte, 0x20},
Macros.defpacket_clientbound :player_info_update_update_display_name, 0x2E, 767, [
actions: {:literal, 0x20},
players: {:array, [
uuid: :uuid,
display_name: {:optional, :nbt}
@ -135,10 +135,6 @@ defmodule Amethyst.ConnectionState.Play do
Macros.defpacket_clientbound :set_center_chunk, 0x54, 767, [
chunk_x: :varint, chunk_z: :varint
]
Macros.defpacket_clientbound :system_chat_message, 0x6C, 767, [
content: :nbt,
overlay: :bool
]
Macros.defpacket_clientbound :game_event, 0x22, 767, [
event: :ubyte, value: :float
@ -161,14 +157,6 @@ defmodule Amethyst.ConnectionState.Play do
def ge_start_waiting_for_level_chunks(767), do: %{packet_type: :game_event, event: 13, value: 0}
Macros.defpacket_serverbound :confirm_teleportation, 0x00, 767, [teleport_id: :varint]
Macros.defpacket_serverbound :chat_message, 0x06, 767, [
message: :string,
timestamp: :long,
salt: :long,
signature: {:optional, {:raw, 256}},
message_count: :varint,
acknowledged: {:fixed_bitset, 20}
]
Macros.defpacket_serverbound :serverbound_plugin_message, 0x12, 767, [channel: :string, data: :raw]
Macros.defpacket_serverbound :keep_alive, 0x18, 767, [id: :long]
Macros.defpacket_serverbound :set_player_position, 0x1A, 767, [
@ -245,12 +233,6 @@ defmodule Amethyst.ConnectionState.Play do
end
end
def handle(%{packet_type: :chat_message, message: msg, timestamp: _, salt: _, signature: _, message_count: _, acknowledged: _}, 767, state) do
# We will never support message signing
state |> Map.get(:game) |> Amethyst.Game.chat(msg)
:ok
end
def handle(%{packet_type: :keep_alive, id: id}, 767, state) do
ka = state |> Map.get(:keepalive)
send(ka, {:respond, id})

View File

@ -39,8 +39,7 @@ defmodule Amethyst.MixProject do
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:uuid, "~> 1.1"},
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
{:jason, "~> 1.4"},
{:req, "~> 0.5.6", runtime: true}
{:jason, "~> 1.4"}
]
end
end

View File

@ -1,10 +0,0 @@
defmodule Sha1Test do
alias Amethyst.Minecraft.Sha1
use ExUnit.Case, async: true
test "Mojang sha1 hash digest" do
assert Sha1.hash("Notch") == "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48"
assert Sha1.hash("jeb_") == "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1"
assert Sha1.hash("simon") == "88e16a1019277b15d58faf0541e11910eb756f6"
end
end

View File

@ -2,12 +2,6 @@ defmodule Example.Game do
require Logger
@behaviour Amethyst.Game
@moduledoc """
Example game used for testing Amethyst.
"""
require Amethyst.NBT.Write
alias Amethyst.NBT
@impl true
def instantiate(supervisor) do
Logger.info("The supervisor for this game is at #{inspect(supervisor)}")
@ -41,36 +35,21 @@ alias Amethyst.NBT
:ok
end
@impl true
def chat(from, message, _state_refs) do
Logger.info("Player at #{inspect(from)} said: #{inspect(message)}")
send(from, {:send_packet, %{
packet_type: :system_chat_message,
content: NBT.Write.compound(%{"text" => NBT.Write.string("You said: #{message}")}),
overlay: false
}})
:ok
end
@impl true
def joinable?(_refs) do
true
end
@impl true
def chunk(_from, {cx, cz}, _state_refs) do
def chunk(_from, {_cx, _cz}, _state_refs) do
# Logger.info("Player at #{inspect(from)} wants to know chunk #{cx}, #{cz}")
(0..255) |> Enum.map(fn y ->
(0..15) |> Enum.map(fn z ->
(0..15) |> Enum.map(fn x ->
gx = cx * 16 + x
gz = cz * 16 + z
gy = y
if rem(gx, 4) == 0 && rem(gy, 4) == 0 && rem(gz, 4) == 0 do
{abs(rem(div(gx + gy + gz, 4), 300)), 7, 7}
if y <= x + z do
3
else
{0, 7, 7}
0
end
end)
end)

View File

@ -2,9 +2,6 @@ import Config
config :amethyst,
port: 25599, # Bogus port for testing, avoids unexpected conflicts
encryption: true, # Whether or not to request encryption from clients.
auth: true, # Whether or not users should be authenticated with Mojang.
session_server: "https://sessionserver.mojang.com", # Base URL to the Mojang session server
compression: 256, # Packets larger than this amount are sent compressed. Set to nil to disable compression.
default_game: Example.Game, # Which game new players should be sent to
release: config_env() == :prod # If this is set to false, Amethyst will perform additional checks at runtime and will handle errors more loosely
encryption: false, # Whether or not to request encryption from clients.
auth: false, # Whether or not users should be authenticated with Mojang.
default_game: Example.Game # Which game new players should be sent to

View File

@ -43,20 +43,14 @@
})
);
package = env: release: pkgs: (pkgs.beamPackages.mixRelease {
#secrets = import ./secrets.nix;
package = env: pkgs: (pkgs.beamPackages.mixRelease {
pname = "amethyst";
version = "0.1.0";
src = ./.;
elixir = pkgs.elixir;
mixEnv = env;
mixReleaseName = release;
# This should just be part of mixRelease, I don't know why we need to do it here
installPhase = ''
runHook preInstall
mix release ${release} --no-deps-check --path "$out"
runHook postInstall
'';
meta.mainProgram = "${release}";
erlangDeterministicBuilds = false; # Technically less pure but allows doctests
removeCookie = false; # Insecure; Access to the file system can allow the cookie to be read and provides remote control of the Erlang VM
mixNixDeps = import ./mix.nix {inherit lib; beamPackages = pkgs.beamPackages;};
@ -70,9 +64,8 @@
in {
packages = eachSystem (pkgs:
{
default = package "prod" "amethyst" pkgs;
dev = package "dev" "amethyst" pkgs;
example = package "prop" "example" pkgs;
default = package "prod" pkgs;
dev = package "dev" pkgs;
}
);

View File

@ -8,7 +8,7 @@ defmodule AmethystUmbrella.MixProject do
elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
deps: deps(),
# default_release: :amethyst,
default_release: :amethyst,
releases: [
amethyst: [

View File

@ -5,19 +5,11 @@
"elixir_math": {:hex, :elixir_math, "0.1.2", "5655bdf7f34e30906f31cdcf3031b43dd522ce8d2936b60ad4696b2c752bf5c9", [:mix], [], "hexpm", "34f4e4384903097a8ec566784fa8e9aa2b741247d225741f07cc48250c2aa64c"},
"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.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"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"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"pre_commit": {:hex, :pre_commit, "0.3.4", "e2850f80be8090d50ad8019ef2426039307ff5dfbe70c736ad0d4d401facf304", [:mix], [], "hexpm", "16f684ba4f1fed1cba6b19e082b0f8d696e6f1c679285fedf442296617ba5f4e"},
"req": {:hex, :req, "0.5.6", "8fe1eead4a085510fe3d51ad854ca8f20a622aae46e97b302f499dfb84f726ac", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
}

130
mix.nix
View File

@ -47,19 +47,6 @@ let
beamDeps = [];
};
elixir_math = buildMix rec {
name = "elixir_math";
version = "0.1.2";
src = fetchHex {
pkg = "elixir_math";
version = "${version}";
sha256 = "34f4e4384903097a8ec566784fa8e9aa2b741247d225741f07cc48250c2aa64c";
};
beamDeps = [];
};
ex_doc = buildMix rec {
name = "ex_doc";
version = "0.34.2";
@ -86,32 +73,6 @@ let
beamDeps = [];
};
finch = buildMix rec {
name = "finch";
version = "0.19.0";
src = fetchHex {
pkg = "finch";
version = "${version}";
sha256 = "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6";
};
beamDeps = [ mime mint nimble_options nimble_pool telemetry ];
};
hpax = buildMix rec {
name = "hpax";
version = "1.0.0";
src = fetchHex {
pkg = "hpax";
version = "${version}";
sha256 = "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601";
};
beamDeps = [];
};
jason = buildMix rec {
name = "jason";
version = "1.4.4";
@ -164,45 +125,6 @@ let
beamDeps = [ makeup ];
};
mime = buildMix rec {
name = "mime";
version = "2.0.6";
src = fetchHex {
pkg = "mime";
version = "${version}";
sha256 = "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e";
};
beamDeps = [];
};
mint = buildMix rec {
name = "mint";
version = "1.6.2";
src = fetchHex {
pkg = "mint";
version = "${version}";
sha256 = "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9";
};
beamDeps = [ hpax ];
};
nimble_options = buildMix rec {
name = "nimble_options";
version = "1.1.1";
src = fetchHex {
pkg = "nimble_options";
version = "${version}";
sha256 = "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44";
};
beamDeps = [];
};
nimble_parsec = buildMix rec {
name = "nimble_parsec";
version = "1.4.0";
@ -216,58 +138,6 @@ let
beamDeps = [];
};
nimble_pool = buildMix rec {
name = "nimble_pool";
version = "1.1.0";
src = fetchHex {
pkg = "nimble_pool";
version = "${version}";
sha256 = "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a";
};
beamDeps = [];
};
pre_commit = buildMix rec {
name = "pre_commit";
version = "0.3.4";
src = fetchHex {
pkg = "pre_commit";
version = "${version}";
sha256 = "16f684ba4f1fed1cba6b19e082b0f8d696e6f1c679285fedf442296617ba5f4e";
};
beamDeps = [];
};
req = buildMix rec {
name = "req";
version = "0.5.6";
src = fetchHex {
pkg = "req";
version = "${version}";
sha256 = "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185";
};
beamDeps = [ finch jason mime ];
};
telemetry = buildRebar3 rec {
name = "telemetry";
version = "1.3.0";
src = fetchHex {
pkg = "telemetry";
version = "${version}";
sha256 = "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6";
};
beamDeps = [];
};
uuid = buildMix rec {
name = "uuid";
version = "1.1.8";