Skip to content

Commit

Permalink
Two map serialization bug fixes that caused certain maps to always be…
Browse files Browse the repository at this point in the history
… re-downloaded on first load due to length mismatch.

1. Store empty warp/tilespec rows and re-encode when saving files.
2. Store raw sign length and pad string values to that length.
  • Loading branch information
ethanmoffat committed Mar 11, 2022
1 parent 4d7787c commit 1acdd75
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 40 deletions.
2 changes: 1 addition & 1 deletion EOLib.IO.Test/Map/MapFilePropertiesTest.cs
Expand Up @@ -120,7 +120,7 @@ private static byte[] CreateExpectedBytes(IMapFileProperties props)
ret.AddRange(props.Checksum);

var fullName = Enumerable.Repeat((byte)0xFF, 24).ToArray();
var encodedName = mapStringEncoderService.EncodeMapString(props.Name);
var encodedName = mapStringEncoderService.EncodeMapString(props.Name, props.Name.Length);
Array.Copy(encodedName, 0, fullName, fullName.Length - encodedName.Length, encodedName.Length);
ret.AddRange(fullName);

Expand Down
37 changes: 32 additions & 5 deletions EOLib.IO.Test/Map/MapFileTest.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using EOLib.IO.Map;
using EOLib.IO.Services;
using EOLib.IO.Services.Serializers;
Expand Down Expand Up @@ -89,7 +90,7 @@ public void MapFile_SerializeToByteArray_HasCorrectFormat()
var mapData = CreateDataForMap(new MapFileProperties().WithWidth(2).WithHeight(2), TileSpec.Arena, 432);
_mapFile = _serializer.DeserializeFromByteArray(mapData);

var actualData = _serializer.SerializeToByteArray(_mapFile);
var actualData = _serializer.SerializeToByteArray(_mapFile, rewriteChecksum: false);

CollectionAssert.AreEqual(mapData, actualData);
}
Expand All @@ -107,6 +108,28 @@ public void MapFile_Width1Height1_HasExpectedGFXAndTiles()
Assert.AreEqual(999, kvp.Value[1, 1]);
}

[Test]
public void MapFile_StoresEmptyWarpRows()
{
_mapFile = new MapFile().WithMapID(1);

var mapData = CreateDataForMap(new MapFileProperties().WithWidth(1).WithHeight(1), TileSpec.BankVault, 1234);
_mapFile = _serializer.DeserializeFromByteArray(mapData);

Assert.That(_mapFile.EmptyWarpRows, Has.Count.EqualTo(1));
}

[Test]
public void MapFile_StoresEmptyTileRows()
{
_mapFile = new MapFile().WithMapID(1);

var mapData = CreateDataForMap(new MapFileProperties().WithWidth(1).WithHeight(1), TileSpec.VultTypo, 4321);
_mapFile = _serializer.DeserializeFromByteArray(mapData);

Assert.That(_mapFile.EmptyTileRows, Has.Count.EqualTo(1));
}

