Add project files.

This commit is contained in:
zc 2022-08-07 15:42:41 +01:00
parent 0b000e586f
commit 9b71ef23ca
78 changed files with 11932 additions and 0 deletions

398
.gitignore vendored Normal file
View File

@ -0,0 +1,398 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

892
IFPSAsmLib/Assembler.cs Normal file
View File

@ -0,0 +1,892 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using IFPSLib;
using IFPSLib.Types;
using IFPSLib.Emit;
using IFPSLib.Emit.FDecl;
using System.Runtime.InteropServices;
using System.Collections;
using System.ComponentModel;
namespace IFPSAsmLib
{
internal static class AssemblerExtensions
{
private enum LiteralParserState : byte
{
Default,
EscapedChar,
EscapedHex,
EscapedUnicode
}
private static char NibbleToHex(this char ch0)
{
// taken from stackoverflow: https://stackoverflow.com/questions/3408706/hexadecimal-string-to-byte-array-in-c/67799940#67799940
// Parser already ensured the char is a valid nibble, so the quick bithacking can be done here
return (char)((ch0 & 0xF) + (ch0 >> 6) | ((ch0 >> 3) & 0x8));
}
internal static string FromLiteral(this string input)
{
if (input[0] != input[input.Length - 1] || input[0] != Constants.STRING_CHAR) throw new ArgumentOutOfRangeException(nameof(input));
StringBuilder literal = new StringBuilder(input.Length - 2);
var state = LiteralParserState.Default;
byte counter = 0;
char escapedChar = (char)0;
foreach (var c in input.Skip(1).Take(input.Length - 2))
{
switch (state)
{
case LiteralParserState.Default:
if (c != '\\') literal.Append(c);
else state = LiteralParserState.EscapedChar;
break;
case LiteralParserState.EscapedChar:
state = LiteralParserState.Default;
switch (c)
{
case '"': literal.Append('"'); break;
case '\\': literal.Append('\\'); break;
case '0': literal.Append('\0'); break;
case 'a': literal.Append('\a'); break;
case 'b': literal.Append('\b'); break;
case 'f': literal.Append('\f'); break;
case 'n': literal.Append('\n'); break;
case 'r': literal.Append('\r'); break;
case 't': literal.Append('\t'); break;
case 'v': literal.Append('\v'); break;
case 'x':
state = LiteralParserState.EscapedHex;
counter = 0;
break;
case 'u':
state = LiteralParserState.EscapedUnicode;
counter = 0;
break;
}
break;
case LiteralParserState.EscapedHex:
escapedChar |= (char)(NibbleToHex(c) << ((2 - 1 - counter) * 4));
counter++;
if (counter == 2)
{
literal.Append(escapedChar);
counter = 0;
escapedChar = (char)0;
state = LiteralParserState.Default;
}
break;
case LiteralParserState.EscapedUnicode:
escapedChar |= (char)(NibbleToHex(c) << ((4 - 1 - counter) * 4));
counter++;
if (counter == 4)
{
literal.Append(escapedChar);
counter = 0;
escapedChar = (char)0;
state = LiteralParserState.Default;
}
break;
}
}
return literal.ToString();
}
}
public static class Assembler
{
private static IEnumerable<ParsedBody> OfType(this IEnumerable<ParsedBody> self, ElementParentType type)
{
return self.Where(x => x.Element.ParentType == type);
}
private static IEnumerable<ParserElement> OfType(this IEnumerable<ParserElement> self, ElementParentType type)
{
return self.Where(x => x.ParentType == type);
}
private static ParsedBody ExpectZeroOrOne(List<ParsedBody> parsed, ElementParentType type)
{
var elems = parsed.OfType(type);
var second = elems.Skip(1).FirstOrDefault();
if (second != null)
{
second.Element.ThrowInvalid();
}
return elems.FirstOrDefault();
}
private static bool IsExported(this ParserElement elem)
{
var value = elem.NextChild?.Value;
return value == Constants.ELEMENT_BODY_EXPORTED || value == Constants.ELEMENT_BODY_IMPORTED;
}
private static void EnsureNoNextChild(this ParserElement val)
{
if (val.NextChild != null) val.NextChild.ThrowInvalid();
}
public static CustomAttribute ParseAttribute(ParserElement attr, Dictionary<string, IType> types, Dictionary<string, IFunction> functions)
{
// name (...)
CustomAttribute ret = new CustomAttribute(attr.Value);
for (var next = attr.Next; next != null; next = next.Next)
{
if (!types.TryGetValue(next.Value, out var immType)) next.ThrowInvalid(string.Format("In attribute {0}: Invalid type", ret.Name));
var data = TryParseData(immType, next.NextChild.Value, functions);
if (data == null) next.NextChild.ThrowInvalid(string.Format("In attribute {0}: Invalid data", ret.Name));
ret.Arguments.Add(data);
}
return ret;
}
private static IType ParseType(ParserElement type, Dictionary<string, IType> types)
{
// .type [(export)] type(...) name
IType ret = null;
var baseType = type.Next;
ParserElement elemName = baseType.Next;
var child = baseType.NextChild;
ParserElement next = child?.Next;
switch (baseType.Value)
{
// primitive(enum_value)
case Constants.TYPE_PRIMITIVE:
child.EnsureNoNextChild();
if (!Enum.TryParse<PascalTypeCode>(child.Value, out var typeCode)) child.ThrowInvalid("Invalid primitive type");
if (!typeCode.IsPrimitive()) child.ThrowInvalid();
if (next != null) next.ThrowInvalid();
ret = new PrimitiveType(typeCode);
break;
// array(type) or array(type,length,start)
case Constants.TYPE_ARRAY:
if (next != null) next.ThrowInvalid();
next = child.NextChild;
if (!types.TryGetValue(child.Value, out var childType)) child.ThrowInvalid("Invalid array type");
if (next == null)
{
ret = new ArrayType(childType);
} else
{
if (next.Next != null) next.Next.ThrowInvalid();
if (!int.TryParse(next.Value, out var arrLen)) next.ThrowInvalid("Invalid static array length");
int idxStart = 0;
next = next.NextChild;
if (next != null)
{
if (next.Next != null) next.Next.ThrowInvalid();
if (!int.TryParse(next.Value, out idxStart)) next.ThrowInvalid("Invalid static array start index");
}
ret = new StaticArrayType(childType, arrLen, idxStart);
}
break;
// class(internalName)
case Constants.TYPE_CLASS:
child.EnsureNoNextChild();
if (next != null) next.ThrowInvalid();
child.ExpectValidName();
ret = new ClassType(child.Value);
break;
// interface(guidString)
case Constants.TYPE_COM_INTERFACE:
child.EnsureNoNextChild();
if (next != null) next.ThrowInvalid();
child.ExpectString();
if (!Guid.TryParse(child.Value.FromLiteral(), out var guid)) child.ThrowInvalid("Invalid COM interface GUID");
ret = new IFPSLib.Types.ComInterfaceType(guid);
break;
// funcptr(declaration)
case Constants.TYPE_FUNCTION_POINTER:
child = baseType.Next;
if (child.NextChild == null) child.ThrowInvalid();
if (next != null) next.ThrowInvalid();
// returnsval|void
bool returnsVal = child.Value == Constants.FUNCTION_RETURN_VAL;
if (!returnsVal && child.Value != Constants.FUNCTION_RETURN_VOID) child.ThrowInvalid("Invalid function pointer return type");
child = child.NextChild;
var argList = new List<FunctionArgumentType>();
if (child.Value != "")
{
for (next = child; next != null; next = next.NextChild)
{
if (next.Next != null) next.Next.ThrowInvalid();
var isInVal = next.Value == Constants.FUNCTION_ARG_IN || next.Value == Constants.FUNCTION_ARG_VAL;
if (!isInVal && next.Value != Constants.FUNCTION_ARG_OUT && next.Value != Constants.FUNCTION_ARG_REF)
child.ThrowInvalid("Invalid function pointer argument type");
argList.Add(isInVal ? FunctionArgumentType.In : FunctionArgumentType.Out);
}
}
ret = new FunctionPointerType(returnsVal, argList);
baseType = baseType.Next;
break;
// record(types...)
case Constants.TYPE_RECORD:
if (next != null) next.ThrowInvalid();
var typeList = new List<IType>();
for (next = child; next != null; next = next.NextChild)
{
if (!types.TryGetValue(next.Value, out childType)) next.ThrowInvalid("Invalid record element type");
typeList.Add(childType);
}
ret = new RecordType(typeList);
break;
// set(bitsize)
case Constants.TYPE_SET:
child.EnsureNoNextChild();
if (!int.TryParse(child.Value, out var setLen) || setLen > 0x100) child.ThrowInvalid("Invalid set bit size");
if (next != null) next.ThrowInvalid();
ret = new SetType(setLen);
break;
default:
baseType.ThrowInvalid();
break;
}
ret.Exported = type.IsExported();
ret.Name = baseType.Next.Value;
if (types.ContainsKey(ret.Name)) baseType.Next.ThrowInvalid("Type already defined");
types.Add(ret.Name, ret);
return ret;
}
private static GlobalVariable ParseGlobal(ParserElement global, Dictionary<string, IType> types, Dictionary<string, GlobalVariable> globals, int index)
{
// .global (export) type name
var next = global.Next;
if (!types.TryGetValue(next.Value, out var type)) next.ThrowInvalid("Global has unknown type");
next = next.Next;
var ret = GlobalVariable.Create(index, type, next.Value);
if (globals.ContainsKey(ret.Name)) next.ThrowInvalid("Global already defined");
ret.Exported = global.IsExported();
globals.Add(ret.Name, ret);
return ret;
}
private static NativeCallingConvention ParseCC(this ParserElement elem)
{
switch (elem.Value)
{
case Constants.FUNCTION_FASTCALL:
return NativeCallingConvention.Register;
case Constants.FUNCTION_PASCAL:
return NativeCallingConvention.Pascal;
case Constants.FUNCTION_CDECL:
return NativeCallingConvention.CDecl;
case Constants.FUNCTION_STDCALL:
return NativeCallingConvention.Stdcall;
default:
elem.ThrowInvalid(); // shouldn't get here, parser should have detected invalid calling convention already
return NativeCallingConvention.Stdcall;
}
}
private static IFunction ParseFunction(ParserElement function, Dictionary<string, IType> types, Dictionary<string, IFunction> functions)
{
IFunction ret = null;
var next = function.Next;
ParserElement child = null;
bool isExternal = next.Value == Constants.FUNCTION_EXTERNAL;
if (isExternal)
{
next = next.Next;
var ext = new ExternalFunction();
ret = ext;
switch (next.Value)
{
case Constants.FUNCTION_EXTERNAL_INTERNAL:
ext.Declaration = new Internal();
break;
case Constants.FUNCTION_EXTERNAL_DLL:
// dll(dllname,procname[,delayload][,alteredsearchpath])
child = next.NextChild;
var dll = new DLL();
dll.DllName = child.Value.FromLiteral();
child = child.NextChild;
dll.ProcedureName = child.Value.FromLiteral();
child = child.NextChild;
if (child != null)
{
if (child.Value == Constants.FUNCTION_EXTERNAL_DLL_DELAYLOAD) dll.DelayLoad = true;
else dll.LoadWithAlteredSearchPath = true;
child = child.NextChild;
if (child != null)
{
if (child.Value == Constants.FUNCTION_EXTERNAL_DLL_DELAYLOAD) dll.DelayLoad = true;
else dll.LoadWithAlteredSearchPath = true;
}
}
// calling convention
next = next.Next;
dll.CallingConvention = next.ParseCC();
ext.Declaration = dll;
break;
case Constants.FUNCTION_EXTERNAL_CLASS:
// class(classname, procname, property)
child = next.NextChild;
var cls = new Class();
cls.ClassName = child.Value;
child = child.NextChild;
cls.FunctionName = child.Value;
cls.IsProperty = child.NextChild != null;
// calling convention
next = next.Next;
cls.CallingConvention = next.ParseCC();
ext.Declaration = cls;
break;
case Constants.FUNCTION_EXTERNAL_COM:
// com(vtbl_index)
child = next.NextChild;
var com = new COM();
com.VTableIndex = uint.Parse(child.Value);
// calling convention
next = next.Next;
com.CallingConvention = next.ParseCC();
ext.Declaration = com;
break;
}
next = next.Next;
} else
{
ret = new ScriptFunction();
}
// void|returnsval/type
// external cannot be type
if (next.Value != Constants.FUNCTION_RETURN_VOID)
{
if (isExternal) ret.ReturnArgument = UnknownType.Instance;
else
{
if (!types.TryGetValue(next.Value, out var retArg)) next.ThrowInvalid(string.Format("In function \"{0}\": Unknown type", ret.Name));
ret.ReturnArgument = retArg;
}
}
next = next.Next;
// name
ret.Name = next.Value;
if (functions.ContainsKey(ret.Name)) next.ThrowInvalid("Function already defined");
// arguments
ret.Arguments = new List<FunctionArgument>();
if (next.NextChild.Value != "")
{
for (child = next.NextChild; child != null; child = child.NextChild)
{
// __in|__val|__out|__ref type|__unknown name
var arg = new FunctionArgument();
bool isInVal = child.Value == Constants.FUNCTION_ARG_IN || child.Value == Constants.FUNCTION_ARG_VAL;
if (!isInVal && child.Value != Constants.FUNCTION_ARG_OUT && child.Value == Constants.FUNCTION_ARG_REF)
child.ThrowInvalid(string.Format("In function \"{0}\": Unknown argument type", ret.Name));
arg.ArgumentType = isInVal ? FunctionArgumentType.In : FunctionArgumentType.Out;
next = child.Next;
if (next == null) child.ThrowInvalid();
// An external function does not have types here, a script function does.
if (isExternal) arg.Type = UnknownType.Instance;
else
{
if (!types.TryGetValue(next.Value, out var argType)) next.ThrowInvalid(string.Format("In function \"{0}\": Unknown type", ret.Name));
arg.Type = argType;
}
if (next.Next != null)
{
next = next.Next;
next.ExpectValidName();
arg.Name = next.Value;
}
ret.Arguments.Add(arg);
}
}
ret.Exported = function.IsExported();
functions.Add(ret.Name, ret);
return ret;
}
private static bool TryParse<T>(string str, out T val)
{
try
{
val = str.Parse<T>();
return true;
} catch
{
val = default;
return false;
}
}
private static T Parse<T>(this string val)
{
return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(val);
}
private static TypedData TryParseData(IType itype, string val, Dictionary<string, IFunction> functions)
{
var type = itype as PrimitiveType;
switch (itype.BaseType)
{
case PascalTypeCode.S8:
if (!TryParse<sbyte>(val, out var _sbyte)) return null;
return TypedData.Create(type, _sbyte);
case PascalTypeCode.U8:
if (!TryParse<byte>(val, out var _byte)) return null;
return TypedData.Create(type, _byte);
case PascalTypeCode.Char:
val = val.FromLiteral();
if (val.Length != 1) return null;
if (val[0] > 0xff) return null;
return TypedData.Create(type, val[0]);
case PascalTypeCode.S16:
if (!TryParse<short>(val, out var _short)) return null;
return TypedData.Create(type, _short);
case PascalTypeCode.U16:
if (!TryParse<ushort>(val, out var _ushort)) return null;
return TypedData.Create(type, _ushort);
case PascalTypeCode.WideChar:
val = val.FromLiteral();
if (val.Length != 1) return null;
return TypedData.Create(type, val[0]);
case PascalTypeCode.S32:
if (!TryParse<int>(val, out var _int)) return null;
return TypedData.Create(type, _int);
case PascalTypeCode.U32:
if (!TryParse<uint>(val, out var _uint)) return null;
return TypedData.Create(type, _uint);
case PascalTypeCode.S64:
if (!TryParse<long>(val, out var _long)) return null;
return TypedData.Create(type, _long);
case PascalTypeCode.Single:
if (!float.TryParse(val, out var _float)) return null;
return TypedData.Create(type, _float);
case PascalTypeCode.Double:
if (!double.TryParse(val, out var _double)) return null;
return TypedData.Create(type, _double);
case PascalTypeCode.Extended:
if (!decimal.TryParse(val, out var _decimal)) return null;
return TypedData.Create(type, _decimal);
case PascalTypeCode.Currency:
if (!decimal.TryParse(val, out var _currency)) return null;
return TypedData.Create(type, new CurrencyWrapper(_currency));
case PascalTypeCode.PChar:
case PascalTypeCode.String:
val = val.FromLiteral();
if (val.Any(x => x > 0xff)) return null;
return TypedData.Create(type, val);
case PascalTypeCode.WideString:
case PascalTypeCode.UnicodeString:
return TypedData.Create(type, val.FromLiteral());
case PascalTypeCode.ProcPtr:
if (!functions.TryGetValue(val, out var func)) return null;
return TypedData.Create(itype as FunctionPointerType, func);
case PascalTypeCode.Set:
if (!val.StartsWith(Constants.INTEGER_BINARY)) return null;
var ba = new BitArray(val.Length - 2);
for (int i = ba.Length - 1; i >= 0; i--)
{
var chr = val[i + 2];
bool bit = chr == Constants.BINARY_TRUE;
if (!bit && chr != Constants.BINARY_FALSE) return null;
ba[i] = bit;
}
return TypedData.Create(type, ba);
default:
return null;
}
}
private static IVariable ParseVariable(
string value,
ScriptFunction function,
Dictionary<string, GlobalVariable> globals,
Dictionary<string, string> aliases
)
{
if (aliases.TryGetValue(value, out var realVar)) value = realVar;
if (globals.TryGetValue(value, out var global)) return global;
for (int i = 0; i < function.Arguments.Count; i++)
{
if (function.Arguments[i].Name != value) continue;
return function.CreateArgumentVariable(i);
}
value = value.ToLower();
if (value == Constants.VARIABLE_RET) return function.CreateReturnVariable();
if (!value.StartsWith(Constants.VARIABLE_LOCAL_PREFIX)) return null;
if (!int.TryParse(value.Substring(Constants.VARIABLE_LOCAL_PREFIX.Length), out var idx)) return null;
if (idx < 0) return null;
return LocalVariable.Create(idx - 1);
}
private static Operand ParseOperandValue(
ParserElement value,
ScriptFunction function,
Dictionary<string, IType> types,
Dictionary<string, GlobalVariable> globals,
Dictionary<string, IFunction> functions,
Dictionary<string, string> aliases,
Dictionary<string, ParserElement> defines
)
{
// first: check define
if (value.NextChild == null && defines.TryGetValue(value.Value, out var defined))
value = defined;
// immediate: type(value)
// variable: name
// immediate index: name[int_elem]
// variable index: name[elem_var]
if (value.NextChild != null)
{
// immediate
if (!types.TryGetValue(value.Value, out var immType)) value.ThrowInvalid(string.Format("In function {0}: Invalid type", function.Name));
var data = TryParseData(immType, value.NextChild.Value, functions);
if (data == null) value.NextChild.ThrowInvalid(string.Format("In function {0}: Invalid data", function.Name));
return new Operand(data);
}
var val = value.Value;
var indexOfStart = val.AsSpan().IndexOf(Constants.START_ARRAY_INDEX);
if (val[val.Length - 1] != Constants.END_ARRAY_INDEX || indexOfStart == -1)
{
// variable
var variable = ParseVariable(val, function, globals, aliases);
if (variable == null) value.ThrowInvalid(string.Format("In function {0}: Used nonexistant variable", function.Name));
return new Operand(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));
if (uint.TryParse(element, out var elementIdx)) return new Operand(baseVar, elementIdx);
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);
}
private static Instruction ParseInstructionFirstPass(
ParserElement insn,
ScriptFunction function,
Dictionary<string, IType> types,
Dictionary<string, GlobalVariable> globals,
Dictionary<string, IFunction> functions,
Dictionary<string, string> aliases,
Dictionary<ParserElement, int> placeholderTable,
Dictionary<string, ParserElement> defines
)
{
ParserElement next = null;
if (!OpCodes.ByName.TryGetValue(insn.Value, out var opcode)) insn.ThrowInvalid(string.Format("In function \"{0}\": Unknown opcode", function.Name));
switch (opcode.OperandType)
{
case OperandType.InlineNone:
return Instruction.Create(opcode);
case OperandType.InlineBrTarget:
case OperandType.InlineBrTargetValue:
case OperandType.InlineEH:
placeholderTable.Add(insn, function.Instructions.Count);
return Instruction.Create(OpCodes.Nop);
case OperandType.InlineValue:
case OperandType.InlineValueSF:
if (insn.Next == null) insn.ThrowInvalid();
return Instruction.Create(opcode, ParseOperandValue(insn.Next, function, types, globals, functions, aliases, defines));
case OperandType.InlineValueValue:
{
next = insn.Next;
if (next == null) insn.ThrowInvalid();
var op0 = ParseOperandValue(next, function, types, globals, functions, aliases, defines);
if (next.Next == null) next.ThrowInvalid();
next = next.Next;
var op1 = ParseOperandValue(next, function, types, globals, functions, aliases, defines);
if (next.Next != null) next.Next.ThrowInvalid();
return Instruction.Create(opcode, op0, op1);
}
case OperandType.InlineFunction:
next = insn.Next;
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 (next.Next != null) next.Next.ThrowInvalid();
return Instruction.Create(opcode, funcOp);
case OperandType.InlineType:
{
next = insn.Next;
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 (next.Next != null) next.Next.ThrowInvalid();
return Instruction.Create(opcode, typeOp);
}
case OperandType.InlineCmpValue:
{
next = insn.Next;
if (next == null) insn.ThrowInvalid();
var op0 = ParseOperandValue(next, function, types, globals, functions, aliases, defines);
if (next.Next == null) next.ThrowInvalid();
next = next.Next;
var op1 = ParseOperandValue(next, function, types, globals, functions, aliases, defines);
if (next.Next == null) next.ThrowInvalid();
next = next.Next;
var op2 = ParseOperandValue(next, function, types, globals, functions, aliases, defines);
if (next.Next != null) next.Next.ThrowInvalid();
return Instruction.Create(opcode, op0, op1, op2);
}
case OperandType.InlineCmpValueType:
{
next = insn.Next;
if (next == null) insn.ThrowInvalid();
var op0 = ParseOperandValue(next, function, types, globals, functions, aliases, defines);
if (next.Next == null) next.ThrowInvalid();
next = next.Next;
var op1 = ParseOperandValue(next, function, types, globals, functions, aliases, defines);
if (next.Next == null) next.ThrowInvalid();
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 (next.Next != null) next.Next.ThrowInvalid();
return Instruction.Create(opcode, op0, op1, typeOp);
}
case OperandType.InlineTypeVariable:
{
if (opcode.Code != Code.SetStackType) insn.ThrowInvalid(string.Format("In function \"{0}\": Unknown opcode", function.Name));
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));
next = next.Next;
next.ExpectValidName();
var variable = ParseVariable(next.Value, function, globals, aliases);
if (variable == null) next.ThrowInvalid(string.Format("In function {0}: Used nonexistant variable", function.Name));
if (next.Next != null) next.Next.ThrowInvalid();
return Instruction.CreateSetStackType(typeOp, variable);
}
default:
insn.ThrowInvalid(string.Format("In function \"{0}\": Unknown opcode", function.Name));
return null;
}
}
private static void ParseLabelEH(ScriptFunction function, ParserElement next, Dictionary<string, int> labels, out int idx)
{
if (next.Value == Constants.LABEL_NULL) idx = -1;
else if (!labels.TryGetValue(next.Value, out idx)) next.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown label", function.Name));
}
private static void ParseInstructions(
ScriptFunction function,
IReadOnlyList<ParserElement> instructions,
Dictionary<string, IType> types,
Dictionary<string, GlobalVariable> globals,
Dictionary<string, IFunction> functions,
Dictionary<string, ParserElement> defines
)
{
// element=>index table for any instruction that references another
var placeholderTable = new Dictionary<ParserElement, int>();
var labels = new Dictionary<string, int>();
var aliases = new Dictionary<string, string>();
// first pass: set up all possible instructions, placeholders for those that reference labels.
// for a label, put it in the table and move on
// for an alias, put it in the table and move on
foreach (var insn in instructions)
{
if (insn.ParentType == ElementParentType.Attribute) continue;
if (insn.ParentType == ElementParentType.Label)
{
if (labels.ContainsKey(insn.Value)) insn.ThrowInvalid(string.Format("In function \"{0}\": Label already used", function.Name));
labels.Add(insn.Value, function.Instructions.Count);
continue;
}
if (insn.ParentType == ElementParentType.Alias)
{
if (aliases.ContainsKey(insn.Next.Value)) insn.Next.ThrowInvalid(string.Format("In function \"{0}\": Alias already used", function.Name));
aliases.Add(insn.Next.Value, insn.Next.Next.Value);
continue;
}
if (insn.ParentType != ElementParentType.Instruction) insn.ThrowInvalid();
function.Instructions.Add(ParseInstructionFirstPass(insn, function, types, globals, functions, aliases, placeholderTable, defines));
}
// second pass: fix up instructions that reference labels.
ParserElement next = null;
int labelIdx = default;
foreach (var placeholder in placeholderTable)
{
var insn = placeholder.Key;
var opcode = OpCodes.ByName[insn.Value]; // already checked earlier, no chance of KeyNotFoundException
switch (opcode.OperandType)
{
case OperandType.InlineBrTarget:
next = insn.Next;
if (next == null) insn.ThrowInvalid();
next.ExpectValidName();
next.EnsureNoNextChild();
if (!labels.TryGetValue(next.Value, out labelIdx)) insn.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown label", function.Name));
if (next.Next != null) next.Next.ThrowInvalid();
function.Instructions[placeholder.Value].Replace(Instruction.Create(opcode, function.Instructions[labelIdx]));
function.Instructions[labelIdx].Referenced = true;
break;
case OperandType.InlineBrTargetValue:
next = insn.Next;
if (next == null) insn.ThrowInvalid();
next.ExpectValidName();
next.EnsureNoNextChild();
if (!labels.TryGetValue(next.Value, out labelIdx)) insn.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown label", function.Name));
if (next.Next == null) next.ThrowInvalid();
next = next.Next;
var op1 = ParseOperandValue(next, function, types, globals, functions, aliases, defines);
if (next.Next != null) next.Next.ThrowInvalid();
function.Instructions[placeholder.Value].Replace(Instruction.Create(opcode, function.Instructions[labelIdx], op1));
function.Instructions[labelIdx].Referenced = true;
break;
case OperandType.InlineEH:
{
Span<int> labelIdxes = stackalloc int[4];
next = insn.Next;
if (next == null) insn.ThrowInvalid();
next.ExpectValidName();
next.EnsureNoNextChild();
ParseLabelEH(function, next, labels, out labelIdxes[0]);
var catchStart = next;
if (next.Next == null) next.ThrowInvalid();
next = next.Next;
next.ExpectValidName();
next.EnsureNoNextChild();
ParseLabelEH(function, next, labels, out labelIdxes[1]);
var finallyStart = next;
if (next.Next == null) next.ThrowInvalid();
next = next.Next;
next.ExpectValidName();
next.EnsureNoNextChild();
ParseLabelEH(function, next, labels, out labelIdxes[2]);
var finallyCatch = next;
if (next.Next == null) next.ThrowInvalid();
next = next.Next;
next.ExpectValidName();
next.EnsureNoNextChild();
if (!labels.TryGetValue(next.Value, out labelIdxes[3])) insn.ThrowInvalid(string.Format("In function \"{0}\": Referenced unknown label", function.Name));
if (next.Next != null) next.Next.ThrowInvalid();
if (labelIdxes[0] == -1 && labelIdxes[1] == -1)
{
catchStart.ThrowInvalid(string.Format("In function \"{0}\": Finally start and catch start can not both be", function.Name));
}
function.Instructions[placeholder.Value].Replace(Instruction.CreateStartEH(
labelIdxes[0] == -1 ? null : function.Instructions[labelIdxes[0]],
labelIdxes[1] == -1 ? null : function.Instructions[labelIdxes[1]],
labelIdxes[2] == -1 ? null : function.Instructions[labelIdxes[2]],
labelIdxes[3] == -1 ? null : function.Instructions[labelIdxes[3]]
));
for (int i = 0; i < labelIdxes.Length; i++)
{
if (labelIdxes[i] == -1) continue;
function.Instructions[labelIdxes[i]].Referenced = true;
}
break;
}
default:
insn.ThrowInvalid(string.Format("In function \"{0}\": Unknown opcode", function.Name));
break;
}
}
}
public static Script Assemble(List<ParsedBody> parsed)
{
int version = Script.VERSION_HIGHEST;
{
var elemVersion = ExpectZeroOrOne(parsed, ElementParentType.FileVersion)?.Element.Next?.Value;
if (elemVersion != null) version = int.Parse(elemVersion);
}
var ret = new Script(version);
string entryPoint = ExpectZeroOrOne(parsed, ElementParentType.EntryPoint)?.Element.Next?.Value;
// types
var typesTable = new Dictionary<string, IType>();
foreach (var typeElem in parsed.OfType(ElementParentType.Type))
{
var type = ParseType(typeElem.Element, typesTable);
foreach (var attr in typeElem.Children.OfType(ElementParentType.Attribute))
type.Attributes.Add(ParseAttribute(attr, typesTable, null));
ret.Types.Add(type);
}
// globals
var globalsTable = new Dictionary<string, GlobalVariable>();
{
var i = 0;
foreach (var globalElem in parsed.OfType(ElementParentType.GlobalVariable))
{
ret.GlobalVariables.Add(ParseGlobal(globalElem.Element, typesTable, globalsTable, i));
i++;
}
}
// defines
var definesTable = new Dictionary<string, ParserElement>();
foreach (var define in parsed.OfType(ElementParentType.Define))
{
var name = define.Element.Next.Next;
if (definesTable.ContainsKey(name.Value)) name.ThrowInvalid("Immediate value already defined");
definesTable.Add(define.Element.Next.Next.Value, define.Element.Next);
}
// functions
var functionsTable = new Dictionary<string, IFunction>();
var funcElemTable = new Dictionary<ParsedBody, ScriptFunction>();
foreach (var funcElem in parsed.OfType(ElementParentType.Function))
{
var func = ParseFunction(funcElem.Element, typesTable, functionsTable);
foreach (var attr in funcElem.Children.OfType(ElementParentType.Attribute))
func.Attributes.Add(ParseAttribute(attr, typesTable, functionsTable));
ret.Functions.Add(func);
var sf = func as ScriptFunction;
if (sf == null) continue;
funcElemTable.Add(funcElem, sf);
}
foreach (var funcElem in parsed.OfType(ElementParentType.Function).Where(x => x.Children.Any()))
{
ParseInstructions(funcElemTable[funcElem], funcElem.Children, typesTable, globalsTable, functionsTable, definesTable);
if (funcElemTable[funcElem].Instructions.Count == 0)
{
funcElem.Element.Tokens.Last().ThrowInvalid(string.Format("In function {0}: no instructions specified", funcElemTable[funcElem].Name));
}
}
if (entryPoint != null) ret.EntryPoint = functionsTable[entryPoint];
return ret;
}
public static Script Assemble(string str) => Assemble(Parser.Parse(str));
}
}

