lib: allow record element names to be specified

asm: allow record element names to be specified
asm: fix some errors being thrown by the wrong parser element
asm: add light obfuscation, when enabled all exported functions will be renamed to "A"
This commit is contained in:
zc 2022-12-09 16:01:32 +00:00
parent f99cb21c5d
commit b85cd7770a
4 changed files with 86 additions and 9 deletions

View File

@ -225,14 +225,19 @@ namespace IFPSAsmLib
break;
// record(types...)
case Constants.TYPE_RECORD:
if (next != null) next.ThrowInvalid();
// next is optional record element name, there can be no more elements after that
if (next != null && (next.Next != null || next.NextChild != null)) next.ThrowInvalid();
var typeList = new List<IType>();
var nameList = new List<string>();
for (next = child; next != null; next = next.NextChild)
{
var childName = string.Empty;
if (next.Next != null) childName = next.Next.Value;
if (!types.TryGetValue(next.Value, out childType)) next.ThrowInvalid("Invalid record element type");
typeList.Add(childType);
nameList.Add(childName);
}
ret = new RecordType(typeList);
ret = new RecordType(typeList, nameList);
break;
// set(bitsize)
case Constants.TYPE_SET:
@ -536,6 +541,8 @@ namespace IFPSAsmLib
return LocalVariable.Create(idx - 1);
}
private static readonly char[] SPLIT_DOT = new char[] { '.' };
private static Operand ParseOperandValue(
ParserElement value,
ScriptFunction function,
@ -554,6 +561,7 @@ namespace IFPSAsmLib
// variable: name
// immediate index: name[int_elem]
// variable index: name[elem_var]
// immediate index for record: name[elem_name]
if (value.NextChild != null)
{
// immediate
@ -573,11 +581,32 @@ namespace IFPSAsmLib
return new Operand(variable);
}
// indexed variable
var baseName = val.Substring(0, indexOfStart);
string element = val.Substring(indexOfStart + 1, val.Length - indexOfStart - 2);
var baseVar = ParseVariable(baseName, function, globals, aliases);
if (baseVar == null) value.ThrowInvalid(string.Format("In function {0}: Used nonexistant variable", function.Name));
// check for immediate index
if (uint.TryParse(element, out var elementIdx)) return new Operand(baseVar, elementIdx);
// not immediate index. if the base is a record, then check the element names
var baseRecord = baseVar.Type as RecordType;
if (baseRecord != null && !string.IsNullOrEmpty(element))
{
elementIdx = (uint)baseRecord.ElementNames.IndexOf(element);
if ((int)elementIdx != -1) return new Operand(baseVar, elementIdx);
}
// might be type.element
var elemSplit = element.Split(SPLIT_DOT, 2);
if (elemSplit.Length == 2 && types.TryGetValue(elemSplit[0], out var wantedType))
{
baseRecord = wantedType as RecordType;
if (baseRecord != null)
{
elementIdx = (uint)baseRecord.ElementNames.IndexOf(elemSplit[1]);
if ((int)elementIdx != -1) return new Operand(baseVar, elementIdx);
}
}
// must be a variable
var elemVar = ParseVariable(element, function, globals, aliases);
if (elemVar == null) value.ThrowInvalid(string.Format("In function {0}: Used nonexistant variable", function.Name));
return new Operand(baseVar, elemVar);
@ -627,7 +656,7 @@ namespace IFPSAsmLib
if (next == null) insn.ThrowInvalid();
next.ExpectValidName();
next.EnsureNoNextChild();
if (!functions.TryGetValue(next.Value, out var funcOp)) insn.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown function", function.Name));
if (!functions.TryGetValue(next.Value, out var funcOp)) next.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown function", function.Name));
if (next.Next != null) next.Next.ThrowInvalid();
return Instruction.Create(opcode, funcOp);
case OperandType.InlineType:
@ -636,7 +665,7 @@ namespace IFPSAsmLib
if (next == null) insn.ThrowInvalid();
next.ExpectValidName();
next.EnsureNoNextChild();
if (!types.TryGetValue(next.Value, out var typeOp)) insn.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown type", function.Name));
if (!types.TryGetValue(next.Value, out var typeOp)) next.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown type", function.Name));
if (next.Next != null) next.Next.ThrowInvalid();
return Instruction.Create(opcode, typeOp);
}
@ -666,7 +695,7 @@ namespace IFPSAsmLib
next = next.Next;
next.ExpectValidName();
next.EnsureNoNextChild();
if (!types.TryGetValue(next.Value, out var typeOp)) insn.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown type", function.Name));
if (!types.TryGetValue(next.Value, out var typeOp)) next.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown type", function.Name));
if (next.Next != null) next.Next.ThrowInvalid();
return Instruction.Create(opcode, op0, op1, typeOp);
}
@ -676,7 +705,7 @@ namespace IFPSAsmLib
next = next.Next;
next.ExpectValidName();
next.EnsureNoNextChild();
if (!types.TryGetValue(next.Value, out var typeOp)) insn.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown type", function.Name));
if (!types.TryGetValue(next.Value, out var typeOp)) next.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown type", function.Name));
next = next.Next;
next.ExpectValidName();
var variable = ParseVariable(next.Value, function, globals, aliases);
@ -884,6 +913,20 @@ namespace IFPSAsmLib
if (entryPoint != null) ret.EntryPoint = functionsTable[entryPoint];
// if obfuscation is required then rename all exported functions to "A".
if (parsed.OfType(ElementParentType.Obfuscate).Any())
{
foreach (var func in ret.Functions.OfType<ScriptFunction>())
{
if (func.Exported)
{
// assumption: script executor only care about InitializeUninstall
if (func.Name.ToLower() == "initializeuninstall") continue;
func.Name = "A";
}
}
}
return ret;
}

