mirror of
https://github.com/Wack0/IFPSTools.NET.git
synced 2025-06-18 10:45:36 -04:00
Add project files.
This commit is contained in:
parent
0b000e586f
commit
9b71ef23ca
398
.gitignore
vendored
Normal file
398
.gitignore
vendored
Normal 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
892
IFPSAsmLib/Assembler.cs
Normal 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
76
IFPSAsmLib/Constants.cs
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
11
IFPSAsmLib/IFPSAsmLib.csproj
Normal file
11
IFPSAsmLib/IFPSAsmLib.csproj
Normal 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
995
IFPSAsmLib/Parser.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
BIN
IFPSLib.Tests/CompiledCode.bin
Normal file
BIN
IFPSLib.Tests/CompiledCode.bin
Normal file
Binary file not shown.
3175
IFPSLib.Tests/CompiledCode.txt
Normal file
3175
IFPSLib.Tests/CompiledCode.txt
Normal file
File diff suppressed because it is too large
Load Diff
96
IFPSLib.Tests/IFPSLib.Tests.csproj
Normal file
96
IFPSLib.Tests/IFPSLib.Tests.csproj
Normal 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>
|
20
IFPSLib.Tests/Properties/AssemblyInfo.cs
Normal file
20
IFPSLib.Tests/Properties/AssemblyInfo.cs
Normal 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")]
|
101
IFPSLib.Tests/Properties/Resources.Designer.cs
generated
Normal file
101
IFPSLib.Tests/Properties/Resources.Designer.cs
generated
Normal 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]";.
|
||||||
|
/// </summary>
|
||||||
|
internal static string CompiledCodeDisasm {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CompiledCodeDisasm", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
IFPSLib.Tests/Properties/Resources.resx
Normal file
127
IFPSLib.Tests/Properties/Resources.resx
Normal 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>
|
48
IFPSLib.Tests/ScriptTest.cs
Normal file
48
IFPSLib.Tests/ScriptTest.cs
Normal 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
11
IFPSLib.Tests/app.config
Normal 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>
|
13
IFPSLib.Tests/packages.config
Normal file
13
IFPSLib.Tests/packages.config
Normal 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>
|
77
IFPSLib/CustomAttribute.cs
Normal file
77
IFPSLib/CustomAttribute.cs
Normal 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
23
IFPSLib/Emit/AluOpCode.cs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
29
IFPSLib/Emit/BytecodeOperandType.cs
Normal file
29
IFPSLib/Emit/BytecodeOperandType.cs
Normal 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
49
IFPSLib/Emit/CmpOpCode.cs
Normal 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
75
IFPSLib/Emit/Code.cs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
443
IFPSLib/Emit/ExternalFunction.cs
Normal file
443
IFPSLib/Emit/ExternalFunction.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
IFPSLib/Emit/FlowControl.cs
Normal file
33
IFPSLib/Emit/FlowControl.cs
Normal 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
678
IFPSLib/Emit/Instruction.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
IFPSLib/Emit/NativeCallingConvention.cs
Normal file
14
IFPSLib/Emit/NativeCallingConvention.cs
Normal 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
98
IFPSLib/Emit/OpCode.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
14
IFPSLib/Emit/OpCodeType.cs
Normal file
14
IFPSLib/Emit/OpCodeType.cs
Normal 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
293
IFPSLib/Emit/OpCodes.cs
Normal 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 = &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
258
IFPSLib/Emit/Operand.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
IFPSLib/Emit/OperandType.cs
Normal file
58
IFPSLib/Emit/OperandType.cs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
15
IFPSLib/Emit/PopEHOpCode.cs
Normal file
15
IFPSLib/Emit/PopEHOpCode.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
251
IFPSLib/Emit/ScriptFunction.cs
Normal file
251
IFPSLib/Emit/ScriptFunction.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
IFPSLib/Emit/SetFlagOpCode.cs
Normal file
21
IFPSLib/Emit/SetFlagOpCode.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
38
IFPSLib/Emit/StackBehaviour.cs
Normal file
38
IFPSLib/Emit/StackBehaviour.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
13
IFPSLib/Emit/VariableType.cs
Normal file
13
IFPSLib/Emit/VariableType.cs
Normal 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
159
IFPSLib/Emit/Variables.cs
Normal 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
79
IFPSLib/Function.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
52
IFPSLib/FunctionArgument.cs
Normal file
52
IFPSLib/FunctionArgument.cs
Normal 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
17
IFPSLib/IFPSLib.csproj
Normal 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
382
IFPSLib/Script.cs
Normal 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
315
IFPSLib/TypedData.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
IFPSLib/Types/ArrayType.cs
Normal file
94
IFPSLib/Types/ArrayType.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
IFPSLib/Types/ClassType.cs
Normal file
39
IFPSLib/Types/ClassType.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
IFPSLib/Types/ComInterfaceType.cs
Normal file
28
IFPSLib/Types/ComInterfaceType.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
IFPSLib/Types/FunctionPointerType.cs
Normal file
61
IFPSLib/Types/FunctionPointerType.cs
Normal 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
104
IFPSLib/Types/IType.cs
Normal 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)" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
IFPSLib/Types/ImmediateFunctionType.cs
Normal file
18
IFPSLib/Types/ImmediateFunctionType.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
17
IFPSLib/Types/InstructionType.cs
Normal file
17
IFPSLib/Types/InstructionType.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
148
IFPSLib/Types/PascalTypeCode.cs
Normal file
148
IFPSLib/Types/PascalTypeCode.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
IFPSLib/Types/PrimitiveType.cs
Normal file
33
IFPSLib/Types/PrimitiveType.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
IFPSLib/Types/RecordType.cs
Normal file
63
IFPSLib/Types/RecordType.cs
Normal 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
73
IFPSLib/Types/SetType.cs
Normal 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
17
IFPSLib/Types/TypeType.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
17
IFPSLib/Types/UnknownType.cs
Normal file
17
IFPSLib/Types/UnknownType.cs
Normal 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
62
IFPSTools.NET.sln
Normal 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
21
LICENSE.txt
Normal 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.
|
451
LoadSaveExtensions/Extensions.cs
Normal file
451
LoadSaveExtensions/Extensions.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
14
LoadSaveExtensions/LoadSaveExtensions.projitems
Normal file
14
LoadSaveExtensions/LoadSaveExtensions.projitems
Normal 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)' < '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>
|
13
LoadSaveExtensions/LoadSaveExtensions.shproj
Normal file
13
LoadSaveExtensions/LoadSaveExtensions.shproj
Normal 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>
|
37
examples/DllImportExample.asm
Normal file
37
examples/DllImportExample.asm
Normal 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
29
examples/HelloWorld.asm
Normal 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.
|
33
examples/HelloWorldWithAttribute.asm
Normal file
33
examples/HelloWorldWithAttribute.asm
Normal 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
14
ifpsasm/App.config
Normal 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
21
ifpsasm/Program.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
ifpsasm/Properties/AssemblyInfo.cs
Normal file
36
ifpsasm/Properties/AssemblyInfo.cs
Normal 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
78
ifpsasm/ifpsasm.csproj
Normal 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
7
ifpsasm/packages.config
Normal 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
6
ifpsdasm/App.config
Normal 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
25
ifpsdasm/Program.cs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
ifpsdasm/Properties/AssemblyInfo.cs
Normal file
36
ifpsdasm/Properties/AssemblyInfo.cs
Normal 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
60
ifpsdasm/ifpsdasm.csproj
Normal 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
106
readme.md
Normal 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
6
uninno/App.config
Normal 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
109
uninno/CRC32.cs
Normal 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
175
uninno/LogFile.cs
Normal 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
93
uninno/LogHeader.cs
Normal 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
225
uninno/Program.cs
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
uninno/Properties/AssemblyInfo.cs
Normal file
36
uninno/Properties/AssemblyInfo.cs
Normal 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
7
uninno/packages.config
Normal 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
73
uninno/uninno.csproj
Normal 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>
|
Loading…
Reference in New Issue
Block a user