76
IFPSAsmLib/Constants.cs Normal file
View File

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSAsmLib
{
internal static class Constants
{
internal const char START_BODY = '(';
internal const char NEXT_BODY = ',';
internal const char END_BODY = ')';
internal const char START_ARRAY_INDEX = '[';
internal const char END_ARRAY_INDEX = ']';
internal const char START_ATTRIBUTE = '[';
internal const char END_ATTRIBUTE = ']';
internal const char STRING_CHAR = '"';
internal const char COMMENT_START = ';';
internal const char ELEMENT_START = '.';
internal const char LABEL_SPECIFIER = ':';
internal const char BINARY_FALSE = '0';
internal const char BINARY_TRUE = '1';
internal const string ELEMENT_VERSION = ".version";
internal const string ELEMENT_ENTRYPOINT = ".entry";
internal const string ELEMENT_TYPE = ".type";
internal const string ELEMENT_ALIAS = ".alias";
internal const string ELEMENT_DEFINE = ".define";
internal const string ELEMENT_GLOBALVARIABLE = ".global";
internal const string ELEMENT_FUNCTION = ".function";
internal const string ELEMENT_BODY_EXPORTED = "export";
internal const string ELEMENT_BODY_IMPORTED = "import";
internal const string FUNCTION_EXTERNAL = "external";
internal const string FUNCTION_EXTERNAL_INTERNAL = "internal";
internal const string FUNCTION_EXTERNAL_DLL = "dll";
internal const string FUNCTION_EXTERNAL_COM = "com";
internal const string FUNCTION_EXTERNAL_CLASS = "class";
internal const string FUNCTION_EXTERNAL_CLASS_PROPERTY = "property";
internal const string FUNCTION_EXTERNAL_DLL_DELAYLOAD = "delayload";
internal const string FUNCTION_EXTERNAL_DLL_ALTEREDSEARCHPATH = "alteredsearchpath";
internal const string FUNCTION_FASTCALL = "__fastcall";
internal const string FUNCTION_PASCAL = "__pascal";
internal const string FUNCTION_CDECL = "__cdecl";
internal const string FUNCTION_STDCALL = "__stdcall";
internal const string FUNCTION_RETURN_VOID = "void";
internal const string FUNCTION_RETURN_VAL = "returnsval";
internal const string FUNCTION_ARG_IN = "__in";
internal const string FUNCTION_ARG_OUT = "__out";
internal const string FUNCTION_ARG_VAL = "__val"; // same as in
internal const string FUNCTION_ARG_REF = "__ref"; // same as out
internal const string TYPE_PRIMITIVE = "primitive";
internal const string TYPE_ARRAY = "array";
internal const string TYPE_CLASS = "class";
internal const string TYPE_COM_INTERFACE = "interface";
internal const string TYPE_FUNCTION_POINTER = "funcptr";
internal const string TYPE_RECORD = "record";
internal const string TYPE_SET = "set";
internal const string INTEGER_BINARY = "0b";
internal const string VARIABLE_RET = "retval";
internal const string VARIABLE_LOCAL_PREFIX = "var";
internal const string LABEL_NULL = "null";
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\IFPSLib\IFPSLib.csproj" />
</ItemGroup>
</Project>

995
IFPSAsmLib/Parser.cs Normal file
View File

@ -0,0 +1,995 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;
namespace IFPSAsmLib
{
internal enum ParserSeparatorType : byte
{
StartBody,
NextBody,
EndBody,
NextOrEndBody,
NextOrStartBody,
Unknown
}
internal static class ParserExtensions
{
private static bool Matches(this char input, ParserSeparatorType type)
{
switch (type)
{
case ParserSeparatorType.StartBody:
return input == Constants.START_BODY;
case ParserSeparatorType.NextBody:
return input == Constants.NEXT_BODY;
case ParserSeparatorType.EndBody:
return input == Constants.END_BODY;
case ParserSeparatorType.NextOrEndBody:
return input == Constants.NEXT_BODY || input == Constants.END_BODY;
case ParserSeparatorType.NextOrStartBody:
return input == Constants.NEXT_BODY || input == Constants.START_BODY;
default:
return false;
}
}
private static ParserSeparatorType GetSeparator(this char input)
{
if (input == Constants.START_BODY) return ParserSeparatorType.StartBody;
if (input == Constants.NEXT_BODY) return ParserSeparatorType.NextBody;
if (input == Constants.END_BODY) return ParserSeparatorType.EndBody;
return ParserSeparatorType.Unknown;
}
private static void ThrowEof(ParserLocation location)
{
throw new InvalidDataException(string.Format("Unexpected end of file [:{0},{1}]", location.Line, location.Column));
}
internal static string ToLiteral(this string input)
{
StringBuilder literal = new StringBuilder(input.Length + 2);
literal.Append(Constants.STRING_CHAR);
foreach (var c in input)
{
switch (c)
{
case '\"': literal.Append("\\\""); break;
case '\\': literal.Append(@"\\"); break;
case '\0': literal.Append(@"\0"); break;
case '\a': literal.Append(@"\a"); break;
case '\b': literal.Append(@"\b"); break;
case '\f': literal.Append(@"\f"); break;
case '\n': literal.Append(@"\n"); break;
case '\r': literal.Append(@"\r"); break;
case '\t': literal.Append(@"\t"); break;
case '\v': literal.Append(@"\v"); break;
default:
// ASCII printable character
if ((c >= 0x20 && c <= 0x7e) || !char.IsControl(c))
{
literal.Append(c);
// As UTF16 escaped character
}
else if (c < 0x100)
{
literal.Append(@"\x");
literal.Append(((int)c).ToString("x2"));
}
else
{
literal.Append(@"\u");
literal.Append(((int)c).ToString("x4"));
}
break;
}
}
literal.Append(Constants.STRING_CHAR);
return literal.ToString();
}
internal static bool AdvanceToNextLine(this ReadOnlySpan<char> str, ref ParserLocation location)
{
while (str[location.Offset] != '\n')
{
location.AdvanceOffset();
if (location.Offset >= str.Length) return false;
}
location.AdvanceOffset();
location = location.AdvanceLine();
return true;
}
internal static bool AdvanceWhiteSpace(this ReadOnlySpan<char> str, ref ParserLocation location, bool disallowNewLine)
{
if (location.Offset >= str.Length) return false;
while (char.IsWhiteSpace(str[location.Offset]) || str[location.Offset] == Constants.COMMENT_START)
{
var offset = location.Offset;
if (disallowNewLine && (str[offset] == '\r' || str[offset] == '\n' || str[offset] == Constants.COMMENT_START)) {
throw new InvalidDataException(string.Format("Unexpected new line [:{0},{1}]", location.Line, location.Column));
}
if (!disallowNewLine && str[offset] == Constants.COMMENT_START) break;
location.AdvanceOffset();
if (str[offset] == '\n')
{
location = location.AdvanceLine();
}
if (location.Offset >= str.Length)
{
if (!disallowNewLine) return false;
ThrowEof(location);
}
}
return true;
}
internal static bool AdvanceWhiteSpaceUntilNewLine(this ReadOnlySpan<char> str, ref ParserLocation location)
{
while (char.IsWhiteSpace(str[location.Offset]) || str[location.Offset] == Constants.COMMENT_START)
{
var offset = location.Offset;
if (str[offset] == Constants.COMMENT_START) return true;
location.AdvanceOffset();
if (str[offset] == '\r' || str[offset] == '\n')
{
if (str[offset] == '\n')
{
location = location.AdvanceLine();
}
return true;
}
if (location.Offset >= str.Length)
ThrowEof(location);
}
return false;
}
private static bool IsValidNibble(this char input)
{
return
(input >= '0' && input <= '9') ||
(input >= 'a' && input <= 'f') ||
(input >= 'A' && input <= 'F')
;
}
internal static ParserEntity GetEntity(this ReadOnlySpan<char> str, ref ParserLocation location, ParserSeparatorType? separatorType = null, bool optional = true)
{
var origLoc = location.Clone();
var start = location.Offset;
var length = 0;
bool inQuotes = false;
byte escapingCount = 0;
byte escapingState = 0;
for (var chr = str[location.Offset]; inQuotes || !char.IsWhiteSpace(chr); location.AdvanceOffset(), length++)
{
if (location.Offset >= str.Length) ThrowEof(location);
chr = str[location.Offset];
// allow for escaped data in quotes
if (inQuotes)
{
if (escapingState == 1)
{
switch (chr)
{
case '\"':
case '\\':
case '0':
case 'a':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
escapingCount = escapingState = 0;
continue;
case 'x': // 2 hexits
escapingCount += 2;
break;
case 'u': // 4 hexits
escapingCount += 4;
break;
default:
throw new InvalidDataException(string.Format("Invalid escape character '{0}' [:{1},{2}]", chr, location.Line, location.Column));
}
escapingState++;
}
else if (escapingState == (escapingCount - 1))
{
escapingCount = escapingState = 0;
}
else if (escapingState == 0)
{
if (chr == '\\')
{
escapingState = escapingCount = 1;
}
else if (chr == '\"')
{
inQuotes = false;
}
}
else
{
if (!chr.IsValidNibble())
throw new InvalidDataException(string.Format("Invalid hex digit '{0}' [:{1},{2}]", chr, location.Line, location.Column));
escapingState++;
}
continue;
} else if (chr == '\"')
{
inQuotes = true;
continue;
}
if (!separatorType.HasValue) continue;
if (chr.Matches(separatorType.Value)) break;
}
if (char.IsWhiteSpace(str[start + length - 1])) length--;
// Allow for whitespace before the separator, do not allow cr or lf.
var found = ParserSeparatorType.Unknown;
if (separatorType.HasValue)
{
for (var chr = str[location.Offset]; char.IsWhiteSpace(chr) && chr != '\r' && chr != '\n'; location.AdvanceOffset())
{
// no operation
}
// Separator must be at this point, if not optional.
found = str[location.Offset].GetSeparator();
var matches = str[location.Offset].Matches(separatorType.Value);
if (!optional && !matches)
{
throw new InvalidDataException(string.Format("After {0}: expected '{1}', got '{2}' {3}", str.Slice(start, length).ToString(), separatorType.Value, found, location));
}
if (matches) location.AdvanceOffset();
}
return new ParserEntity(str.Slice(start, length), origLoc, found);
}
internal static void EnsureBodyEmpty(this ReadOnlySpan<char> str, ref ParserLocation location)
{
if (location.Offset >= str.Length) ThrowEof(location);
if (str[location.Offset] != Constants.END_BODY) throw new InvalidDataException(string.Format("Expected empty declaration body {0}", location));
location.AdvanceOffset();
}
}
internal class ParserLocation
{
internal int Offset;
internal int Line;
internal int Column;
internal ParserLocation Clone()
{
return new ParserLocation()
{
Offset = Offset,
Line = Line,
Column = Column
};
}
internal ParserLocation AdvanceLine()
{
return new ParserLocation()
{
Offset = Offset,
Line = Line + 1,
Column = 0
};
}
internal void AdvanceOffset()
{
Offset++;
Column++;
}
public override string ToString()
{
return string.Format("[:{0},{1}]", Line, Column);
}
internal string ToStringWithOffset(int offset)
{
return string.Format("[:{0},{1}]", Line, Column + offset);
}
}
internal readonly ref struct ParserEntity
{
internal readonly ReadOnlySpan<char> Value;
internal readonly ParserLocation Location;
internal readonly ParserSeparatorType? SeparatorType;
internal bool FoundSeparator => SeparatorType.HasValue;
internal ParserEntity(ReadOnlySpan<char> val, ParserLocation loc, ParserSeparatorType type)
{
Value = val;
Location = loc;
SeparatorType = null;
if (type != ParserSeparatorType.Unknown) SeparatorType = type;
}
public override string ToString()
{
return Value.ToString();
}
internal string ToStringForError()
{
return string.Format("{0} {1}", Value.ToString().ToLiteral(), Location);
}
internal string ToStringForError(int offset)
{
return string.Format("{0} {1}", Value.ToString().ToLiteral(), Location.ToStringWithOffset(offset));
}
internal void ThrowInvalid()
{
throw new InvalidDataException(string.Format("Invalid token {0}", ToStringForError()));
}
internal bool Equals(string str)
{
if (Value.Length != str.Length) return false;
for (int i = 0; i < Value.Length; i++)
{
if (Value[i] != str[i]) return false;
}
return true;
}
internal void ExpectToken(string token)
{
if (!Equals(token)) ThrowInvalid();
}
internal void ExpectValidName()
{
var indexOf = Value.IndexOfAny(Constants.STRING_CHAR, Constants.START_BODY, Constants.END_BODY);
if (indexOf == -1) return;
ThrowInvalid();
}
internal void ExpectString()
{
if (Value.Length < 2) ThrowInvalid();
if (Value[0] != Constants.STRING_CHAR) ThrowInvalid();
if (Value[Value.Length - 1] != Constants.STRING_CHAR) ThrowInvalid();
}
internal void ExpectSeparatorType(ParserSeparatorType type)
{
var actualType = SeparatorType.HasValue ? SeparatorType.Value : ParserSeparatorType.Unknown;
if (type == ParserSeparatorType.NextOrEndBody)
{
if (actualType == ParserSeparatorType.NextBody || actualType == ParserSeparatorType.EndBody) type = actualType;
}
if (type == ParserSeparatorType.NextOrStartBody)
{
if (actualType == ParserSeparatorType.NextBody || actualType == ParserSeparatorType.StartBody) type = actualType;
}
if (type != actualType)
throw new InvalidDataException(string.Format("After {0}: expected '{1}', got '{2}' {3}", Value.ToString(), type, actualType, Location));
}
}
public enum ElementParentType : byte
{
Unknown,
FileVersion,
EntryPoint,
Attribute,
Type,
GlobalVariable,
Function,
Alias,
Define,
Instruction,
Label,
}
public class ParserElement
{
/// <summary>
/// Value of this element
/// </summary>
public readonly string Value;
/// <summary>
/// The next element on this line; null if not present.
/// </summary>
public ParserElement Next { get; internal set; } = null;
/// <summary>
/// The next child element (elements inside brackets); null if not present.
/// </summary>
public ParserElement NextChild { get; internal set; } = null;
/// <summary>
/// Line number of this element
/// </summary>
public readonly int Line;
/// <summary>
/// Column index of this element
/// </summary>
public readonly int Column;
/// <summary>
/// Parent element type
/// </summary>
public ElementParentType ParentType;
internal ParserElement(ParserEntity entity, ElementParentType type)
{
Value = entity.ToString();
Line = entity.Location.Line;
Column = entity.Location.Column;
ParentType = type;
}
public IEnumerable<ParserElement> Children
{
get
{
for (var next = NextChild; next != null; next = next.NextChild) yield return next;
}
}
public IEnumerable<ParserElement> Tokens
{
get
{
yield return this;
for (var next = Next; next != null; next = next.Next) yield return next;
}
}
internal string ToStringForError()
{
return string.Format("{0} [:{1},{2}]", Value.ToLiteral(), Line, Column);
}
internal void ThrowInvalid()
{
ThrowInvalid("Invalid token");
}
internal void ThrowInvalid(string error)
{
throw new InvalidDataException(string.Format("{0} {1}", error, ToStringForError()));
}
internal void ExpectValidName()
{
var indexOf = Value.AsSpan().IndexOfAny(Constants.STRING_CHAR, Constants.START_BODY, Constants.END_BODY);
if (indexOf == -1) return;
ThrowInvalid();
}
internal void ExpectString()
{
if (Value.Length < 2) ThrowInvalid();
if (Value[0] != Constants.STRING_CHAR) ThrowInvalid();
if (Value[Value.Length - 1] != Constants.STRING_CHAR) ThrowInvalid();
}
public override string ToString()
{
return Value;
}
}
public class ParsedBody
{
/// <summary>
/// The main element; type, function, global, or entry point.
/// </summary>
public readonly ParserElement Element;
/// <summary>
/// Children instruction elements of a function element.
/// </summary>
public readonly IReadOnlyList<ParserElement> Children;
private static IReadOnlyList<ParserElement> s_EmptyList = new List<ParserElement>(0).AsReadOnly();
internal ParsedBody(ParserElement elem) : this(elem, s_EmptyList)
{
}
internal ParsedBody(ParserElement elem, List<ParserElement> list) : this(elem, list == null ? s_EmptyList : list.AsReadOnly())
{
}
private ParsedBody(ParserElement elem, IReadOnlyList<ParserElement> list)
{
Element = elem;
Children = list;
}
}
public static class Parser
{
private static void ParseTokenWithUnknownBody(
ReadOnlySpan<char> str,
ref ParserLocation location,
ElementParentType parentType,
ParserElement current,
ref ParserElement next,
bool baseType
)
{
ParserElement currentChild = null, nextChild = null;
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")
{
ParseTokenWithUnknownBody(str, ref location, parentType, next, ref next, false);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
typeNext.ExpectToken(string.Empty);
return;
}
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, true);
currentChild = nextChild = next.NextChild = new ParserElement(typeNext, parentType);
while (!typeNext.FoundSeparator || typeNext.SeparatorType.Value != ParserSeparatorType.EndBody)
{
bool nextIsChild = typeNext.FoundSeparator;
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, true);
if (nextIsChild)
{
currentChild.NextChild = new ParserElement(typeNext, parentType);
currentChild = nextChild = currentChild.NextChild;
}
else
{
nextChild.Next = new ParserElement(typeNext, parentType);
nextChild = nextChild.Next;
}
}
}
internal static ParserElement Parse(this ReadOnlySpan<char> str, ref ParserLocation location)
{
ParserElement current = null, next = null, nextChild = null;
var parentType = default(ElementParentType);
while (true)
{
if (!str.AdvanceWhiteSpace(ref location, false)) return null;
var chr = str[location.Offset];
// comment
if (chr == Constants.COMMENT_START)
{
if (!str.AdvanceToNextLine(ref location)) return null;
continue;
}
// element
if (chr == Constants.ELEMENT_START)
{
var type = str.GetEntity(ref location, ParserSeparatorType.StartBody, true);
var typeString = type.ToString();
switch (typeString)
{
case Constants.ELEMENT_VERSION:
// .version int
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.FileVersion;
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
var typeNext = str.GetEntity(ref location);
if (!int.TryParse(typeNext.Value.ToString(), out var __int))
typeNext.ThrowInvalid();
current.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) current.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_ENTRYPOINT:
// .entry function_name
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.EntryPoint;
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
current.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) current.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_TYPE:
// .type [(export)] type(...) name
parentType = ElementParentType.Type;
current = new ParserElement(type, parentType);
if (type.FoundSeparator)
{
str.AdvanceWhiteSpace(ref location, true);
var typeChild = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
if (!typeChild.Value.IsEmpty)
{
if (!typeChild.Equals(Constants.ELEMENT_BODY_IMPORTED))
typeChild.ExpectToken(Constants.ELEMENT_BODY_EXPORTED);
current.NextChild = new ParserElement(typeChild, parentType);
}
}
// type(...), will be looked at later
ParseTokenWithUnknownBody(str, ref location, parentType, current, ref next, true);
// name
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_ALIAS:
// .alias varname additionalname
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.Alias;
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
current.Next = next = new ParserElement(typeNext, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_DEFINE:
// .define immediate_operand defname
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.Define;
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.StartBody, false);
typeNext.ExpectValidName();
current.Next = next = new ParserElement(typeNext, parentType);
// operand is an immediate value: type(data)
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
next.NextChild = new ParserElement(typeNext, parentType);
// name
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.ThrowInvalid();
return current;
case Constants.ELEMENT_GLOBALVARIABLE:
// .global [(export)] typename varname
parentType = ElementParentType.GlobalVariable;
current = next = new ParserElement(type, parentType);
if (type.FoundSeparator)
{
str.AdvanceWhiteSpace(ref location, true);
var typeChild = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
if (!typeChild.Value.IsEmpty)
{
if (!typeChild.Equals(Constants.ELEMENT_BODY_IMPORTED))
typeChild.ExpectToken(Constants.ELEMENT_BODY_EXPORTED);
current.NextChild = new ParserElement(typeChild, parentType);
}
}
// typename
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = new ParserElement(typeNext, parentType);
next = next.Next;
// varname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_FUNCTION:
// function
// .function [(export)] external callingconv internal|com(vtblindex)|class(...)|dll(...) returnsval|void name (...)
// .function [(export)] void|typename name(...)
parentType = ElementParentType.Function;
current = next = new ParserElement(type, parentType);
if (type.FoundSeparator)
{
str.AdvanceWhiteSpace(ref location, true);
var typeChild = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
if (!typeChild.Value.IsEmpty)
{
if (!typeChild.Equals(Constants.ELEMENT_BODY_IMPORTED))
typeChild.ExpectToken(Constants.ELEMENT_BODY_EXPORTED);
current.NextChild = new ParserElement(typeChild, parentType);
}
}
// external|void|typename
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
current.Next = new ParserElement(typeNext, parentType);
next = next.Next;
str.AdvanceWhiteSpace(ref location, true);
if (next.Value == Constants.FUNCTION_EXTERNAL)
{
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.StartBody, true);
typeNext.ExpectValidName();
if (typeNext.Equals(Constants.FUNCTION_EXTERNAL_INTERNAL))
{
typeNext.ExpectSeparatorType(ParserSeparatorType.Unknown);
next.Next = next = new ParserElement(typeNext, parentType);
}
else
{
if (typeNext.Equals(Constants.FUNCTION_EXTERNAL_COM))
{
// com(vtblindex)
typeNext.ExpectSeparatorType(ParserSeparatorType.StartBody);
next.Next = next = new ParserElement(typeNext, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
if (!int.TryParse(typeNext.Value.ToString(), out __int))
typeNext.ThrowInvalid();
next.NextChild = new ParserElement(typeNext, parentType);
}
else if (typeNext.Equals(Constants.FUNCTION_EXTERNAL_CLASS))
{
// class(classname,funcname[,property])
typeNext.ExpectSeparatorType(ParserSeparatorType.StartBody);
next.Next = next = new ParserElement(typeNext, parentType);
// classname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextBody, false);
typeNext.ExpectValidName();
next.NextChild = nextChild = new ParserElement(typeNext, parentType);
// funcname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
typeNext.ExpectValidName();
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
// property
if (typeNext.SeparatorType == ParserSeparatorType.NextBody)
{
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
typeNext.ExpectToken(Constants.FUNCTION_EXTERNAL_CLASS_PROPERTY);
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
}
}
else if (typeNext.Equals(Constants.FUNCTION_EXTERNAL_DLL))
{
// dll(dllname,procname[,delayload][,alteredsearchpath])
typeNext.ExpectSeparatorType(ParserSeparatorType.StartBody);
next.Next = next = new ParserElement(typeNext, parentType);
// dllname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextBody, false);
typeNext.ExpectString();
next.NextChild = nextChild = new ParserElement(typeNext, parentType);
// procname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
typeNext.ExpectString();
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
bool firstWasDelayLoad = false;
// delayload|alteredsearchpath
if (typeNext.SeparatorType == ParserSeparatorType.NextBody)
{
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
if (!typeNext.Equals(Constants.FUNCTION_EXTERNAL_DLL_DELAYLOAD))
{
if (!typeNext.Equals(Constants.FUNCTION_EXTERNAL_DLL_ALTEREDSEARCHPATH)) typeNext.ThrowInvalid();
}
else firstWasDelayLoad = true;
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
}
// other delayload|alteredsearchpath
if (typeNext.SeparatorType == ParserSeparatorType.NextBody)
{
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
typeNext.ExpectToken(
firstWasDelayLoad ?
Constants.FUNCTION_EXTERNAL_DLL_ALTEREDSEARCHPATH :
Constants.FUNCTION_EXTERNAL_DLL_DELAYLOAD
);
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
}
}
else
typeNext.ThrowInvalid();
str.AdvanceWhiteSpace(ref location, true);
// calling convention
typeNext = str.GetEntity(ref location);
if (
!typeNext.Equals(Constants.FUNCTION_FASTCALL) &&
!typeNext.Equals(Constants.FUNCTION_PASCAL) &&
!typeNext.Equals(Constants.FUNCTION_CDECL) &&
!typeNext.Equals(Constants.FUNCTION_STDCALL)
)
typeNext.ThrowInvalid();
next.Next = next = new ParserElement(typeNext, parentType);
}
str.AdvanceWhiteSpace(ref location, true);
// returnsval|void
typeNext = str.GetEntity(ref location);
if (!typeNext.Equals(Constants.FUNCTION_RETURN_VAL) && !typeNext.Equals(Constants.FUNCTION_RETURN_VOID)) typeNext.ThrowInvalid();
next.Next = next = new ParserElement(typeNext, parentType);
}
// name(...), will be looked at later
ParseTokenWithUnknownBody(str, ref location, parentType, next, ref next, false);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.ThrowInvalid();
return current;
default:
type.ThrowInvalid();
break;
}
}
// attribute
if (chr == Constants.START_ATTRIBUTE)
{
parentType = ElementParentType.Attribute;
location.AdvanceOffset();
str.AdvanceWhiteSpace(ref location, true);
// name(operands)
var type = str.GetEntity(ref location, ParserSeparatorType.StartBody, false);
type.ExpectValidName();
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
// operands
next = current;
if (str[location.Offset] != Constants.END_BODY)
{
do
{
// must be immediate value: type(data)
type = str.GetEntity(ref location, ParserSeparatorType.StartBody, false);
type.ExpectValidName();
next.Next = next = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
type = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
next.NextChild = new ParserElement(type, parentType);
// next token may be a comma or end, let's find out
str.AdvanceWhiteSpace(ref location, true);
// expecting a blank token
type = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
type.ExpectToken(string.Empty);
} while (type.SeparatorType == ParserSeparatorType.NextBody);
}
else location.AdvanceOffset();
str.AdvanceWhiteSpace(ref location, true);
if (str[location.Offset] != Constants.END_ATTRIBUTE)
{
type.ThrowInvalid();
}
location.AdvanceOffset();
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) type.ThrowInvalid();
return current;
}
// instruction or label
parentType = ElementParentType.Label;
var data = str.GetEntity(ref location);
data.ExpectValidName();
if (data.Value[data.Value.Length - 1] == Constants.LABEL_SPECIFIER) // label
{
data = new ParserEntity(
data.Value.Slice(0, data.Value.Length - 1),
data.Location,
data.SeparatorType.HasValue ? data.SeparatorType.Value : ParserSeparatorType.Unknown
);
var label = new ParserElement(data, parentType);
if (label.Value == Constants.LABEL_NULL) label.ThrowInvalid();
return label;
}
// instruction
parentType = ElementParentType.Instruction;
// opcode ...
current = new ParserElement(data, parentType);
if (str.AdvanceWhiteSpaceUntilNewLine(ref location)) return current;
// operands
next = current;
do
{
data = str.GetEntity(ref location, ParserSeparatorType.NextOrStartBody, true);
data.ExpectValidName();
next.Next = next = new ParserElement(data, parentType);
if (data.FoundSeparator && data.SeparatorType == ParserSeparatorType.StartBody)
{
// operand is an immediate value: type(data)
str.AdvanceWhiteSpace(ref location, true);
data = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
next.NextChild = new ParserElement(data, parentType);
// next token may be a comma or newline, let's find out
if (str.AdvanceWhiteSpaceUntilNewLine(ref location)) break;
// expecting a blank token with NextBody
data = str.GetEntity(ref location, ParserSeparatorType.NextBody, false);
data.ExpectToken(string.Empty);
}
} while (!str.AdvanceWhiteSpaceUntilNewLine(ref location));
return current;
}
}
public static List<ParsedBody> Parse(ReadOnlySpan<char> str)
{
var location = new ParserLocation();
var ret = new List<ParsedBody>();
ParserElement last = null;
List<ParserElement> list = null;
List<ParserElement> attributes = null;
do
{
var elem = str.Parse(ref location);
if (elem == null)
{
if (last != null)
{
ret.Add(new ParsedBody(last, list));
}
break;
}
switch (elem.ParentType)
{
case ElementParentType.Unknown:
elem.ThrowInvalid();
break;
case ElementParentType.Attribute:
if (attributes == null) attributes = new List<ParserElement>();
attributes.Add(elem);
break;
case ElementParentType.Instruction:
case ElementParentType.Label:
case ElementParentType.Alias:
if (attributes != null) elem.ThrowInvalid();
if (list == null) list = new List<ParserElement>();
if (last == null) elem.ThrowInvalid();
list.Add(elem);
break;
case ElementParentType.Function:
if (last != null)
{
ret.Add(new ParsedBody(last, list));
list = null;
}
list = attributes;
attributes = null;
last = elem;
break;
default:
if (last != null)
{
ret.Add(new ParsedBody(last, list));
last = null;
list = null;
}
if (elem.ParentType != ElementParentType.Type && attributes != null) elem.ThrowInvalid();
ret.Add(new ParsedBody(elem, attributes));
attributes = null;
break;
}
} while (true);
return ret;
}
public static List<ParsedBody> Parse(string str) => Parse(str.AsSpan());
}
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8EEC05F9-82F0-4EC4-94C6-728F80D50959}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>IFPSLib.Tests</RootNamespace>
<AssemblyName>IFPSLib.Tests</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="ScriptTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="CompiledCode.bin" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IFPSAsmLib\IFPSAsmLib.csproj">
<Project>{63b7741e-d712-4277-b345-f5b77ac80eb1}</Project>
<Name>IFPSAsmLib</Name>
</ProjectReference>
<ProjectReference Include="..\IFPSLib\IFPSLib.csproj">
<Project>{e83bf0b3-4427-41e8-9a99-9df8951de711}</Project>
<Name>IFPSLib</Name>
<EmbedInteropTypes>False</EmbedInteropTypes>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="CompiledCode.txt" />
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets'))" />
<Error Condition="!Exists('..\packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets'))" />
</Target>
<Import Project="..\packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets')" />
<Import Project="..\packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.3\build\netstandard2.0\NETStandard.Library.targets')" />
</Project>