View File

@ -22,6 +22,7 @@ namespace IFPSAsmLib
internal const char BINARY_FALSE = '0';
internal const char BINARY_TRUE = '1';
internal const string ELEMENT_OBFUSCATE = ".obfuscate";
internal const string ELEMENT_VERSION = ".version";
internal const string ELEMENT_ENTRYPOINT = ".entry";
internal const string ELEMENT_TYPE = ".type";

View File

@ -386,6 +386,7 @@ namespace IFPSAsmLib
public enum ElementParentType : byte
{
Unknown,
Obfuscate,
FileVersion,
EntryPoint,
Attribute,
@ -526,12 +527,12 @@ namespace IFPSAsmLib
bool baseType
)
{
ParserElement currentChild = null, nextChild = null;
ParserElement currentChild, nextChild;
str.AdvanceWhiteSpace(ref location, true);
var typeNext = str.GetEntity(ref location, ParserSeparatorType.StartBody, false);
typeNext.ExpectValidName();
current.Next = next = new ParserElement(typeNext, parentType);
if (baseType && parentType == ElementParentType.Type && next.Value == "funcptr")
if (baseType && parentType == ElementParentType.Type && next.Value == Constants.TYPE_FUNCTION_POINTER)
{
ParseTokenWithUnknownBody(str, ref location, parentType, next, ref next, false);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
@ -580,6 +581,13 @@ namespace IFPSAsmLib
var typeString = type.ToString();
switch (typeString)
{
case Constants.ELEMENT_OBFUSCATE:
// .obfuscate
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.Obfuscate;
current = new ParserElement(type, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) current.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_VERSION:
// .version int
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);

View File

@ -9,7 +9,8 @@ namespace IFPSLib.Types
/// Defines a record.
/// A record in PascalScript (and Pascal) is equivalent to a struct in C-like languages.
/// In PascalScript, it's usually used for FFI.
/// Elements are typed but have no name; in bytecode they are referenced by index using array-index operands.
/// Elements are typed but have no name internally; in bytecode they are referenced by index using array-index operands.
/// For quality of life purposes, we allow names to be specified for elements, but they will not be retained when saving out bytecode.
/// </summary>
public class RecordType : TypeBase
{
@ -20,11 +21,22 @@ namespace IFPSLib.Types
/// </summary>
public IList<IType> Elements { get; internal set; } = new List<IType>();
/// <summary>
/// Names of the record's elements.
/// </summary>
public IList<string> ElementNames { get; internal set; } = null;
private RecordType() { }
public RecordType(IList<IType> elements)
{
Elements = elements;
ElementNames = new List<string>();
}
public RecordType(IList<IType> elements, IList<string> elementNames) : this(elements)
{
ElementNames = elementNames;
}
internal static new RecordType Load(BinaryReader br, Script script)
@ -48,13 +60,26 @@ namespace IFPSLib.Types
}
}
private static readonly string[] INVALID_CHARS_FOR_NAME = { " ", ",", "(", ")" };
public override string ToString()
{
var sb = new StringBuilder(base.ToString() + "record(");
var nameCount = ElementNames == null ? 0 : ElementNames.Count;
for (int i = 0; i < Elements.Count; i++)
{
sb.Append(i == 0 ? "" : ",");
sb.Append(Elements[i].Name);
if (i < nameCount)
{
var name = new StringBuilder(ElementNames[i]);
foreach (var chr in INVALID_CHARS_FOR_NAME) name = name.Replace(chr, string.Empty);
if (name.Length != 0)
{
sb.Append(' ');
sb.Append(name);
}
}
}
sb.AppendFormat(") {0}", Name);
return sb.ToString();