From fae5bb31396b7926cce214cb4e9b07743656a536 Mon Sep 17 00:00:00 2001 From: Kodi Craft Date: Sun, 7 Jul 2024 11:41:21 +0200 Subject: [PATCH] Begin writing code for reading and writing packets --- .credo.exs | 188 +++++++++++++++++++++++++++++++++++++++ .envrc | 1 + .formatter.exs | 4 + .gitignore | 32 +++++++ .vscode/launch.json | 34 +++++++ README.md | 21 +++++ config/runtime.exs | 4 + flake.lock | 64 +++++++++++++ flake.nix | 91 +++++++++++++++++++ lib/amethyst.ex | 20 +++++ lib/apps/tcp_listener.ex | 35 ++++++++ lib/data.ex | 109 +++++++++++++++++++++++ lib/servers/stage1.ex | 5 ++ mix.exs | 28 ++++++ mix.lock | 6 ++ mix.nix | 64 +++++++++++++ test/data_test.exs | 30 +++++++ test/test_helper.exs | 1 + 18 files changed, 737 insertions(+) create mode 100644 .credo.exs create mode 100644 .envrc create mode 100644 .formatter.exs create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 README.md create mode 100644 config/runtime.exs create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 lib/amethyst.ex create mode 100644 lib/apps/tcp_listener.ex create mode 100644 lib/data.ex create mode 100644 lib/servers/stage1.ex create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 mix.nix create mode 100644 test/data_test.exs create mode 100644 test/test_helper.exs diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..7e62a68 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,188 @@ +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + ], + excluded: [~r"/_build/", ~r"/deps/"] + }, + plugins: [], + requires: [], + strict: false, + parse_timeout: 5000, + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + {Credo.Check.Design.TagFIXME, []}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.WrongTestFileExtension, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now) + {Credo.Check.Refactor.UtcNowTruncate, []}, + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {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, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {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, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88e125b --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +amethyst-*.tar + +# Temporary files, for example, from tests. +/tmp/ + +# Language server logs +.elixir_ls/ + +# Direnv +.direnv \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ea789c4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,34 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "mix_task", + "name": "mix (Default task)", + "request": "launch", + "projectDir": "${workspaceRoot}", + "task": "run", + "taskArgs": [ + "--no-halt" + ], + "exitAfterTaskReturns": false + }, + { + "type": "mix_task", + "name": "mix test", + "request": "launch", + "task": "test", + "taskArgs": [ + "--trace" + ], + "startApps": true, + "projectDir": "${workspaceRoot}", + "requireFiles": [ + "test/**/test_helper.exs", + "test/**/*_test.exs" + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..102ab62 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Amethyst + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `amethyst` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:amethyst, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/config/runtime.exs b/config/runtime.exs new file mode 100644 index 0000000..3cd9ca6 --- /dev/null +++ b/config/runtime.exs @@ -0,0 +1,4 @@ +import Config + +config :amethyst, + port: 25599 # Bogus port for testing, avoids unexpected conflicts diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..646104d --- /dev/null +++ b/flake.lock @@ -0,0 +1,64 @@ +{ + "nodes": { + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1720066371, + "narHash": "sha256-uPlLYH2S0ACj0IcgaK9Lsf4spmJoGejR9DotXiXSBZQ=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "622f829f5fe69310a866c8a6cd07e747c44ef820", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1720181791, + "narHash": "sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nix-github-actions": "nix-github-actions", + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..85b281b --- /dev/null +++ b/flake.nix @@ -0,0 +1,91 @@ +{ + description = "Amethyst - Experimental Minecraft server in Elixir"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + nix-github-actions.url = "github:nix-community/nix-github-actions"; + nix-github-actions.inputs.nixpkgs.follows = "nixpkgs"; + + systems.url = "github:nix-systems/default"; + }; + + outputs = { + self, + nixpkgs, + systems, + nix-github-actions, + ... + } @ inputs: let + inherit (nixpkgs) lib; + + # Set the Erlang version + erlangVersion = "erlang_25"; + # Set the Elixir version + elixirVersion = "elixir_1_17"; + + eachSystem = f: + nixpkgs.lib.genAttrs (import systems) ( + system: + f (import nixpkgs { + inherit system; + overlays = [ + (final: _: let + erlang = final.beam.interpreters.${erlangVersion}; + beamPackages = final.beam.packages.${erlangVersion}; + elixir = beamPackages.${elixirVersion}; + in { + inherit erlang elixir; + inherit (beamPackages) elixir-ls hex; + inherit (beamPackages) mixRelease; + }) + ]; + }) + ); + + #secrets = import ./secrets.nix; + + package = env: pkgs: (pkgs.beamPackages.mixRelease { + pname = "amethyst"; + version = "0.1.0"; + src = ./.; + elixir = pkgs.elixir; + mixEnv = env; + 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;}; + }).overrideAttrs (old: { + preInstall = '' + # Run automated tests + mix test --no-deps-check --no-start --color + ''; + }); + + in { + packages = eachSystem (pkgs: + { + default = package "prod" pkgs; + dev = package "dev" pkgs; + } + ); + + checks = self.packages; + + githubActions = nix-github-actions.lib.mkGithubMatrix { + checks = nixpkgs.lib.getAttrs [ "x86_64-linux" ] self.packages; + }; + + devShells = eachSystem ( + pkgs: { + default = pkgs.mkShell { + buildInputs = with pkgs; [ + erlang + elixir + elixir-ls + mix2nix + ]; + }; + } + ); + }; +} diff --git a/lib/amethyst.ex b/lib/amethyst.ex new file mode 100644 index 0000000..08e886d --- /dev/null +++ b/lib/amethyst.ex @@ -0,0 +1,20 @@ +defmodule Amethyst.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + Supervisor.child_spec({Task, fn -> Amethyst.TCPListener.accept(Application.fetch_env!(:amethyst, :port)) end}, restart: :permanent), + {Task.Supervisor, name: Amethyst.ConnectionSupervisor}, + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Amethyst.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/lib/apps/tcp_listener.ex b/lib/apps/tcp_listener.ex new file mode 100644 index 0000000..6e0d963 --- /dev/null +++ b/lib/apps/tcp_listener.ex @@ -0,0 +1,35 @@ +defmodule Amethyst.TCPListener do + require Logger + @moduledoc """ + This module listens for TCP connections and creates server tasks to handle them. + """ + + def accept(port) do + {:ok, socket} = :gen_tcp.listen(port, [:binary, packet: 0, active: false, reuseaddr: true]) + Logger.info("Listening on port #{port}") + loop_acceptor(socket) + end + + @spec loop_acceptor(socket :: :gen_tcp.socket()) :: no_return() + defp loop_acceptor(socket) do + {:ok, client} = :gen_tcp.accept(socket) + {:ok, pid} = Task.Supervisor.start_child(Amethyst.ConnectionSupervisor, fn -> serve(client) end) + :ok = :gen_tcp.controlling_process(client, pid) + Logger.info("Received connection from #{inspect(client)}, assigned to PID #{inspect(pid)}") + loop_acceptor(socket) + end + + defp serve(socket) do + socket |> read_line() |> write_line(socket) + serve(socket) + end + + defp read_line(socket) do + {:ok, data} = :gen_tcp.recv(socket, 0) + data + end + + defp write_line(line, socket) do + :gen_tcp.send(socket, line) + end +end diff --git a/lib/data.ex b/lib/data.ex new file mode 100644 index 0000000..cb2ef22 --- /dev/null +++ b/lib/data.ex @@ -0,0 +1,109 @@ +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. + """ + + def varint(value) when value in -2_147_483_648..2_147_483_647 do + <> = <> # This is a trick to allow the arithmetic shift to act as a logical shift + varnum("", value) + end + + def varint(_) do + raise ArgumentError, "Value is out of range for a varint" + end + + def varlong(value) when value in -9_223_372_036_854_775_808..9_223_372_036_854_775_807 + do + <> = <> + varnum("", value) + end + + def varlong(_) do + raise ArgumentError, "Value is out of range for a varlong" + end + + defp varnum(acc, value) when value in 0..127 do + acc <> <<0::1, value::7-big>> + end + + defp varnum(acc, value) do + acc <> <<1::1, value::7-big>> |> varnum(value >>> 7) + end + + def string(value) do + <> + 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. + + 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 and Amethyst.Minecraft.Read.stop/1 + to get the data out of the chain. If you pass the option `reverse: true` to stop/1, the list will be reversed, this may + provide better performance if you do not mind the inconvenience of the reversed list. + + iex> alias Amethyst.Minecraft.Read + iex> [_, _, _] = Read.start(<<1, 999::16, 64>>) |> Read.bool() |> Read.short() |> Read.byte() |> Read.stop() + [true, 999, 64] + """ + + def start(binary) do + {[], binary} + end + + def stop({acc, rest}, opts \\ []) do + if Keyword.get(opts, :force_empty, false) and bit_size(rest) != 0 do + raise ArgumentError, "Expected no more data, but there is still #{bit_size(rest)} bits left" + end + case Keyword.get(opts, :reverse, false) do + true -> acc + false -> Enum.reverse(acc) + end + end + + def bool({acc, <>}) do + {[value != 0 | acc], rest} + end + + def byte({acc, <>}) do + {[value | acc], rest} + end + + def ubyte({acc, <>}) do + {[value | acc], rest} + end + + def short({acc, <>}) do + {[value | acc], rest} + end + + def ushort({acc, <>}) do + {[value | acc], rest} + end + + def int({acc, <>}) do + {[value | acc], rest} + end + + def long({acc, <>}) do + {[value | acc], rest} + end + + def float({acc, <>}) do + {[value | acc], rest} + end + + def double({acc, <>}) do + {[value | acc], rest} + end +end diff --git a/lib/servers/stage1.ex b/lib/servers/stage1.ex new file mode 100644 index 0000000..0c49bf7 --- /dev/null +++ b/lib/servers/stage1.ex @@ -0,0 +1,5 @@ +defmodule Amethyst.Server.Stage1 do + @moduledoc """ + This module contains the stage 1 (Handshaking) server logic. + """ +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..8c18e4b --- /dev/null +++ b/mix.exs @@ -0,0 +1,28 @@ +defmodule Amethyst.MixProject do + use Mix.Project + + def project do + [ + app: :amethyst, + version: "0.1.0", + elixir: "~> 1.17", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {Amethyst.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:credo, "~> 1.7", only: [:dev, :test], runtime: false} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..fe7f552 --- /dev/null +++ b/mix.lock @@ -0,0 +1,6 @@ +%{ + "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"}, + "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"}, +} diff --git a/mix.nix b/mix.nix new file mode 100644 index 0000000..8779454 --- /dev/null +++ b/mix.nix @@ -0,0 +1,64 @@ +{ lib, beamPackages, overrides ? (x: y: {}) }: + +let + buildRebar3 = lib.makeOverridable beamPackages.buildRebar3; + buildMix = lib.makeOverridable beamPackages.buildMix; + buildErlangMk = lib.makeOverridable beamPackages.buildErlangMk; + + self = packages // (overrides self packages); + + packages = with beamPackages; with self; { + bunt = buildMix rec { + name = "bunt"; + version = "1.0.0"; + + src = fetchHex { + pkg = "bunt"; + version = "${version}"; + sha256 = "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"; + }; + + beamDeps = []; + }; + + credo = buildMix rec { + name = "credo"; + version = "1.7.7"; + + src = fetchHex { + pkg = "credo"; + version = "${version}"; + sha256 = "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"; + }; + + beamDeps = [ bunt file_system jason ]; + }; + + file_system = buildMix rec { + name = "file_system"; + version = "1.0.0"; + + src = fetchHex { + pkg = "file_system"; + version = "${version}"; + sha256 = "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"; + }; + + beamDeps = []; + }; + + jason = buildMix rec { + name = "jason"; + version = "1.4.3"; + + src = fetchHex { + pkg = "jason"; + version = "${version}"; + sha256 = "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"; + }; + + beamDeps = []; + }; + }; +in self + diff --git a/test/data_test.exs b/test/data_test.exs new file mode 100644 index 0000000..b3fd4c7 --- /dev/null +++ b/test/data_test.exs @@ -0,0 +1,30 @@ +defmodule WriteTest do + use ExUnit.Case + doctest Amethyst.Minecraft.Write + doctest Amethyst.Minecraft.Read + + test "writing a varint" do + assert Amethyst.Minecraft.Write.varint(0) == <<0x00>> + assert Amethyst.Minecraft.Write.varint(127) == <<0x7F>> + assert Amethyst.Minecraft.Write.varint(128) == <<0x80, 0x01>> + assert Amethyst.Minecraft.Write.varint(255) == <<0xFF, 0x01>> + assert Amethyst.Minecraft.Write.varint(2_147_483_647) == <<0xFF, 0xFF, 0xFF, 0xFF, 0x07>> + assert Amethyst.Minecraft.Write.varint(-1) == <<0xFF, 0xFF, 0xFF, 0xFF, 0x0F>> + assert Amethyst.Minecraft.Write.varint(-2_147_483_648) == <<0x80, 0x80, 0x80, 0x80, 0x08>> + end + + test "writing a varlong" do + assert Amethyst.Minecraft.Write.varlong(0) == <<0x00>> + assert Amethyst.Minecraft.Write.varlong(127) == <<0x7F>> + assert Amethyst.Minecraft.Write.varlong(128) == <<0x80, 0x01>> + assert Amethyst.Minecraft.Write.varlong(255) == <<0xFF, 0x01>> + assert Amethyst.Minecraft.Write.varlong(2_147_483_647) == <<0xFF, 0xFF, 0xFF, 0xFF, 0x07>> + assert Amethyst.Minecraft.Write.varlong(9_223_372_036_854_775_807) == <<0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F>> + assert Amethyst.Minecraft.Write.varlong(-1) == <<0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01>> + end + + test "writing a string" do + assert Amethyst.Minecraft.Write.string("Hello, world!") == <<0x0D, "Hello, world!">> + assert Amethyst.Minecraft.Write.string("") == <<0x00>> + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()