View File

@ -0,0 +1,20 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("IFPSLib.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("IFPSLib.Tests")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("8eec05f9-82f0-4ec4-94c6-728f80d50959")]
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,101 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace IFPSLib.Tests.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IFPSLib.Tests.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] CompiledCode {
get {
object obj = ResourceManager.GetObject("CompiledCode", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized string similar to .version 23
///
///.entry !MAIN
///
///.type primitive(Pointer) Pointer
///.type primitive(U32) U32
///.type primitive(Variant) Variant
///.type primitive(PChar) PChar
///.type primitive(Currency) Currency
///.type primitive(Extended) Extended
///.type primitive(Double) Double
///.type primitive(Single) Single
///.type primitive(S64) S64
///.type primitive(String) String
///.type primitive(U32) U32_2
///.type primitive(S32) S32
///.type primitive(S16) S16
///.type primitive(U16) U16
///.type primitive(S8) S8
///.type(export) funcptr(void()) ANY [rest of string was truncated]&quot;;.
/// </summary>
internal static string CompiledCodeDisasm {
get {
return ResourceManager.GetString("CompiledCodeDisasm", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="CompiledCode" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\CompiledCode.bin;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="CompiledCodeDisasm" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\CompiledCode.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
</root>

View File

@ -0,0 +1,48 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using IFPSLib;
using IFPSAsmLib;
using IFPSLib.Tests.Properties;
namespace IFPSLib.Tests
{
[TestClass]
public class ScriptTest
{
private static readonly string origB64 = Convert.ToBase64String(Resources.CompiledCode);
[TestMethod]
public void TestLoadSave()
{
// Load the script.
var script = Script.Load(Resources.CompiledCode);
// Ensure it's not null.
Assert.IsNotNull(script);
// For an official script (compiled by inno setup), the entrypoint is the first function.
Assert.AreEqual(script.EntryPoint, script.Functions[0]);
// Save the script.
var savedBytes = script.Save();
// Convert to base64 for later.
var saved = Convert.ToBase64String(savedBytes);
// Load the saved script.
var scriptSaved = Script.Load(savedBytes);
// Save again.
var savedTwice = Convert.ToBase64String(scriptSaved.Save());
// Ensure both saved scripts equal each other.
Assert.AreEqual(saved, savedTwice);
// Ensure the saved script equals the original.
Assert.AreEqual(saved, origB64);
// Ensure the disassemblies are equal.
Assert.AreEqual(script.Disassemble(), scriptSaved.Disassemble());
}
[TestMethod]
public void TestAsm()
{
var script = Assembler.Assemble(Resources.CompiledCodeDisasm);
var savedB64 = Convert.ToBase64String(script.Save());
Assert.AreEqual(savedB64, origB64);
}
}
}

11
IFPSLib.Tests/app.config Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net472" />
<package id="MSTest.TestAdapter" version="2.2.7" targetFramework="net472" />
<package id="MSTest.TestFramework" version="2.2.7" targetFramework="net472" />
<package id="NativeMemoryArray" version="1.2.0" targetFramework="net472" />
<package id="NETStandard.Library" version="2.0.3" targetFramework="net472" />
<package id="SharpFloatLibrary" version="1.0.4" targetFramework="net472" />
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
<package id="System.Memory" version="4.5.4" targetFramework="net472" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net472" />
</packages>

View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib
{
/// <summary>
/// Represents additional type/function metadata, similar to .NET attributes.
/// </summary>
public class CustomAttribute
{
/// <summary>
/// Attribute type name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Attribute arguments (indexed by constructor argument number).
/// </summary>
public IList<TypedData> Arguments { get; internal set; } = new List<TypedData>();
public CustomAttribute(string name)
{
Name = name;
}
internal static IList<CustomAttribute> LoadList(BinaryReader br, Script script)
{
var listLen = br.Read<int>();
var ret = new List<CustomAttribute>(listLen);
for (int idxAttr = 0; idxAttr < listLen; idxAttr++)
{
var nameLen = br.Read<uint>();
var attr = new CustomAttribute(br.ReadAsciiString(nameLen));
var argsLen = br.Read<int>();
attr.Arguments = new List<TypedData>(argsLen);
for (int idxArg = 0; idxArg < argsLen; idxArg++)
{
attr.Arguments.Add(TypedData.Load(br, script));
}
ret.Add(attr);
}
return ret;
}
internal static void SaveList(BinaryWriter bw, IList<CustomAttribute> list, Script.SaveContext ctx)
{
bw.Write<int>(list.Count);
for (int idxAttr = 0; idxAttr < list.Count; idxAttr++)
{
var attr = list[idxAttr];
bw.WriteAsciiString(attr.Name.ToUpper(), true);
bw.Write<int>(attr.Arguments.Count);
for (int idxArg = 0; idxArg < attr.Arguments.Count; idxArg++)
{
attr.Arguments[idxArg].Save(bw, ctx);
}
}
}
public override string ToString()
{
var sb = new StringBuilder("[");
sb.Append(Name);
sb.Append('(');
for (int i = 0; i < Arguments.Count; i++)
{
if (i != 0) sb.Append(", ");
sb.Append(Arguments[i]);
}
sb.Append(")]");
return sb.ToString();
}
}
}

23
IFPSLib/Emit/AluOpCode.cs Normal file
View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
/// <summary>
/// Type of an ALU operation for the "calculate" instruction at the bytecode level.
/// </summary>
public enum AluOpCode : byte
{
Add,
Sub,
Mul,
Div,
Mod,
Shl,
Shr,
And,
Or,
Xor,
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
/// <summary>
/// Type of a PascalScript operand at the bytecode level.
/// </summary>
internal enum BytecodeOperandType : byte
{
/// <summary>
/// Operand refers to a local or global variable, or an argument.
/// </summary>
Variable,
/// <summary>
/// Operand refers to an immediate typed constant.
/// </summary>
Immediate,
/// <summary>
/// Operand refers to an element of an indexed variable, where the index is specified as an immediate 32-bit constant.
/// </summary>
IndexedImmediate,
/// <summary>
/// Operand refers to an element of an indexed variable, where the index is specified as a local or global variable, or an argument.
/// </summary>
IndexedVariable
}
}

49
IFPSLib/Emit/CmpOpCode.cs Normal file
View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
/// <summary>
/// Type of a compare operation for the "cmp" instruction at the bytecode level.
/// </summary>
public enum CmpOpCode : byte
{
/// <summary>
/// Greater than or equal to
/// </summary>
Ge,
/// <summary>
/// Less than or equal to
/// </summary>
Le,
/// <summary>
/// Greater than
/// </summary>
Gt,
/// <summary>
/// Less than
/// </summary>
Lt,
/// <summary>
/// Not equal
/// </summary>
Ne,
/// <summary>
/// Equal
/// </summary>
Eq,
/// <summary>
/// If operand 2 is Variant[], checks if operand 1 when converted to COM VARIANT is in the array.
/// If operand 2 is a Set, checks if operand 1 is in the Set.
/// Invalid if operand 2 is any other type
/// </summary>
In,
/// <summary>
/// Operand 1 must be a Class, operand 2 is u32 type index.
/// The type referenced by operand 2 must be a Class.
/// Checks if operand 1 is of the Class type referenced by operand 2.
/// </summary>
Is
}
}

75
IFPSLib/Emit/Code.cs Normal file
View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
/// <summary>
/// A PascalScript opcode at the bytecode level.
/// </summary>
public enum Code : ushort
{
Assign,
Calculate,
Push,
PushVar,
Pop,
Call,
Jump,
JumpNZ,
JumpZ,
Ret,
SetStackType,
PushType,
Compare,
CallVar,
SetPtr, // removed between 2003 and 2005
SetZ,
Neg,
SetFlag,
JumpF,
StartEH,
PopEH,
Not,
Cpval,
Inc,
Dec,
PopJump,
PopPopJump,
Nop = 0xff,
Add = (Calculate << 8) | (AluOpCode.Add),
Sub = (Calculate << 8) | (AluOpCode.Sub),
Mul = (Calculate << 8) | (AluOpCode.Mul),
Div = (Calculate << 8) | (AluOpCode.Div),
Mod = (Calculate << 8) | (AluOpCode.Mod),
Shl = (Calculate << 8) | (AluOpCode.Shl),
Shr = (Calculate << 8) | (AluOpCode.Shr),
And = (Calculate << 8) | (AluOpCode.And),
Or = (Calculate << 8) | (AluOpCode.Or),
Xor = (Calculate << 8) | (AluOpCode.Xor),
Ge = (Compare << 8) | (CmpOpCode.Ge),
Le = (Compare << 8) | (CmpOpCode.Le),
Gt = (Compare << 8) | (CmpOpCode.Gt),
Lt = (Compare << 8) | (CmpOpCode.Lt),
Ne = (Compare << 8) | (CmpOpCode.Ne),
Eq = (Compare << 8) | (CmpOpCode.Eq),
In = (Compare << 8) | (CmpOpCode.In),
Is = (Compare << 8) | (CmpOpCode.Is),
SetFlagNZ = (SetFlag << 8) | (SetFlagOpCode.NotZero),
SetFlagZ = (SetFlag << 8) | (SetFlagOpCode.Zero),
EndTry = (PopEH << 8) | (PopEHOpCode.EndTry),
EndFinally = (PopEH << 8) | (PopEHOpCode.EndFinally),
EndCatch = (PopEH << 8) | (PopEHOpCode.EndCatch),
EndCF = (PopEH << 8) | (PopEHOpCode.EndHandler),
UNKNOWN1 = 0xff00,
UNKNOWN_ALU,
UNKNOWN_CMP,
UNKNOWN_SF,
UNKNOWN_POPEH,
}
}

View File

@ -0,0 +1,443 @@
using Cysharp.Collections;
using IFPSLib.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Emit
{
namespace FDecl
{
public abstract class Base
{
internal bool HasReturnArgument;
/// <summary>
/// If null, argument information is unknown.
/// </summary>
internal IList<FunctionArgument> Arguments { get; set; }
internal virtual string Name => "";
public abstract int Size { get; }
protected const string DllString = "dll:";
protected const string ClassString = "class:";
protected const string ComString = "intf:.";
internal static Base Load(BinaryReader br)
{
var fdeclLen = br.Read<uint>();
using (var fdeclMem = new NativeMemoryArray<byte>(fdeclLen, true))
{
var fdeclSpan = fdeclMem.AsSpan();
br.Read(fdeclSpan);
using (var brDecl = new BinaryReader(fdeclMem.AsStream(), Encoding.UTF8, true))
{
if (fdeclSpan.EqualsAsciiString(0, DllString))
{
brDecl.BaseStream.Position = DllString.Length;
return DLL.Load(brDecl);
}
else if (fdeclSpan.EqualsAsciiString(0, ClassString))
{
brDecl.BaseStream.Position = ClassString.Length;
return Class.Load(brDecl);
}
else if (fdeclSpan.EqualsAsciiString(0, ComString))
{
brDecl.BaseStream.Position = ComString.Length;
return COM.Load(brDecl);
}
else
{
return Internal.Load(brDecl);
}
}
}
}
internal void Save(BinaryWriter bw, Script.SaveContext ctx)
{
using (var ms = new MemoryStream())
{
using (var msbw = new BinaryWriter(ms, Encoding.UTF8, true))
{
SaveCore(msbw, ctx);
if (ms.Length > uint.MaxValue) throw new InvalidOperationException("Declaration length is greater than 4GB");
bw.Write<uint>((uint)ms.Length);
bw.Write(ms);
}
}
}
internal abstract void SaveCore(BinaryWriter bw, Script.SaveContext ctx);
protected void LoadArguments(BinaryReader br)
{
HasReturnArgument = br.ReadByte() != 0;
Arguments = FunctionArgument.LoadForExternal(br);
}
protected void SaveArguments(BinaryWriter bw)
{
bw.Write<bool>(HasReturnArgument);
foreach (var arg in Arguments)
{
bw.Write<bool>(arg.ArgumentType == FunctionArgumentType.Out);
}
}
protected int SizeOfArguments => sizeof(byte) + (sizeof(byte) * Arguments.Count);
}
public abstract class BaseCC : Base
{
public NativeCallingConvention CallingConvention;
public override string ToString()
{
switch (CallingConvention)
{
case NativeCallingConvention.Register:
return "__fastcall";
case NativeCallingConvention.Pascal:
return "__pascal";
case NativeCallingConvention.CDecl:
return "__cdecl";
case NativeCallingConvention.Stdcall:
return "__stdcall";
default:
return "";
}
}
}
public sealed class DLL : BaseCC
{
public string DllName;
public string ProcedureName;
public bool DelayLoad;
public bool LoadWithAlteredSearchPath;
internal override string Name => string.Format("{0}!{1}", DllName, ProcedureName);
internal static new DLL Load(BinaryReader br)
{
var ret = new DLL();
ret.DllName = br.ReadAsciiStringTerminated();
ret.ProcedureName = br.ReadAsciiStringTerminated();
ret.CallingConvention = (NativeCallingConvention)br.ReadByte();
ret.DelayLoad = br.ReadByte() != 0;
ret.LoadWithAlteredSearchPath = br.ReadByte() != 0;
ret.LoadArguments(br);
return ret;
}
public override string ToString()
{
return string.Format(
"dll(\"{0}\",\"{1}\"{2}{3}) {4}",
DllName.Replace("\"", "\\\""),
ProcedureName.Replace("\"", "\\\""),
DelayLoad ? "delayload, " : "",
LoadWithAlteredSearchPath ? "alteredsearchpath" : "",
base.ToString()
);
}
public override int Size =>
DllString.Length +
Encoding.ASCII.GetByteCount(DllName) + sizeof(byte) +
Encoding.ASCII.GetByteCount(ProcedureName) + sizeof(byte) +
sizeof(NativeCallingConvention) +
sizeof(byte) + sizeof(byte) +
SizeOfArguments;
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
bw.WriteAsciiString(DllString);
bw.WriteAsciiStringTerminated(DllName);
bw.WriteAsciiStringTerminated(ProcedureName);
bw.Write(CallingConvention);
bw.Write<byte>((byte)(DelayLoad ? 1 : 0));
bw.Write<byte>((byte)(LoadWithAlteredSearchPath ? 1 : 0));
SaveArguments(bw);
}
}
public sealed class Class : BaseCC
{
public string ClassName;
public string FunctionName;
public bool IsProperty = false;
internal override string Name => string.Format("{0}->{1}", ClassName, FunctionName);
private const byte TERMINATOR = (byte)'|';
internal static new Class Load(BinaryReader br)
{
var ret = new Class();
// check for a special type
if ((br.BaseStream.Length - br.BaseStream.Position) == 1)
{
ret.ClassName = "Class";
ret.HasReturnArgument = true;
ret.CallingConvention = NativeCallingConvention.Pascal;
var special = br.ReadByte();
switch (special)
{
case (byte)'+':
ret.FunctionName = "CastToType";
ret.Arguments = new List<FunctionArgument>()
{
new FunctionArgument() {
Type = null,
ArgumentType = FunctionArgumentType.Out
},
new FunctionArgument()
{
Type = null,
ArgumentType = FunctionArgumentType.Out
}
};
break;
case (byte)'-':
ret.FunctionName = "SetNil";
ret.Arguments = new List<FunctionArgument>()
{
new FunctionArgument() {
Type = null,
ArgumentType = FunctionArgumentType.Out
},
};
break;
default:
throw new InvalidDataException(string.Format("Unknown special type: 0x{0:x2}", special));
}
return ret;
}
ret.ClassName = br.ReadAsciiStringTerminated(TERMINATOR);
ret.FunctionName = br.ReadAsciiStringTerminated(TERMINATOR);
ret.IsProperty = ret.FunctionName[ret.FunctionName.Length - 1] == '@';
if (ret.IsProperty)
{
ret.FunctionName = ret.FunctionName.Substring(0, ret.FunctionName.Length - 1);
}
ret.CallingConvention = (NativeCallingConvention)br.ReadByte();
ret.LoadArguments(br);
return ret;
}
public override string ToString()
{
return string.Format(
"class({0}, {1}{2}) {3}",
ClassName,
FunctionName,
IsProperty ? ", property" : "",
base.ToString()
);
}
private int SizeBody()
{
if (ClassName == "Class" && (FunctionName == "CastToType" || FunctionName == "SetNil")) return 1;
return Encoding.ASCII.GetByteCount(ClassName) + sizeof(byte) +
Encoding.ASCII.GetByteCount(FunctionName) + sizeof(byte) +
(IsProperty ? sizeof(byte) : 0) +
sizeof(NativeCallingConvention) +
SizeOfArguments;
}
public override int Size =>
ClassString.Length + SizeBody();
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
bw.WriteAsciiString(ClassString);
if (ClassName == "Class")
{
if (FunctionName == "CastToType")
{
bw.Write<byte>((byte)'+');
return;
}
else if (FunctionName == "SetNil")
{
bw.Write<byte>((byte)'-');
return;
}
}
bw.WriteAsciiStringTerminated(ClassName, TERMINATOR);
var sb = new StringBuilder(FunctionName);
if (IsProperty) sb.Append('@');
bw.WriteAsciiStringTerminated(sb.ToString(), TERMINATOR);
bw.Write(CallingConvention);
SaveArguments(bw);
}
}
public sealed class COM : BaseCC
{
public uint VTableIndex;
internal override string Name => string.Format("CoInterface->vtbl[{0}]", VTableIndex);
internal static new COM Load(BinaryReader br)
{
var ret = new COM();
ret.VTableIndex = br.Read<uint>();
ret.CallingConvention = (NativeCallingConvention)br.ReadByte();
ret.LoadArguments(br);
return ret;
}
public override string ToString()
{
return string.Format("com({0}) {1}", VTableIndex, base.ToString());
}
public override int Size =>
ComString.Length + sizeof(uint) + sizeof(NativeCallingConvention) + SizeOfArguments;
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
bw.WriteAsciiString(ComString);
bw.Write<uint>(VTableIndex);
bw.Write(CallingConvention);
SaveArguments(bw);
}
}
public sealed class Internal : Base
{
internal static new Internal Load(BinaryReader br)
{
var ret = new Internal();
ret.LoadArguments(br);
return ret;
}
public override string ToString()
{
return "internal";
}
public override int Size => SizeOfArguments;
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
SaveArguments(bw);
}
}
}
/// <summary>
/// A native function that imports from a DLL or an internal implementation
/// </summary>
public class ExternalFunction : FunctionBase
{
public FDecl.Base Declaration = null;
private bool ExportsName => string.IsNullOrEmpty(Declaration?.Name);
public override IType ReturnArgument { get; set; }
/// <summary>
/// If null, argument information is unknown.
/// </summary>
public override IList<FunctionArgument> Arguments {
get => Declaration?.Arguments;
set
{
if (Declaration == null) throw new NullReferenceException("No declaration is present");
Declaration.Arguments = value;
}
}
internal static ExternalFunction Load(BinaryReader br, Script script, bool exported)
{
var ret = new ExternalFunction();
ret.Exported = exported;
var namelen = br.ReadByte();
if (namelen != 0)
ret.Name = br.ReadAsciiString(namelen);
if (exported)
{
ret.Declaration = FDecl.Base.Load(br);
if (ret.Declaration.HasReturnArgument) ret.ReturnArgument = UnknownType.Instance;
if (string.IsNullOrEmpty(ret.Name)) ret.Name = ret.Declaration.Name;
}
return ret;
}
public override string ToString()
{
var sb = new StringBuilder(".function");
if (Exported) sb.Append("(import)");
sb.Append(" external ");
if (Declaration != null)
sb.Append(Declaration);
sb.Append(' ');
sb.Append(ReturnArgument != null ? "returnsval " : "void ");
sb.Append(Name);
if (Declaration != null)
{
sb.Append('(');
for (int i = 0; i < Arguments.Count; i++)
{
sb.Append(i == 0 ? "" : ",");
sb.Append(Arguments[i].ToString());
}
sb.Append(')');
}
return sb.ToString();
}
private byte NameLength() => (byte)Math.Min(0xff, Encoding.ASCII.GetByteCount(Name));
public override int Size => sizeof(byte) + NameLength() + (Declaration == null ? 0 : Declaration.Size);
internal override void Save(BinaryWriter bw, Script.SaveContext ctx)
{
FunctionFlags flags = FunctionFlags.External;
if (Exported)
{
flags |= FunctionFlags.Exported;
Declaration.HasReturnArgument = ReturnArgument != null;
}
if (Attributes.Count != 0) flags |= FunctionFlags.HasAttributes;
bw.Write(flags);
if (ExportsName)
{
bw.Write<byte>(NameLength());
var internalName = Name.ToUpper();
if (Name.Length > 0xff) internalName = Name.Substring(0, 0xff);
bw.WriteAsciiString(internalName);
}
else bw.Write<byte>(0);
if (Declaration != null)
{
Declaration.Save(bw, ctx);
}
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
/// <summary>
/// Describes how an instruction alters control flow.
/// </summary>
public enum FlowControl
{
/// <summary>
/// Instruction branches to another block.
/// </summary>
Branch,
/// <summary>
/// Instruction calls a function.
/// </summary>
Call,
/// <summary>
/// Instruction branches to another block if a condition passes.
/// </summary>
Cond_Branch,
/// <summary>
/// Normal flow of control; to the next instruction.
/// </summary>
Next,
/// <summary>
/// Returns to the caller.
/// </summary>
Return
}
}

678
IFPSLib/Emit/Instruction.cs Normal file
View File

@ -0,0 +1,678 @@
using IFPSLib.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections;
namespace IFPSLib.Emit
{
public sealed class Instruction : IEnumerable<Operand>
{
/// <summary>
/// The opcode
/// </summary>
public OpCode OpCode;
/// <summary>
/// The operands
/// </summary>
public IReadOnlyList<Operand> Operands => m_Operands.AsReadOnly();
/// <summary>
/// Offset of the instruction in the function body
/// </summary>
public uint Offset;
/// <summary>
/// Referenced by an operand of another instruction
/// </summary>
public bool Referenced;
private List<Operand> m_Operands = new List<Operand>();
/// <summary>
/// Gets or sets the operand at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the operand to get or set.</param>
/// <returns>The operand at the specified index.</returns>
/// <exception cref="ArgumentOutOfRangeException">No operand exists at the given index; or the operand to set is incompatible with the existing operand.</exception>
public Operand this[int index]
{
get => m_Operands[index];
set
{
if (!m_Operands[index].SimilarType(value)) throw new ArgumentOutOfRangeException(nameof(value));
m_Operands[index] = value;
}
}
/// <summary>
/// Returns an enumerator that iterates through the operands.
/// </summary>
/// <returns>An enumerator for the operands list.</returns>
public IEnumerator<Operand> GetEnumerator()
{
return m_Operands.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through the operands.
/// </summary>
/// <returns>An enumerator for the operands list.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return m_Operands.GetEnumerator();
}
/// <summary>
/// Default constructor
/// </summary>
internal Instruction() { }
/// <summary>
/// Constructor
/// </summary>
/// <param name="opcode">Opcode</param>
internal Instruction(OpCode opcode)
{
OpCode = opcode;
m_Operands = new List<Operand>(0);
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="operands">Operands</param>
internal Instruction(OpCode opcode, List<Operand> operands) : this(opcode)
{
m_Operands = operands;
}
/// <summary>
/// Creates a new instruction with no operand
/// </summary>
/// <param name="opcode">Opcode</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode)
{
if (opcode.OperandType != OperandType.InlineNone) throw new ArgumentOutOfRangeException(nameof(opcode), "Must be a no-operand opcode");
return new Instruction(opcode);
}
/// <summary>
/// Creates a new instruction with one operand
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="operand">Operand</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, Operand operand)
{
if (opcode.OperandType != OperandType.InlineValue && opcode.OperandType != OperandType.InlineValueSF)
throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have a value operand");
return new Instruction(opcode, new List<Operand>(1) { operand });
}
/// <summary>
/// Creates a new instruction with an immediate operand
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="val">Immediate operand</param>
/// <typeparam name="TType">Type of operand, must be a PascalScript primitive</typeparam>
/// <returns>New instruction</returns>
public static Instruction Create<TType>(OpCode opcode, TType val)
=> Create(opcode, Operand.Create(val));
/// <summary>
/// Creates a new instruction with a variable operand
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="var">Variable operand</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, IVariable var)
=> Create(opcode, Operand.Create(var));
/// <summary>
/// Creates a new branch instruction
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="target">Branch target</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, Instruction target)
{
if (opcode.OperandType != OperandType.InlineBrTarget) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have an instruction operand");
target.Referenced = true;
return new Instruction(opcode, new List<Operand>(1) { Operand.Create(target) });
}
/// <summary>
/// Creates a new instruction with two operands
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="op0">First operand</param>
/// <param name="op1">Second operand</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, Operand op0, Operand op1)
{
if (opcode.OperandType != OperandType.InlineValueValue) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have two value operands");
return new Instruction(opcode, new List<Operand>(2) { op0, op1 });
}
/// <summary>
/// Creates a new instruction with two variable operands
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="op0">First operand</param>
/// <param name="op1">Second operand</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, IVariable op0, IVariable op1)
=> Create(opcode, Operand.Create(op0), Operand.Create(op1));
/// <summary>
/// Creates a new instruction with a variable operand and an immediate operand
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="op0">First operand</param>
/// <param name="op1">Second operand</param>
/// <typeparam name="TType">Type of second operand, must be a PascalScript primitive</typeparam>
/// <returns>New instruction</returns>
public static Instruction Create<TType>(OpCode opcode, IVariable op0, TType val)
=> Create(opcode, Operand.Create(op0), Operand.Create(val));
/// <summary>
/// Creates a new branch instruction with an operand
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="target">Branch target</param>
/// <param name="operand">Operand</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, Instruction target, Operand operand)
{
if (opcode.OperandType != OperandType.InlineBrTargetValue) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have an instruction and a value operand");
return new Instruction(opcode, new List<Operand>(2) { Operand.Create(target), operand });
}
/// <summary>
/// Creates a new branch instruction with a variable operand
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="target">Branch target</param>
/// <param name="operand">Operand</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, Instruction target, IVariable var)
=> Create(opcode, target, Operand.Create(var));
/// <summary>
/// Creates a new call instruction
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="func">Function</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, IFunction func)
{
if (opcode.OperandType != OperandType.InlineFunction) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have a function operand");
return new Instruction(opcode, new List<Operand>(1) { Operand.Create(func) });
}
/// <summary>
/// Creates a new instruction with a type operand
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="type">Type</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, Types.IType type)
{
if (opcode.OperandType != OperandType.InlineType) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have a function operand");
return new Instruction(opcode, new List<Operand>(1) { Operand.Create(type) });
}
/// <summary>
/// Creates a new compare instruction
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="op0">Destination</param>
/// <param name="op1">Left hand side</param>
/// <param name="op2">Right hand side</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, Operand op0, Operand op1, Operand op2)
{
if (opcode.OperandType != OperandType.InlineCmpValue) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have three value operands");
return new Instruction(opcode, new List<Operand>(3) { op0, op1, op2 });
}
/// <summary>
/// Creates a new compare instruction with two variables and an immediate
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="op0">Destination</param>
/// <param name="op1">Left hand side</param>
/// <param name="op2">Right hand side</param>
/// <typeparam name="TType">Type of right hand side, must be a PascalScript primitive</typeparam>
/// <returns>New instruction</returns>
public static Instruction Create<TType>(OpCode opcode, IVariable op0, IVariable op1, TType op2)
=> Create(opcode, Operand.Create(op0), Operand.Create(op1), Operand.Create(op2));
/// <summary>
/// Creates a new compare instruction with three variables
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="op0">Destination</param>
/// <param name="op1">Left hand side</param>
/// <param name="op2">Right hand side</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, IVariable op0, IVariable op1, IVariable op2)
=> Create(opcode, Operand.Create(op0), Operand.Create(op1), Operand.Create(op2));
/// <summary>
/// Creates a new compare-type instruction
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="op0">Destination</param>
/// <param name="op1">Left hand side</param>
/// <param name="type">Right hand side (type)</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, Operand op0, Operand op1, Types.IType type)
{
if (opcode.OperandType != OperandType.InlineCmpValueType) throw new ArgumentOutOfRangeException(nameof(opcode), "Opcode does not have two value operands and a type operand");
return new Instruction(opcode, new List<Operand>(3) { op0, op1, Operand.Create(type) });
}
/// <summary>
/// Creates a new compare-type instruction with two variables
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="op0">Destination</param>
/// <param name="op1">Left hand side</param>
/// <param name="type">Right hand side (type)</param>
/// <returns>New instruction</returns>
public static Instruction Create(OpCode opcode, IVariable op0, IVariable op1, Types.IType type)
=> Create(opcode, Operand.Create(op0), Operand.Create(op1), type);
/// <summary>
/// Creates a new StartEH instruction
/// </summary>
/// <param name="Finally">Start of first finally block</param>
/// <param name="Catch">Start of catch block</param>
/// <param name="CatchFinally">Start of second finally block</param>
/// <param name="End">End of last exception handler block</param>
/// <returns>New instruction</returns>
public static Instruction CreateStartEH(Instruction Finally, Instruction Catch, Instruction CatchFinally, Instruction End)
{
return new Instruction(OpCodes.StartEH, new List<Operand>(4) { Operand.Create(Finally), Operand.Create(Catch), Operand.Create(CatchFinally), Operand.Create(End) });
}
/// <summary>
/// Creates a new StartEH instruction for a try-finally block
/// </summary>
/// <param name="Finally">Start of finally block</param>
/// <param name="End">End of finally block</param>
/// <returns>New instruction</returns>
public static Instruction CreateTryFinally(Instruction Finally, Instruction End)
=> CreateStartEH(Finally, null, null, End);
/// <summary>
/// Create a new StartEH instruction for a try-catch block
/// </summary>
/// <param name="Catch">Start of catch block</param>
/// <param name="End">End of catch block</param>
/// <returns>New instruction</returns>
public static Instruction CreateTryCatch(Instruction Catch, Instruction End)
=> CreateStartEH(null, Catch, null, End);
/// <summary>
/// Create a new StartEH instruction for a try-catch-finally block
/// </summary>
/// <param name="Catch">Start of catch block</param>
/// <param name="Finally">Start of finally block</param>
/// <param name="End">End of finally block</param>
/// <returns>New instruction</returns>
public static Instruction CreateTryCatchFinally(Instruction Catch, Instruction Finally, Instruction End)
=> CreateStartEH(null, Catch, Finally, End);
/// <summary>
/// Creates a SetStackType instruction (removed after version 21 or 22)
/// </summary>
/// <param name="type">Type to set variable to</param>
/// <param name="variable">Variable to set</param>
/// <returns>New instruction</returns>
public static Instruction CreateSetStackType(Types.IType type, IVariable variable)
{
return new Instruction(OpCodes.SetStackType, new List<Operand>(2) { Operand.Create(type), Operand.Create(variable) });
}
/// <summary>
/// Replaces this instruction with the content of another. Use instead of replacing an instruction in an array in case it is referenced by another instruction.
/// </summary>
/// <param name="insn">New instruction</param>
public void Replace(Instruction insn)
{
OpCode = insn.OpCode;
m_Operands = insn.m_Operands;
Offset = insn.Offset;
Referenced = insn.Referenced;
}
private static uint FixBranchOffset(BinaryReader br, uint data)
{
return data + (uint)br.BaseStream.Position;
}
private static uint FixBranchOffsetEH(BinaryReader br, uint data)
{
if (data == uint.MaxValue) return uint.MaxValue;
return FixBranchOffset(br, data);
}
private static uint ReadBranchOffset(BinaryReader br)
{
return FixBranchOffset(br, br.Read<uint>());
}
internal static Instruction Load(BinaryReader br, Script script, ScriptFunction function)
{
var ret = new Instruction();
ret.Offset = (uint)br.BaseStream.Position;
OpCode opcode = null;
var opFirstByte = (Code)br.ReadByte();
switch (opFirstByte)
{
case Code.Calculate:
var calcSecond = (AluOpCode)br.ReadByte();
if (calcSecond > AluOpCode.Xor) opcode = OpCodes.UNKNOWN_ALU;
else opcode = OpCodes.AluOpCodes[(int)calcSecond];
break;
case Code.Compare:
var cmpSecond = (CmpOpCode)br.ReadByte();
if (cmpSecond > CmpOpCode.Is) opcode = OpCodes.UNKNOWN_CMP;
else opcode = OpCodes.CmpOpCodes[(int)cmpSecond];
break;
case Code.PopEH:
var popEhSecond = (PopEHOpCode)br.ReadByte();
if (popEhSecond > PopEHOpCode.EndHandler) opcode = OpCodes.UNKNOWN_POPEH;
else opcode = OpCodes.PopEHOpCodes[(int)popEhSecond];
break;
case Code.SetFlag:
// placeholder
opcode = OpCodes.UNKNOWN_SF;
break;
case Code.SetStackType:
if (script.FileVersion >= Script.VERSION_MAX_SETSTACKTYPE) opcode = OpCodes.UNKNOWN1;
else opcode = OpCodes.SetStackType;
break;
default:
opcode = OpCodes.OneByteOpCodes[(int)opFirstByte];
break;
}
ret.OpCode = opcode;
switch (opcode.OperandType)
{
case OperandType.InlineNone:
ret.m_Operands = new List<Operand>(0);
break;
case OperandType.InlineValue:
ret.m_Operands = new List<Operand>(1) { Operand.LoadValue(br, script, function) };
break;
case OperandType.InlineBrTarget:
ret.m_Operands = new List<Operand>(1) { new Operand(new TypedData(InstructionType.Instance, ReadBranchOffset(br))) };
break;
case OperandType.InlineValueValue:
ret.m_Operands = new List<Operand>(2) { Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function) };
break;
case OperandType.InlineBrTargetValue:
var brOffset = br.Read<uint>();
var valOp = Operand.LoadValue(br, script, function);
ret.m_Operands = new List<Operand>(2) { new Operand(new TypedData(InstructionType.Instance, FixBranchOffset(br, brOffset))), valOp };
break;
case OperandType.InlineFunction:
ret.m_Operands = new List<Operand>(1) { Operand.Create(script.Functions[(int)br.Read<uint>()]) };
break;
case OperandType.InlineType:
ret.m_Operands = new List<Operand>(1) { Operand.Create(script.Types[(int)br.Read<uint>()]) };
break;
case OperandType.InlineCmpValue:
ret.m_Operands = new List<Operand>(3) { Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function), Operand.LoadValue(br, script, function) };
break;
case OperandType.InlineCmpValueType:
ret.m_Operands = new List<Operand>(3) {
Operand.LoadValue(br, script, function),
Operand.LoadValue(br, script, function),
Operand.Create(script.Types[(int)br.Read<uint>()])
};
break;
case OperandType.InlineEH:
var br0 = br.Read<uint>();
var br1 = br.Read<uint>();
var br2 = br.Read<uint>();
var br3 = br.Read<uint>();
ret.m_Operands = new List<Operand>(4) {
new Operand(new TypedData(InstructionType.Instance, FixBranchOffsetEH(br, br0))),
new Operand(new TypedData(InstructionType.Instance, FixBranchOffsetEH(br, br1))),
new Operand(new TypedData(InstructionType.Instance, FixBranchOffsetEH(br, br2))),
new Operand(new TypedData(InstructionType.Instance, FixBranchOffsetEH(br, br3)))
};
break;
case OperandType.InlineTypeVariable:
ret.m_Operands = new List<Operand>(2) {
Operand.Create(script.Types[(int)br.Read<uint>()]),
new Operand(VariableBase.Load(br, script, function))
};
return ret;
case OperandType.InlineValueSF:
ret.m_Operands = new List<Operand>(1) { Operand.LoadValue(br, script, function) };
var sfSecond = (SetFlagOpCode)br.ReadByte();
if (sfSecond > SetFlagOpCode.Zero) opcode = OpCodes.UNKNOWN_SF;
else opcode = OpCodes.SetFlagOpCodes[(int)sfSecond];
ret.OpCode = opcode;
break;
default:
throw new ArgumentOutOfRangeException(string.Format("Unknown OperandType {0}", opcode.OperandType));
}
return ret;
}
internal void FixUpBranchTargets(Dictionary<uint, Instruction> table)
{
for (int i = 0; i < m_Operands.Count; i++)
{
var operand = m_Operands[i];
if (operand.m_Type != BytecodeOperandType.Immediate) continue;
if (operand.ImmediateTyped.Type.BaseType != PascalTypeCode.Instruction) continue;
if (!(operand.Immediate is uint)) continue;
var target = operand.ImmediateAs<uint>();
if (OpCode.Code == Code.StartEH && target == uint.MaxValue) m_Operands[i] = Operand.Create((Instruction)null);
else
{
m_Operands[i] = Operand.Create(table[target]);
table[target].Referenced = true;
}
}
}
/// <summary>
/// Disassembles the instruction without labelling it
/// </summary>
/// <returns>Instruction text</returns>
public override string ToString()
{
return ToString(false);
}
/// <summary>
/// Disassembles the instruction
/// </summary>
/// <param name="forDisasm">If true, labels the instruction if it's referenced by another instruction</param>
/// <returns>Instruction text</returns>
public string ToString(bool forDisasm)
{
var sb = new StringBuilder();
if (Referenced) sb.AppendLine(string.Format("loc_{0}:", Offset.ToString("x")));
if (forDisasm) sb.Append('\t');
sb.Append(OpCode);
for (int i = 0; i < m_Operands.Count; i++)
{
sb.Append(i == 0 ? " " : ", ");
sb.Append(m_Operands[i]);
}
return sb.ToString();
}
/// <summary>
/// Gets the size of the instruction
/// </summary>
public int Size
{
get
{
var ret = OpCode.Size;
switch (OpCode.OperandType)
{
case OperandType.InlineNone:
break;
case OperandType.InlineValue:
case OperandType.InlineValueSF:
ret += m_Operands[0].Size;
break;
case OperandType.InlineBrTarget:
ret += sizeof(uint);
break;
case OperandType.InlineValueValue:
ret += m_Operands[0].Size + m_Operands[1].Size;
break;
case OperandType.InlineBrTargetValue:
ret += sizeof(uint) + m_Operands[1].Size;
break;
case OperandType.InlineFunction:
case OperandType.InlineType:
ret += sizeof(int);
break;
case OperandType.InlineCmpValue:
ret += m_Operands[0].Size + m_Operands[1].Size + m_Operands[2].Size;
break;
case OperandType.InlineCmpValueType:
ret += m_Operands[0].Size + m_Operands[1].Size + sizeof(int);
break;
case OperandType.InlineEH:
ret += sizeof(uint) * 4;
break;
case OperandType.InlineTypeVariable:
ret += sizeof(int) + m_Operands[1].Variable.Size;
break;
}
return ret;
}
}
private void FixUpReference(int index, Dictionary<Instruction, uint> table, bool allowedNull = false)
{
var insn = m_Operands[index].ImmediateAs<Instruction>();
if (insn == null)
{
if (!allowedNull) throw new InvalidOperationException("Instruction is null");
return;
}
if (!table.ContainsKey(insn)) throw new InvalidOperationException("Referenced instruction is not in the same function as the referencing instruction");
insn.Referenced = true;
}
internal void FixUpReferences(Dictionary<Instruction, uint> table)
{
switch (OpCode.OperandType)
{
case OperandType.InlineBrTarget:
case OperandType.InlineBrTargetValue:
FixUpReference(0, table);
break;
case OperandType.InlineEH:
FixUpReference(0, table, true);
FixUpReference(1, table, true);
FixUpReference(2, table, true);
FixUpReference(3, table, true);
break;
}
}
private uint GetEHOffset(Operand operand, Dictionary<Instruction, uint> table)
{
var insn = operand.ImmediateAs<Instruction>();
if (insn == null) return uint.MaxValue;
return table[insn] - Offset - (uint)Size;
}
internal void Save(BinaryWriter bw, Script.SaveContext ctx, Dictionary<Instruction, uint> table)
{
if (OpCode.Size == 2)
{
var firstByte = (byte)((int)OpCode.Code >> 8);
bw.Write<byte>(firstByte);
if (OpCode.OperandType != OperandType.InlineValueSF)
{
// not setflag, so second byte follows the first
bw.Write<byte>((byte)OpCode.Code);
}
}
else if (OpCode.Size == 1)
{
bw.Write<byte>((byte)OpCode.Code);
}
else throw new InvalidOperationException(string.Format("Invalid opcode size of {0} bytes", OpCode.Size));
switch (OpCode.OperandType)
{
case OperandType.InlineNone:
break;
case OperandType.InlineValue:
m_Operands[0].Save(bw, ctx);
break;
case OperandType.InlineBrTarget:
bw.Write<uint>(table[m_Operands[0].ImmediateAs<Instruction>()] - Offset - (uint)Size);
break;
case OperandType.InlineValueValue:
m_Operands[0].Save(bw, ctx);
m_Operands[1].Save(bw, ctx);
break;
case OperandType.InlineBrTargetValue:
bw.Write<uint>(table[m_Operands[0].ImmediateAs<Instruction>()] - Offset - (uint)Size);
m_Operands[1].Save(bw, ctx);
break;
case OperandType.InlineFunction:
bw.Write<int>(ctx.GetFunctionIndex(m_Operands[0].ImmediateAs<IFunction>()));
break;
case OperandType.InlineType:
bw.Write<int>(ctx.GetTypeIndex(m_Operands[0].ImmediateAs<IType>()));
break;
case OperandType.InlineCmpValue:
m_Operands[0].Save(bw, ctx);
m_Operands[1].Save(bw, ctx);
m_Operands[2].Save(bw, ctx);
break;
case OperandType.InlineCmpValueType:
m_Operands[0].Save(bw, ctx);
m_Operands[1].Save(bw, ctx);
bw.Write<int>(ctx.GetTypeIndex(m_Operands[2].ImmediateAs<IType>()));
break;
case OperandType.InlineEH:
bw.Write<uint>(GetEHOffset(m_Operands[0], table));
bw.Write<uint>(GetEHOffset(m_Operands[1], table));
bw.Write<uint>(GetEHOffset(m_Operands[2], table));
bw.Write<uint>(GetEHOffset(m_Operands[3], table));
break;
case OperandType.InlineTypeVariable:
bw.Write<int>(ctx.GetTypeIndex(m_Operands[0].ImmediateAs<IType>()));
((VariableBase)m_Operands[1].Variable).Save(bw, ctx);
break;
case OperandType.InlineValueSF:
m_Operands[0].Save(bw, ctx);
// setflag; second opcode byte is after the operand for some reason
bw.Write<byte>((byte)OpCode.Code);
break;
default:
throw new ArgumentOutOfRangeException(string.Format("Unknown OperandType {0}", OpCode.OperandType));
}
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
public enum NativeCallingConvention : byte
{
Register,
Pascal,
CDecl,
Stdcall
}
}

