Skip to content

Commit

Permalink
add rich_presence_ascii_string_lookup function (#538)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamiras authored Oct 1, 2024
1 parent aed3799 commit f5cb7b0
Show file tree
Hide file tree
Showing 7 changed files with 436 additions and 12 deletions.
15 changes: 15 additions & 0 deletions Source/Data/Field.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,21 @@ public static uint GetByteSize(FieldSize size)
}
}

/// <summary>
/// Gets the size needed to read the specified number of bytes in little-endian order.
/// </summary>
public static FieldSize GetSizeForBytes(int bytes)
{
switch (bytes)
{
case 1: return FieldSize.Byte;
case 2: return FieldSize.Word;
case 3: return FieldSize.TByte;
case 4: return FieldSize.DWord;
default: return FieldSize.None;
}
}

/// <summary>
/// Gets whether or not the field references memory.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions Source/Parser/AchievementScriptInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ internal static InterpreterScope GetGlobalScope()
_globalScope.AddFunction(new RichPresenceValueFunction());
_globalScope.AddFunction(new RichPresenceMacroFunction());
_globalScope.AddFunction(new RichPresenceLookupFunction());
_globalScope.AddFunction(new RichPresenceAsciiStringLookupFunction());

_globalScope.AddFunction(new AlwaysTrueFunction());
_globalScope.AddFunction(new AlwaysFalseFunction());
Expand Down
1 change: 0 additions & 1 deletion Source/Parser/Expressions/Trigger/FieldFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using RATools.Data;
using System.Linq;

namespace RATools.Parser.Expressions.Trigger
{
Expand Down
9 changes: 1 addition & 8 deletions Source/Parser/Functions/AsciiStringEqualsFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,7 @@ public override bool Evaluate(InterpreterScope scope, out ExpressionBase result)
break;
}

FieldSize size;
switch (remaining)
{
case 1: size = FieldSize.Byte; break;
case 2: size = FieldSize.Word; break;
case 3: size = FieldSize.TByte; break;
default: size = FieldSize.DWord; break;
}
FieldSize size = Field.GetSizeForBytes(Math.Min(remaining, 4));

var scan = address.Clone();
scan.Field = new Field { Type = address.Field.Type, Size = size, Value = address.Field.Value + (uint)offset };
Expand Down
219 changes: 219 additions & 0 deletions Source/Parser/Functions/RichPresenceAsciiStringLookupFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
using RATools.Data;
using RATools.Parser.Expressions;
using RATools.Parser.Expressions.Trigger;
using System;

