Begin writing code for reading and writing packets
This commit is contained in:
commit
fae5bb3139
188
.credo.exs
Normal file
188
.credo.exs
Normal file
@ -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 <name>`. 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`.
|
||||
#
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
@ -0,0 +1,4 @@
|
||||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -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
|
34
.vscode/launch.json
vendored
Normal file
34
.vscode/launch.json
vendored
Normal file
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
21
README.md
Normal file
21
README.md
Normal file
@ -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 <https://hexdocs.pm/amethyst>.
|
||||
|
4
config/runtime.exs
Normal file
4
config/runtime.exs
Normal file
@ -0,0 +1,4 @@
|
||||
import Config
|
||||
|
||||
config :amethyst,
|
||||
port: 25599 # Bogus port for testing, avoids unexpected conflicts
|
64
flake.lock
Normal file
64
flake.lock
Normal file
@ -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
|
||||
}
|
91
flake.nix
Normal file
91
flake.nix
Normal file
@ -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
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
20
lib/amethyst.ex
Normal file
20
lib/amethyst.ex
Normal file
@ -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
|
35
lib/apps/tcp_listener.ex
Normal file
35
lib/apps/tcp_listener.ex
Normal file
@ -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
|
109
lib/data.ex
Normal file
109
lib/data.ex
Normal file
@ -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
|
||||
<<value::32-unsigned>> = <<value::32-signed>> # 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
|
||||
<<value::64-unsigned>> = <<value::64-signed>>
|
||||
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
|
||||
<<varint(byte_size(value))::binary, value::binary>>
|
||||
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, <<value, rest::bitstring>>}) do
|
||||
{[value != 0 | acc], rest}
|
||||
end
|
||||
|
||||
def byte({acc, <<value::big-signed, rest::binary>>}) do
|
||||
{[value | acc], rest}
|
||||
end
|
||||
|
||||
def ubyte({acc, <<value::big-unsigned, rest::binary>>}) do
|
||||
{[value | acc], rest}
|
||||
end
|
||||
|
||||
def short({acc, <<value::16-big-signed, rest::binary>>}) do
|
||||
{[value | acc], rest}
|
||||
end
|
||||
|
||||
def ushort({acc, <<value::16-big-unsigned, rest::binary>>}) do
|
||||
{[value | acc], rest}
|
||||
end
|
||||
|
||||
def int({acc, <<value::32-big-signed, rest::binary>>}) do
|
||||
{[value | acc], rest}
|
||||
end
|
||||
|
||||
def long({acc, <<value::64-big-signed, rest::binary>>}) do
|
||||
{[value | acc], rest}
|
||||
end
|
||||
|
||||
def float({acc, <<value::32-float-big, rest::binary>>}) do
|
||||
{[value | acc], rest}
|
||||
end
|
||||
|
||||
def double({acc, <<value::64-float-big, rest::binary>>}) do
|
||||
{[value | acc], rest}
|
||||
end
|
||||
end
|
5
lib/servers/stage1.ex
Normal file
5
lib/servers/stage1.ex
Normal file
@ -0,0 +1,5 @@
|
||||
defmodule Amethyst.Server.Stage1 do
|
||||
@moduledoc """
|
||||
This module contains the stage 1 (Handshaking) server logic.
|
||||
"""
|
||||
end
|
28
mix.exs
Normal file
28
mix.exs
Normal file
@ -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
|
6
mix.lock
Normal file
6
mix.lock
Normal file
@ -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"},
|
||||
}
|
64
mix.nix
Normal file
64
mix.nix
Normal file
@ -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
|
||||
|
30
test/data_test.exs
Normal file
30
test/data_test.exs
Normal file
@ -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
|
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
@ -0,0 +1 @@
|
||||
ExUnit.start()
|
Loading…
Reference in New Issue
Block a user