98
IFPSLib/Emit/OpCode.cs Normal file
View File

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
public class OpCode
{
/// <summary>
/// Gets the size of the opcode, either 1 or 2 bytes.
/// </summary>
public int Size
{
get
{
var firstByte = (Code)(((short)Code) >> 8);
if (firstByte == Code.Compare || firstByte == Code.Calculate || firstByte == Code.PopEH || firstByte == Code.SetFlag)
return sizeof(short);
else return sizeof(byte);
}
}
public OpCode(string name, byte first, byte second, OperandType operandType, FlowControl flowControl, StackBehaviour push, StackBehaviour pop)
: this(name, (Code)((first << 8) | second), operandType, flowControl, OpCodeType.Experimental, push, pop, true)
{ }
internal OpCode(string name, Code code, OperandType operandType, FlowControl flowControl, OpCodeType opCodeType,
StackBehaviour push = StackBehaviour.Push0, StackBehaviour pop = StackBehaviour.Pop0, bool experimental = false
)
{
Name = name;
Code = code;
OperandType = operandType;
FlowControl = flowControl;
OpcodeType = opCodeType;
StackBehaviourPush = push;
StackBehaviourPop = pop;
if (!experimental)
{
OpCode[] arr = null;
switch ((Code)(((short)Code) >> 8))
{
case Code.Calculate:
arr = OpCodes.AluOpCodes;
break;
case Code.Compare:
arr = OpCodes.CmpOpCodes;
break;
case Code.PopEH:
arr = OpCodes.PopEHOpCodes;
break;
case Code.SetFlag:
arr = OpCodes.SetFlagOpCodes;
break;
case 0:
arr = OpCodes.OneByteOpCodes;
break;
}
if (arr != null) arr[(short)Code & 0xff] = this;
OpCodes.m_OpCodesByName[name] = this;
}
}
public override string ToString()
{
return Name;
}
/// <summary>
/// The opcode name
/// </summary>
public readonly string Name;
/// <summary>
/// The opcode as a <see cref="Code"/> enum
/// </summary>
public readonly Code Code;
/// <summary>
/// Operand type
/// </summary>
public readonly OperandType OperandType;
/// <summary>
/// Flow control info
/// </summary>
public readonly FlowControl FlowControl;
/// <summary>
/// Opcode type
/// </summary>
public readonly OpCodeType OpcodeType;
/// <summary>
/// Push stack behaviour
/// </summary>
public readonly StackBehaviour StackBehaviourPush;
/// <summary>
/// Pop stack behaviour
/// </summary>
public readonly StackBehaviour StackBehaviourPop;
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
public enum OpCodeType : byte
{
Macro,
Prefix,
Primitive,
Experimental
}
}

293
IFPSLib/Emit/OpCodes.cs Normal file
View File