private byte[] CreateDataForMap(IMapFileProperties mapFileProperties, TileSpec spec, int gfx = 1)
{
var ret = new List<byte>();
Expand All @@ -119,14 +142,18 @@ private byte[] CreateDataForMap(IMapFileProperties mapFileProperties, TileSpec s
ret.AddRange(nes.EncodeNumber(0, 1)); //chest spawns

//tiles
ret.AddRange(nes.EncodeNumber(1, 1)); //count
ret.AddRange(nes.EncodeNumber(2, 1)); //count (rows)
ret.AddRange(nes.EncodeNumber(1, 1)); //y
ret.AddRange(nes.EncodeNumber(1, 1)); //count
ret.AddRange(nes.EncodeNumber(1, 1)); //count (cols)
ret.AddRange(nes.EncodeNumber(1, 1)); //x
ret.AddRange(nes.EncodeNumber((byte)spec, 1)); //tilespec
ret.AddRange(nes.EncodeNumber(0, 1)); //y
ret.AddRange(nes.EncodeNumber(0, 1)); //count (cols) (empty row)

//warps
ret.AddRange(nes.EncodeNumber(0, 1));
//warps (empty row)
ret.AddRange(nes.EncodeNumber(1, 1)); //count
ret.AddRange(nes.EncodeNumber(1, 1)); //y
ret.AddRange(nes.EncodeNumber(0, 1)); //count

//gfx
foreach (var layer in (MapLayer[]) Enum.GetValues(typeof(MapLayer)))
Expand Down
38 changes: 36 additions & 2 deletions EOLib.IO.Test/Map/MapStringEncoderServiceTest.cs
Expand Up @@ -23,7 +23,7 @@ public void EncodeThenDecode_ReturnsOriginalString()
{
const string expected = "Test map string to encode";

var bytes = _service.EncodeMapString(expected);
var bytes = _service.EncodeMapString(expected, expected.Length);
var actual = _service.DecodeMapString(bytes);

Assert.AreEqual(expected, actual);
Expand All @@ -38,7 +38,7 @@ public void EncodeString_ReturnsExpectedBytes_FromKnownString()
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 104, 41, 104, 94
};

var actualBytes = _service.EncodeMapString(name);
var actualBytes = _service.EncodeMapString(name, name.Length);

CollectionAssert.AreEqual(expectedBytes, actualBytes);
}
Expand All @@ -56,5 +56,39 @@ public void DecodeString_ReturnsExpectedString_FromKnownBytes()

Assert.AreEqual(expected, actual);
}

[Test]
public void EncodeString_InvalidLength_Throws()
{
Assert.That(() => _service.EncodeMapString("123", 0), Throws.ArgumentException);
}

[Test]
public void EncodeString_ExtraLength_PadsData()
{
const string TestString = "12345";
const int LengthWithPadding = 8;

var actual = _service.EncodeMapString(TestString, LengthWithPadding);

Assert.That(actual, Has.Length.EqualTo(LengthWithPadding));

int i = 0;
for (; i < LengthWithPadding - TestString.Length; i++)
Assert.That(actual[i], Is.EqualTo((byte)0xFF));
Assert.That(actual[i], Is.Not.EqualTo((byte)0xFF));
}

[Test]
public void EncodeString_ExtraLength_DecodesToExpectedValue()
{
const string TestString = "12345";
const int LengthWithPadding = 8;

var encoded = _service.EncodeMapString(TestString, LengthWithPadding);
var original = _service.DecodeMapString(encoded);

Assert.That(original, Is.EqualTo(TestString));
}
}
}
6 changes: 4 additions & 2 deletions EOLib.IO/Map/IMapFile.cs
Expand Up @@ -7,7 +7,9 @@ public interface IMapFile
IMapFileProperties Properties { get; }

IReadOnlyMatrix<TileSpec> Tiles { get; }
IReadOnlyList<int> EmptyTileRows { get; }
IReadOnlyMatrix<WarpMapEntity> Warps { get; }
IReadOnlyList<int> EmptyWarpRows { get; }
IReadOnlyDictionary<MapLayer, IReadOnlyMatrix<int>> GFX { get; }
IReadOnlyDictionary<MapLayer, IReadOnlyList<int>> EmptyGFXRows { get; }
IReadOnlyList<NPCSpawnMapEntity> NPCSpawns { get; }
Expand All @@ -19,9 +21,9 @@ public interface IMapFile

IMapFile WithMapProperties(IMapFileProperties mapFileProperties);

IMapFile WithTiles(Matrix<TileSpec> tiles);
IMapFile WithTiles(Matrix<TileSpec> tiles, List<int> emptyTileRows);

IMapFile WithWarps(Matrix<WarpMapEntity> warps);
IMapFile WithWarps(Matrix<WarpMapEntity> warps, List<int> emptyWarpRows);

IMapFile WithGFX(Dictionary<MapLayer, Matrix<int>> gfx, Dictionary<MapLayer, List<int>> emptyLayers);

