diff --git a/IFPSAsmLib/Assembler.cs b/IFPSAsmLib/Assembler.cs index af719a9..14a1f8c 100644 --- a/IFPSAsmLib/Assembler.cs +++ b/IFPSAsmLib/Assembler.cs @@ -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(); + var nameList = new List(); 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()) + { + if (func.Exported) + { + // assumption: script executor only care about InitializeUninstall + if (func.Name.ToLower() == "initializeuninstall") continue; + func.Name = "A"; + } + } + } + return ret; } diff --git a/IFPSAsmLib/Constants.cs b/IFPSAsmLib/Constants.cs index 128234d..9bb3d38 100644 --- a/IFPSAsmLib/Constants.cs +++ b/IFPSAsmLib/Constants.cs @@ -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"; diff --git a/IFPSAsmLib/Parser.cs b/IFPSAsmLib/Parser.cs index 5797978..a61e3f8 100644 --- a/IFPSAsmLib/Parser.cs +++ b/IFPSAsmLib/Parser.cs @@ -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); diff --git a/IFPSLib/Types/RecordType.cs b/IFPSLib/Types/RecordType.cs index 2d395df..7e5eb3d 100644 --- a/IFPSLib/Types/RecordType.cs +++ b/IFPSLib/Types/RecordType.cs @@ -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. /// public class RecordType : TypeBase { @@ -20,11 +21,22 @@ namespace IFPSLib.Types /// public IList Elements { get; internal set; } = new List(); + /// + /// Names of the record's elements. + /// + public IList ElementNames { get; internal set; } = null; + private RecordType() { } public RecordType(IList elements) { Elements = elements; + ElementNames = new List(); + } + + public RecordType(IList elements, IList 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();