diff --git a/README.md b/README.md index 3769685..58565e1 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ An Erlang/OTP library which can manipulate NBT (Named Binary Tag) data. This library currently is up to date with the [1.0 spec](https://web.archive.org/web/20110723210920/http://www.minecraft.net/docs/NBT.txt). -Please note that the API may change before the first major version is released. +Please note that the API may change before the first major version is released. +If you want to avoid API breakage, please wait until version 1.0.0 is released. Quick navigation: [Build](#building-the-library) @@ -34,9 +35,9 @@ Depending on your build tool, add the following line to the "dependencies" secti | Build Tool | Dependency line | | --------------------- | ------- | -| rebar3 (Erlang/OTP) | `{erl_nbt, "0.1.0"}` | -| mix (Elixir) | `{:erl_nbt "~> 0.1.0"}` | -| erlang.mk (Erlang) | `dep_erl_nbt = hex 0.1.0` | +| rebar3 (Erlang/OTP) | `{erl_nbt, "0.2.0"}` | +| mix (Elixir) | `{:erl_nbt "~> 0.2.0"}` | +| erlang.mk (Erlang) | `dep_erl_nbt = hex 0.2.0` | Documentation ----- diff --git a/src/erl_nbt.app.src b/src/erl_nbt.app.src index 936aa42..7c79591 100644 --- a/src/erl_nbt.app.src +++ b/src/erl_nbt.app.src @@ -1,6 +1,6 @@ {application, erl_nbt, [ {description, "An Erlang/OTP library for manipulating NBT (Named Binary Tag) data"}, - {vsn, "0.1.0"}, + {vsn, "0.2.0"}, {licenses, ["Apache 2.0"]}, {links, [ {"GitHub", "https://github.com/hypothermic/erl_nbt"} diff --git a/src/erl_nbt_decode.erl b/src/erl_nbt_decode.erl index acead67..7d034a4 100644 --- a/src/erl_nbt_decode.erl +++ b/src/erl_nbt_decode.erl @@ -116,6 +116,24 @@ decode_tag(<>, Count, Depth, Output) when Id decode_tag(Rest3, Count + 1, Depth, Output#{Name => {?TAG_DOUBLE_TYPE, Double}}); +decode_tag(<>, Count, Depth, Output) when Id =:= ?TAG_BYTE_ARRAY_ID -> + {ok, Name, Rest2} = decode_string(Rest), + {ok, ByteArray, Rest3} = decode_byte_array(Rest2), + + decode_tag(Rest3, Count + 1, Depth, Output#{Name => {?TAG_BYTE_ARRAY_TYPE, ByteArray}}); + +decode_tag(<>, Count, Depth, Output) when Id =:= ?TAG_INT_ARRAY_ID -> + {ok, Name, Rest2} = decode_string(Rest), + {ok, IntArray, Rest3} = decode_int_array(Rest2), + + decode_tag(Rest3, Count + 1, Depth, Output#{Name => {?TAG_INT_ARRAY_TYPE, IntArray}}); + +decode_tag(<>, Count, Depth, Output) when Id =:= ?TAG_LONG_ARRAY_ID -> + {ok, Name, Rest2} = decode_string(Rest), + {ok, LongArray, Rest3} = decode_long_array(Rest2), + + decode_tag(Rest3, Count + 1, Depth, Output#{Name => {?TAG_LONG_ARRAY_TYPE, LongArray}}); + decode_tag(<>, Count, Depth, Output) when Id =:= ?TAG_STRING_ID -> {ok, Name, Rest2} = decode_string(Rest), {ok, String, Rest3} = decode_string(Rest2), @@ -150,5 +168,21 @@ decode_float(<>) -> decode_double(<>) -> {ok, Double, Rest}. +decode_byte_array(<>) -> + read_next_raw(Rest, 8, Length, []). + +decode_int_array(<>) -> + read_next_raw(Rest, 32, Length, []). + +decode_long_array(<>) -> + read_next_raw(Rest, 64, Length, []). + decode_string(<>) -> - {ok, binary_to_list(String), Rest}. \ No newline at end of file + {ok, binary_to_list(String), Rest}. + +read_next_raw(Data, UnitLength, Remaining, Out) when Remaining > 0 -> + <> = Data, + read_next_raw(Rest, UnitLength, Remaining-1, [Value|Out]); + +read_next_raw(Data, _UnitLength, _Remaining, Out) -> + {ok, lists:reverse(Out), Data}. diff --git a/src/erl_nbt_encode.erl b/src/erl_nbt_encode.erl index c99802b..bc0911a 100644 --- a/src/erl_nbt_encode.erl +++ b/src/erl_nbt_encode.erl @@ -72,6 +72,30 @@ encode_tag(Key, {?TAG_DOUBLE_TYPE, Value}) -> Value:64/big-signed-float >>; +encode_tag(Key, {?TAG_BYTE_ARRAY_TYPE, Value}) -> + << + ?TAG_BYTE_ARRAY_ID:8/unsigned-integer, + (encode_string(Key))/binary, + (length(Value)):32/big-signed-integer, + (lists:foldl(fun (E, Acc) -> <> end, <<>>, Value))/binary + >>; + +encode_tag(Key, {?TAG_INT_ARRAY_TYPE, Value}) -> + << + ?TAG_INT_ARRAY_ID:8/unsigned-integer, + (encode_string(Key))/binary, + (length(Value)):32/big-signed-integer, + (lists:foldl(fun (E, Acc) -> <> end, <<>>, Value))/binary + >>; + +encode_tag(Key, {?TAG_LONG_ARRAY_TYPE, Value}) -> + << + ?TAG_LONG_ARRAY_ID:8/unsigned-integer, + (encode_string(Key))/binary, + (length(Value)):32/big-signed-integer, + (lists:foldl(fun (E, Acc) -> <> end, <<>>, Value))/binary + >>; + encode_tag(Key, {?TAG_STRING_TYPE, Value}) -> << ?TAG_STRING_ID:8/unsigned-integer, diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..e518bf8 --- /dev/null +++ b/test/README.md @@ -0,0 +1,11 @@ +# erl_nbt tests + +A quick overview what every test is for + +| Name | Description | +| --------------- | -------------------------------------------------------- | +| Basic | Tests the parser using Notch's "basic" example file | +| Integers | Tests support for Byte, Short, Int, Long tags | +| Floating Points | Tests support for Float and Double tags | +| Integer Arrays | Tests support for ByteArray, IntArray and LongArray tags | +| Compounds | Tests support for (nested) Compound tags | \ No newline at end of file diff --git a/test/basic_test.erl b/test/basic_test.erl index 8882239..d01759c 100644 --- a/test/basic_test.erl +++ b/test/basic_test.erl @@ -10,26 +10,39 @@ -author("Matthijs Bakker "). -copyright("Copyright (C) 2021 hypothermic.nl"). +-include_lib("erl_nbt.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(TEST_FILE, "test/basic.nbt"). --define(EXPECTED_NBT, #{"hello world" => #{"name" => "Bananrama"}}). +-define(EXPECTED_NBT, + #{ + "hello world" => #{ + "name" => {?TAG_STRING_TYPE, "Bananrama"} + } + }). basic_test_() -> [ - {"decode file basic.nbt", - fun () -> - {ok, InputData} = file:read_file(?TEST_FILE), - {ok, DecodedNbt} = erl_nbt:decode(InputData), + {"decode file basic.nbt", + fun () -> + {ok, InputData} = file:read_file(?TEST_FILE), + {ok, DecodedNbt} = erl_nbt:decode(InputData), - ?assertEqual(?EXPECTED_NBT, DecodedNbt) - end - }, - {"encode map and compare with file basic.nbt", - fun () -> - {ok, EncodedNbt} = erl_nbt:encode(?EXPECTED_NBT), - {ok, CorrectData} = file:read_file(?TEST_FILE), + ?assertEqual(?EXPECTED_NBT, DecodedNbt) + end + }, + {"encode map and compare with file basic.nbt", + fun () -> + {ok, EncodedNbt} = erl_nbt:encode(?EXPECTED_NBT), + {ok, CorrectData} = file:read_file(?TEST_FILE), - ?assertEqual(CorrectData, EncodedNbt) - end - } + ?assertEqual(CorrectData, EncodedNbt) + end + }, + {"encode then decode", + fun () -> + {ok, EncodedNbt} = erl_nbt:encode(?EXPECTED_NBT), + {ok, DecodedNbt} = erl_nbt:decode(EncodedNbt), + ?assertEqual(DecodedNbt, ?EXPECTED_NBT) + end + } ]. \ No newline at end of file diff --git a/test/compounds_test.erl b/test/compounds_test.erl index 3eded8b..cba0a37 100644 --- a/test/compounds_test.erl +++ b/test/compounds_test.erl @@ -52,5 +52,12 @@ compounds_test_() -> [ % So we only test if the length of these binaries are equal. ?assertEqual(byte_size(CorrectData), byte_size(EncodedNbt)) end + }, + {"encode then decode", + fun () -> + {ok, EncodedNbt} = erl_nbt:encode(?EXPECTED_NBT), + {ok, DecodedNbt} = erl_nbt:decode(EncodedNbt), + ?assertEqual(DecodedNbt, ?EXPECTED_NBT) + end } ]. \ No newline at end of file diff --git a/test/floating_points_test.erl b/test/floating_points_test.erl index cfef093..d36c789 100644 --- a/test/floating_points_test.erl +++ b/test/floating_points_test.erl @@ -23,7 +23,8 @@ "z" => {?TAG_DOUBLE_TYPE, -799.2}, "stance" => {?TAG_FLOAT_TYPE, 87.0} } - }). + } +). floating_points_test_() -> [ {"decode file floating_points.nbt", @@ -44,5 +45,12 @@ floating_points_test_() -> [ % So we only test if the length of these binaries are equal. ?assertEqual(byte_size(CorrectData), byte_size(EncodedNbt)) end + }, + {"encode then decode", + fun () -> + {ok, EncodedNbt} = erl_nbt:encode(?EXPECTED_NBT), + {ok, DecodedNbt} = erl_nbt:decode(EncodedNbt), + ?assertEqual(DecodedNbt, ?EXPECTED_NBT) + end } ]. \ No newline at end of file diff --git a/test/integer_arrays.nbt b/test/integer_arrays.nbt new file mode 100644 index 0000000..64ea4b3 Binary files /dev/null and b/test/integer_arrays.nbt differ diff --git a/test/integer_arrays_test.erl b/test/integer_arrays_test.erl new file mode 100644 index 0000000..38de6ac --- /dev/null +++ b/test/integer_arrays_test.erl @@ -0,0 +1,54 @@ +%%%------------------------------------------------------------------- +%%% @author Matthijs Bakker +%%% @copyright (C) 2021 hypothermic.nl +%%% @doc +%%% Tests the erl_nbt functionality on the "integers.nbt" example +%%% @end +%%% Created : 8. Jul 2021 10:25 PM +%%%------------------------------------------------------------------- +-module(integer_arrays_test). +-author("Matthijs Bakker "). +-copyright("Copyright (C) 2021 hypothermic.nl"). + +-include_lib("erl_nbt.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-define(TEST_FILE, "test/integer_arrays.nbt"). +-define(EXPECTED_NBT, + #{ + "arrays" => #{ + "bunch of bytes" => {?TAG_BYTE_ARRAY_TYPE, [-43, 67, 49, 108]}, + "flock of ints" => {?TAG_INT_ARRAY_TYPE, [420, 2147483646, -82131929]}, + "lots of longs" => {?TAG_LONG_ARRAY_TYPE, [-921312010, 69, 129391203]} + } + } +). + +integer_arrays_test_() -> [ + {"decode file integer_arrays.nbt", + fun () -> + {ok, InputData} = file:read_file(?TEST_FILE), + {ok, DecodedNbt} = erl_nbt:decode(InputData), + + ?assertEqual(?EXPECTED_NBT, DecodedNbt) + end + }, + {"encode map and compare with file integer_arrays.nbt", + fun () -> + {ok, EncodedNbt} = erl_nbt:encode(?EXPECTED_NBT), + {ok, CorrectData} = file:read_file(?TEST_FILE), + + % Because child order of compound tags is not guaranteed + % to be preserved with NBT tags, we can't test for equality. + % So we only test if the length of these binaries are equal. + ?assertEqual(byte_size(CorrectData), byte_size(EncodedNbt)) + end + }, + {"encode then decode", + fun () -> + {ok, EncodedNbt} = erl_nbt:encode(?EXPECTED_NBT), + {ok, DecodedNbt} = erl_nbt:decode(EncodedNbt), + ?assertEqual(DecodedNbt, ?EXPECTED_NBT) + end + } +]. \ No newline at end of file diff --git a/test/integers_test.erl b/test/integers_test.erl index 35924cd..c17c95a 100644 --- a/test/integers_test.erl +++ b/test/integers_test.erl @@ -22,7 +22,8 @@ "my int" => {?TAG_INT_TYPE, 20012318}, "my long" => {?TAG_LONG_TYPE, 91499321421} } - }). + } +). integers_test_() -> [ {"decode file integers.nbt", @@ -43,5 +44,12 @@ integers_test_() -> [ % So we only test if the length of these binaries are equal. ?assertEqual(byte_size(CorrectData), byte_size(EncodedNbt)) end + }, + {"encode then decode", + fun () -> + {ok, EncodedNbt} = erl_nbt:encode(?EXPECTED_NBT), + {ok, DecodedNbt} = erl_nbt:decode(EncodedNbt), + ?assertEqual(DecodedNbt, ?EXPECTED_NBT) + end } ]. \ No newline at end of file