Expand Down
18 changes: 16 additions & 2 deletions EOLib.IO/Map/MapFile.cs
Expand Up @@ -11,7 +11,9 @@ public class MapFile : IMapFile
public IMapFileProperties Properties { get; private set; }

public IReadOnlyMatrix<TileSpec> Tiles => _mutableTiles;
public IReadOnlyList<int> EmptyTileRows => _mutableEmptyTileRows;
public IReadOnlyMatrix<WarpMapEntity> Warps => _mutableWarps;
public IReadOnlyList<int> EmptyWarpRows => _mutableEmptyWarpRows;
public IReadOnlyDictionary<MapLayer, IReadOnlyMatrix<int>> GFX => _readOnlyGFX;
public IReadOnlyDictionary<MapLayer, IReadOnlyList<int>> EmptyGFXRows => _readOnlyEmptyGFXRows;
public IReadOnlyList<NPCSpawnMapEntity> NPCSpawns => _mutableNPCSpawns;
Expand All @@ -20,7 +22,9 @@ public class MapFile : IMapFile
public IReadOnlyList<SignMapEntity> Signs => _mutableSigns;

private Matrix<TileSpec> _mutableTiles;
private List<int> _mutableEmptyTileRows;
private Matrix<WarpMapEntity> _mutableWarps;
private List<int> _mutableEmptyWarpRows;
private Dictionary<MapLayer, Matrix<int>> _mutableGFX;
private IReadOnlyDictionary<MapLayer, IReadOnlyMatrix<int>> _readOnlyGFX;
private Dictionary<MapLayer, List<int>> _mutableEmptyGFXRows;
Expand All @@ -33,7 +37,9 @@ public class MapFile : IMapFile
public MapFile()
: this(new MapFileProperties(),
Matrix<TileSpec>.Empty,
new List<int>(),
Matrix<WarpMapEntity>.Empty,
new List<int>(),
new Dictionary<MapLayer, Matrix<int>>(),
new Dictionary<MapLayer, List<int>>(),
new List<NPCSpawnMapEntity>(),
Expand All @@ -48,7 +54,9 @@ public MapFile()

private MapFile(IMapFileProperties properties,
Matrix<TileSpec> tiles,
List<int> emptyTileSpecRows,
Matrix<WarpMapEntity> warps,
List<int> emptyWarpRows,
Dictionary<MapLayer, Matrix<int>> gfx,
Dictionary<MapLayer, List<int>> emptyGFXRows,
List<NPCSpawnMapEntity> npcSpawns,
Expand All @@ -58,7 +66,9 @@ public MapFile()
{
Properties = properties;
_mutableTiles = tiles;
_mutableEmptyTileRows = emptyTileSpecRows;
_mutableWarps = warps;
_mutableEmptyWarpRows = emptyWarpRows;
_mutableGFX = gfx;
_mutableEmptyGFXRows = emptyGFXRows;
SetReadOnlyGFX();
Expand All @@ -81,17 +91,19 @@ public IMapFile WithMapProperties(IMapFileProperties mapFileProperties)
return newMap;
}

public IMapFile WithTiles(Matrix<TileSpec> tiles)
public IMapFile WithTiles(Matrix<TileSpec> tiles, List<int> emptyTileRows)
{
var newMap = MakeCopy(this);
newMap._mutableTiles = tiles;
newMap._mutableEmptyTileRows = emptyTileRows;
return newMap;
}

public IMapFile WithWarps(Matrix<WarpMapEntity> warps)
public IMapFile WithWarps(Matrix<WarpMapEntity> warps, List<int> emptyWarpRows)
{
var newMap = MakeCopy(this);
newMap._mutableWarps = warps;
newMap._mutableEmptyWarpRows = emptyWarpRows;
return newMap;
}

Expand Down Expand Up @@ -182,7 +194,9 @@ private static MapFile MakeCopy(MapFile source)
return new MapFile(
source.Properties,
source._mutableTiles,
source._mutableEmptyTileRows,
source._mutableWarps,
source._mutableEmptyWarpRows,
source._mutableGFX,
source._mutableEmptyGFXRows,
source._mutableNPCSpawns,
Expand Down
16 changes: 13 additions & 3 deletions EOLib.IO/Map/SignMapEntity.cs
Expand Up @@ -14,16 +14,19 @@ public class SignMapEntity : IMapEntity

public string Message { get; private set; }

public int RawLength { get; private set; }

public SignMapEntity()
: this(-1, -1, string.Empty, String.Empty)
: this(-1, -1, string.Empty, String.Empty, 0)
{ }

private SignMapEntity(int x, int y, string title, string message)
private SignMapEntity(int x, int y, string title, string message, int rawLength)
{
X = x;
Y = y;
Title = title;
Message = message;
RawLength = rawLength;
}

public SignMapEntity WithX(int x)
Expand Down Expand Up @@ -54,9 +57,16 @@ public SignMapEntity WithMessage(string message)
return newEntity;
}

public SignMapEntity WithRawLength(int length)
{
var newEntity = MakeCopy(this);
newEntity.RawLength = length;
return newEntity;
}

private static SignMapEntity MakeCopy(SignMapEntity src)
{
return new SignMapEntity(src.X, src.Y, src.Title, src.Message);
return new SignMapEntity(src.X, src.Y, src.Title, src.Message, src.RawLength);
}
}
}
2 changes: 1 addition & 1 deletion EOLib.IO/Services/IMapStringEncoderService.cs
Expand Up @@ -4,6 +4,6 @@ public interface IMapStringEncoderService
{
string DecodeMapString(byte[] chars);

byte[] EncodeMapString(string s);
byte[] EncodeMapString(string s, int length);
}
}
32 changes: 23 additions & 9 deletions EOLib.IO/Services/MapStringEncoderService.cs
@@ -1,5 +1,6 @@
using AutomaticTypeMapper;
using System;
using System.Linq;
using System.Text;