namespace RATools.Parser.Functions
{
internal class RichPresenceAsciiStringLookupFunction : FunctionDefinitionExpression
{
public RichPresenceAsciiStringLookupFunction()
: base("rich_presence_ascii_string_lookup")
{
Parameters.Add(new VariableDefinitionExpression("name"));
Parameters.Add(new VariableDefinitionExpression("address"));
Parameters.Add(new VariableDefinitionExpression("dictionary"));

Parameters.Add(new VariableDefinitionExpression("fallback"));
DefaultParameters["fallback"] = new StringConstantExpression("");
}

public override bool Evaluate(InterpreterScope scope, out ExpressionBase result)
{
var name = GetStringParameter(scope, "name", out result);
if (name == null)
return false;

var dictionary = GetDictionaryParameter(scope, "dictionary", out result);
if (dictionary == null)
return false;
if (dictionary.Count == 0)
{
result = new ErrorExpression("dictionary is empty", dictionary);
return false;
}

var fallback = GetStringParameter(scope, "fallback", out result);
if (fallback == null)
return false;

var address = GetMemoryAddressParameter(scope, "address", out result);
if (address == null)
return false;

var maxLength = 0xFFFFFF;
foreach (var pair in dictionary.Entries)
{
var stringKey = pair.Key as StringConstantExpression;
if (stringKey == null)
{
result = new ConversionErrorExpression(pair.Key, ExpressionType.StringConstant);
return false;
}

maxLength = Math.Min(stringKey.Value.Length + 1, maxLength);
}

int offset = 0;
int length = 4;
DictionaryExpression hashedDictionary = null;

for (int i = 0; i < maxLength - 3; i += 4)
{
offset = i;
hashedDictionary = BuildHashedDictionary(dictionary, offset, length);

if (hashedDictionary != null)
break;
}

if (hashedDictionary == null)
{
// could not find key aligned to 4 bytes. try intermediate offsets
for (int i = 2; i < maxLength - 3; i += 4)
{
offset = i;
hashedDictionary = BuildHashedDictionary(dictionary, offset, length);

if (hashedDictionary != null)
break;
}

// still no match, try matching the end of the string
if (hashedDictionary == null)
{
length = maxLength & 3;
if (length > 0)
{
offset = maxLength & ~3;
hashedDictionary = BuildHashedDictionary(dictionary, offset, length);
}
}
}

ExpressionBase expression = null;

if (hashedDictionary == null)
{
for (length = 8; length < maxLength; length += 4)
{
hashedDictionary = BuildSummedHashDictionary(dictionary, length);
if (hashedDictionary != null)
{
var summedExpression = new MemoryValueExpression();
for (int i = 0; i < length; i += 4)
{
var clone = address.Clone();
clone.Field = new Field
{
Type = FieldType.MemoryAddress,
Size = FieldSize.DWord,
Value = address.Field.Value + (uint)i,
};
summedExpression.ApplyMathematic(clone, MathematicOperation.Add);
}

expression = summedExpression;
break;
}
}

if (hashedDictionary == null)
{
result = new ErrorExpression("Could not find a unique sequence of characters within the available keys", dictionary);
return false;
}
}
else
{
// apply the offset and potentially change the size of the read
var clone = address.Clone();
clone.Field = new Field
{
Type = FieldType.MemoryAddress,
Size = Field.GetSizeForBytes(length),
Value = address.Field.Value + (uint)offset,
};
expression = clone;
}

result = new RichPresenceLookupExpression(name, expression) { Items = hashedDictionary, Fallback = fallback };
CopyLocation(result);
result.MakeReadOnly();
return true;
}

private static DictionaryExpression BuildSummedHashDictionary(DictionaryExpression dictionary, int length)
{
var hashedDictionary = new DictionaryExpression { Location = dictionary.Location };

foreach (var pair in dictionary.Entries)
{
var stringKey = (StringConstantExpression)pair.Key;
var hash = CreateHashKey(stringKey, 0, 4).Value;
for (int i = 4; i < length; i += 4)
hash += CreateHashKey(stringKey, i, 4).Value;

var hashKey = new IntegerConstantExpression(hash) { Location = stringKey.Location };
if (hashedDictionary.GetEntry(hashKey) != null)
return null;

hashedDictionary.Add(hashKey, pair.Value);
}

return hashedDictionary;
}

private static DictionaryExpression BuildHashedDictionary(DictionaryExpression dictionary, int offset, int length)
{
var hashedDictionary = new DictionaryExpression { Location = dictionary.Location };

foreach (var pair in dictionary.Entries)
{
var stringKey = (StringConstantExpression)pair.Key;
var hashKey = CreateHashKey(stringKey, offset, length);

if (hashedDictionary.GetEntry(hashKey) != null)
return null;

hashedDictionary.Add(hashKey, pair.Value);
}

return hashedDictionary;
}

private static IntegerConstantExpression CreateHashKey(StringConstantExpression stringKey, int index, int length)
{
int value = (index < stringKey.Value.Length) ? stringKey.Value[index] : 0;
switch (length)
{
case 4:
if (index + 3 < stringKey.Value.Length)
value |= stringKey.Value[index + 3] << 24;
goto case 3;

case 3:
if (index + 2 < stringKey.Value.Length)
value |= stringKey.Value[index + 2] << 16;
goto case 2;

case 2:
if (index + 1 < stringKey.Value.Length)
value |= stringKey.Value[index + 1] << 8;
goto default;

default:
break;
}

return new IntegerConstantExpression(value) { Location = stringKey.Location };
}

public override bool Invoke(InterpreterScope scope, out ExpressionBase result)
{
var functionCall = scope.GetContext<FunctionCallExpression>();
result = new ErrorExpression(Name.Name + " has no meaning outside of a rich_presence_display call", functionCall.FunctionName);
return false;
}
}
}
6 changes: 3 additions & 3 deletions Source/Parser/RichPresenceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ public ErrorExpression AddLookupField(ExpressionBase func, StringConstantExpress
if (_valueFields.ContainsKey(name.Value))
return new ErrorExpression("A rich_presence_value already exists for '" + name.Value + "'", name);

var tinyDict = new TinyDictionary<int, string>();
var lookupDict = new Dictionary<int, string>();
foreach (var entry in dict.Entries)
{
var key = entry.Key as IntegerConstantExpression;
Expand All @@ -304,13 +304,13 @@ public ErrorExpression AddLookupField(ExpressionBase func, StringConstantExpress
if (value == null)
return new ErrorExpression("value is not a string", entry.Value);

tinyDict[key.Value] = value.Value;
lookupDict[key.Value] = value.Value;
}

_lookupFields[name.Value] = new Lookup
{
Func = func,
Entries = tinyDict,
Entries = lookupDict,
Fallback = fallback
};

Expand Down
Loading

0 comments on commit f5cb7b0

Please sign in to comment.