@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
public static class OpCodes
{
private static void FillTable(OpCode[] table, OpCode op)
{
for (int i = 0; i < table.Length; i++)
if (table[i] == null) table[i] = op;
}
/// <summary>
/// All one-byte opcodes
/// </summary>
public static readonly OpCode[] OneByteOpCodes = new OpCode[byte.MaxValue + 1];
/// <summary>
/// All two-byte ALU opcodes (first byte is 0x01)
/// </summary>
public static readonly OpCode[] AluOpCodes = new OpCode[(int)AluOpCode.Xor + 1];
/// <summary>
/// All two-byte comparison opcodes (first byte is 0x0C)
/// </summary>
public static readonly OpCode[] CmpOpCodes = new OpCode[(int)CmpOpCode.Is + 1];
/// <summary>
/// All two-byte exception handler leave opcodes (first byte is 0x14)
/// </summary>
public static readonly OpCode[] PopEHOpCodes = new OpCode[(int)PopEHOpCode.EndHandler + 1];
/// <summary>
/// All two-byte set flag opcodes (first byte is 0x11).
/// The second byte is after the operand for some reason.
/// </summary>
public static readonly OpCode[] SetFlagOpCodes = new OpCode[(int)SetFlagOpCode.Zero + 1];
/// <summary>
/// All non-experimental opcodes, indexed by opcode name.
/// </summary>
public static IReadOnlyDictionary<string, OpCode> ByName => m_OpCodesByName;
internal static readonly Dictionary<string, OpCode> m_OpCodesByName = new Dictionary<string, OpCode>();
#pragma warning disable 1591 // disable XML doc warning
public static readonly OpCode UNKNOWN1 = new OpCode("UNKNOWN1", Code.UNKNOWN1, OperandType.InlineNone, FlowControl.Next, OpCodeType.Experimental, StackBehaviour.Push0, StackBehaviour.Pop0, true);
public static readonly OpCode UNKNOWN_ALU = new OpCode("UNKNOWN_ALU", Code.UNKNOWN_ALU, OperandType.InlineNone, FlowControl.Next, OpCodeType.Experimental, StackBehaviour.Push0, StackBehaviour.Pop0, true);
public static readonly OpCode UNKNOWN_CMP = new OpCode("UNKNOWN_CMP", Code.UNKNOWN_CMP, OperandType.InlineNone, FlowControl.Next, OpCodeType.Experimental, StackBehaviour.Push0, StackBehaviour.Pop0, true);
public static readonly OpCode UNKNOWN_POPEH = new OpCode("UNKNOWN_POPEH", Code.UNKNOWN_POPEH, OperandType.InlineNone, FlowControl.Next, OpCodeType.Experimental, StackBehaviour.Push0, StackBehaviour.Pop0, true);
public static readonly OpCode UNKNOWN_SF = new OpCode("UNKNOWN_SF", Code.UNKNOWN_SF, OperandType.InlineValueSF, FlowControl.Next, OpCodeType.Experimental, StackBehaviour.Push0, StackBehaviour.Pop0, true);
/// <summary>
/// Loads the second value (usually an immediate) into the first value; <c>op0 = op1</c><br/>
/// If op0 is a pointer, then op1 is written to that pointer; <c>*op0 = op1</c>
/// </summary>
public static readonly OpCode Assign = new OpCode("assign", Code.Assign, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// Adds the second value to the first value; <c>op0 += op1</c>
/// </summary>
public static readonly OpCode Add = new OpCode("add", Code.Add, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Subtracts the second value from the first value; <c>op0 -= op1</c>
/// </summary>
public static readonly OpCode Sub = new OpCode("sub", Code.Sub, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Multiplies the second value with the first value; <c>op0 *= op1</c>
/// </summary>
public static readonly OpCode Mul = new OpCode("mul", Code.Mul, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Divides the second value from the first value; <c>op0 /= op1</c>
/// </summary>
public static readonly OpCode Div = new OpCode("div", Code.Div, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Modulo divides the second value from the first value; <c>op0 %= op1</c>
/// </summary>
public static readonly OpCode Mod = new OpCode("mod", Code.Mod, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Shifts the first value left by the second value; <c>op0 <<= op1</c>
/// </summary>
public static readonly OpCode Shl = new OpCode("shl", Code.Shl, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Shifts the first value right by the second value; <c>op0 >>= op1</c>
/// </summary>
public static readonly OpCode Shr = new OpCode("shr", Code.Shr, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Bitwise ANDs the first value by the second value; <c>op0 &= op1</c>
/// </summary>
public static readonly OpCode And = new OpCode("and", Code.And, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Bitwise ORs the first value by the second value; <c>op0 |= op1</c>
/// </summary>
public static readonly OpCode Or = new OpCode("or", Code.Or, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Bitwise XORs the first value by the second value; <c>op0 ^= op1</c>
/// </summary>
public static readonly OpCode Xor = new OpCode("xor", Code.Xor, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Pushes the operand to the top of the stack.
/// </summary>
public static readonly OpCode Push = new OpCode("push", Code.Push, OperandType.InlineValue, FlowControl.Next, OpCodeType.Primitive, StackBehaviour.Push1);
/// <summary>
/// Pushes a pointer to the operand to the top of the stack.
/// </summary>
public static readonly OpCode PushVar = new OpCode("pushvar", Code.PushVar, OperandType.InlineValue, FlowControl.Next, OpCodeType.Primitive, StackBehaviour.Push1);
/// <summary>
/// Removes the value currently on top of the stack.<br/>
/// If that value is a return address, or the stack is empty, the operation is invalid.
/// </summary>
public static readonly OpCode Pop = new OpCode("pop", Code.Pop, OperandType.InlineNone, FlowControl.Next, OpCodeType.Primitive, StackBehaviour.Push0, StackBehaviour.Pop1);
/// <summary>
/// Calls the function indicated by the provided index.<br/>
/// Return address gets pushed to the top of the stack (function index, offset to next instruction, stack pointer).<br/><br/>
/// Calling convention:
/// <list type="bullet">
/// <item><description>push arguments onto the stack, from last to first</description></item>
/// <item><description>if function returns a value, push a pointer to the return value</description></item>
/// </list>
/// </summary>
public static readonly OpCode Call = new OpCode("call", Code.Call, OperandType.InlineFunction, FlowControl.Call, OpCodeType.Primitive);
/// <summary>
/// Unconditionally branches to the target <see cref="Instruction"/>.
/// </summary>
public static readonly OpCode Jump = new OpCode("jump", Code.Jump, OperandType.InlineBrTarget, FlowControl.Branch, OpCodeType.Primitive);
/// <summary>
/// If the value operand is not zero, branch to the target <see cref="Instruction"/>.
/// </summary>
public static readonly OpCode JumpNZ = new OpCode("jnz", Code.JumpNZ, OperandType.InlineBrTargetValue, FlowControl.Cond_Branch, OpCodeType.Primitive);
/// <summary>
/// If the value operand is zero, branch to the target <see cref="Instruction"/>.
/// </summary>
public static readonly OpCode JumpZ = new OpCode("jz", Code.JumpZ, OperandType.InlineBrTargetValue, FlowControl.Cond_Branch, OpCodeType.Primitive);
/// <summary>
/// Returns from the current function.<br/>
/// Any finally blocks inside exception handlers will be executed first.<br/>
/// Pops the entirety of the current function's stack frame.<br/>
/// </summary>
public static readonly OpCode Ret = new OpCode("ret", Code.Ret, OperandType.InlineNone, FlowControl.Return, OpCodeType.Primitive, StackBehaviour.Push0, StackBehaviour.Varpop);
/// <summary>
/// <b>Removed between version 20 (1.20) and 22 (1.30), after that point this is an invalid opcode.</b><br/>
/// Destructs the given variable, creates a new one of the provided type.<br/>
/// If the provided type is <see cref="Types.PascalTypeCode.ReturnAddress"/> then the operation is invalid.
/// </summary>
public static readonly OpCode SetStackType = new OpCode("setstacktype", Code.SetStackType, OperandType.InlineTypeVariable, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// Pushes a new uninitialised value of the given type to the top of the stack.
/// </summary>
public static readonly OpCode PushType = new OpCode("pushtype", Code.PushType, OperandType.InlineType, FlowControl.Next, OpCodeType.Primitive, StackBehaviour.Push1);
/// <summary>
/// <c>op0 = op1 >= op2</c>
/// </summary>
public static readonly OpCode Ge = new OpCode("ge", Code.Ge, OperandType.InlineCmpValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// <c>op0 = op1 <= op2</c>
/// </summary>
public static readonly OpCode Le = new OpCode("le", Code.Le, OperandType.InlineCmpValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// <c>op0 = op1 > op2</c>
/// </summary>
public static readonly OpCode Gt = new OpCode("gt", Code.Gt, OperandType.InlineCmpValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// <c>op0 = op1 < op2</c>
/// </summary>
public static readonly OpCode Lt = new OpCode("lt", Code.Lt, OperandType.InlineCmpValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// <c>op0 = op1 != op2</c>
/// </summary>
public static readonly OpCode Ne = new OpCode("ne", Code.Ne, OperandType.InlineCmpValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// <c>op0 = op1 == op2</c>
/// </summary>
public static readonly OpCode Eq = new OpCode("eq", Code.Eq, OperandType.InlineCmpValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// If operand 2 is Variant[], checks if operand 1 when converted to COM VARIANT is in the array.<br/>
/// If operand 2 is a Set, checks if operand 1 is in the Set.<br/>
/// Invalid if operand 2 is any other type.<br/>
/// Result (0 or 1) is placed in operand 0
/// </summary>
public static readonly OpCode In = new OpCode("in", Code.In, OperandType.InlineCmpValue, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Operand 1 must be a <see cref="Types.ClassType"/>, operand 2 is a <see cref="Types.TypeType"/> that must be a <see cref="Types.ClassType"/>
/// Checks if operand 1 is of the <see cref="Types.ClassType"/> referenced by operand 2.
/// </summary>
public static readonly OpCode Is = new OpCode("is", Code.Is, OperandType.InlineCmpValueType, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Calls the function indicated by the value operand (as a <see cref="Types.FunctionPointerType"/> to the entry point).
/// </summary>
public static readonly OpCode CallVar = new OpCode("callvar", Code.CallVar, OperandType.InlineValue, FlowControl.Call, OpCodeType.Primitive, StackBehaviour.Push1);
/// <summary>
/// op0 must be a pointer.<br/>
/// If op1 is not a pointer, loads the equivalent address of op1 into op0; <c>op0 = &amp;op1</c><br/>
/// If op1 is a pointer, loads op1 into op0; <c>op0 = op1</c>
/// </summary>
public static readonly OpCode SetPtr = new OpCode("setptr", Code.SetPtr, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// <c>op0 = op0 == 0</c>
/// </summary>
public static readonly OpCode SetZ = new OpCode("setz", Code.SetZ, OperandType.InlineValue, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// <c>op0 = -op0</c>
/// </summary>
public static readonly OpCode Neg = new OpCode("neg", Code.Neg, OperandType.InlineValue, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// <c>jf = op0 != 0</c>
/// </summary>
public static readonly OpCode SetFlagNZ = new OpCode("sfnz", Code.SetFlagNZ, OperandType.InlineValueSF, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// <c>jf = op0 == 0</c>
/// </summary>
public static readonly OpCode SetFlagZ = new OpCode("sfz", Code.SetFlagZ, OperandType.InlineValueSF, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// If the jump flag (set or unset by <see cref="SetFlagNZ"/> or <see cref="SetFlagZ"/>) is set, branch to the target <see cref="Instruction"/>
/// </summary>
public static readonly OpCode JumpF = new OpCode("jf", Code.JumpF, OperandType.InlineBrTarget, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// Initialises an exception handler.<br/>
/// The operands are four target <see cref="Instruction"/>s which may be <c>null</c>.<br/>
/// op0 is the instruction that starts a Finally block.
/// op1 is the instruction that starts a Catch block.
/// op2 is the instruction that starts a Finally block after a Catch block.
/// op3 is the instruction after the last exception handler block.
/// </summary>
public static readonly OpCode StartEH = new OpCode("starteh", Code.StartEH, OperandType.InlineEH, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// Leaves the current Try block and unconditionally branches to the end of the exception handler.
/// </summary>
public static readonly OpCode EndTry = new OpCode("endtry", Code.EndTry, OperandType.InlineNone, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Leaves the current Finally block and unconditionally branches to the end of the exception handler.
/// </summary>
public static readonly OpCode EndFinally = new OpCode("endfinally", Code.EndFinally, OperandType.InlineNone, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Leaves the current Catch block and unconditionally branches to the end of the exception handler.
/// </summary>
public static readonly OpCode EndCatch = new OpCode("endcatch", Code.EndCatch, OperandType.InlineNone, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// Leaves the current Finally block after a Catch block and unconditionally branches to the end of the exception handler.
/// </summary>
public static readonly OpCode EndCF = new OpCode("endcf", Code.EndCF, OperandType.InlineNone, FlowControl.Next, OpCodeType.Macro);
/// <summary>
/// <c>op0 = ~op0</c>
/// </summary>
public static readonly OpCode Not = new OpCode("not", Code.Not, OperandType.InlineValue, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// <c>*op0 = *op1</c>
/// </summary>
public static readonly OpCode Cpval = new OpCode("cpval", Code.Cpval, OperandType.InlineValueValue, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// <c>op0++</c>
/// </summary>
public static readonly OpCode Inc = new OpCode("inc", Code.Inc, OperandType.InlineValue, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// <c>op0--</c>
/// </summary>
public static readonly OpCode Dec = new OpCode("dec", Code.Dec, OperandType.InlineValue, FlowControl.Next, OpCodeType.Primitive);
/// <summary>
/// Pops one value off of the stack (with no type restriction) and branches to the target <see cref="Instruction"/>
/// </summary>
public static readonly OpCode PopJump = new OpCode("popjump", Code.PopJump, OperandType.InlineBrTarget, FlowControl.Branch, OpCodeType.Primitive, StackBehaviour.Push0, StackBehaviour.Pop1);
/// <summary>
/// Pops two values off of the stack (with no type restriction) and branches to the target <see cref="Instruction"/>
/// </summary>
public static readonly OpCode PopPopJump = new OpCode("poppopjump", Code.PopPopJump, OperandType.InlineBrTarget, FlowControl.Branch, OpCodeType.Primitive, StackBehaviour.Push0, StackBehaviour.Pop2);
/// <summary>
/// No operation
/// </summary>
public static readonly OpCode Nop = new OpCode("nop", Code.Nop, OperandType.InlineNone, FlowControl.Next, OpCodeType.Primitive);
#pragma warning restore
static OpCodes()
{
FillTable(OneByteOpCodes, UNKNOWN1);
FillTable(AluOpCodes, UNKNOWN_ALU);
FillTable(CmpOpCodes, UNKNOWN_CMP);
FillTable(PopEHOpCodes, UNKNOWN_POPEH);
FillTable(SetFlagOpCodes, UNKNOWN_SF);
}
}
}

258
IFPSLib/Emit/Operand.cs Normal file
View File

@ -0,0 +1,258 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace IFPSLib.Emit
{
/// <summary>
/// A PascalScript bytecode instruction operand.
/// </summary>
public class Operand
{
private struct IndexedVariableValue
{
internal IVariable Variable;
internal IndexedValue Index;
}
[StructLayout(LayoutKind.Explicit)]
private struct IndexedValue
{
[FieldOffset(0)]
internal StrongBox<uint> m_Immediate;
[FieldOffset(0)]
internal IVariable Variable;
}
[StructLayout(LayoutKind.Explicit)]
private struct Value
{
[FieldOffset(0)]
internal TypedData Immediate;
[FieldOffset(0)]
internal IVariable Variable;
[FieldOffset(0)]
internal IndexedVariableValue Indexed;
}
internal BytecodeOperandType m_Type;
private Value m_Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T EnsureType<T>(ref T value, BytecodeOperandType type)
{
if (m_Type != type) throw new InvalidOperationException();
return value;
}
private IndexedVariableValue Indexed
{
get
{
if (m_Type != BytecodeOperandType.IndexedImmediate && m_Type != BytecodeOperandType.IndexedVariable) throw new InvalidOperationException();
return m_Value.Indexed;
}
}
public TypedData ImmediateTyped => EnsureType(ref m_Value.Immediate, BytecodeOperandType.Immediate);
public object Immediate => ImmediateTyped.Value;
public TType ImmediateAs<TType>() => ImmediateTyped.ValueAs<TType>();
public IVariable Variable => EnsureType(ref m_Value.Variable, BytecodeOperandType.Variable);
public IVariable IndexedVariable => Indexed.Variable;
public uint IndexImmediate => EnsureType(ref m_Value.Indexed.Index.m_Immediate.Value, BytecodeOperandType.IndexedImmediate);
public IVariable IndexVariable => EnsureType(ref m_Value.Indexed.Index.Variable, BytecodeOperandType.IndexedVariable);
public Operand(TypedData imm)
{
m_Type = BytecodeOperandType.Immediate;
m_Value.Immediate = imm;
}
public Operand(IVariable var)
{
m_Type = BytecodeOperandType.Variable;
m_Value.Variable = var;
}
public Operand(IVariable arr, uint immIdx)
{
m_Type = BytecodeOperandType.IndexedImmediate;
m_Value.Indexed.Variable = arr;
m_Value.Indexed.Index.m_Immediate = new StrongBox<uint>(immIdx);
}
public Operand(IVariable arr, IVariable varIdx)
{
m_Type = BytecodeOperandType.IndexedVariable;
m_Value.Indexed.Variable = arr;
m_Value.Indexed.Index.Variable = varIdx;
}
public static Operand Create<TType>(Types.PrimitiveType type, TType value)
{
return new Operand(TypedData.Create(type, value));
}
public static Operand Create<TType>(TType value)
{
return new Operand(TypedData.Create(value));
}
internal static Operand Create(Types.IType value)
{
return new Operand(TypedData.Create(value));
}
internal static Operand Create(Instruction value)
{
return new Operand(TypedData.Create(value));
}
internal static Operand Create(IFunction value)
{
return new Operand(TypedData.Create(value));
}
public static Operand Create(IVariable arr, uint idxValue)
{
return new Operand(arr, idxValue);
}
public static Operand Create(IVariable var)
{
return new Operand(var);
}
public static Operand Create(IVariable arr, IVariable varIdx)
{
return new Operand(arr, varIdx);
}
internal static Operand LoadValue(BinaryReader br, Script script, ScriptFunction function)
{
var type = (BytecodeOperandType)br.ReadByte();
switch (type)
{
case BytecodeOperandType.Variable:
return new Operand(VariableBase.Load(br, script, function));
case BytecodeOperandType.Immediate:
return new Operand(TypedData.Load(br, script));
case BytecodeOperandType.IndexedImmediate:
{
var variable = VariableBase.Load(br, script, function);
var idx = br.Read<uint>();
return Create(variable, idx);
}
case BytecodeOperandType.IndexedVariable:
{
var variable = VariableBase.Load(br, script, function);
var idx = VariableBase.Load(br, script, function);
return new Operand(variable, idx);
}
default:
throw new ArgumentOutOfRangeException(string.Format("Invalid operand type {0}", (byte)type));
}
}
public override string ToString()
{
switch (m_Type)
{
case BytecodeOperandType.Variable:
return Variable.Name;
case BytecodeOperandType.Immediate:
return ImmediateTyped.ToString();
case BytecodeOperandType.IndexedImmediate:
return String.Format("{0}[{1}]", IndexedVariable.Name, IndexImmediate);
case BytecodeOperandType.IndexedVariable:
return String.Format("{0}[{1}]", IndexedVariable.Name, IndexVariable.Name);
default:
return "";
}
}
public int Size
{
get
{
const int HEADER = sizeof(BytecodeOperandType);
switch (m_Type)
{
case BytecodeOperandType.Variable:
return Variable.Size + HEADER;
case BytecodeOperandType.Immediate:
switch (ImmediateTyped.Type.BaseType)
{
case Types.PascalTypeCode.Type:
case Types.PascalTypeCode.Instruction:
case Types.PascalTypeCode.Function:
return ImmediateTyped.Size;
default:
return ImmediateTyped.Size + HEADER;
}
case BytecodeOperandType.IndexedImmediate:
return IndexedVariable.Size + sizeof(uint) + HEADER;
case BytecodeOperandType.IndexedVariable:
return IndexedVariable.Size + IndexVariable.Size + HEADER;
default:
throw new InvalidOperationException();
}
}
}
internal void Save(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write(m_Type);
switch (m_Type)
{
case BytecodeOperandType.Variable:
((VariableBase)Variable).Save(bw, ctx);
break;
case BytecodeOperandType.Immediate:
var baseType = ImmediateTyped.Type.BaseType;
if (baseType == Types.PascalTypeCode.Type || baseType == Types.PascalTypeCode.Instruction || baseType == Types.PascalTypeCode.Function)
throw new InvalidOperationException(string.Format("Immediate operand is of incorrect type {0}", baseType));
ImmediateTyped.Save(bw, ctx);
break;
case BytecodeOperandType.IndexedImmediate:
((VariableBase)IndexedVariable).Save(bw, ctx);
bw.Write<uint>(IndexImmediate);
break;
case BytecodeOperandType.IndexedVariable:
((VariableBase)IndexedVariable).Save(bw, ctx);
((VariableBase)IndexVariable).Save(bw, ctx);
break;
default:
throw new InvalidOperationException();
}
}
/// <summary>
/// Returns true if this operand is compatible with another, such that this operand can be overwritten in an instruction with the other.
/// </summary>
/// <param name="value">Other operand</param>
/// <returns>True if overwriting is fine, false if not.</returns>
internal bool SimilarType(Operand value)
{
// Internal operand type must match.
if (m_Type != value.m_Type) return false;
// If immediate, and a psuedo-type, it must match.
if (m_Type == BytecodeOperandType.Immediate)
{
var baseType = ImmediateTyped.Type.BaseType;
if (baseType == Types.PascalTypeCode.Type || baseType == Types.PascalTypeCode.Instruction || baseType == Types.PascalTypeCode.Function)
{
return baseType == value.ImmediateTyped.Type.BaseType;
}
return baseType != Types.PascalTypeCode.Unknown;
}
return true;
}
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
public enum OperandType : byte
{
/// <summary>
/// Variable or typed immediate
/// </summary>
InlineValue,
/// <summary>
/// No operand
/// </summary>
InlineNone,
/// <summary>
/// Signed 32-bit branch target
/// </summary>
InlineBrTarget,
/// <summary>
/// Two <see cref="InlineValue"/>s
/// </summary>
InlineValueValue,
/// <summary>
/// <see cref="InlineBrTarget"/> and <see cref="InlineValue"/>
/// </summary>
InlineBrTargetValue,
/// <summary>
/// 32-bit function index
/// </summary>
InlineFunction,
/// <summary>
/// 32-bit type index
/// </summary>
InlineType,
/// <summary>
/// Three <see cref="InlineValue"/>s
/// </summary>
InlineCmpValue,
/// <summary>
/// Two <see cref="InlineValue"/>s followed by <see cref="InlineType"/>
/// </summary>
InlineCmpValueType,
/// <summary>
/// Four unsigned 32-bit branch targets (uint.MaxValue for null)
/// </summary>
InlineEH,
/// <summary>
/// <see cref="InlineType"/> followed by a variable
/// </summary>
InlineTypeVariable,
/// <summary>
/// <see cref="InlineValue"/> followed by <see cref="OpCodes.SetFlag"/> second opcode byte
/// </summary>
InlineValueSF,
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
public enum PopEHOpCode : byte
{
EndTry,
EndFinally,
EndCatch,
EndHandler
}
}

View File

@ -0,0 +1,251 @@
using IFPSLib.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
namespace IFPSLib.Emit
{
/// <summary>
/// Internal function implemented by script bytecode.
/// </summary>
public sealed class ScriptFunction : FunctionBase
{
public override IType ReturnArgument { get; set; }
/// <summary>
/// If null, argument information is unknown.
/// </summary>
public override IList<FunctionArgument> Arguments { get; set; }
public IList<Instruction> Instructions = new List<Instruction>();
internal uint CodeOffset;
internal uint CodeLength;
private const char ARGUMENT_TYPE_IN = '@';
private const char ARGUMENT_TYPE_OUT = '!';
internal static ScriptFunction Load(BinaryReader br, Script script, bool exported)
{
var ret = new ScriptFunction();
ret.Exported = exported;
ret.CodeOffset = br.Read<uint>();
ret.CodeLength = br.Read<uint>();
if (exported)
{
var nameLen = br.Read<uint>();
ret.Name = br.ReadAsciiString(nameLen);
// Function declaration for a scriptfunction is ascii strings split by space
var decLen = br.Read<uint>();
var decl = br.ReadAsciiString(decLen).Split(' ');
// First one is return type, or -1 for void
if (!int.TryParse(decl[0], out var retType) || retType == -1) ret.ReturnArgument = null;
else ret.ReturnArgument = script.Types[retType];
// Next are arguments. First char is the argument type ('@' means in, otherwise '!'), followed by the type index
ret.Arguments = new List<FunctionArgument>(decl.Length - 1);
for (int i = 1; i < decl.Length; i++) {
var arg = new FunctionArgument();
arg.ArgumentType = (decl[i][0] == ARGUMENT_TYPE_IN ? FunctionArgumentType.In : FunctionArgumentType.Out);
if (!int.TryParse(decl[i].Substring(1), out var idxType) || idxType < 0) arg.Type = null;
else arg.Type = script.Types[idxType];
arg.Name = string.Format("Arg{0}", i);
ret.Arguments.Add(arg);
}
}
return ret;
}
internal void LoadInstructions(BinaryReader br, Script script)
{
br = br.Slice(CodeOffset, CodeLength);
// Load all instructions
while (br.BaseStream.Position < br.BaseStream.Length)
{
Instructions.Add(Instruction.Load(br, script, this));
}
// Create a table of offset->instruction
var table = new Dictionary<uint, Instruction>();
foreach (var i in Instructions) table.Add(i.Offset, i);
// Walk each instruction and fix up branch targets
foreach (var i in Instructions)
{
switch (i.OpCode.OperandType)
{
case OperandType.InlineBrTarget:
case OperandType.InlineBrTargetValue:
case OperandType.InlineEH:
i.FixUpBranchTargets(table);
break;
}
}
}
public override string ToString()
{
var sb = new StringBuilder(".function");
if (Exported) sb.Append("(export)");
sb.Append(' ');
if (ReturnArgument == null) sb.Append("void ");
else sb.AppendFormat("{0} ", ReturnArgument.Name);
sb.AppendFormat("{0}(", Name);
for (int i = 0; i < Arguments.Count; i++)
{
sb.Append(i == 0 ? "" : ",");
sb.Append(Arguments[i].ToString());
}
sb.Append(')');
return sb.ToString();
}
private string ExportDeclString(Script.SaveContext ctx)
{
if (!Exported) return string.Empty;
int typeIdx;
if (ReturnArgument == null) typeIdx = -1;
else typeIdx = ctx.GetTypeIndex(ReturnArgument);
var sb = new StringBuilder(typeIdx.ToString());
foreach (var arg in Arguments)
{
sb.Append(' ');
sb.Append(arg.ArgumentType == FunctionArgumentType.In ? ARGUMENT_TYPE_IN : ARGUMENT_TYPE_OUT);
sb.Append(ctx.GetTypeIndex(arg.Type));
}
return sb.ToString();
}
/// <summary>
/// Gets the size of a not exported ScriptFunction.
/// </summary>
public override int Size => sizeof(uint) * 2;
/// <summary>
/// Gets the size of all instructions.
/// </summary>
public int InstructionsSize => Instructions.Sum(x => x.Size);
public ArgumentVariable CreateArgumentVariable(int index)
{
var isVoid = ReturnArgument == null;
var ret = ArgumentVariable.Create(index + (isVoid ? 0 : 1), isVoid);
ret.Name = Arguments[index].Name;
return ret;
}
public ArgumentVariable CreateArgumentVariable(int index, string name)
{
var ret = CreateArgumentVariable(index);
ret.Name = name;
return ret;
}
public ArgumentVariable CreateReturnVariable()
{
if (ReturnArgument == null) throw new InvalidOperationException();
return ArgumentVariable.Create(0, false);
}
internal override void Save(BinaryWriter bw, Script.SaveContext ctx)
{
FunctionFlags flags = 0;
var EDecl = ExportDeclString(ctx);
if (Exported)
{
flags |= FunctionFlags.Exported;
}
if (Attributes.Count != 0) flags |= FunctionFlags.HasAttributes;
bw.Write(flags);
// save dummy offset and length, will be overwritten later.
bw.Write<uint>(0);
bw.Write<uint>(0);
if (Exported)
{
bw.WriteAsciiString(Name.ToUpper(), true);
bw.WriteAsciiString(EDecl, true);
}
}
public uint UpdateInstructionOffsets()
{
uint offset = 0;
var count = Instructions.Count;
for (int i = 0; i < count; i++)
{
Instructions[i].Offset = offset;
offset += (uint)Instructions[i].Size;
}
return offset;
}
private Dictionary<Instruction, uint> UpdateInstructionCrossReferencesBeforeSave()
{
do
{
// Build the table.
var table = new Dictionary<Instruction, uint>();
var knownOffsets = new HashSet<uint>();
foreach (var insn in Instructions)
{
if (!knownOffsets.Add(insn.Offset))
{
UpdateInstructionOffsets();
continue;
}
table.Add(insn, insn.Offset);
}
// Fix up the targets.
foreach (var insn in Instructions)
insn.FixUpReferences(table);
return table;
} while (true);
}
public void UpdateInstructionCrossReferences()
{
UpdateInstructionCrossReferencesBeforeSave();
}
internal void SaveInstructions(BinaryWriter bw, Script.SaveContext ctx)
{
UpdateInstructionOffsets();
var table = UpdateInstructionCrossReferencesBeforeSave();
var insnOffset = bw.BaseStream.Position;
if (insnOffset > uint.MaxValue) throw new InvalidOperationException("Instruction offset must be in the first 4GB");
var insnOffset32 = (uint)insnOffset;
foreach (var insn in Instructions)
{
insn.Save(bw, ctx, table);
}
var insnAfter = bw.BaseStream.Position;
var insnsLength = insnAfter - insnOffset;
if (insnsLength > uint.MaxValue) throw new InvalidOperationException("Instructions length cannot be over 4GB");
bw.BaseStream.Position = ctx.GetFunctionOffset(this);
bw.Write<uint>(insnOffset32);
bw.Write<uint>((uint)insnsLength);
bw.BaseStream.Position = insnAfter;
}
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
/// <summary>
/// Type of a set flag operation for the "setflag" instruction at the bytecode level.
/// </summary>
public enum SetFlagOpCode : byte
{
/// <summary>
/// Sets the jump flag if the variable is not zero.
/// </summary>
NotZero,
/// <summary>
/// Sets the jump flag if the variable is zero.
/// </summary>
Zero
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
/// <summary>
/// Describes how values are pushed onto or popped off a stack.
/// </summary>
public enum StackBehaviour : byte
{
/// <summary>
/// No values are popped off the stack
/// </summary>
Pop0,
/// <summary>
/// One value is popped off the stack
/// </summary>
Pop1,
/// <summary>
/// Two values are popped off the stack
/// </summary>
Pop2,
/// <summary>
/// A variable number of values are popped off the stack
/// </summary>
Varpop,
/// <summary>
/// No values are pushed onto the stack
/// </summary>
Push0,
/// <summary>
/// One value is pushed onto the stack
/// </summary>
Push1
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Emit
{
public enum VariableType
{
Global,
Local,
Argument
}
}

159
IFPSLib/Emit/Variables.cs Normal file
View File

@ -0,0 +1,159 @@
using IFPSLib.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Emit
{
/// <summary>
/// Represents a variable that can be represented in an operand.
/// </summary>
public interface IVariable
{
IType Type { get; }
string Name { get; set; }
VariableType VarType { get; }
int Index { get; }
int Size { get; }
}
public abstract class VariableBase : IVariable
{
public IType Type { get; internal set; }
public string Name { get; set; }
public abstract VariableType VarType { get; }
public int Index { get; internal set; }
public int Size => sizeof(uint);
internal const int MAX_GLOBALS = 0x40000000;
internal const int MAX_ARGS = 0x20000000;
internal static VariableBase Load(BinaryReader reader, Script script, ScriptFunction function)
{
var idx = reader.Read<uint>();
if (idx < MAX_GLOBALS) return script.GlobalVariables[(int)idx];
int sIdx = ((int)idx - MAX_GLOBALS - MAX_ARGS);
if (sIdx >= 0) return new LocalVariable(sIdx);
return new ArgumentVariable((-sIdx) - 1, function.ReturnArgument == null);
}
internal abstract void Save(BinaryWriter bw, Script.SaveContext ctx);
}
public sealed class LocalVariable : VariableBase
{
public override VariableType VarType => VariableType.Local;
internal override void Save(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write<uint>((uint)(MAX_GLOBALS + MAX_ARGS + Index));
}
public static LocalVariable Create(int index)
{
if (index < 0) throw new ArgumentOutOfRangeException(nameof(index));
return new LocalVariable(index + 1);
}
internal LocalVariable(int index)
{
Index = index;
Name = string.Format("Var{0}", index);
}
}
public sealed class ArgumentVariable : VariableBase
{
public override VariableType VarType => VariableType.Argument;
internal static ArgumentVariable Create(int index, bool isVoid)
{
if (index < 0 || index >= MAX_ARGS) throw new ArgumentOutOfRangeException(nameof(index));
return new ArgumentVariable(index, isVoid);
}
internal override void Save(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write<uint>((uint)(MAX_GLOBALS + MAX_ARGS - Index - 1));
}
internal ArgumentVariable(int index, bool isVoid)
{
Index = index;
if (!isVoid && Index == 0) Name = "RetVal";
else Name = string.Format("Arg{0}", index + (isVoid ? 1 : 0));
}
}
public sealed class GlobalVariable : VariableBase
{
public bool Exported { get; set; }
public override VariableType VarType => VariableType.Global;
internal GlobalVariable(int index)
{
Index = index;
Name = string.Format("Global{0}", index);
}
public static GlobalVariable Create(int index)
{
if (index < 0 || index >= MAX_GLOBALS) throw new ArgumentOutOfRangeException(nameof(index));
return new GlobalVariable(index);
}
public static GlobalVariable Create(int index, IType type)
{
var ret = Create(index);
ret.Type = type;
return ret;
}
public static GlobalVariable Create(int index, IType type, string name)
{
var ret = Create(index, type);
ret.Name = name;
return ret;
}
internal static GlobalVariable Load(BinaryReader br, Script script, int index)
{
var ret = new GlobalVariable(index);
ret.Type = script.Types[(int)br.Read<uint>()];
var flags = br.ReadByte();
if ((flags & 1) != 0)
{
var len = br.Read<uint>();
ret.Name = br.ReadAsciiString(len);
ret.Exported = true;
}
return ret;
}
public override string ToString()
{
return string.Format(".global{0} {1} {2}", Exported ? "(import)" : "", Type.Name, Name);
}
internal override void Save(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write<uint>((uint)Index);
}
internal void SaveHeader(BinaryWriter bw, Script.SaveContext ctx)
{
// type
bw.Write<int>(ctx.GetTypeIndex(Type));
// flags
bw.Write<bool>(Exported);
if (Exported)
{
bw.WriteAsciiString(Name.ToUpper(), true);
}
}
}
}

79
IFPSLib/Function.cs Normal file
View File

@ -0,0 +1,79 @@
using IFPSLib.Emit;
using IFPSLib.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib
{
[Flags]
public enum FunctionFlags : byte
{
External = (1 << 0),
ExternalWithDeclaration = External | (1 << 1),
Exported = (1 << 1),
HasAttributes = (1 << 2)
}
/// <summary>
/// Represents a function.
/// </summary>
public interface IFunction
{
IList<CustomAttribute> Attributes { get; }
string Name { get; set; }
/// <summary>
/// If null, function returns void.
/// </summary>
IType ReturnArgument { get; set; }
IList<FunctionArgument> Arguments { get; set; }
bool Exported { get; set; }
int Size { get; }
}
/// <summary>
/// Base implementation of a function.
/// </summary>
public abstract class FunctionBase : IFunction
{
public IList<CustomAttribute> Attributes { get; internal set; } = new List<CustomAttribute>();
public string Name { get; set; }
public bool Exported { get; set; }
public abstract IType ReturnArgument { get; set; }
/// <summary>
/// If null, argument information is unknown.
/// </summary>
public abstract IList<FunctionArgument> Arguments { get; set; }
public abstract int Size { get; }
internal static FunctionBase Load(BinaryReader br, Script script)
{
var flags = (FunctionFlags)br.ReadByte();
FunctionBase ret = null;
var exported = (flags & FunctionFlags.Exported) != 0;
if ((flags & FunctionFlags.External) != 0)
{
ret = ExternalFunction.Load(br, script, exported);
} else
{
ret = ScriptFunction.Load(br, script, exported);
}
if ((flags & FunctionFlags.HasAttributes) != 0)
ret.Attributes = CustomAttribute.LoadList(br, script);
return ret;
}
internal abstract void Save(BinaryWriter bw, Script.SaveContext ctx);
}
}

View File

@ -0,0 +1,52 @@
using IFPSLib.Types;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib
{
public enum FunctionArgumentType : byte
{
Out,
In
}
public sealed class FunctionArgument
{
/// <summary>
/// If null, type is unknown.
/// </summary>
public IType Type;
public FunctionArgumentType ArgumentType;
public string Name;
internal static IList<FunctionArgument> LoadForExternal(BinaryReader br)
{
var count = (int)(br.BaseStream.Length - br.BaseStream.Position);
var ret = new List<FunctionArgument>(count);
for (int i = 0; i < count; i++)
{
ret.Add(new FunctionArgument()
{
Type = null,
ArgumentType = br.ReadByte() != 0 ? FunctionArgumentType.Out : FunctionArgumentType.In
});
}
return ret;
}
public override string ToString()
{
var noName = string.IsNullOrEmpty(Name);
return string.Format("{0} {1}{2}{3}",
ArgumentType == FunctionArgumentType.Out ? "__out" : "__in",
Type == null ? "__unknown" : Type.Name,
noName ? "" : " ",
noName ? "" : Name
);
}
}
}

17
IFPSLib/IFPSLib.csproj Normal file
View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NativeMemoryArray" Version="1.2.0" />
<PackageReference Include="SharpFloatLibrary" Version="1.0.4" />
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<Import Project="..\LoadSaveExtensions\LoadSaveExtensions.projitems" Label="Shared" />
</Project>

382
IFPSLib/Script.cs Normal file
View File

@ -0,0 +1,382 @@
using IFPSLib.Types;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Linq;
using Cysharp.Collections;
using IFPSLib.Emit;
using System.Threading;
namespace IFPSLib
{
public sealed class Script
{
private const int VERSION_LOWEST = 12;
public const int VERSION_HIGHEST = 23;
internal const int VERSION_MIN_ATTRIBUTES = 21;
internal const int VERSION_MAX_SETSTACKTYPE = 22; // Is this correct?
internal const int VERSION_MIN_STATICARRAYSTART = 23;
/// <summary>
/// Internal version of this file.
/// </summary>
public int FileVersion;
/// <summary>
/// Entry point. Can be null.
/// </summary>
public IFunction EntryPoint = null;
/// <summary>
/// List of types declared in this script by internal index.
/// </summary>
public IList<IType> Types = new List<IType>();
/// <summary>
/// List of functions declared in this script by internal index.
/// </summary>
public IList<IFunction> Functions = new List<IFunction>();
/// <summary>
/// List of global variables declared in this script by internal index.
/// </summary>
public IList<GlobalVariable> GlobalVariables = new List<GlobalVariable>();
private struct ScriptHeaderAfterMagic
{
// Fields get set directly by memcpy
#pragma warning disable CS0649
internal int
Version, NumTypes, NumFuncs, NumVars, IdxEntryPoint, ImportSize;
#pragma warning restore CS0649
}
public Script(int version)
{
FileVersion = version;
}
public Script() : this(VERSION_HIGHEST) { }
private static Script LoadCore(Stream stream, bool leaveOpen = false)
{
using (var br = new BinaryReader(stream, Encoding.UTF8, leaveOpen))
{
return LoadCore(br);
}
}
private static Script LoadCore(BinaryReader br)
{
{
Span<byte> magic = stackalloc byte[4];
br.Read(magic);
if (magic[0] != 'I' || magic[1] != 'F' || magic[2] != 'P' || magic[3] != 'S') throw new InvalidDataException();
}
var header = br.Read<ScriptHeaderAfterMagic>();
if (header.Version < VERSION_LOWEST || header.Version > VERSION_HIGHEST) throw new InvalidDataException(string.Format("Invalid version: {0}", header.Version));
var ret = new Script(header.Version);
ret.Types = new List<IType>(header.NumTypes);
var typesToNames = new Dictionary<string, IType>();
var samename = new Dictionary<string, int>();
for (int i = 0; i < header.NumTypes; i++)
{
var type = TypeBase.Load(br, ret);
if (string.IsNullOrEmpty(type.Name)) type.Name = string.Format("Type{0}", i);
if (typesToNames.ContainsKey(type.Name))
{
int count = 0;
if (samename.TryGetValue(type.Name, out count)) count++;
else count = 1;
samename[type.Name] = count;
type.Name += "_" + (count + 1);
}
typesToNames.Add(type.Name, type);
ret.Types.Add(type);
}
ret.Functions = new List<IFunction>(header.NumFuncs);
samename = new Dictionary<string, int>();
var funcsToNames = new Dictionary<string, IFunction>();
for (int i = 0; i < header.NumFuncs; i++)
{
var func = FunctionBase.Load(br, ret);
if (funcsToNames.ContainsKey(func.Name))
{
int count = 0;
if (samename.TryGetValue(func.Name, out count)) count++;
else count = 1;
samename[func.Name] = count;
func.Name += "_" + (count + 1);
}
funcsToNames.Add(func.Name, func);
ret.Functions.Add(func);
}
ret.GlobalVariables = new List<GlobalVariable>(header.NumVars);
for (int i = 0; i < header.NumVars; i++)
{
ret.GlobalVariables.Add(GlobalVariable.Load(br, ret, i));
}
foreach (var func in ret.Functions.OfType<ScriptFunction>())
{
func.LoadInstructions(br, ret);
}
if (header.IdxEntryPoint >= 0 && header.IdxEntryPoint < header.NumFuncs)
ret.EntryPoint = ret.Functions[header.IdxEntryPoint];
return ret;
}
public static Script Load(byte[] bytes) => LoadCore(new MemoryStream(bytes, 0, bytes.Length, false, true));
public static Script Load(MemoryStream stream) => LoadCore(stream, true);
public static Script Load(UnmanagedMemoryStream stream) => LoadCore(stream);
public static Script Load(Stream stream) => LoadAsync(stream).GetAwaiter().GetResult();
public static async Task<Script> LoadAsync(Stream stream)
{
// use a NativeMemoryArray to allow for full 64-bit length
using (var buffer = new NativeMemoryArray<byte>(stream.Length, true))
{
var ums = buffer.AsStream(FileAccess.ReadWrite);
await stream.CopyToAsync(ums);
ums.Position = 0;
return LoadCore(ums);
}
}
internal class SaveContext
{
internal readonly Dictionary<IType, int> tblTypes;
internal readonly Dictionary<IFunction, int> tblFunctions;
internal readonly Dictionary<GlobalVariable, int> tblGlobals;
internal readonly Dictionary<IFunction, long> tblFunctionOffsets;
internal readonly int FileVersion;
internal SaveContext(Script script)
{
tblTypes = new Dictionary<IType, int>();
tblFunctions = new Dictionary<IFunction, int>();
tblGlobals = new Dictionary<GlobalVariable, int>();
tblFunctionOffsets = new Dictionary<IFunction, long>();
FileVersion = script.FileVersion;
for (int i = 0; i < script.Types.Count; i++) tblTypes.Add(script.Types[i], i);
for (int i = 0; i < script.Functions.Count; i++) tblFunctions.Add(script.Functions[i], i);
for (int i = 0; i < script.GlobalVariables.Count; i++) tblGlobals.Add(script.GlobalVariables[i], i);
}
internal int GetTypeIndex(IType type)
{
if (type == null)
{
throw new ArgumentOutOfRangeException(string.Format("Used an unknown type"));
}
if (!tblTypes.TryGetValue(type, out var idx))
{
throw new KeyNotFoundException(string.Format("Used unreferenced type {0}, make sure it's added to the Types list.", type.Name));
}
return idx;
}
internal int GetFunctionIndex(IFunction function)
{
if (function == null)
{
throw new ArgumentOutOfRangeException(string.Format("Used an unknown function"));
}
if (!tblFunctions.TryGetValue(function, out var idx))
{
throw new KeyNotFoundException(string.Format("Used unreferenced function {0}, make sure it's added to the Functions list.", function.Name));
}
return idx;
}
internal long GetFunctionOffset(IFunction function)
{
if (function == null)
{
throw new ArgumentOutOfRangeException(string.Format("Used an unknown function"));
}
if (!tblFunctionOffsets.TryGetValue(function, out var idx))
{
throw new KeyNotFoundException(string.Format("Used unreferenced function {0}, make sure it's added to the Functions list.", function.Name));
}
return idx + sizeof(FunctionFlags);
}
}
private void SaveCore(BinaryWriter bw)
{
// Set up save context.
var ctx = new SaveContext(this);
bw.Write((byte)'I');
bw.Write((byte)'F');
bw.Write((byte)'P');
bw.Write((byte)'S');
{
var header = default(ScriptHeaderAfterMagic);
header.Version = FileVersion;
header.NumTypes = Types.Count;
header.NumFuncs = Functions.Count;
header.NumVars = GlobalVariables.Count;
var idxEntryPoint = -1;
if (EntryPoint != null) idxEntryPoint = ctx.GetFunctionIndex(EntryPoint);
header.IdxEntryPoint = idxEntryPoint;
header.ImportSize = 0;
bw.Write(header);
}
// Write types.
foreach (var type in Types.OfType<TypeBase>()) type.Save(bw, ctx);
// Write functions.
foreach (var func in Functions.OfType<FunctionBase>())
{
ctx.tblFunctionOffsets.Add(func, bw.BaseStream.Position);
func.Save(bw, ctx);
if (func.Attributes.Count != 0) CustomAttribute.SaveList(bw, func.Attributes, ctx);
}
// Write global variables.
foreach (var global in GlobalVariables)
{
global.SaveHeader(bw, ctx);
}
// Write function bodies.
foreach (var func in Functions.OfType<ScriptFunction>())
{
func.SaveInstructions(bw, ctx);
}
}
private void SaveCore(Stream stream, bool leaveOpen = false)
{
using (var bw = new BinaryWriter(stream, Encoding.UTF8, leaveOpen))
{
SaveCore(bw);
}
}
public byte[] Save()
{
using (var ms = new MemoryStream())
{
Save(ms);
return ms.ToArray();
}
}
public void Save(MemoryStream stream) => SaveCore(stream, true);
public void Save(Stream stream) => SaveAsync(stream).GetAwaiter().GetResult();
public async Task SaveAsync(Stream stream)
{
using (var ms = new MemoryStream())
{
// ensure stream can be written to before saving to memory
ms.CopyTo(stream);
Save(ms);
ms.Position = 0;
await ms.CopyToAsync(stream);
}
}
/// <summary>
/// Disassembles the script.
/// </summary>
/// <returns>Disassembly string</returns>
public string Disassemble()
{
var sb = new StringBuilder();
sb.AppendFormat(".version {0}", FileVersion).AppendLine().AppendLine();
if (EntryPoint != null) sb.AppendFormat(".entry {0}", EntryPoint.Name).AppendLine().AppendLine();
foreach (var type in Types)
{
foreach (var attr in type.Attributes)
{
sb.AppendLine(attr.ToString());
}
sb.AppendLine(type.ToString());
}
if (Types.Any()) sb.AppendLine();
foreach (var global in GlobalVariables)
{
sb.AppendLine(global.ToString());
}
if (GlobalVariables.Any()) sb.AppendLine();
foreach (var func in Functions)
{
foreach (var attr in func.Attributes)
{
sb.AppendLine(attr.ToString());
}
sb.AppendLine(func.ToString());
var sf = func as ScriptFunction;
if (sf != null)
{
var stackcount = 0;
bool changed = true;
foreach (var insn in sf.Instructions)
{
sb.Append(insn.ToString(true));
changed = true;
switch (insn.OpCode.StackBehaviourPush)
{
case StackBehaviour.Push1:
stackcount++;
break;
default:
changed = false;
break;
}
if (!changed)
{
changed = true;
switch (insn.OpCode.StackBehaviourPop)
{
case StackBehaviour.Pop1:
stackcount--;
break;
case StackBehaviour.Pop2:
stackcount -= 2;
break;
default:
changed = false;
break;
}
}
if (changed) sb.AppendFormat(" ; StackCount = {0}", stackcount);
sb.AppendLine();
}
}
sb.AppendLine();
}
sb.AppendLine();
return sb.ToString();
}
}
}

315
IFPSLib/TypedData.cs Normal file
View File

@ -0,0 +1,315 @@
using IFPSLib.Types;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.IO;
using SharpFloat.FloatingPoint;
using System.Runtime.InteropServices;
using System.Collections;
using System.Runtime.CompilerServices;
namespace IFPSLib
{
/// <summary>
/// Represents an object with a PascalScript base type.
/// </summary>
public class TypedData
{
public IType Type { get; }
public object Value { get; }
internal TypedData(IType type, object value)
{
Type = type;
Value = value;
}
public static TypedData Create<TType>(PrimitiveType type, TType value)
{
if (!type.BaseType.EqualsType(typeof(TType))) throw new ArgumentOutOfRangeException(nameof(value));
return new TypedData(type, value);
}
public static TypedData Create(FunctionPointerType type, IFunction value)
{
return new TypedData(type, value);
}
public static TypedData Create<TType>(TType value)
=> Create(PrimitiveType.Create<TType>(), value);
internal static TypedData Create(IType value)
{
return new TypedData(TypeType.Instance, value);
}
internal static TypedData Create(Emit.Instruction value)
{
return new TypedData(InstructionType.Instance, value);
}
internal static TypedData Create(IFunction value)
{
return new TypedData(ImmediateFunctionType.Instance, value);
}
internal static TypedData Create(BitArray bitarr)
{
return new TypedData(new SetType(bitarr.Length), bitarr);
}
public TType ValueAs<TType>()
{
return (TType)Value;
}
internal static TypedData Load(BinaryReader br, Script script)
{
var idxType = br.Read<uint>();
var type = script.Types[(int)idxType];
switch (type.BaseType)
{
case PascalTypeCode.S8:
return new TypedData(type, br.Read<sbyte>());
case PascalTypeCode.U8:
return new TypedData(type, br.Read<byte>());
case PascalTypeCode.Char:
return new TypedData(type, (char)br.Read<byte>());
case PascalTypeCode.S16:
return new TypedData(type, br.Read<short>());
case PascalTypeCode.U16:
return new TypedData(type, br.Read<ushort>());
case PascalTypeCode.WideChar:
return new TypedData(type, br.Read<char>());
case PascalTypeCode.S32:
return new TypedData(type, br.Read<int>());
case PascalTypeCode.U32:
return new TypedData(type, br.Read<uint>());
case PascalTypeCode.S64:
return new TypedData(type, br.Read<long>());
case PascalTypeCode.Single:
return new TypedData(type, br.Read<float>());
case PascalTypeCode.Double:
return new TypedData(type, br.Read<double>());
case PascalTypeCode.Extended:
// BUGBUG: there must be something beter than this... but for now, it'll do
return new TypedData(type, decimal.Parse(br.Read<ExtF80>().ToString()));
case PascalTypeCode.Currency:
return new TypedData(type, new CurrencyWrapper(decimal.FromOACurrency(br.Read<long>())));
case PascalTypeCode.PChar:
case PascalTypeCode.String:
var asciilen = br.Read<uint>();
return new TypedData(type, br.ReadAsciiString(asciilen));
case PascalTypeCode.WideString:
case PascalTypeCode.UnicodeString:
var unicodelen = br.Read<uint>();
return new TypedData(type, br.ReadUnicodeString(unicodelen));
case PascalTypeCode.ProcPtr:
var funcIdx = br.Read<uint>();
return new TypedData(type, script.Functions[(int)funcIdx]);
case PascalTypeCode.Set:
return new TypedData(type, ((SetType)type).Load(br));
default:
throw new ArgumentOutOfRangeException(nameof(type));
}
}
internal void WriteValue<T>(BinaryWriter bw) where T : unmanaged
{
bw.Write(ValueAs<T>());
}
internal void Save(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write<int>(ctx.GetTypeIndex(Type));
switch (Type.BaseType)
{
case PascalTypeCode.S8:
WriteValue<sbyte>(bw);
break;
case PascalTypeCode.U8:
WriteValue<byte>(bw);
break;
case PascalTypeCode.Char:
bw.Write<byte>((byte)ValueAs<char>());
break;
case PascalTypeCode.S16:
WriteValue<short>(bw);
break;
case PascalTypeCode.U16:
WriteValue<ushort>(bw);
break;
case PascalTypeCode.WideChar:
WriteValue<char>(bw);
break;
case PascalTypeCode.S32:
WriteValue<int>(bw);
break;
case PascalTypeCode.U32:
WriteValue<uint>(bw);
break;
case PascalTypeCode.S64:
WriteValue<long>(bw);
break;
case PascalTypeCode.Single:
WriteValue<float>(bw);
break;
case PascalTypeCode.Double:
WriteValue<double>(bw);
break;
case PascalTypeCode.Extended:
if (!ExtF80.TryParse(ValueAs<decimal>().ToString(), out var extf))
throw new ArgumentOutOfRangeException("Value {0} cannot fit into an 80-bit floating point number");
bw.Write(extf);
break;
case PascalTypeCode.Currency:
bw.Write<long>(decimal.ToOACurrency(ValueAs<CurrencyWrapper>().WrappedObject));
break;
case PascalTypeCode.PChar:
case PascalTypeCode.String:
bw.WriteAsciiString(ValueAs<string>(), true);
break;
case PascalTypeCode.WideString:
case PascalTypeCode.UnicodeString:
bw.Write<int>(Encoding.Unicode.GetByteCount(ValueAs<string>()) / sizeof(short));
bw.WriteUnicodeString(ValueAs<string>());
break;
case PascalTypeCode.ProcPtr:
bw.Write<int>(ctx.GetFunctionIndex(ValueAs<IFunction>()));
break;
case PascalTypeCode.Set:
((SetType)Type).Save(bw, ValueAs<BitArray>());
break;
default:
throw new InvalidOperationException();
}
}
public int Size
{
get
{
const int HEADER = sizeof(uint); // type index
switch (Type.BaseType)
{
case PascalTypeCode.S8:
return sizeof(sbyte) + HEADER;
case PascalTypeCode.U8:
case PascalTypeCode.Char:
return sizeof(byte) + HEADER;
case PascalTypeCode.S16:
return sizeof(short) + HEADER;
case PascalTypeCode.U16:
return sizeof(ushort) + HEADER;
case PascalTypeCode.WideChar:
return sizeof(char) + HEADER;
case PascalTypeCode.S32:
return sizeof(int) + HEADER;
case PascalTypeCode.U32:
return sizeof(uint) + HEADER;
case PascalTypeCode.S64:
return sizeof(long) + HEADER;
case PascalTypeCode.Single:
return sizeof(float) + HEADER;
case PascalTypeCode.Double:
return sizeof(double) + HEADER;
case PascalTypeCode.Extended:
return Unsafe.SizeOf<ExtF80>() + HEADER;
case PascalTypeCode.Currency:
return sizeof(long) + HEADER;
case PascalTypeCode.PChar:
case PascalTypeCode.String:
return sizeof(uint) + Encoding.ASCII.GetByteCount(ValueAs<string>()) + HEADER;
case PascalTypeCode.WideString:
case PascalTypeCode.UnicodeString:
return sizeof(uint) + Encoding.Unicode.GetByteCount(ValueAs<string>()) + HEADER;
case PascalTypeCode.ProcPtr:
return sizeof(uint) + HEADER;
case PascalTypeCode.Set:
return (Type as SetType).ByteSize + HEADER;
case PascalTypeCode.Type:
return HEADER; // 32-bit type index only
case PascalTypeCode.Instruction:
return HEADER; // 32-bit offset
case PascalTypeCode.Function:
return HEADER; // 32-bit function index only
default:
throw new InvalidOperationException(string.Format("Data of type {0} cannot be serialised", Type.BaseType));
}
}
}
public override string ToString()
{
switch (Type.BaseType)
{
case PascalTypeCode.Type:
return ValueAs<IType>().Name;
case PascalTypeCode.Instruction:
if (Value == null) return "null";
return string.Format("loc_{0}", ValueAs<Emit.Instruction>().Offset.ToString("x"));
case PascalTypeCode.ProcPtr:
return string.Format("{0}({1})", Type.Name, ValueAs<IFunction>().Name);
case PascalTypeCode.Function:
return ValueAs<IFunction>().Name;
default:
if (Value is string)
{
return string.Format("{0}({1})", Type.Name, ValueAs<string>().ToLiteral());
}
if (Value is char)
{
return string.Format("{0}({1})", Type.Name, new string(ValueAs<char>(), 1).ToLiteral());
}
if (Value is BitArray)
{
var val = ValueAs<BitArray>();
var sb = new StringBuilder(Type.Name);
sb.Append("(0b");
for (int i = val.Count - 1; i >= 0; i--)
{
sb.Append(val[i] ? '1' : '0');
}
sb.Append(')');
return sb.ToString();
}
return string.Format("{0}({1})", Type.Name, Value);
}
}
}
}

View File

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Types
{
public abstract class ArrayTypeBase : TypeBase
{
public abstract IType ElementType { get; internal set; }
}
public sealed class ArrayType : ArrayTypeBase
{
public override IType ElementType { get; internal set; }
public override PascalTypeCode BaseType => PascalTypeCode.Array;
private ArrayType() { }
public ArrayType(IType Element)
{
ElementType = Element;
}
internal static new ArrayType Load(BinaryReader br, Script script)
{
var ret = new ArrayType();
var idxType = br.Read<uint>();
ret.ElementType = script.Types[(int)idxType];
return ret;
}
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write<int>(ctx.GetTypeIndex(ElementType));
}
public override string ToString()
{
return string.Format(base.ToString() + "array({0}) {1}", ElementType.Name, Name);
}
}
public sealed class StaticArrayType : ArrayTypeBase
{
public override IType ElementType { get; internal set; }
public override PascalTypeCode BaseType => PascalTypeCode.StaticArray;
public int Size { get; internal set; }
public int StartIndex { get; internal set; }
private const int MAXIMIUM_SIZE = (int)(uint.MaxValue / sizeof(uint));
private StaticArrayType() { }
public StaticArrayType(IType Element, int size, int startIndex = 0)
{
ElementType = Element;
if (size > MAXIMIUM_SIZE) throw new ArgumentOutOfRangeException(nameof(size));
Size = size;
StartIndex = startIndex;
}
internal static new StaticArrayType Load(BinaryReader br, Script script)
{
var ret = new StaticArrayType();
var idxType = br.Read<uint>();
ret.ElementType = script.Types[(int)idxType];
ret.Size = br.Read<int>();
if (ret.Size > MAXIMIUM_SIZE) throw new InvalidDataException();
if (script.FileVersion >= Script.VERSION_MIN_STATICARRAYSTART)
{
ret.StartIndex = br.Read<int>();
}
return ret;
}
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write<int>(ctx.GetTypeIndex(ElementType));
if (Size > MAXIMIUM_SIZE) throw new InvalidDataException(string.Format("Static array length (0x{0:x}) too large, maximum 0x{1:x}", Size, MAXIMIUM_SIZE));
bw.Write<int>(Size);
if (ctx.FileVersion >= Script.VERSION_MIN_STATICARRAYSTART)
bw.Write<int>(StartIndex);
}
public override string ToString()
{
return string.Format(base.ToString() + "array({0},{1},{2}) {3}", ElementType.Name, Size, StartIndex, Name);
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Types
{
public class ClassType : TypeBase
{
public override PascalTypeCode BaseType => PascalTypeCode.Class;
public string InternalName { get; set; }
private ClassType() { }
public ClassType(string intName)
{
InternalName = intName;
}
internal static ClassType Load(BinaryReader br)
{
var length = br.Read<uint>();
var type = new ClassType();
type.InternalName = type.Name = br.ReadAsciiString(length);
return type;
}
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
bw.WriteAsciiString(InternalName.ToUpper(), true);
}
public override string ToString()
{
return string.Format(base.ToString() + "class({0}) {1}", InternalName, Name);
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Types
{
public class ComInterfaceType : TypeBase
{
public override PascalTypeCode BaseType => PascalTypeCode.Interface;
public Guid Guid;
public ComInterfaceType(Guid guid)
{
Guid = guid;
}
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write(Guid);
}
public override string ToString()
{
return string.Format(base.ToString() + "interface(\"{0}\") {1}", Guid, Name);
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Types
{
public class FunctionPointerType : TypeBase
{
public override PascalTypeCode BaseType => PascalTypeCode.ProcPtr;
public bool HasReturnValue;
public IList<FunctionArgumentType> Arguments;
public FunctionPointerType(bool hasReturn, IList<FunctionArgumentType> args)
{
HasReturnValue = hasReturn;
Arguments = args;
}
internal static FunctionPointerType Load(BinaryReader br)
{
var length = br.Read<uint>() & 0xff;
Span<byte> bytes = stackalloc byte[(int)length];
br.Read(bytes);
var hasReturn = bytes[0] != 0;
var args = new List<FunctionArgumentType>((int)length - 1);
for (int i = 1; i < length; i++)
{
args.Add(bytes[i] == 1 ? FunctionArgumentType.Out : FunctionArgumentType.In);
}
return new FunctionPointerType(hasReturn, args);
}
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
int len = (Arguments.Count + 1);
if (len > 0xff) throw new InvalidOperationException(string.Format("{0} arguments present, the maximum is 254", Arguments.Count));
bw.Write<int>(len);
bw.Write<bool>(HasReturnValue);
for (int i = 0; i < Arguments.Count; i++)
{
bw.Write<bool>(Arguments[i] == FunctionArgumentType.Out);
}
}
public override string ToString()
{
var sb = new StringBuilder(base.ToString() + "funcptr(");
sb.Append(HasReturnValue ? "returnsval" : "void");
sb.Append('(');
for (int i = 0; i < Arguments.Count; i++)
{
sb.Append(i == 0 ? "" : ",");
sb.Append(Arguments[i] == FunctionArgumentType.In ? "__in" : "__out");
}
sb.AppendFormat(")) {0}", Name);
return sb.ToString();
}
}
}

104
IFPSLib/Types/IType.cs Normal file
View File

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Types
{
public interface IType
{
PascalTypeCode BaseType { get; }
string Name { get; set; }
bool Exported { get; set; }
IList<CustomAttribute> Attributes { get; }
}
public abstract class TypeBase : IType
{
public abstract PascalTypeCode BaseType { get; }
public string Name { get; set; }
public bool Exported { get; set; }
public IList<CustomAttribute> Attributes { get; internal set; } = new List<CustomAttribute>();
private const byte EXPORTED_FLAG = 0x80;
internal static TypeBase Load(BinaryReader br, Script script)
{
byte baseType = br.ReadByte();
bool isExported = (baseType & EXPORTED_FLAG) != 0;
var typeCode = (PascalTypeCode) (baseType & ~EXPORTED_FLAG);
var ret = LoadCore(br, script, typeCode);
if (isExported)
{
var length = br.Read<uint>();
ret.Name = br.ReadAsciiString(length);
ret.Exported = true;
}
if (script.FileVersion >= Script.VERSION_MIN_ATTRIBUTES)
{
ret.Attributes = CustomAttribute.LoadList(br, script);
}
return ret;
}
private static TypeBase LoadCore(BinaryReader br, Script script, PascalTypeCode typeCode)
{
if (typeCode.IsPrimitive()) return new PrimitiveType(typeCode);
switch (typeCode)
{
case PascalTypeCode.Class:
return ClassType.Load(br);
case PascalTypeCode.ProcPtr:
return FunctionPointerType.Load(br);
case PascalTypeCode.Interface:
return new ComInterfaceType(br.Read<Guid>());
case PascalTypeCode.Set:
return new SetType((int)br.Read<uint>());
case PascalTypeCode.StaticArray:
return StaticArrayType.Load(br, script);
case PascalTypeCode.Array:
return ArrayType.Load(br, script);
case PascalTypeCode.Record:
return RecordType.Load(br, script);
default:
throw new InvalidDataException(string.Format("Invalid type: ", typeCode));
}
}
internal void Save(BinaryWriter bw, Script.SaveContext ctx)
{
var code = (byte)BaseType;
if (Exported) code |= EXPORTED_FLAG;
bw.Write(code);
SaveCore(bw, ctx);
if (Exported)
{
bw.WriteAsciiString(Name.ToUpper(), true);
}
if (ctx.FileVersion >= Script.VERSION_MIN_ATTRIBUTES)
{
CustomAttribute.SaveList(bw, Attributes, ctx);
}
}
internal virtual void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
throw new InvalidOperationException();
}
public override string ToString()
{
return string.Format(".type{0} ", Exported ? "(export)" : "");
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Types
{
/// <summary>
/// Pseudo-type describing a function index.
/// </summary>
public class ImmediateFunctionType : TypeBase
{
public override PascalTypeCode BaseType => PascalTypeCode.Function;
private ImmediateFunctionType() { }
public static readonly ImmediateFunctionType Instance = new ImmediateFunctionType();
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Types
{
/// <summary>
/// Psuedo-type describing an instruction.
/// </summary>
public class InstructionType : TypeBase
{
private InstructionType() { }
public override PascalTypeCode BaseType => PascalTypeCode.Instruction;
public readonly static InstructionType Instance = new InstructionType();
}
}

View File

@ -0,0 +1,148 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace IFPSLib.Types
{
/// <summary>
/// A PascalScript primitive type at the bytecode level.
/// </summary>
public enum PascalTypeCode : byte
{
ReturnAddress,
U8,
S8,
U16,
S16,
U32,
S32,
Single,
Double,
Extended,
String,
Record,
Array,
Pointer,
PChar,
ResourcePointer,
Variant,
S64,
Char,
WideString,
WideChar,
ProcPtr,
StaticArray,
Set,
Currency,
Class,
Interface,
NotificationVariant,
UnicodeString,
PsuedoTypeStart = 0x80,
Enum = 0x81,
Type,
ExtClass,
// Invalid pseudo-typecode used to specify an unknown type
Unknown = 0xFD,
// Invalid pseudo-typecode used for ImmediateFunctionType
Function = 0xFE,
// Invalid pseudo-typecode used for InstructionType
Instruction = 0xFF
}
public static partial class EnumHelpers
{
private static readonly IReadOnlyDictionary<Type, PascalTypeCode> s_NetTypeToTypeCode = new Dictionary<Type, PascalTypeCode>()
{
{ typeof(byte), PascalTypeCode.U8 },
{ typeof(sbyte), PascalTypeCode.S8 },
{ typeof(ushort), PascalTypeCode.S16 },
{ typeof(short), PascalTypeCode.U16 },
{ typeof(uint), PascalTypeCode.U32 },
{ typeof(int), PascalTypeCode.S32 },
{ typeof(long), PascalTypeCode.S64 },
{ typeof(float), PascalTypeCode.Single },
{ typeof(double), PascalTypeCode.Double },
{ typeof(decimal), PascalTypeCode.Extended },
{ typeof(CurrencyWrapper), PascalTypeCode.Currency },
{ typeof(VariantWrapper), PascalTypeCode.Variant },
{ typeof(char), PascalTypeCode.WideChar },
{ typeof(string), PascalTypeCode.UnicodeString }, // can also be TypeCode.WideString, at least one known compiledcode.bin uses just UnicodeString though
{ typeof(IntPtr), PascalTypeCode.Pointer },
{ typeof(IFunction), PascalTypeCode.ProcPtr },
{ typeof(IType), PascalTypeCode.Type },
{ typeof(BitArray), PascalTypeCode.Set }
//{ typeof(Array), TypeCode.Array },
//{ typeof(ValueType), TypeCode.Record },
//{ typeof(object), TypeCode.Class }
};
private static readonly IReadOnlyDictionary<PascalTypeCode, Type> s_TypeCodeToNetType = new Dictionary<PascalTypeCode, Type>()
{
{ PascalTypeCode.U8, typeof(byte) },
{ PascalTypeCode.S8, typeof(sbyte) },
{ PascalTypeCode.S16, typeof(short) },
{ PascalTypeCode.U16, typeof(ushort) },
{ PascalTypeCode.S32, typeof(int) },
{ PascalTypeCode.U32, typeof(uint) },
{ PascalTypeCode.S64, typeof(long) },
{ PascalTypeCode.Single, typeof(float) },
{ PascalTypeCode.Double, typeof(double) },
{ PascalTypeCode.Extended, typeof(decimal) },
{ PascalTypeCode.Currency, typeof(CurrencyWrapper) },
{ PascalTypeCode.Variant, typeof(VariantWrapper) },
{ PascalTypeCode.WideChar, typeof(char) },
{ PascalTypeCode.Char, typeof(char) },
{ PascalTypeCode.UnicodeString, typeof(string) },
{ PascalTypeCode.WideString, typeof(string) },
{ PascalTypeCode.PChar, typeof(string) },
{ PascalTypeCode.String, typeof(string) },
{ PascalTypeCode.Pointer, typeof(IntPtr) },
{ PascalTypeCode.ProcPtr, typeof(IFunction) },
{ PascalTypeCode.Type, typeof(IType) },
{ PascalTypeCode.Set, typeof(BitArray) }
};
private static readonly ISet<PascalTypeCode> s_PrimitiveTypes = new HashSet<PascalTypeCode>()
{
PascalTypeCode.U8,
PascalTypeCode.S8,
PascalTypeCode.U16,
PascalTypeCode.S16,
PascalTypeCode.U32,
PascalTypeCode.S32,
PascalTypeCode.S64,
PascalTypeCode.Single,
PascalTypeCode.Double,
PascalTypeCode.Currency,
PascalTypeCode.Extended,
PascalTypeCode.String,
PascalTypeCode.Pointer,
PascalTypeCode.PChar,
PascalTypeCode.Variant,
PascalTypeCode.Char,
PascalTypeCode.UnicodeString,
PascalTypeCode.WideString,
PascalTypeCode.WideChar
};
public static PascalTypeCode ToIFPSTypeCode(this Type type) => s_NetTypeToTypeCode[type];
public static bool IsPrimitive(this PascalTypeCode code) => s_PrimitiveTypes.Contains(code);
public static bool EqualsType(this PascalTypeCode code, Type type)
{
if (!s_TypeCodeToNetType.TryGetValue(code, out var result)) return false;
return result == type;
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Types
{
public class PrimitiveType : TypeBase
{
public override PascalTypeCode BaseType { get; }
public PrimitiveType(PascalTypeCode baseType)
{
if (!baseType.IsPrimitive()) throw new ArgumentOutOfRangeException(nameof(baseType));
BaseType = baseType;
Name = baseType.ToString();
}
public static PrimitiveType Create<TType>()
{
return new PrimitiveType(typeof(TType).ToIFPSTypeCode());
}
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
}
public override string ToString()
{
return string.Format(base.ToString() + "primitive({0}) {1}", BaseType, Name);
}
}
}

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Types
{
/// <summary>
/// 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.
/// </summary>
public class RecordType : TypeBase
{
public override PascalTypeCode BaseType => PascalTypeCode.Record;
/// <summary>
/// Types of the record's elements.
/// </summary>
public IList<IType> Elements { get; internal set; } = new List<IType>();
private RecordType() { }
public RecordType(IList<IType> elements)
{
Elements = elements;
}
internal static new RecordType Load(BinaryReader br, Script script)
{
var ret = new RecordType();
var count = (int)br.Read<uint>();
ret.Elements = new List<IType>(count);
for (int i = 0; i < count; i++)
{
ret.Elements.Add(script.Types[(int)br.Read<uint>()]);
}
return ret;
}
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write<int>(Elements.Count);
for (int i = 0; i < Elements.Count; i++)
{
bw.Write<int>(ctx.GetTypeIndex(Elements[i]));
}
}
public override string ToString()
{
var sb = new StringBuilder(base.ToString() + "record(");
for (int i = 0; i < Elements.Count; i++)
{
sb.Append(i == 0 ? "" : ",");
sb.Append(Elements[i].Name);
}
sb.AppendFormat(") {0}", Name);
return sb.ToString();
}
}
}

73
IFPSLib/Types/SetType.cs Normal file
View File

@ -0,0 +1,73 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IFPSLib.Types
{
/// <summary>
/// Set of an enumeration, implemented internally as a bit vector.
/// </summary>
public class SetType : TypeBase
{
public override PascalTypeCode BaseType => PascalTypeCode.Set;
private int m_SizeInBits;
public int BitSize
{
get => m_SizeInBits;
internal set
{
if (value < 0 || value > 0x100) throw new ArgumentOutOfRangeException(nameof(value));
m_SizeInBits = value;
}
}
public int ByteSize
{
get
{
var ret = m_SizeInBits / 8; // could be >> 3 except readability -- should get optimised by something
var extraBits = m_SizeInBits % 8; // could be & 7 except readability -- should get optimised by something
// the "clever" (branchless) implementation would be: (extraBits / extraBits)
// instead, have the optimiser do its job
var hasExtraBits = extraBits != 0;
if (hasExtraBits) ret++;
return ret;
}
}
public SetType(int sizeInBits)
{
BitSize = sizeInBits;
}
internal BitArray Load(BinaryReader br)
{
var bytes = new byte[ByteSize];
br.Read(bytes);
var ret = new BitArray(bytes);
ret.Length = BitSize;
return ret;
}
internal void Save(BinaryWriter bw, BitArray val)
{
var bytes = new byte[ByteSize];
val.CopyTo(bytes, 0);
bw.Write(bytes);
}
internal override void SaveCore(BinaryWriter bw, Script.SaveContext ctx)
{
bw.Write<int>(BitSize);
}
public override string ToString()
{
return string.Format(base.ToString() + "set({0}) {1}", BitSize, Name);
}
}
}

17
IFPSLib/Types/TypeType.cs Normal file
View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Types
{
/// <summary>
/// Pseudo-type describing a type.
/// </summary>
public class TypeType : TypeBase
{
private TypeType() { }
public override PascalTypeCode BaseType => PascalTypeCode.Type;
public readonly static TypeType Instance = new TypeType();
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace IFPSLib.Types
{
/// <summary>
/// Pseudo-type describing an unknown value.
/// </summary>
public class UnknownType : TypeBase
{
private UnknownType() { }
public override PascalTypeCode BaseType => PascalTypeCode.Unknown;
public readonly static UnknownType Instance = new UnknownType();
}
}

62
IFPSTools.NET.sln Normal file
View File

@ -0,0 +1,62 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32505.173
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFPSLib", "IFPSLib\IFPSLib.csproj", "{E83BF0B3-4427-41E8-9A99-9DF8951DE711}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IFPSLib.Tests", "IFPSLib.Tests\IFPSLib.Tests.csproj", "{8EEC05F9-82F0-4EC4-94C6-728F80D50959}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ifpsdasm", "ifpsdasm\ifpsdasm.csproj", "{DFFB4698-B963-4D91-9362-C6E34D0C4DE5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ifpsasm", "ifpsasm\ifpsasm.csproj", "{8C1A1532-1A40-46DB-BBDF-B4489BD0F966}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IFPSAsmLib", "IFPSAsmLib\IFPSAsmLib.csproj", "{63B7741E-D712-4277-B345-F5B77AC80EB1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uninno", "uninno\uninno.csproj", "{5092701D-8D7E-4AB4-9877-DADCE1266B3D}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "LoadSaveExtensions", "LoadSaveExtensions\LoadSaveExtensions.shproj", "{AF654578-B67D-4E5E-9CA6-E8AED9D0051A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E83BF0B3-4427-41E8-9A99-9DF8951DE711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E83BF0B3-4427-41E8-9A99-9DF8951DE711}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E83BF0B3-4427-41E8-9A99-9DF8951DE711}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E83BF0B3-4427-41E8-9A99-9DF8951DE711}.Release|Any CPU.Build.0 = Release|Any CPU
{8EEC05F9-82F0-4EC4-94C6-728F80D50959}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8EEC05F9-82F0-4EC4-94C6-728F80D50959}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8EEC05F9-82F0-4EC4-94C6-728F80D50959}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8EEC05F9-82F0-4EC4-94C6-728F80D50959}.Release|Any CPU.Build.0 = Release|Any CPU
{DFFB4698-B963-4D91-9362-C6E34D0C4DE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFFB4698-B963-4D91-9362-C6E34D0C4DE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFFB4698-B963-4D91-9362-C6E34D0C4DE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFFB4698-B963-4D91-9362-C6E34D0C4DE5}.Release|Any CPU.Build.0 = Release|Any CPU
{8C1A1532-1A40-46DB-BBDF-B4489BD0F966}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C1A1532-1A40-46DB-BBDF-B4489BD0F966}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C1A1532-1A40-46DB-BBDF-B4489BD0F966}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C1A1532-1A40-46DB-BBDF-B4489BD0F966}.Release|Any CPU.Build.0 = Release|Any CPU
{63B7741E-D712-4277-B345-F5B77AC80EB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63B7741E-D712-4277-B345-F5B77AC80EB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63B7741E-D712-4277-B345-F5B77AC80EB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63B7741E-D712-4277-B345-F5B77AC80EB1}.Release|Any CPU.Build.0 = Release|Any CPU
{5092701D-8D7E-4AB4-9877-DADCE1266B3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5092701D-8D7E-4AB4-9877-DADCE1266B3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5092701D-8D7E-4AB4-9877-DADCE1266B3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5092701D-8D7E-4AB4-9877-DADCE1266B3D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {38867BBD-57E7-47B5-934D-F29B37678766}
EndGlobalSection
GlobalSection(SharedMSBuildProjectFiles) = preSolution
LoadSaveExtensions\LoadSaveExtensions.projitems*{5092701d-8d7e-4ab4-9877-dadce1266b3d}*SharedItemsImports = 4
LoadSaveExtensions\LoadSaveExtensions.projitems*{af654578-b67d-4e5e-9ca6-e8aed9d0051a}*SharedItemsImports = 13
LoadSaveExtensions\LoadSaveExtensions.projitems*{e83bf0b3-4427-41e8-9a99-9df8951de711}*SharedItemsImports = 5
EndGlobalSection
EndGlobal

21
LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,451 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Text;
internal static class LoadSaveExtensions
{
private static MethodInfo s_GetBuffer = typeof(MemoryStream).GetMethod("InternalGetBuffer", BindingFlags.Instance | BindingFlags.NonPublic);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte[] InternalGetBuffer(this MemoryStream stream)
{
try
{
return stream.GetBuffer();
}
catch
{
return (byte[])s_GetBuffer.Invoke(stream, Array.Empty<object>());
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckLength(this Stream stream, Span<byte> span, long position)
{
var expectedLength = span.Length;
var remainingLength = stream.Length - position;
if (remainingLength < expectedLength) throw new EndOfStreamException();
}
private static BinaryReader SliceCore(this MemoryStream stream, long position, long length)
{
checked
{
if (position > int.MaxValue || length > int.MaxValue) throw new EndOfStreamException();
var buffer = new byte[(int)length];
Buffer.BlockCopy(stream.InternalGetBuffer(), (int)position, buffer, 0, (int)length);
return new BinaryReader(new MemoryStream(buffer, 0, buffer.Length, false, true), Encoding.UTF8, true);
}
}
private static BinaryReader SliceCore(this UnmanagedMemoryStream stream, long position, long length)
{
checked
{
if ((position + length) > stream.Length) throw new EndOfStreamException();
unsafe
{
return new BinaryReader(new UnmanagedMemoryStream(&stream.PositionPointer[position - stream.Position], length), Encoding.UTF8, true);
}
}
}
private static string ReadTerminatedCore(this MemoryStream stream, int position, byte terminator)
{
long pos64 = position;
if (position < 0) pos64 = stream.Position;
var underlyingBuffer = stream.InternalGetBuffer();
for (long i = 0; i + pos64 < stream.Length; i++)
{
if (underlyingBuffer[pos64 + i] == terminator)
{
stream.Position += i + 1;
unsafe
{
fixed (byte* ptr = &underlyingBuffer[pos64])
return Encoding.ASCII.GetString(ptr, (int)i);
}
}
}
return string.Empty;
}
private static string ReadTerminatedCore(this UnmanagedMemoryStream stream, int position, byte terminator)
{
long pos64 = position;
if (position < 0) pos64 = stream.Position;
unsafe
{
var ptr = stream.PositionPointer;
for (long i = 0; i + pos64 < stream.Length; i++)
{
if (ptr[i] == terminator)
{
stream.Position += i + 1;
return Encoding.ASCII.GetString(ptr, (int)i);
}
}
}
return string.Empty;
}
private static void WriteTerminatedCore(this MemoryStream stream, string str, byte terminator)
{
// Ensure that there's enough space available in the buffer.
var oldPosition = stream.Position;
var count = Encoding.ASCII.GetByteCount(str);
stream.Position += count;
stream.WriteByte(terminator);
// Write the actual bytes.
var underlyingBuffer = stream.InternalGetBuffer();
unsafe
{
fixed (byte* ptr = &underlyingBuffer[oldPosition])
fixed (char* pStr = str)
Encoding.ASCII.GetBytes(pStr, str.Length, ptr, count);
}
}
private static void WriteAsciiStringCore(this MemoryStream stream, string str, bool withLength)
{
var count = Encoding.ASCII.GetByteCount(str);
// Write the length if needed.
if (withLength)
{
stream.WriteCore(AsSpan(ref count).AsBytes());
}
// Ensure that there's enough space available in the buffer.
var oldPosition = stream.Position;
stream.Position += count - 1;
stream.WriteByte(0);
// Write the actual bytes.
var underlyingBuffer = stream.InternalGetBuffer();
unsafe
{
fixed (byte* ptr = &underlyingBuffer[oldPosition])
fixed (char* pStr = str)
Encoding.ASCII.GetBytes(pStr, str.Length, ptr, count);
}
}
private static void WriteUnicodeStringCore(this MemoryStream stream, string str)
{
// Ensure that there's enough space available in the buffer.
var oldPosition = stream.Position;
var count = Encoding.Unicode.GetByteCount(str);
stream.Position += count - 1;
stream.WriteByte(0);
// Write the actual bytes.
var underlyingBuffer = stream.InternalGetBuffer();
unsafe
{
fixed (byte* ptr = &underlyingBuffer[oldPosition])
fixed (char* pStr = str)
Encoding.Unicode.GetBytes(pStr, str.Length, ptr, count);
}
}
private static void ReadCore(this MemoryStream stream, Span<byte> span, int position)
{
long pos64 = position;
if (position < 0) pos64 = stream.Position;
stream.CheckLength(span, pos64);
var underlyingBuffer = stream.InternalGetBuffer();
unsafe
{
fixed (byte* ptr = &underlyingBuffer[pos64])
Buffer.MemoryCopy(ptr, Unsafe.AsPointer(ref span[0]), span.Length, span.Length);
}
stream.Position += span.Length;
}
private static void ReadCore(this UnmanagedMemoryStream stream, Span<byte> span, int position)
{
long pos64 = position;
if (position < 0) pos64 = stream.Position;
stream.CheckLength(span, pos64);
unsafe
{
Buffer.MemoryCopy(&stream.PositionPointer[pos64 - stream.Position], Unsafe.AsPointer(ref span[0]), span.Length, span.Length);
}
stream.Position += span.Length;
}
private static void WriteCore(this MemoryStream stream, Span<byte> span)
{
// Ensure that there's enough space available in the buffer.
var oldPosition = stream.Position;
stream.Position += span.Length - 1;
stream.WriteByte(0);
// Write the actual bytes.
var underlyingBuffer = stream.InternalGetBuffer();
unsafe
{
fixed (byte* ptr = &underlyingBuffer[oldPosition])
Buffer.MemoryCopy(Unsafe.AsPointer(ref span[0]), ptr, span.Length, span.Length);
}
}
internal static void Read<T>(this BinaryReader br, Span<T> span) where T : unmanaged
{
Read(br, span.AsBytes(), -1);
}
internal static void Write<T>(this BinaryWriter bw, Span<T> span) where T : unmanaged
{
Write(bw, span.AsBytes());
}
internal static string ReadAsciiString(this BinaryReader br, uint length, int position = -1)
{
if (length == 0) return string.Empty;
const int MAX_LENGTH = 0x40000000;
if (length > MAX_LENGTH) throw new ArgumentOutOfRangeException(nameof(length));
Span<byte> span = stackalloc byte[(int)length];
br.Read(span, position);
unsafe
{
return Encoding.ASCII.GetString((byte*)Unsafe.AsPointer(ref span[0]), (int)length);
}
}
internal static void WriteAsciiString(this BinaryWriter bw, string str, bool withLength = false)
{
if (str.Length == 0) return;
const int MAX_LENGTH = 0x40000000;
if (str.Length > MAX_LENGTH) throw new ArgumentOutOfRangeException(nameof(str));
bw.GetBaseCore().WriteAsciiStringCore(str, withLength);
return;
}
internal static void WriteAsciiStringStatic(this BinaryWriter bw, string str, int length)
{
if (length == 0) return;
var _base = bw.GetBaseCore();
if (str.Length > length) _base.WriteAsciiStringCore(str.Substring(0, length), false);
else if (str.Length == length) _base.WriteAsciiStringCore(str, false);
else
{
_base.WriteAsciiStringCore(str, false);
// write a terminator
_base.WriteByte(0);
// write another terminator at the end of the buffer
_base.Position += (length - str.Length - 2);
_base.WriteByte(0);
}
}
internal static string ReadAsciiStringTerminated(this BinaryReader br, byte terminator = 0, int position = -1)
{
var stream = br.BaseStream;
switch (stream)
{
case MemoryStream ms:
return ms.ReadTerminatedCore(position, terminator);
case UnmanagedMemoryStream ums:
return ums.ReadTerminatedCore(position, terminator);
default:
throw new ArgumentOutOfRangeException();
}
}
internal static void WriteAsciiStringTerminated(this BinaryWriter bw, string str, byte terminator = 0)
{
if (str.Length == 0) return;
const int MAX_LENGTH = 0x40000000;
if (str.Length > MAX_LENGTH) throw new ArgumentOutOfRangeException(nameof(str));
bw.GetBaseCore().WriteTerminatedCore(str, terminator);
return;
}
internal static string ParseAsciiString(this Span<byte> bytes, int offset, int length)
{
if (offset + length > bytes.Length) return string.Empty;
unsafe
{
return Encoding.ASCII.GetString((byte*)Unsafe.AsPointer(ref bytes[offset]), length);
}
}
internal static bool EqualsAsciiString(this Span<byte> bytes, int offset, string str)
{
return bytes.ParseAsciiString(offset, str.Length) == str;
}
internal static string ReadUnicodeString(this BinaryReader br, uint length, int position = -1)
{
if (length == 0) return string.Empty;
const int MAX_LENGTH = 0x40000000;
if (length > MAX_LENGTH) throw new ArgumentOutOfRangeException(nameof(length));
length *= 2;
Span<byte> span = stackalloc byte[(int)length];
br.Read(span, position);
unsafe
{
return Encoding.Unicode.GetString((byte*)Unsafe.AsPointer(ref span[0]), (int)length);
}
}
internal static void WriteUnicodeString(this BinaryWriter bw, string str)
{
if (str.Length == 0) return;
const int MAX_LENGTH = 0x40000000;
if (str.Length > MAX_LENGTH) throw new ArgumentOutOfRangeException(nameof(str));
bw.GetBaseCore().WriteUnicodeStringCore(str);
return;
}
internal static void Read(this BinaryReader br, Span<byte> span, int position = -1)
{
if (span.Length == 0) return;
var stream = br.BaseStream;
switch (stream)
{
case MemoryStream ms:
ms.ReadCore(span, position);
return;
case UnmanagedMemoryStream ums:
ums.ReadCore(span, position);
return;
default:
throw new ArgumentOutOfRangeException();
}
}
private static MemoryStream GetBaseCore(this BinaryWriter bw)
{
var stream = bw.BaseStream as MemoryStream;
if (stream == null) throw new ArgumentOutOfRangeException();
return stream;
}
internal static void Write(this BinaryWriter bw, Span<byte> span)
{
if (span.Length == 0) return;
bw.GetBaseCore().WriteCore(span);
}
internal static void Write(this BinaryWriter bw, MemoryStream ms)
{
bw.Write(new Span<byte>(ms.InternalGetBuffer(), 0, (int)ms.Length));
}
internal static byte[] GetBase(this BinaryWriter bw)
{
return bw.GetBaseCore().InternalGetBuffer();
}
internal static BinaryReader Slice(this BinaryReader br, long position, long length)
{
var stream = br.BaseStream;
switch (stream)
{
case MemoryStream ms:
return ms.SliceCore(position, length);
case UnmanagedMemoryStream ums:
return ums.SliceCore(position, length);
default:
throw new ArgumentOutOfRangeException();
}
}
internal static T Read<T>(this BinaryReader br) where T : unmanaged
{
var val = default(T);
var span = AsSpan(ref val);
br.Read(span);
return val;
}
internal static void Write<T>(this BinaryWriter bw, T val) where T : unmanaged
{
var span = AsSpan(ref val);
bw.Write(span);
}
internal static Span<T> AsSpan<T>(ref T val) where T : unmanaged
{
unsafe
{
return new Span<T>(Unsafe.AsPointer(ref val), 1);
}
}
internal static Span<byte> AsBytes<T>(this Span<T> span) where T : unmanaged
{
unsafe
{
return new Span<byte>(Unsafe.AsPointer(ref span[0]), span.Length * Unsafe.SizeOf<T>());
}
}
internal static Span<T> AsOther<T>(this Span<byte> span) where T : unmanaged
{
unsafe
{
return new Span<T>(Unsafe.AsPointer(ref span[0]), span.Length / Unsafe.SizeOf<T>());
}
}
internal static string ToLiteral(this string input)
{
StringBuilder literal = new StringBuilder(input.Length + 2);
literal.Append("\"");
foreach (var c in input)
{
switch (c)
{
case '\"': literal.Append("\\\""); break;
case '\\': literal.Append(@"\\"); break;
case '\0': literal.Append(@"\0"); break;
case '\a': literal.Append(@"\a"); break;
case '\b': literal.Append(@"\b"); break;
case '\f': literal.Append(@"\f"); break;
case '\n': literal.Append(@"\n"); break;
case '\r': literal.Append(@"\r"); break;
case '\t': literal.Append(@"\t"); break;
case '\v': literal.Append(@"\v"); break;
default:
// ASCII printable character
if ((c >= 0x20 && c <= 0x7e) || !char.IsControl(c))
{
literal.Append(c);
// As UTF16 escaped character
}
else if (c < 0x100)
{
literal.Append(@"\x");
literal.Append(((int)c).ToString("x2"));
}
else
{
literal.Append(@"\u");
literal.Append(((int)c).ToString("x4"));
}
break;
}
}
literal.Append("\"");
return literal.ToString();
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>af654578-b67d-4e5e-9ca6-e8aed9d0051a</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>LoadSaveExtensions</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Extensions.cs" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>af654578-b67d-4e5e-9ca6-e8aed9d0051a</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="LoadSaveExtensions.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

View File

@ -0,0 +1,37 @@
; Pointer isn't used directly by this script.
; However, pushvar pushes a Pointer to the stack, and the runtime requires it to be present.
; If it's not present, trying to call any function in the script will cause null deref.
.type primitive(Pointer) Pointer
; Declare types used by this script.
.type primitive(S32) S32
.type primitive(U32) U32
.type primitive(UnicodeString) UnicodeString
.type(export) primitive(U8) TMsgBoxType ; used internally by MsgBox
.type(export) primitive(U8) Boolean
; Declare an imported external function.
; function MsgBox(const Text: String; const Typ: TMsgBoxType; const Buttons: Integer): Integer;
.function(import) external internal returnsval MsgBox(__val __unknown,__val __unknown,__val __unknown)
; Import from a DLL.
.function(import) external dll("kernelbase.dll", "ExitProcess") __stdcall void ExitProcess(__val __unknown)
; Define the ntstatus code.
.define U32(0xC0000001) STATUS_UNSUCCESSFUL
; Declare the exported script function.
.function(export) Boolean InitializeUninstall()
pushtype S32 ; Declares a variable to store MsgBox's return value
.alias MsgBoxRet Var1 ; Give that variable a name
push S32(0) ; MB_OK ; Buttons argument for MsgBox()
push TMsgBoxType(0) ; mbInformation ; Typ argument for MsgBox()
push UnicodeString("Hello IFPS world! Let's call ExitProcess()!") ; Text argument for MsgBox()
pushvar MsgBoxRet ; Push pointer to return value
call MsgBox ; Call MsgBox()
; Callee should clear stack after a function call.
; We are however about to call ExitProcess which will not return.
push STATUS_UNSUCCESSFUL ; Make you cry
call ExitProcess ; Say goodbye
; PascalScript runtime interprets VM bytecode. Execution will not reach here!

29
examples/HelloWorld.asm Normal file
View File

@ -0,0 +1,29 @@
; Pointer isn't used directly by this script.
; However, pushvar pushes a Pointer to the stack, and the runtime requires it to be present.
; If it's not present, trying to call any function in the script will cause null deref.
.type primitive(Pointer) Pointer
; Declare types used by this script.
.type primitive(S32) S32
.type primitive(UnicodeString) UnicodeString
.type(export) primitive(U8) TMsgBoxType ; used internally by MsgBox
.type(export) primitive(U8) Boolean
; Declare an imported external function.
; function MsgBox(const Text: String; const Typ: TMsgBoxType; const Buttons: Integer): Integer;
.function(import) external internal returnsval MsgBox(__val __unknown,__val __unknown,__val __unknown)
; Declare the exported script function.
.function(export) Boolean InitializeUninstall()
pushtype S32 ; Declares a variable to store MsgBox's return value
.alias MsgBoxRet Var1 ; Give that variable a name
push S32(0) ; MB_OK ; Buttons argument for MsgBox()
push TMsgBoxType(0) ; mbInformation ; Typ argument for MsgBox()
push UnicodeString("Hello IFPS world!") ; Text argument for MsgBox()
pushvar MsgBoxRet ; Pointer to return value
call MsgBox ; Call MsgBox()
; Callee should clear stack after a function call.
; However, "ret" instruction will clear the stack for us.
assign RetVal, Boolean(0) ; Prepare to return 0
ret ; Return to caller.

View File

@ -0,0 +1,33 @@
; Pointer isn't used directly by this script.
; However, pushvar pushes a Pointer to the stack, and the runtime requires it to be present.
; If it's not present, trying to call any function in the script will cause null deref.
.type primitive(Pointer) Pointer
; Declare types used by this script.
.type primitive(S32) S32
.type primitive(String) AsciiString
.type primitive(UnicodeString) UnicodeString
.type(export) primitive(U8) TMsgBoxType ; used internally by MsgBox
.type(export) primitive(U8) Boolean
; Declare an imported external function.
; function MsgBox(const Text: String; const Typ: TMsgBoxType; const Buttons: Integer): Integer;
.function(import) external internal returnsval MsgBox(__val __unknown,__val __unknown,__val __unknown)
; Declare the following function to have an Event attribute.
; See https://jrsoftware.org/ishelp/index.php?topic=scriptevents&anchor=eventattributes
[Event(AsciiString("InitializeUninstall"))] ; Called on uninstall start.
; Declare the script function. Does not need to be exported as it has an Event attribute.
.function Boolean init()
pushtype S32 ; Declares a variable to store MsgBox's return value
.alias MsgBoxRet Var1 ; Give that variable a name
push S32(0) ; MB_OK ; Buttons argument for MsgBox()
push TMsgBoxType(0) ; mbInformation ; Typ argument for MsgBox()
push UnicodeString("Hello IFPS world!") ; Text argument for MsgBox()
pushvar MsgBoxRet ; Pointer to return value
call MsgBox ; Call MsgBox()
; Callee should clear stack after a function call.
; However, "ret" instruction will clear the stack for us.
assign RetVal, Boolean(0) ; Prepare to return 0
ret ; Return to caller.

14
ifpsasm/App.config Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

21
ifpsasm/Program.cs Normal file
View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace ifpsasm
{
internal class Program
{
static void Main(string[] args)
{
var arg = args[0];
var ext = Path.GetExtension(arg);
var bin = arg.Substring(0, arg.Length - ext.Length) + ".bin";
var script = IFPSAsmLib.Assembler.Assemble(File.ReadAllText(args[0]));
script.Save(File.OpenWrite(bin));
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ifpsasm")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ifpsasm")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("8c1a1532-1a40-46db-bbdf-b4489bd0f966")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

78
ifpsasm/ifpsasm.csproj Normal file
View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8C1A1532-1A40-46DB-BBDF-B4489BD0F966}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>ifpsasm</RootNamespace>
<AssemblyName>ifpsasm</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IFPSAsmLib\IFPSAsmLib.csproj">
<Project>{63b7741e-d712-4277-b345-f5b77ac80eb1}</Project>
<Name>IFPSAsmLib</Name>
</ProjectReference>
<ProjectReference Include="..\IFPSLib\IFPSLib.csproj">
<Project>{e83bf0b3-4427-41e8-9a99-9df8951de711}</Project>
<Name>IFPSLib</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

7
ifpsasm/packages.config Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
<package id="System.Memory" version="4.5.4" targetFramework="net472" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net472" />
</packages>

6
ifpsdasm/App.config Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

25
ifpsdasm/Program.cs Normal file
View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using IFPSLib;
using IFPSLib.Emit;
namespace ifpsdasm
{
internal class Program
{
static void Main(string[] args)
{
foreach (var arg in args)
{
var ext = Path.GetExtension(arg);
var dis = arg.Substring(0, arg.Length - ext.Length) + ".txt";
using (var stream = File.OpenRead(arg))
File.WriteAllText(dis, Script.Load(stream).Disassemble());
}
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ifpsdasm")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ifpsdasm")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("dffb4698-b963-4d91-9362-c6e34d0c4de5")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

60
ifpsdasm/ifpsdasm.csproj Normal file
View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{DFFB4698-B963-4D91-9362-C6E34D0C4DE5}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>ifpsdasm</RootNamespace>
<AssemblyName>ifpsdasm</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IFPSLib\IFPSLib.csproj">
<Project>{e83bf0b3-4427-41e8-9a99-9df8951de711}</Project>
<Name>IFPSLib</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

106
readme.md Normal file
View File

@ -0,0 +1,106 @@
# IFPSTools.NET
Successor to IFPSTools; for working with RemObject PascalScript compiled bytecode files.
Written in C#, libraries target .NET Standard 2.0 and console applications target .NET Framework.
Contains the following:
## IFPSLib
Library for reading, modifying, creating, writing and disassembling compiled IFPS scripts. Saving a loaded script without modification is expected to output an identical binary, not doing so is considered a bug.
The API is modeled on dnlib's.
A known bug is that the `Extended` primitive in IFPSLib will always refer internally to an 80-bit floating point value; any compiled IFPS script intended for an architecture that is not x86 (32-bit) will use 64-bit floating point values.
## IFPSAsmLib
Library implementing an assembler for IFPS scripts.
Depends on IFPSLib.
Assembling the output of IFPSLib's disassembler is expected to output an identical binary, not doing so is considered a bug.
## ifpsdasm
Wrapper around IFPSLib's disassember functionality.
Usage: `ifpsdasm CompiledCode.bin` will disassemble `CompiledCode.bin` into `CompiledCode.txt`.
## ifpsasm
Wrapper around IFPSAsmLib.
Usage: `ifpsasm file.txt` will assemble `file.txt` into `file.bin`
## uninno
The Inno Setup Uninstaller Configuration Creator.
This tool creates an Inno Setup uninstaller `*.dat` file (ie, `unins000.dat`) from a compiled IFPS script.
This allows the usage of the Inno Setup uninstaller as a lolbin; most systems would have several versions already on the system, and signed samples can be found quite easily (even MS signed a few: Skype, VSCode, Azure Storage Explorer...)
See `uninno --help` for usage instructions.
`innounp` does not extract the uninstaller, but for a signed sample, the uninstaller equals the setup core executable; so the uninstaller can be dumped by running an Inno Setup installer under a debugger and setting a breakpoint on `CreateProcessW`, then copying it out of `%TEMP%`.
After dumping an uninstaller, the required version number can be obtained by getting xref to the string `"Install was done in 64-bit mode but not running 64-bit Windows now"`; further up should be `mov ecx, <version constant> ; mov dx, 0x20` or similar.
Bit 31 means Unicode; `0x86000500` means `6.0.5u`; `0x06000500` means `6.0.5`.
## Differences from earlier IFPSTools
Compared to the earlier IFPSTools, IFPSTools.NET implements file saving, modifying, assembling...
The disassembler also includes additional functionality that was not implemented in the earlier IFPSTools, for example COM vtable functions and function/type attributes all disassemble correctly.
## IFPS assembly format
Quoted string literals are similar to C# regular string literals, except `\U` is not allowed, and `\x` specifies exactly one byte (two nibbles).
Declarations:
- `.version <int32>` - script file version. If not present, will be set to `VERSION_HIGHEST` (`23`) by default.
- `.entry <funcname>` - specifies the entry point of the script, which must have the declaration `.function(export) void name()`.
- `.type <declaration> <name>` - declares a named type (which can be exported if `.type(export)` is used), of which the following are allowed:
- `primitive(<prim>)` - primitive type, of which `prim` can be `U8` (unsigned 8-bit integer), `S8` (signed 8-bit integer) , `U16` (unsigned 16-bit integer), `S16` (signed 16-bit integer), `U32` (unsigned 32-bit integer), `S32` (signed 32-bit integer), `S64` (signed 64-bit integer), `Single` (32-bit float), `Double` (64-bit float), `Currency` (OLE Automation tagCY), `Extended` (80-bit float), `String` (ASCII string), `Pointer`, `PChar` , `Variant` (OLE Automation VARIANT), `Char` (ASCII character), `UnicodeString`, `WideString` (same as `UnicodeString`), or `WideChar` (UTF-16 character).
- `array(<type>)` - variable length array, where `type` is the name of a previously declared type.
- `array(<type>,<length>,<startindex>)` - static array, where `type` is the name of a previously declared type; `length` is the number of elements in the array; and `startindex` is the base index of the array.
- `class(<internalname>)` - an internally-defined class, where `internalname` is the internal name of that class (for example `class(TMainForm)`).
- `interface("guidstring")` - a COM interface, of a GUID that is specified as a string; example: `interface("00000000-0000-0000-C000-000000000046")` is IUnknown.
- `funcptr(<declaration>)` - a function pointer, where `declaration` is an external function declaration: `void|returnsval (__in|__out,...)`
- `record(...)` - a record (ie, structure), where the body is the list of element types which must be previously-defined: for example `record(U8,U8,U16)` would be the equivalent of `struct { U8; U8; U16; }`. Elements have no name and are accessed by their element index.
- `set(<bitsize>)` - a set (ie, bit array), of `bitsize` bits in length.
- `.global <type> <name>` - declares a global variable, which can be exported if `.global(export)` is used, of a previously declared type and specified name.
- `.function(export) external <externaltype> void|returnsval name(externalargs...)` - declares an external function, which in practise must always be exported. External functions do not store argument types, as such an external argument must be declared in the form `__in|__out|__val|__ref __unknown` (`__ref` is equivalent to `__out` and `__val` is equivalent to `__in`). The following types of external functions are allowed:
- `com(<vtableindex>) callingconvention` - a COM vtable function, where `vtableindex` is the vtable index (not offset) of the function to call, for example `com(1) __stdcall` would refer to `IUnknown::AddRef`.
- `class(<classname>, <funcname>) callingconvention` - an internally implemented class function or property, for example `class(Class, CastToType) __pascal` or `class(TControl, Left, property) __pascal`.
- `dll("dllname", "procname") callingconvention` - an exported function from a DLL, for example `dll("kernelbase.dll", "ExitProcess") __stdcall`. Additional `delayload` and `alteredsearchpath` arguments are also supported.
- `internal` - an internally implemented function, for example `MsgBox()` in Inno Setup.
- The allowed calling conventions are `__stdcall`, `__cdecl`, `__pascal` and `__fastcall`.
- `.function(export) void|<type> name(...)` - declares a function implemented as PascalScript bytecode, which must be followed by at least one instruction. The specified return type must have been previously declared. Parameters are specified as `__in|__out|__val|__ref type name`, for example `__in TControl Arg1`. (`__ref` is equivalent to `__out` and `__val` is equivalent to `__in`)
- `.alias <name> <varname>` - must be specified as part of a PascalScript bytecode function. Declares an alias of a variable name, valid until the end of the function. Local variables are normally numbered as `Var1` (etc); declaring an alias allows a local variable to be given a name.
- `.define <immediate> <name>` - defines a name to any immediate operand to be used in any instruction. Example: `.define U32(0xC0000001) STATUS_UNSUCCESSFUL`.
Attributes in the form of `[attribute(...)]` are allowed to be specified before functions and types. They only have meaning to the host application; Inno Setup uses exactly one attribute - functions can have an `event(UnicodeString)` attribute, see the documentation. An example of that would be: `[event(UnicodeString("InitializeUninstall"))]`.
Labels are allowed as part of a PascalScript bytecode function, in the form of `label:`. Labels only exist for a single function, and can be used as an operand in branch instructions. A label can not have the name `null`.
Instruction operands have several forms:
- Operands that refer to an instruction do so via a label. For the `starteh` instruction, `null` is allowed to specify an empty operand.
- Operands that refer to a type (like in the `pushtype` instruction) do so via the type name.
- Operands that refer to a function (like the `call` instruction) do so via the function name.
- Operands that refer to a variable only (the obsolete `setstacktype` instruction) do so via the variable name.
- Other operands can refer to a variable (via its name), a constant (via the syntax `type(value)`; function pointers use the function name, strings use quoted literals); or an array or record variable indexed by integer constant (`RecordOrArrayVar[0]`) or by variable name (`RecordOrArrayVar[IndexVar]`).
## Examples
The `examples` directory contains a few example scripts, intended to be used with uninno:
- `HelloWorld.asm` is a simple hello world example.
- `HelloWorldWithAttribute.asm` demonstrates an initialisation function using an `Event` attribute.
- `DllImportExample.asm` demonstrates importing a function from a DLL.
## IFPS runtime considerations
It is possible to assemble or save a script which would be considered invalid by the runtime.
Notably, even if it is not used by your script, a compiled script without the `primitive(Pointer)` type included is invalid; the runtime expects it to be present and calling any function in a script without such a type present leads to a null pointer dereference.

6
uninno/App.config Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

109
uninno/CRC32.cs Normal file
View File

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace uninno
{
/// <summary>
/// Port of the same CRC32 code used by inno setup
/// </summary>
public static class CRC32
{
// inno setup precalcs the table, just hardcode it
private static readonly uint[] s_Table =
{
0, 0x77073096, 0xEE0E612C, 0x990951BA,
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};
public static uint Update(uint CurCRC, ReadOnlySpan<byte> Buf)
{
for (int i = 0; i < Buf.Length; i++)
{
CurCRC = s_Table[(byte)(CurCRC ^ Buf[i])] ^ (CurCRC >> 8);
}
return CurCRC;
}
public static uint Get(ReadOnlySpan<byte> Buf) {
return Update(uint.MaxValue, Buf) ^ uint.MaxValue;
}
public static void Save(BinaryWriter bw, Action<BinaryWriter> SaveBody)
{
var pos = bw.BaseStream.Position;
SaveBody(bw);
bw.BaseStream.Position -= sizeof(uint);
var len = (int)(bw.BaseStream.Position - pos);
var buf = bw.GetBase();
var crc = Get(new ReadOnlySpan<byte>(buf, (int)pos, len));
bw.Write<uint>(crc);
}
}
}

175
uninno/LogFile.cs Normal file
View File

@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
namespace uninno
{
public enum RecordType : ushort
{
UserDefined = 0x01,
StartInstall = 0x10,
EndInstall = 0x11,
CompiledCode = 0x20,
Run = 0x80,
DeleteDirOrFiles = 0x81,
DeleteFile = 0x82,
DeleteGroupOrItem = 0x83,
IniDeleteEntry = 0x84,
IniDeleteSection = 0x85,
RegDeleteEntireKey = 0x86,
RegClearValue = 0x87,
RegDeleteKeyIfEmpty = 0x88,
RegDeleteValue = 0x89,
DecrementSharedCount = 0x8A,
RefreshFileAssoc = 0x8B,
MexCheck = 0x8C,
}
internal struct BlockHeader
{
internal uint Size, NotSize, CRC;
internal BlockHeader(uint Size, uint CRC)
{
this.Size = Size;
NotSize = ~Size;
this.CRC = CRC;
}
}
public struct Record
{
public byte[] Data;
private readonly RecordType type;
private readonly uint ExtraData;
public Record(RecordType type, byte[] data, uint ExtraData = 0)
{
this.type = type;
Data = data;
this.ExtraData = ExtraData;
}
private const int HEADER_LENGTH = sizeof(RecordType) + sizeof(uint) + sizeof(uint);
private static void WriteToSpan<T>(ref Span<byte> span, T val) where T : unmanaged
{
span.AsOther<T>()[0] = val;
span = span.Slice(Unsafe.SizeOf<T>());
}
private static bool TryWrite(Span<byte> pageBuffer, ReadOnlySpan<byte> source, ref int bufOffset, ref int recOffset, bool body)
{
if (bufOffset >= pageBuffer.Length) return true;
var sourceOffset = recOffset;
if (body) sourceOffset -= HEADER_LENGTH;
var realLen = Math.Min(pageBuffer.Length - bufOffset, source.Length - sourceOffset);
if (realLen == 0) return true;
var realSource = source.Slice(sourceOffset, realLen);
realSource.CopyTo(pageBuffer.Slice(bufOffset));
recOffset += realLen;
bufOffset += realLen;
return realLen + sourceOffset != source.Length;
}
internal bool FillBuffer(Span<byte> pageBuffer, ref int bufOffset, ref int recOffset)
{
if (recOffset < HEADER_LENGTH)
{
Span<byte> header = stackalloc byte[HEADER_LENGTH];
{
var part = header;
WriteToSpan(ref part, type);
WriteToSpan(ref part, ExtraData);
WriteToSpan(ref part, Data.Length);
//WriteToSpan(ref part, (byte)0xFE); // << 32bit length, negative means unicode
//WriteToSpan(ref part, Data.Length * (Unicode ? -1 : 1));
}
if (TryWrite(pageBuffer, header, ref bufOffset, ref recOffset, false)) return true;
}
return TryWrite(pageBuffer, Data, ref bufOffset, ref recOffset, true);
}
}
public class LogFile
{
public LogHeader Header = new LogHeader();
public List<Record> Records = new List<Record>();
public static MemoryStream ReadBody(Stream stream)
{
var ms = new MemoryStream();
using (var br = new BinaryReader(stream, Encoding.UTF8, true))
{
while (stream.Position < stream.Length)
{
// length
var size = br.ReadUInt32();
// ~length
var notsize = br.ReadUInt32();
// crc
var crc = br.ReadUInt32();
if (~size != notsize) throw new InvalidDataException("Block header incorrect");
// read the block
var block = new byte[size];
stream.Read(block, 0, (int)size);
// check the crc
if (CRC32.Get(block) != crc) throw new InvalidDataException("Block CRC incorrect");
// write to memorystream
ms.Write(block, 0, (int)size);
}
}
ms.Position = 0;
return ms;
}
public void Save(BinaryWriter bw)
{
// write dummy header
var pos = bw.BaseStream.Position;
Header.Unmanaged.NumRecs = Records.Count;
bw.BaseStream.Position += LogHeader.ByteLength - 1;
bw.BaseStream.WriteByte(0);
const int PAGE_LENGTH = 0x1000;
Span<byte> pageBuffer = stackalloc byte[PAGE_LENGTH];
int bufOffset = 0, recOffset = 0;
foreach (var record in Records)
{
while (record.FillBuffer(pageBuffer, ref bufOffset, ref recOffset))
{
// Buffer is full. Write this block.
var block = new BlockHeader(PAGE_LENGTH, CRC32.Get(pageBuffer));
bw.Write(block);
bw.Write(pageBuffer);
bufOffset = 0;
}
recOffset = 0;
}
if (bufOffset != 0)
{
// Buffer was partially wrote to. Write the final block.
var partial = pageBuffer.Slice(0, bufOffset);
var block = new BlockHeader((uint)bufOffset, CRC32.Get(partial));
bw.Write(block);
bw.Write(partial);
}
// Fill in the file length and write the header.
var posAfter = bw.BaseStream.Position;
Header.Unmanaged.EndOffset = (uint)bw.BaseStream.Length;
bw.BaseStream.Position = pos;
Header.Save(bw);
bw.BaseStream.Position = pos;
}
}
}

93
uninno/LogHeader.cs Normal file
View File

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;
using System.Runtime.CompilerServices;
namespace uninno
{
[Flags]
public enum LogFlags : uint
{
AdminInstalled = 1 << 0,
DisableRecordChecksums = 1 << 1,
ModernStyle = 1 << 2,
AlwaysRestart = 1 << 3,
ChangesEnvironment = 1 << 4,
Win64 = 1 << 5,
PowerUserInstalled = 1 << 6,
AdminInstallMode = 1 << 7,
}
public class LogHeader
{
private const string HEADER_32 = "Inno Setup Uninstall Log (b)";
private const string HEADER_64 = "Inno Setup Uninstall Log (b) 64-bit";
[StructLayout(LayoutKind.Explicit, Size = sizeof(int) * 27)]
public struct ReservedData
{
}
public struct UnmanagedPart
{
#pragma warning disable CS0649 // Most of this is don't care, some gets set by reference anyway...
internal int Version, NumRecs;
internal uint EndOffset;
internal LogFlags Flags;
internal ReservedData Reserved;
internal int CRC;
#pragma warning restore CS0649
}
public bool Is64Bit;
public string AppId;
public string AppName;
public UnmanagedPart Unmanaged;
public LogHeader()
{
// This value has been the same for over 10 years.
// For a unicode uninstaller, it will rectify this automatically.
Unmanaged.Version = 48;
}
public static int ByteLength => 0x40 + 0x80 + 0x80 + Unsafe.SizeOf<UnmanagedPart>();
internal static LogHeader LoadForMimic(Stream stream)
{
byte[] buffer = new byte[0x80];
var br = new BinaryReader(new MemoryStream(buffer, 0, 0x80, true, true), Encoding.UTF8, true);
var ret = new LogHeader();
stream.Read(buffer, 0, 0x40);
var header = br.ReadAsciiStringTerminated();
ret.Is64Bit = header == HEADER_64;
if (!ret.Is64Bit && header != HEADER_32) throw new InvalidDataException("Invalid uninstall config header");
br.BaseStream.Position = 0;
stream.Read(buffer, 0, 0x80);
ret.AppId = br.ReadAsciiStringTerminated();
br.BaseStream.Position = 0;
stream.Read(buffer, 0, 0x80);
ret.AppName = br.ReadAsciiStringTerminated();
return ret;
}
private void SaveCore(BinaryWriter bw)
{
if (Is64Bit) Unmanaged.Flags = LogFlags.Win64;
bw.WriteAsciiStringStatic(Is64Bit ? HEADER_64 : HEADER_32, 0x40);
bw.WriteAsciiStringStatic(AppId, 0x80);
bw.WriteAsciiStringStatic(AppName, 0x80);
bw.Write(Unmanaged);
}
internal void Save(BinaryWriter bw)
{
CRC32.Save(bw, SaveCore);
}
}
}

225
uninno/Program.cs Normal file
View File

@ -0,0 +1,225 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace uninno
{
internal class Program
{
private static void Usage()
{
Console.WriteLine("Usage: {0} compiledcode uninsdat --version <version>", Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().Location));
Console.WriteLine();
Console.WriteLine("uninno: Inno Setup Uninstaller Configuration Creator.");
Console.WriteLine("Creates an Inno Setup uninstaller configuration containing a compiled IFPS script.");
Console.WriteLine("This can be used with a signed Inno Setup uninstaller as a lolbin to execute custom scripts.");
Console.WriteLine();
Console.WriteLine("Additional arguments:");
Console.WriteLine("--platform 32|64 - Sets the uninstaller configuration platform (32-bit or 64-bit)");
Console.WriteLine("--appid <appid> - Sets the AppID in the configuration file");
Console.WriteLine("--appname <appname> - Sets the AppName in the configuration file");
Console.WriteLine("--fileversion <version> - File version number to use (default is 48). For a Unicode Inno Setup you might want to pass 1048.");
Console.WriteLine("--version <version> - [REQUIRED] Inno Setup version number, postfixed with 'u' for unicode: for example, 6.2.1u");
Console.WriteLine("--mimic <path> - Path to an Inno Setup uninstaller configuration to read the previous five arguments from");
Console.WriteLine("Example: --platform 32 --appid {8E14ADF3-1B18-4711-87BD-E3827D395466} --appname \"Microsoft Azure Storage Explorer\" --fileversion 1048 --version 6.0.5u");
Console.WriteLine("Example: --mimic=\"%localappdata%\\Programs\\Microsoft Azure Storage Explorer\\unins000.dat\"");
Console.WriteLine();
}
static void Main(string[] args)
{
if (args.Length < 2)
{
Usage();
return;
}
Dictionary<string, string> OptionalArgs = new Dictionary<string, string>();
int requiredArgsIndex = 0;
int optionalArgsIndex = 0;
if (args.Length > 2)
{
var optionalAtStart = args[0].StartsWith("--");
if (!optionalAtStart && !args[2].StartsWith("--"))
{
Usage();
return;
}
if (optionalAtStart)
{
for (int i = 0; i < args.Length; i++)
{
if (!args[i].StartsWith("--"))
{
requiredArgsIndex = i;
break;
}
if (i > args.Length - 3)
{
Usage();
return;
}
}
} else
{
optionalArgsIndex = 2;
}
var optionalArgsEnd = args.Length;
if (requiredArgsIndex > optionalArgsIndex) optionalArgsEnd = optionalArgsIndex;
for (int i = optionalArgsIndex; i < optionalArgsEnd; i++)
{
var arg = args[i].Substring(2);
switch (arg.ToLower())
{
case "platform":
case "appid":
case "appname":
case "fileversion":
case "version":
case "mimic":
OptionalArgs[arg] = args[i + 1];
i++;
break;
default:
Usage();
return;
}
}
}
var header = new LogHeader();
uint ExtraData = 0;
bool IsUnicode = false;
if (OptionalArgs.TryGetValue("mimic", out var mimic))
{
LogHeader mimicHeader;
try
{
using (var fs = File.OpenRead(mimic))
{
mimicHeader = LogHeader.LoadForMimic(fs);
var body = LogFile.ReadBody(fs);
using (var bodyReader = new BinaryReader(body, Encoding.UTF8, true))
{
while (body.Position < body.Length)
{
var type = bodyReader.Read<RecordType>();
var extra = bodyReader.Read<uint>();
var len = bodyReader.Read<uint>();
if (type != RecordType.CompiledCode)
{
body.Position += len;
continue;
}
ExtraData = extra;
IsUnicode = (extra & 0x80000000) != 0;
break;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Error when loading {0}: {1}", mimic, ex);
Usage();
return;
}
header.Is64Bit = mimicHeader.Is64Bit;
header.AppId = mimicHeader.AppId;
header.AppName = mimicHeader.AppName;
header.Unmanaged.Version = mimicHeader.Unmanaged.Version;
}
else
{
if (OptionalArgs.TryGetValue("platform", out var platform))
{
header.Is64Bit = platform == "64";
if (platform != "32" && !header.Is64Bit)
{
Usage();
return;
}
}
header.AppId = header.AppName = "";
if (!OptionalArgs.TryGetValue("appid", out header.AppId)) header.AppId = "";
if (!OptionalArgs.TryGetValue("appname", out header.AppName)) header.AppName = "";
if (OptionalArgs.TryGetValue("fileversion", out var verString))
{
if (int.TryParse(verString, out var version)) header.Unmanaged.Version = version;
}
if (OptionalArgs.TryGetValue("version", out verString))
{
var versionSplit = verString.Split('.');
if (!uint.TryParse(versionSplit[0], out var one) || !uint.TryParse(versionSplit[1], out var two)) {
Usage();
return;
}
if (versionSplit[2].EndsWith("u"))
{
IsUnicode = true;
versionSplit[2] = versionSplit[2].Substring(0, versionSplit[2].Length - 1);
}
if (!uint.TryParse(versionSplit[2], out var three))
{
Usage();
return;
}
ExtraData = (one << 24) | (two << 16) | (three << 8) | (IsUnicode ? 0x80000000u : 0);
}
else
{
Usage();
return;
}
}
var file = new LogFile();
file.Header = header;
var ms = new MemoryStream();
{
// first: script
ms.WriteByte(0xfe);
var data = File.ReadAllBytes(args[requiredArgsIndex]);
var dataLen = data.Length;
// If Unicode Inno Setup, then the script length needs to be divisible by sizeof(char).
bool needsExtraByte = IsUnicode && (dataLen & 1) != 0;
if (needsExtraByte) dataLen++;
ms.Write(BitConverter.GetBytes(dataLen * (IsUnicode ? -1 : 1)), 0, sizeof(int));
ms.Write(data, 0, data.Length);
if (needsExtraByte) ms.WriteByte(0);
// second: leadbytes, size = 0x20
ms.WriteByte(0x20);
ms.Position += 0x20;
// 2-5: can be len=0
ms.WriteByte(0);
ms.WriteByte(0);
ms.WriteByte(0);
ms.WriteByte(0);
// 6: len=4, uint array count=0
ms.WriteByte(4);
ms.WriteByte(0);
ms.WriteByte(0);
ms.WriteByte(0);
ms.WriteByte(0);
}
file.Records.Add(new Record(RecordType.CompiledCode, ms.ToArray(), ExtraData));
ms = new MemoryStream();
var bw = new BinaryWriter(ms, Encoding.UTF8, true);
file.Save(bw);
ms.Position = 0;
using (var fs = File.OpenWrite(args[requiredArgsIndex + 1]))
ms.CopyTo(fs);
Console.WriteLine("Saved binary to {0}", args[requiredArgsIndex + 1]);
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("uninno")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("uninno")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5092701d-8d7e-4ab4-9877-dadce1266b3d")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

7
uninno/packages.config Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
<package id="System.Memory" version="4.5.5" targetFramework="net472" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net472" />
</packages>

73
uninno/uninno.csproj Normal file
View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{5092701D-8D7E-4AB4-9877-DADCE1266B3D}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>uninno</RootNamespace>
<AssemblyName>uninno</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CRC32.cs" />
<Compile Include="LogFile.cs" />
<Compile Include="LogHeader.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="..\LoadSaveExtensions\LoadSaveExtensions.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>