namespace EOLib.IO.Services
Expand All @@ -9,16 +10,18 @@ public class MapStringEncoderService : IMapStringEncoderService
{
public string DecodeMapString(byte[] chars)
{
Array.Reverse(chars);
var copy = new byte[chars.Length];
Array.Copy(chars, copy, chars.Length);
Array.Reverse(copy);

bool flippy = chars.Length % 2 == 1;
bool flippy = copy.Length % 2 == 1;

for (int i = 0; i < chars.Length; ++i)
for (int i = 0; i < copy.Length; ++i)
{
byte c = chars[i];
byte c = copy[i];
if (c == 0xFF)
{
Array.Resize(ref chars, i);
Array.Resize(ref copy, i);
break;
}

Expand All @@ -35,17 +38,20 @@ public string DecodeMapString(byte[] chars)
c = (byte)(0x9F - c);
}

chars[i] = c;
copy[i] = c;
flippy = !flippy;
}

return Encoding.ASCII.GetString(chars);
return Encoding.ASCII.GetString(copy);
}

public byte[] EncodeMapString(string s)
public byte[] EncodeMapString(string s, int length)
{
if (length < s.Length)
throw new ArgumentException("Length should be greater than or equal to string length", nameof(length));

byte[] chars = Encoding.ASCII.GetBytes(s);
bool flippy = chars.Length % 2 == 1;
bool flippy = length % 2 == 1;
int i;
for (i = 0; i < chars.Length; ++i)
{
Expand All @@ -66,6 +72,14 @@ public byte[] EncodeMapString(string s)
flippy = !flippy;
}
Array.Reverse(chars);

if (length > s.Length)
{
var tmp = Enumerable.Repeat((byte)0xFF, length).ToArray();
chars.CopyTo(tmp, length - s.Length);
chars = tmp;
}

return chars;
}
}
Expand Down

0 comments on commit 1acdd75

Please sign in to comment.