├── .gitattributes ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── EProjectFile.sln ├── EProjectFile ├── ClassInfo.cs ├── ClassPublicityInfo.cs ├── CodeDataParser.cs ├── CodeFolderInfo.cs ├── CompilationSchemeInfo.cs ├── ConstantInfo.cs ├── Context │ ├── BlockByteifierContext.cs │ └── BlockParserContext.cs ├── DllDeclareInfo.cs ├── ECDependencyInfo.cs ├── EProjectFile.csproj ├── EditorTabInfo │ ├── ClassEditorTabInfo.cs │ ├── FormDesignerTabInfo.cs │ ├── GeneralEditorTabInfo.cs │ ├── IEditorTabInfo.cs │ ├── PredefinedEditorTabInfos.cs │ └── PureTableEditorTabInfo.cs ├── Encryption │ ├── EncryptionOptions.cs │ ├── EplSecret.EC.cs │ ├── EplSecret.EStd.cs │ ├── EplSecret.cs │ └── IEplSecretFactory.cs ├── EplDocument.cs ├── EplSystemId.cs ├── Expressions │ ├── AccessArrayExpression.cs │ ├── AccessMemberExpression.cs │ ├── ArrayLiteralEnd.cs │ ├── ArrayLiteralExpression.cs │ ├── BoolLiteral.cs │ ├── CallExpression.cs │ ├── ConstantExpression.cs │ ├── DateTimeLiteral.cs │ ├── DefaultValueExpression.cs │ ├── EmnuConstantExpression.cs │ ├── Expression.cs │ ├── In0x38Expression.cs │ ├── MethodPtrExpression.cs │ ├── NumberLiteral.cs │ ├── ParamListEnd.cs │ ├── ParamListExpression.cs │ ├── StringLiteral.cs │ └── VariableExpression.cs ├── FormControlInfo.cs ├── FormElementInfo.cs ├── FormInfo.cs ├── FormMenuInfo.cs ├── IDToNameMap.cs ├── IHasId.cs ├── IHasMemoryAddress.cs ├── IToTextCodeAble.cs ├── IndexedEventInfo.cs ├── Internal │ ├── ByteArrayHexConverter.cs │ ├── BytesUtils.cs │ ├── CryptoECTransform.cs │ ├── EStdCryptoTransform.cs │ ├── EditorTabInfoJsonConverter.cs │ ├── EncodingJsonConverter.cs │ ├── ExtensionMethod.cs │ ├── ImmutableByteArrayHexConverter.cs │ ├── JsonUtils.cs │ ├── PrefixedStream.cs │ ├── RC4Crypto.cs │ ├── SectionJsonConverter.cs │ ├── StreamWithImmutableExtension.cs │ └── TextCodeUtils.cs ├── LibraryRefInfo.cs ├── MethodCodeDataWriterArgs.cs ├── MethodInfo.cs ├── ProjectFileReader.cs ├── ProjectFileWriter.cs ├── Properties │ └── launchSettings.json ├── RawSectionInfo.cs ├── RemovedDefinedItemInfo.cs ├── Sections │ ├── ClassPublicitySection.cs │ ├── CodeSection.cs │ ├── ConditionalCompilationSection.cs │ ├── ECDependenciesSection.cs │ ├── EPackageInfoSection.cs │ ├── ESystemInfoSection.cs │ ├── EditorInfoSection.cs │ ├── EndOfFileSection.cs │ ├── EventIndicesSection.cs │ ├── FolderSection.cs │ ├── GeneralSection.cs │ ├── ISection.cs │ ├── InitECSection.cs │ ├── LosableSection.cs │ ├── PredefinedSections.cs │ ├── ProjectConfigExSection.cs │ ├── ProjectConfigSection.cs │ └── ResourceSection.cs ├── Statements │ ├── CounterStatement.cs │ ├── DoWhileStatement.cs │ ├── ExpressionStatement.cs │ ├── ForStatement.cs │ ├── IfElseStatement.cs │ ├── IfStatement.cs │ ├── LoopStatement.cs │ ├── Statement.cs │ ├── StatementBlock.cs │ ├── SwitchStatement.cs │ ├── UnexaminedStatement.cs │ └── WhileStatement.cs ├── StructInfo.cs └── VariableInfo │ ├── AbstractVariableInfo.cs │ ├── ClassVariableInfo.cs │ ├── DllParameterInfo.cs │ ├── GlobalVariableInfo.cs │ ├── LocalVariableInfo.cs │ ├── MethodParameterInfo.cs │ └── StructMemberInfo.cs ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '45 7 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" -------------------------------------------------------------------------------- /EProjectFile.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32616.157 5 | MinimumVisualStudioVersion = 15.0.0.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EProjectFile", "EProjectFile\EProjectFile.csproj", "{702755B9-2203-4D4A-A208-4AA1A6AD328B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {702755B9-2203-4D4A-A208-4AA1A6AD328B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {702755B9-2203-4D4A-A208-4AA1A6AD328B}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {702755B9-2203-4D4A-A208-4AA1A6AD328B}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {702755B9-2203-4D4A-A208-4AA1A6AD328B}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {780DC048-A079-4BFD-B965-6DCC242083CD} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /EProjectFile/ClassInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System; 3 | using System.IO; 4 | using System.Text; 5 | using System.Linq; 6 | using QIQI.EProjectFile.Sections; 7 | using QIQI.EProjectFile.Internal; 8 | using System.Collections.Generic; 9 | 10 | namespace QIQI.EProjectFile 11 | { 12 | public class ClassInfo : IHasId, IHasMemoryAddress, IToTextCodeAble 13 | { 14 | public int Id { get; } 15 | 16 | public ClassInfo(int id) 17 | { 18 | this.Id = id; 19 | } 20 | 21 | public int MemoryAddress { get; set; } 22 | /// 23 | /// 窗口程序集 中指定关联窗口的 24 | /// 25 | /// 26 | public int Form { get; set; } 27 | public int BaseClass { get; set; } 28 | public string Name { get; set; } 29 | public string Comment { get; set; } 30 | public List Methods { get; set; } 31 | public List Variables { get; set; } 32 | 33 | public static List ReadClasses(BinaryReader r, Encoding encoding) 34 | { 35 | return r.ReadBlocksWithIdAndMemoryAddress((reader, id, memoryAddress) => new ClassInfo(id) 36 | { 37 | MemoryAddress = memoryAddress, 38 | Form = reader.ReadInt32(), 39 | BaseClass = reader.ReadInt32(), 40 | Name = reader.ReadStringWithLengthPrefix(encoding), 41 | Comment = reader.ReadStringWithLengthPrefix(encoding), 42 | Methods = reader.ReadInt32sListWithFixedLength(reader.ReadInt32() / 4), 43 | Variables = AbstractVariableInfo.ReadVariables(reader, encoding, x => new ClassVariableInfo(x)) 44 | }); 45 | } 46 | public static void WriteClasses(BinaryWriter w, Encoding encoding, List classes) 47 | { 48 | w.WriteBlocksWithIdAndMemoryAddress(classes, (writer, elem) => 49 | { 50 | writer.Write(elem.Form); 51 | writer.Write(elem.BaseClass); 52 | writer.WriteStringWithLengthPrefix(encoding, elem.Name); 53 | writer.WriteStringWithLengthPrefix(encoding, elem.Comment); 54 | if (elem.Methods == null) 55 | { 56 | writer.Write(0); 57 | } 58 | else 59 | { 60 | writer.Write(elem.Methods.Count * 4); 61 | foreach (var x in elem.Methods) writer.Write(x); 62 | } 63 | AbstractVariableInfo.WriteVariables(writer, encoding, elem.Variables); 64 | }); 65 | } 66 | public override string ToString() 67 | { 68 | return JsonSerializer.Serialize(this, JsonUtils.Options); 69 | } 70 | /// 71 | /// 到文本代码(结尾无换行) 72 | /// 73 | /// 命名映射器 74 | /// 输出目标 75 | /// 起始缩进 76 | /// 若为null,不写出下属方法 77 | /// 是否输出子程序代码 78 | public void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent, CodeSection codeSection, bool writeCode = true) 79 | { 80 | TextCodeUtils.WriteDefinitionCode(writer, indent, "程序集", nameMap.GetUserDefinedName(Id), BaseClass == 0 || BaseClass == -1 ? "" : nameMap.GetUserDefinedName(BaseClass), "", Comment); 81 | writer.WriteLine(); 82 | TextCodeUtils.JoinAndWriteCode(Variables, Environment.NewLine, nameMap, writer, indent); 83 | if (codeSection != null) 84 | { 85 | writer.WriteLine(); 86 | writer.WriteLine(); 87 | var methodId = Methods.ToDictionary(x => x); 88 | TextCodeUtils.JoinAndWriteCode(codeSection.Methods.Where(x => methodId.ContainsKey(x.Id)), Environment.NewLine + Environment.NewLine, nameMap, writer, indent, writeCode); 89 | } 90 | } 91 | public void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent) 92 | { 93 | ToTextCode(nameMap, writer, indent, null); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /EProjectFile/ClassPublicityInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Text.Json; 6 | 7 | namespace QIQI.EProjectFile 8 | { 9 | public class ClassPublicityInfo 10 | { 11 | public int Class { get; set; } 12 | public int Flags { get; set; } 13 | public bool Public { get => (Flags & 0x1) != 0; set => Flags = (Flags & ~0x1) | (value ? 0x1 : 0); } 14 | public bool Hidden { get => (Flags & 0x2) != 0; set => Flags = (Flags & ~0x2) | (value ? 0x2 : 0); } 15 | 16 | public override string ToString() 17 | { 18 | return JsonSerializer.Serialize(this, JsonUtils.Options); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EProjectFile/CodeFolderInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System.IO; 3 | using System.Text; 4 | using System.Text.Json; 5 | 6 | namespace QIQI.EProjectFile 7 | { 8 | public class CodeFolderInfo 9 | { 10 | public bool Expand { get; set; } 11 | public int Key { get; } 12 | public int ParentKey { get; set; } 13 | public string Name { get; set; } 14 | public int[] Children { get; set; } 15 | public CodeFolderInfo(int key) 16 | { 17 | this.Key = key; 18 | } 19 | 20 | public override string ToString() 21 | { 22 | return JsonSerializer.Serialize(this, JsonUtils.Options); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /EProjectFile/CompilationSchemeInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile 6 | { 7 | public class CompilationSchemeInfo 8 | { 9 | public string Name { get; set; } 10 | public string Features { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /EProjectFile/Context/BlockByteifierContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace QIQI.EProjectFile.Context 7 | { 8 | public struct BlockByteifierContext 9 | { 10 | public BlockByteifierContext(Encoding encoding, bool cryptEC) 11 | { 12 | this.Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding)); 13 | this.CryptEC = cryptEC; 14 | } 15 | 16 | public Encoding Encoding; 17 | public bool CryptEC; 18 | 19 | public byte[] Collect(Action writeTo) 20 | { 21 | byte[] data; 22 | using (var writer = new BinaryWriter(new MemoryStream(), Encoding)) 23 | { 24 | writeTo(writer); 25 | writer.Flush(); 26 | data = ((MemoryStream)writer.BaseStream).ToArray(); 27 | } 28 | return data; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /EProjectFile/Context/BlockParserContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace QIQI.EProjectFile.Context 7 | { 8 | public struct BlockParserContext 9 | { 10 | public BlockParserContext(byte[] data, Encoding encoding, bool cryptEC) 11 | { 12 | Data = data ?? throw new ArgumentNullException(nameof(data)); 13 | Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding)); 14 | CryptEC = cryptEC; 15 | } 16 | 17 | private readonly byte[] Data; 18 | public int DataLength => Data.Length; 19 | public readonly Encoding Encoding; 20 | public readonly bool CryptEC; 21 | 22 | public TResult Consume(Func consumer) 23 | { 24 | using var reader = new BinaryReader(new MemoryStream(Data, false)); 25 | return consumer(reader); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /EProjectFile/DllDeclareInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace QIQI.EProjectFile 9 | { 10 | public class DllDeclareInfo : IHasId, IHasMemoryAddress, IToTextCodeAble 11 | { 12 | public int Id { get; } 13 | 14 | public DllDeclareInfo(int id) 15 | { 16 | this.Id = id; 17 | } 18 | 19 | public int MemoryAddress { get; set; } 20 | public int Flags { get; set; } 21 | public bool Public { get => (Flags & 0x2) != 0; set => Flags = (Flags & ~0x2) | (value ? 0x2 : 0); } 22 | public bool Hidden { get => (Flags & 0x4) != 0; set => Flags = (Flags & ~0x4) | (value ? 0x4 : 0); } 23 | public int ReturnDataType { get; set; } 24 | public string Name { get; set; } 25 | public string Comment { get; set; } 26 | public string EntryPoint { get; set; } 27 | public string LibraryName { get; set; } 28 | public List Parameters { get; set; } 29 | public static List ReadDllDeclares(BinaryReader r, Encoding encoding) 30 | { 31 | return r.ReadBlocksWithIdAndMemoryAddress((reader, id, memoryAddress) => new DllDeclareInfo(id) 32 | { 33 | MemoryAddress = memoryAddress, 34 | Flags = reader.ReadInt32(), 35 | ReturnDataType = reader.ReadInt32(), 36 | Name = reader.ReadStringWithLengthPrefix(encoding), 37 | Comment = reader.ReadStringWithLengthPrefix(encoding), 38 | LibraryName = reader.ReadStringWithLengthPrefix(encoding), 39 | EntryPoint = reader.ReadStringWithLengthPrefix(encoding), 40 | Parameters = AbstractVariableInfo.ReadVariables(reader, encoding, x => new DllParameterInfo(x)) 41 | }); 42 | } 43 | public static void WriteDllDeclares(BinaryWriter w, Encoding encoding, List dllDeclares) 44 | { 45 | w.WriteBlocksWithIdAndMemoryAddress(dllDeclares, (writer, elem) => 46 | { 47 | writer.Write(elem.Flags); 48 | writer.Write(elem.ReturnDataType); 49 | writer.WriteStringWithLengthPrefix(encoding, elem.Name); 50 | writer.WriteStringWithLengthPrefix(encoding, elem.Comment); 51 | writer.WriteStringWithLengthPrefix(encoding, elem.LibraryName); 52 | writer.WriteStringWithLengthPrefix(encoding, elem.EntryPoint); 53 | AbstractVariableInfo.WriteVariables(writer, encoding, elem.Parameters); 54 | }); 55 | } 56 | public void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent) 57 | { 58 | TextCodeUtils.WriteDefinitionCode(writer, indent, "DLL命令", nameMap.GetUserDefinedName(Id), nameMap.GetDataTypeName(ReturnDataType), LibraryName, EntryPoint, Public ? "公开" : "", Comment); 59 | writer.WriteLine(); 60 | TextCodeUtils.JoinAndWriteCode(Parameters, Environment.NewLine, nameMap, writer, indent + 1); 61 | } 62 | public override string ToString() 63 | { 64 | return JsonSerializer.Serialize(this, JsonUtils.Options); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /EProjectFile/ECDependencyInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | 9 | namespace QIQI.EProjectFile 10 | { 11 | public class ECDependencyInfo 12 | { 13 | public struct PackedIds 14 | { 15 | [JsonInclude] 16 | public int Start; 17 | [JsonInclude] 18 | public int Count; 19 | 20 | public override string ToString() 21 | { 22 | return JsonSerializer.Serialize(this, JsonUtils.Options); 23 | } 24 | } 25 | 26 | [DefaultValue(2)] 27 | public int InfoVersion { get; set; } = 2; 28 | public int FileSize { get; set; } 29 | public DateTime FileLastModifiedDate { get; set; } = DateTime.FromFileTime(0); 30 | public bool ReExport { get; set; } 31 | public string Name { get; set; } 32 | public string Path { get; set; } 33 | public List DefinedIds { get; set; } 34 | 35 | public override string ToString() 36 | { 37 | return JsonSerializer.Serialize(this, JsonUtils.Options); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /EProjectFile/EProjectFile.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | netstandard2.0 6 | true 7 | true 8 | true 9 | snupkg 10 | Epl Project Editor 11 | Epl Project Editor 12 | OpenEpl 13 | A free and unencumbered software released into the public domain (https://unlicense.org/) 14 | false 15 | true 16 | QIQI.EProjectFile 17 | EPL 18 | OpenEpl 19 | https://github.com/OpenEpl/EProjectFile 20 | QIQI.EProjectFile 21 | 1.9.4 22 | false 23 | Unlicense 24 | True 25 | 1591;1701;1702 26 | docs/README.md 27 | 8.0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | all 37 | runtime; build; native; contentfiles; analyzers 38 | 39 | 40 | build; analyzers 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /EProjectFile/EditorTabInfo/ClassEditorTabInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Context; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace QIQI.EProjectFile.EditorTabInfo 8 | { 9 | public class ClassEditorTabInfo : IEditorTabInfo 10 | { 11 | private class KeyImpl : IEditorTabInfoKey 12 | { 13 | public byte TypeId => 1; 14 | 15 | public ClassEditorTabInfo Parse(BlockParserContext context) 16 | { 17 | return context.Consume(reader => 18 | { 19 | if (reader.ReadByte() != TypeId) 20 | { 21 | throw new Exception($"Mismatched type for {nameof(ClassEditorTabInfo)}"); 22 | } 23 | var that = new ClassEditorTabInfo() 24 | { 25 | ClassId = reader.ReadInt32(), 26 | ElemId = reader.ReadInt16(), 27 | Offset = reader.ReadInt32() & 0x7FFFFFFF, 28 | ColumnInTable = reader.ReadByte(), 29 | SelectionStart = reader.ReadInt32(), 30 | SelectionCurrent = reader.ReadInt32(), 31 | SelectionEndpoints = new List() 32 | }; 33 | while (reader.BaseStream.Position < reader.BaseStream.Length) 34 | { 35 | var elemId = reader.ReadInt16(); 36 | var offset = reader.ReadInt32(); 37 | that.SelectionEndpoints.Add(new ClassEditorEndpoint() 38 | { 39 | ElemId = elemId, 40 | Offset = offset 41 | }); 42 | } 43 | return that; 44 | }); 45 | } 46 | } 47 | public static readonly IEditorTabInfoKey Key = new KeyImpl(); 48 | public byte TypeId => Key.TypeId; 49 | 50 | /// 51 | /// 对于表格元素为行索引,对于代码元素为所在行的代码数据位置(字节) 52 | /// 53 | /// 54 | public int Offset { get; set; } 55 | 56 | /// 57 | /// 对于表格元素为列索引,对于代码元素为0 58 | /// 59 | public byte ColumnInTable { get; set; } 60 | 61 | /// 62 | /// 使选择方向不同,可能大于 也可能小于,若两者相等表示无行内选区(可能有多行选择存在) 63 | /// 64 | /// 多行选择时此值与 相等,选择点以 为准 65 | public int SelectionStart { get; set; } 66 | 67 | /// 68 | /// 当前光标位置 69 | /// 70 | /// 多行选择时此值与 相等,选择点以 为准 71 | public int SelectionCurrent { get; set; } 72 | 73 | public int ClassId { get; set; } 74 | 75 | /// 76 | /// -N 表示第 N 个表格,自然数表示对应的子程序索引(从0开始) 77 | /// 78 | /// 79 | public short ElemId { get; set; } 80 | 81 | /// 82 | /// 标记一个选择点,以开始、结束交叉出现,可以存在多个不连续的选择区 83 | /// 84 | public List SelectionEndpoints { get; set; } 85 | 86 | public void WriteTo(BinaryWriter writer, Encoding encoding) 87 | { 88 | writer.Write(20 + (SelectionEndpoints?.Count ?? 0) * 6); 89 | writer.Write(TypeId); 90 | writer.Write(ClassId); 91 | writer.Write(ElemId); 92 | writer.Write(Offset | unchecked((int)0x80000000)); 93 | writer.Write(ColumnInTable); 94 | writer.Write(SelectionStart); 95 | writer.Write(SelectionCurrent); 96 | if (SelectionEndpoints != null) 97 | { 98 | foreach (var item in SelectionEndpoints) 99 | { 100 | writer.Write(item.ElemId); 101 | writer.Write(item.Offset); 102 | } 103 | } 104 | } 105 | } 106 | public struct ClassEditorEndpoint 107 | { 108 | /// 109 | /// -N 表示第 N 个表格,自然数表示对应的子程序索引(从0开始) 110 | /// 111 | /// 112 | public short ElemId { get; set; } 113 | 114 | /// 115 | /// 对于表格元素为行索引,对于代码元素为所在行的代码数据位置(字节) 116 | /// 117 | /// 118 | public int Offset { get; set; } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /EProjectFile/EditorTabInfo/FormDesignerTabInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Context; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace QIQI.EProjectFile.EditorTabInfo 8 | { 9 | public class FormDesignerTabInfo : IEditorTabInfo 10 | { 11 | private class KeyImpl : IEditorTabInfoKey 12 | { 13 | public byte TypeId => 5; 14 | 15 | public FormDesignerTabInfo Parse(BlockParserContext context) 16 | { 17 | return context.Consume(reader => 18 | { 19 | if (reader.ReadByte() != TypeId) 20 | { 21 | throw new Exception($"Mismatched type for {nameof(FormDesignerTabInfo)}"); 22 | } 23 | var that = new FormDesignerTabInfo() 24 | { 25 | FormId = reader.ReadInt32(), 26 | UnitIds = new List() 27 | }; 28 | 29 | while (reader.BaseStream.Position < reader.BaseStream.Length) 30 | { 31 | var unitId = reader.ReadInt32(); 32 | that.UnitIds.Add(unitId); 33 | } 34 | return that; 35 | }); 36 | } 37 | } 38 | public static readonly IEditorTabInfoKey Key = new KeyImpl(); 39 | public byte TypeId => Key.TypeId; 40 | public int FormId { get; set; } 41 | public List UnitIds { get; set; } 42 | 43 | public void WriteTo(BinaryWriter writer, Encoding encoding) 44 | { 45 | writer.Write(5 + (UnitIds?.Count ?? 0) * 4); 46 | writer.Write(TypeId); 47 | writer.Write(FormId); 48 | if (UnitIds != null) 49 | { 50 | foreach (var unitId in UnitIds) 51 | { 52 | writer.Write(unitId); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /EProjectFile/EditorTabInfo/GeneralEditorTabInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System.IO; 3 | using System.Text; 4 | using System.Text.Json.Serialization; 5 | 6 | namespace QIQI.EProjectFile.EditorTabInfo 7 | { 8 | public class GeneralEditorTabInfo : IEditorTabInfo 9 | { 10 | [JsonConstructor] 11 | public GeneralEditorTabInfo(byte type, byte[] data) 12 | { 13 | TypeId = type; 14 | Data = data; 15 | } 16 | 17 | public byte TypeId { get; } 18 | 19 | [JsonConverter(typeof(ByteArrayHexConverter))] 20 | public byte[] Data { get; set; } 21 | 22 | public void WriteTo(BinaryWriter writer, Encoding encoding) 23 | { 24 | if (Data is null) 25 | { 26 | writer.Write(1); 27 | writer.Write(TypeId); 28 | return; 29 | } 30 | writer.Write(1 + Data.Length); 31 | writer.Write(TypeId); 32 | writer.Write(Data); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /EProjectFile/EditorTabInfo/IEditorTabInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Context; 2 | using QIQI.EProjectFile.Internal; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace QIQI.EProjectFile.EditorTabInfo 8 | { 9 | public interface IEditorTabInfoKey where TEditorTabInfo : IEditorTabInfo 10 | { 11 | public byte TypeId { get; } 12 | TEditorTabInfo Parse(BlockParserContext context); 13 | } 14 | [JsonConverter(typeof(EditorTabInfoJsonConverter))] 15 | public interface IEditorTabInfo 16 | { 17 | public byte TypeId { get; } 18 | public void WriteTo(BinaryWriter writer, Encoding encoding); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /EProjectFile/EditorTabInfo/PredefinedEditorTabInfos.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace QIQI.EProjectFile.EditorTabInfo 5 | { 6 | public class PredefinedEditorTabInfos 7 | { 8 | public static Dictionary> Keys { get; } = new IEditorTabInfoKey[]{ 9 | ClassEditorTabInfo.Key, 10 | StructEditorTabInfo.Key, 11 | GlobalVariableEditorTabInfo.Key, 12 | DllDeclareEditorTabInfo.Key, 13 | FormDesignerTabInfo.Key, 14 | ConstantEditorTabInfo.Key, 15 | ImageResourceEditorTabInfo.Key, 16 | SoundResourceEditorTabInfo.Key, 17 | }.ToDictionary(x => x.TypeId); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /EProjectFile/EditorTabInfo/PureTableEditorTabInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Context; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace QIQI.EProjectFile.EditorTabInfo 8 | { 9 | public abstract class PureTableEditorTabInfo : IEditorTabInfo 10 | { 11 | protected class KeyImplForPureTable : IEditorTabInfoKey where TEditorTabInfo : PureTableEditorTabInfo, new() 12 | { 13 | public KeyImplForPureTable(byte type) 14 | { 15 | TypeId = type; 16 | } 17 | 18 | public byte TypeId { get; } 19 | 20 | public TEditorTabInfo Parse(BlockParserContext context) 21 | { 22 | return context.Consume(reader => 23 | { 24 | if (reader.ReadByte() != TypeId) 25 | { 26 | throw new Exception($"Mismatched type for {typeof(TEditorTabInfo).Name}"); 27 | } 28 | var that = new TEditorTabInfo() 29 | { 30 | Offset = reader.ReadInt32() & 0x7FFFFFFF, 31 | ColumnInTable = reader.ReadByte(), 32 | SelectionStart = reader.ReadInt32(), 33 | SelectionCurrent = reader.ReadInt32(), 34 | SelectionEndpoints = new List() 35 | }; 36 | while (reader.BaseStream.Position < reader.BaseStream.Length) 37 | { 38 | that.SelectionEndpoints.Add(reader.ReadInt32()); 39 | } 40 | return that; 41 | }); 42 | } 43 | } 44 | 45 | public abstract byte TypeId { get; } 46 | 47 | /// 48 | /// 对于表格元素为行索引 49 | /// 50 | public int Offset { get; set; } 51 | 52 | /// 53 | /// 对于表格元素为列索引 54 | /// 55 | public byte ColumnInTable { get; set; } 56 | 57 | /// 58 | /// 使选择方向不同,可能大于 也可能小于,若两者相等表示无行内选区(可能有多行选择存在) 59 | /// 60 | /// 多行选择时此值与 相等,选择点以 为准 61 | public int SelectionStart { get; set; } 62 | 63 | /// 64 | /// 当前光标位置 65 | /// 66 | /// 多行选择时此值与 相等,选择点以 为准 67 | public int SelectionCurrent { get; set; } 68 | 69 | /// 70 | /// 标记一个选择点,以开始、结束交叉出现,可以存在多个不连续的选择区 71 | /// 72 | /// 73 | public List SelectionEndpoints { get; set; } 74 | 75 | public void WriteTo(BinaryWriter writer, Encoding encoding) 76 | { 77 | writer.Write(14 + (SelectionEndpoints?.Count ?? 0) * 4); 78 | writer.Write(TypeId); 79 | writer.Write(Offset | unchecked((int)0x80000000)); 80 | writer.Write(ColumnInTable); 81 | writer.Write(SelectionStart); 82 | writer.Write(SelectionCurrent); 83 | if (SelectionEndpoints != null) 84 | { 85 | foreach (var item in SelectionEndpoints) 86 | { 87 | writer.Write(item); 88 | } 89 | } 90 | } 91 | } 92 | public class StructEditorTabInfo : PureTableEditorTabInfo 93 | { 94 | public static readonly IEditorTabInfoKey Key = new KeyImplForPureTable(2); 95 | public override byte TypeId => Key.TypeId; 96 | } 97 | public class GlobalVariableEditorTabInfo : PureTableEditorTabInfo 98 | { 99 | public static readonly IEditorTabInfoKey Key = new KeyImplForPureTable(3); 100 | public override byte TypeId => Key.TypeId; 101 | } 102 | public class DllDeclareEditorTabInfo : PureTableEditorTabInfo 103 | { 104 | public static readonly IEditorTabInfoKey Key = new KeyImplForPureTable(4); 105 | public override byte TypeId => Key.TypeId; 106 | } 107 | public class ConstantEditorTabInfo : PureTableEditorTabInfo 108 | { 109 | public static readonly IEditorTabInfoKey Key = new KeyImplForPureTable(6); 110 | public override byte TypeId => Key.TypeId; 111 | } 112 | public class ImageResourceEditorTabInfo : PureTableEditorTabInfo 113 | { 114 | public static readonly IEditorTabInfoKey Key = new KeyImplForPureTable(7); 115 | public override byte TypeId => Key.TypeId; 116 | } 117 | public class SoundResourceEditorTabInfo : PureTableEditorTabInfo 118 | { 119 | public static readonly IEditorTabInfoKey Key = new KeyImplForPureTable(8); 120 | public override byte TypeId => Key.TypeId; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /EProjectFile/Encryption/EncryptionOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Encryption 6 | { 7 | public abstract class EplEncryptionOptions 8 | { 9 | public sealed class EStd : EplEncryptionOptions, IEquatable 10 | { 11 | public EplSecret.EStd Password; 12 | 13 | public override bool Equals(object obj) 14 | { 15 | return Equals(obj as EStd); 16 | } 17 | 18 | public bool Equals(EStd other) 19 | { 20 | return !(other is null) && 21 | EqualityComparer.Default.Equals(Password, other.Password); 22 | } 23 | 24 | public override int GetHashCode() 25 | { 26 | return -1081153288 + EqualityComparer.Default.GetHashCode(Password); 27 | } 28 | 29 | public static bool operator ==(EStd left, EStd right) 30 | { 31 | return EqualityComparer.Default.Equals(left, right); 32 | } 33 | 34 | public static bool operator !=(EStd left, EStd right) 35 | { 36 | return !(left == right); 37 | } 38 | } 39 | public sealed class EC : EplEncryptionOptions, IEquatable 40 | { 41 | public EplSecret.EC Password; 42 | public string PasswordHint; 43 | 44 | public override bool Equals(object obj) 45 | { 46 | return Equals(obj as EC); 47 | } 48 | 49 | public bool Equals(EC other) 50 | { 51 | return !(other is null) && 52 | EqualityComparer.Default.Equals(Password, other.Password) && 53 | PasswordHint == other.PasswordHint; 54 | } 55 | 56 | public override int GetHashCode() 57 | { 58 | int hashCode = 1281113049; 59 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Password); 60 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(PasswordHint); 61 | return hashCode; 62 | } 63 | 64 | public static bool operator ==(EC left, EC right) 65 | { 66 | return EqualityComparer.Default.Equals(left, right); 67 | } 68 | 69 | public static bool operator !=(EC left, EC right) 70 | { 71 | return !(left == right); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /EProjectFile/Encryption/EplSecret.EStd.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | using System.Runtime.CompilerServices; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | 9 | namespace QIQI.EProjectFile.Encryption 10 | { 11 | public abstract partial class EplSecret 12 | { 13 | public sealed class EStd: EplSecret, IEquatable 14 | { 15 | private class FactoryImpl : IEplSecretFactory 16 | { 17 | public EStd Create(byte[] key) 18 | { 19 | var status = new RC4Crypto(key, 256).UnsafeGetStatus(); 20 | return new EStd(CalculateSecretID(key), Unsafe.As>(ref status)); 21 | } 22 | 23 | public EStd Create(string key) 24 | { 25 | return Create(Encoding.GetEncoding("gbk").GetBytes(key)); 26 | } 27 | 28 | private static ImmutableArray CalculateSecretID(byte[] key) 29 | { 30 | byte[] hash = MD5.Create().ComputeHash(key); 31 | StringBuilder stringBuilder = new StringBuilder(); 32 | for (int i = hash.Length - 1; i >= 0; i--) 33 | { 34 | stringBuilder.Append(hash[i].ToString("x2")); 35 | } 36 | 37 | var bytes = Encoding.ASCII.GetBytes(stringBuilder.ToString()); 38 | return Unsafe.As>(ref bytes); 39 | } 40 | } 41 | 42 | public static readonly IEplSecretFactory Factory = new FactoryImpl(); 43 | 44 | public override ImmutableArray SecretId { get; } 45 | public override ImmutableArray IV { get; } 46 | 47 | public EStd(ImmutableArray secretId, ImmutableArray iv) 48 | { 49 | SecretId = secretId; 50 | IV = iv; 51 | } 52 | 53 | public override bool Equals(object obj) 54 | { 55 | return Equals(obj as EStd); 56 | } 57 | 58 | public bool Equals(EStd other) 59 | { 60 | return !(other is null) && 61 | SecretId.Equals(other.SecretId) && 62 | IV.Equals(other.IV); 63 | } 64 | 65 | public override int GetHashCode() 66 | { 67 | int hashCode = -1032396774; 68 | hashCode = hashCode * -1521134295 + SecretId.GetHashCode(); 69 | hashCode = hashCode * -1521134295 + IV.GetHashCode(); 70 | return hashCode; 71 | } 72 | 73 | public static bool operator ==(EStd left, EStd right) 74 | { 75 | return EqualityComparer.Default.Equals(left, right); 76 | } 77 | 78 | public static bool operator !=(EStd left, EStd right) 79 | { 80 | return !(left == right); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /EProjectFile/Encryption/EplSecret.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | 3 | namespace QIQI.EProjectFile.Encryption 4 | { 5 | public abstract partial class EplSecret 6 | { 7 | public abstract ImmutableArray SecretId { get; } 8 | public abstract ImmutableArray IV { get; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /EProjectFile/Encryption/IEplSecretFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Encryption 6 | { 7 | public interface IEplSecretFactory where T : EplSecret 8 | { 9 | public T Create(byte[] key); 10 | public T Create(string key); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /EProjectFile/EplDocument.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Context; 2 | using QIQI.EProjectFile.Encryption; 3 | using QIQI.EProjectFile.Sections; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace QIQI.EProjectFile 11 | { 12 | public class EplDocument 13 | { 14 | public List Sections { get; } = new List(); 15 | public Encoding DetermineEncoding() 16 | { 17 | return GetOrNull(ESystemInfoSection.Key)?.DetermineEncoding() ?? Encoding.GetEncoding("gbk"); 18 | } 19 | 20 | public TSection GetOrNull(ISectionKey key) where TSection : ISection 21 | { 22 | if (Sections.FirstOrDefault(x => x.SectionKey == key.SectionKey) is TSection it) 23 | { 24 | return it; 25 | } 26 | else 27 | { 28 | return default; 29 | } 30 | } 31 | 32 | public TSection Get(ISectionKey key) where TSection : ISection 33 | { 34 | return (TSection)Sections.First(x => x.SectionKey == key.SectionKey); 35 | } 36 | 37 | public void Load(Stream stream, ProjectFileReader.OnInputPassword inputPassword = null) 38 | { 39 | var encoding = Encoding.GetEncoding("gbk"); 40 | Sections.Clear(); 41 | using (var reader = new ProjectFileReader(stream, inputPassword)) 42 | { 43 | while (!reader.IsFinish) 44 | { 45 | var rawSection = reader.ReadSection(); 46 | ISection section; 47 | if (PredefinedSections.Keys.TryGetValue(rawSection.Key, out var sectionKey)) 48 | { 49 | section = sectionKey.Parse(new BlockParserContext(rawSection.Data, encoding, reader.CryptEC)); 50 | } 51 | else 52 | { 53 | section = new GeneralSection(rawSection); 54 | } 55 | if (section is ESystemInfoSection systemInfo) 56 | { 57 | encoding = systemInfo.DetermineEncoding(); 58 | } 59 | if (!(section is EndOfFileSection)) 60 | { 61 | Sections.Add(section); 62 | } 63 | } 64 | } 65 | } 66 | public void Save(Stream stream) => Save(stream, null); 67 | public void Save(Stream stream, EplEncryptionOptions encryptionOptions) 68 | { 69 | var encoding = Encoding.GetEncoding("gbk"); 70 | using (var writer = new ProjectFileWriter(stream, encryptionOptions)) 71 | { 72 | var context = new BlockByteifierContext(encoding, writer.CryptEC); 73 | foreach (var section in Sections) 74 | { 75 | if (section is ESystemInfoSection systemInfo) 76 | { 77 | encoding = systemInfo.DetermineEncoding(); 78 | } 79 | writer.WriteSection(new RawSectionInfo(section.SectionKey, section.SectionName, section.IsOptional, section.ToBytes(context))); 80 | } 81 | { 82 | var section = EndOfFileSection.Instance; 83 | writer.WriteSection(new RawSectionInfo(section.SectionKey, section.SectionName, section.IsOptional, section.ToBytes(context))); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /EProjectFile/EplSystemId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QIQI.EProjectFile 4 | { 5 | public class EplSystemId 6 | { 7 | /// 8 | /// Not a Variable 9 | /// 10 | /// 11 | /// 12 | internal const int Id_NaV = 0x0500FFFE; 13 | 14 | public const int Type_Method = 0x04000000; 15 | public const int Type_Global = 0x05000000; 16 | /// 17 | /// 普通程序集(非窗口程序集、类模块) 18 | /// 19 | public const int Type_StaticClass = 0x09000000; 20 | public const int Type_Dll = 0x0A000000; 21 | /// 22 | /// 程序集变量 或 类模块成员 23 | /// 24 | public const int Type_ClassMember = 0x15000000; 25 | public const int Type_Constant = 0x18000000; 26 | /// 27 | /// 窗口程序集 28 | /// 29 | public const int Type_FormClass = 0x19000000; 30 | /// 31 | /// 局部变量 或 子程序参数(非DLL命令) 32 | /// 33 | public const int Type_Local = 0x25000000; 34 | public const int Type_ImageResource = 0x28000000; 35 | public const int Type_StructMember = 0x35000000; 36 | public const int Type_SoundResource = 0x38000000; 37 | public const int Type_Struct = 0x41000000; 38 | public const int Type_DllParameter = 0x45000000; 39 | public const int Type_Class = 0x49000000; 40 | public const int Type_Form = 0x52000000; 41 | public const int Type_FormSelf = 0x06000000; 42 | public const int Type_FormControl = 0x16000000; 43 | public const int Type_FormMenu = 0x26000000; 44 | 45 | public const int Mask_Num = 0x00FFFFFF; 46 | public const int Mask_Type = unchecked((int)0xFF000000); 47 | 48 | /// 49 | /// 只用于用户定义Id 50 | /// 51 | /// 欲获取类型的Id 52 | /// 指定Id所属类型,参考EplSystemId.Type_* 53 | public static int GetType(int id) => id & Mask_Type; 54 | 55 | /// 56 | /// 无类型(如 某子程序无返回值) 57 | /// 58 | public const int DataType_Void = unchecked((int)0x00000000); 59 | /// 60 | /// 通用型 61 | /// 62 | public const int DataType_Any = unchecked((int)0x80000000); 63 | public const int DataType_Byte = unchecked((int)0x80000101); 64 | public const int DataType_Short = unchecked((int)0x80000201); 65 | public const int DataType_Int = unchecked((int)0x80000301); 66 | public const int DataType_Long = unchecked((int)0x80000401); 67 | public const int DataType_Float = unchecked((int)0x80000501); 68 | public const int DataType_Double = unchecked((int)0x80000601); 69 | public const int DataType_Bool = unchecked((int)0x80000002); 70 | public const int DataType_DateTime = unchecked((int)0x80000003); 71 | public const int DataType_String = unchecked((int)0x80000004); 72 | public const int DataType_Bin = unchecked((int)0x80000005); 73 | public const int DataType_MethodPtr = unchecked((int)0x80000006); 74 | /// 75 | /// 条件语句型(SDT_STATMENT),通常只用于支持库命令的参数,如 核心库 的 查找 命令 76 | /// 77 | public const int DataType_Lambda = unchecked((int)0x80000008); 78 | 79 | public static int MakeSureIsSpecifiedType(int id, params int[] type) => Array.IndexOf(type, GetType(id)) >= 0 ? id : throw new Exception("不是指定类型的Id"); 80 | 81 | public static bool IsLibDataType(int id) => (id & 0xFF000000) == 0 && id != DataType_Void; 82 | 83 | /// 84 | /// 合成库类型Id 85 | /// 86 | /// 索引从0开始(CStyle) 87 | /// 索引从0开始(CStyle) 88 | /// 89 | public static int MakeLibDataTypeId(short lib, short type) => ((int)(lib + 1) << 16) | (int)(type + 1); 90 | /// 91 | /// 分解库类型Id 92 | /// 93 | /// 94 | /// 索引从0开始(CStyle) 95 | /// 索引从0开始(CStyle) 96 | public static void DecomposeLibDataTypeId(int id, out short lib, out short type) 97 | { 98 | if (!IsLibDataType(id)) throw new Exception("DecomposeLibDataTypeId只能处理库类型Id"); 99 | unchecked 100 | { 101 | lib = (short)(id >> 16); 102 | lib--; 103 | type = (short)id; 104 | type--; 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/AccessArrayExpression.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Expressions 6 | { 7 | /// 8 | /// 访问数组成员表达式,多维数组通过多个AccessArrayExpression嵌套表示 9 | /// 10 | public class AccessArrayExpression : In0x38Expression 11 | { 12 | public readonly Expression Target; 13 | public readonly Expression Index; 14 | public AccessArrayExpression(Expression target, Expression index) 15 | { 16 | this.Target = target; 17 | this.Index = index; 18 | } 19 | 20 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 21 | { 22 | Target.ToTextCode(nameMap, writer, indent); 23 | writer.Write("["); 24 | Index.ToTextCode(nameMap, writer, indent); 25 | writer.Write("]"); 26 | } 27 | 28 | internal override void WriteTo(MethodCodeDataWriterArgs a, bool need0x1DAnd0x37) 29 | { 30 | if (need0x1DAnd0x37) 31 | { 32 | a.VariableReference.Write(a.Offest); 33 | a.ExpressionData.Write((byte)0x1D); 34 | a.ExpressionData.Write((byte)0x38); 35 | } 36 | if (Target is In0x38Expression) 37 | { 38 | ((In0x38Expression)Target).WriteTo(a, false); 39 | } 40 | else 41 | { 42 | a.ExpressionData.Write(EplSystemId.Id_NaV); 43 | a.ExpressionData.Write((byte)0x3A); 44 | Target.WriteTo(a); 45 | } 46 | a.ExpressionData.Write((byte)0x3A); 47 | if (Index is NumberLiteral) 48 | { 49 | a.ExpressionData.Write((byte)0x3B); 50 | a.ExpressionData.Write((int)((NumberLiteral)Index).Value); 51 | } 52 | else if (Index is In0x38Expression) 53 | { 54 | a.ExpressionData.Write((byte)0x38); 55 | ((In0x38Expression)Index).WriteTo(a, false); 56 | a.ExpressionData.Write((byte)0x37); 57 | } 58 | else 59 | { 60 | Index.WriteTo(a); 61 | } 62 | if (need0x1DAnd0x37) a.ExpressionData.Write((byte)0x37); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/AccessMemberExpression.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace QIQI.EProjectFile.Expressions 5 | { 6 | /// 7 | /// 访问对象成员表达式 8 | /// 9 | public class AccessMemberExpression : In0x38Expression 10 | { 11 | public readonly Expression Target; 12 | public readonly short LibraryId; 13 | public readonly int StructId; 14 | public readonly int MemberId; 15 | public AccessMemberExpression(Expression target, int structId, int memberId) 16 | { 17 | Target = target; 18 | LibraryId = -2; 19 | StructId = structId; 20 | MemberId = memberId; 21 | } 22 | public AccessMemberExpression(Expression target, short libraryId, int structId, int memberId) 23 | { 24 | Target = target; 25 | LibraryId = libraryId; 26 | StructId = structId; 27 | MemberId = memberId; 28 | } 29 | 30 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 31 | { 32 | if (Target is VariableExpression varExpr 33 | && EplSystemId.GetType(varExpr.Id) == EplSystemId.Type_FormSelf) 34 | { 35 | // 在窗口程序集直接访问窗口系统属性的情况 36 | // Do nothing 37 | } 38 | else 39 | { 40 | Target.ToTextCode(nameMap, writer, indent); 41 | writer.Write("."); 42 | } 43 | if (LibraryId == -2) 44 | { 45 | // 当出现 窗口名.xxx 访问模式 46 | // 既有可能为 窗口.系统属性 也有可能为 窗口.用户窗口组件名 47 | if (EplSystemId.GetType(StructId) == EplSystemId.Type_Form 48 | && (MemberId & 0xFF000000) == 0) 49 | { 50 | writer.Write(nameMap.GetLibTypeMemberName(0, 0, MemberId - 1)); 51 | } 52 | else 53 | { 54 | writer.Write(nameMap.GetUserDefinedName(MemberId)); 55 | } 56 | } 57 | else 58 | { 59 | writer.Write(nameMap.GetLibTypeMemberName(LibraryId, StructId, MemberId)); 60 | } 61 | } 62 | 63 | internal override void WriteTo(MethodCodeDataWriterArgs a, bool need0x1DAnd0x37) 64 | { 65 | if (need0x1DAnd0x37) 66 | { 67 | a.VariableReference.Write(a.Offest); 68 | a.ExpressionData.Write((byte)0x1D); 69 | a.ExpressionData.Write((byte)0x38); 70 | } 71 | if (Target is In0x38Expression) 72 | { 73 | ((In0x38Expression)Target).WriteTo(a, false); 74 | } 75 | else 76 | { 77 | a.ExpressionData.Write(EplSystemId.Id_NaV); 78 | a.ExpressionData.Write((byte)0x3A); 79 | Target.WriteTo(a); 80 | } 81 | a.ExpressionData.Write((byte)0x39); 82 | if (LibraryId == -2) 83 | { 84 | a.ExpressionData.Write(MemberId); 85 | a.ExpressionData.Write(StructId); 86 | } 87 | else 88 | { 89 | a.ExpressionData.Write(MemberId + 1); 90 | a.ExpressionData.Write((StructId + 1) & 0xFFFF | (LibraryId + 1) << 16); 91 | } 92 | if (need0x1DAnd0x37) a.ExpressionData.Write((byte)0x37); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/ArrayLiteralEnd.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Expressions 6 | { 7 | /// 8 | /// 解析时临时标记(数组字面量结束标识) 9 | /// 10 | internal class ArrayLiteralEnd : Expression 11 | { 12 | public static readonly ArrayLiteralEnd Instance = new ArrayLiteralEnd(); 13 | private ArrayLiteralEnd() 14 | { 15 | if (Instance != null) throw new NotSupportedException(); 16 | } 17 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) => throw new NotImplementedException(); 18 | 19 | internal override void WriteTo(MethodCodeDataWriterArgs a) => a.ExpressionData.Write((byte)0x20); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/ArrayLiteralExpression.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace QIQI.EProjectFile.Expressions 8 | { 9 | /// 10 | /// 数组字面量(常量集),包括各种类型数组、字节集等 11 | /// 12 | public class ArrayLiteralExpression : Expression, IList 13 | { 14 | private readonly List items = new List(); 15 | 16 | public Expression this[int index] { get => items[index]; set => items[index] = value; } 17 | 18 | public int Count => items.Count; 19 | 20 | public bool IsReadOnly => ((IList)items).IsReadOnly; 21 | 22 | public void Add(Expression item) 23 | { 24 | items.Add(item); 25 | } 26 | 27 | public void Clear() 28 | { 29 | items.Clear(); 30 | } 31 | 32 | public bool Contains(Expression item) 33 | { 34 | return items.Contains(item); 35 | } 36 | 37 | public void CopyTo(Expression[] array, int arrayIndex) 38 | { 39 | items.CopyTo(array, arrayIndex); 40 | } 41 | 42 | public IEnumerator GetEnumerator() 43 | { 44 | return ((IList)items).GetEnumerator(); 45 | } 46 | 47 | public int IndexOf(Expression item) 48 | { 49 | return items.IndexOf(item); 50 | } 51 | 52 | public void Insert(int index, Expression item) 53 | { 54 | items.Insert(index, item); 55 | } 56 | 57 | public bool Remove(Expression item) 58 | { 59 | return items.Remove(item); 60 | } 61 | 62 | public void RemoveAt(int index) 63 | { 64 | items.RemoveAt(index); 65 | } 66 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 67 | { 68 | writer.Write("{"); 69 | TextCodeUtils.JoinAndWriteCode(this, ", ", nameMap, writer, indent); 70 | writer.Write("}"); 71 | } 72 | internal override void WriteTo(MethodCodeDataWriterArgs a) 73 | { 74 | a.ExpressionData.Write((byte)0x1F); 75 | items.ForEach(x => x.WriteTo(a)); 76 | ArrayLiteralEnd.Instance.WriteTo(a); 77 | } 78 | 79 | IEnumerator IEnumerable.GetEnumerator() 80 | { 81 | return ((IList)items).GetEnumerator(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/BoolLiteral.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Expressions 6 | { 7 | /// 8 | /// 逻辑型字面量 9 | /// 10 | public class BoolLiteral : Expression 11 | { 12 | #pragma warning disable CS0612 // 类型或成员已过时 13 | public static readonly BoolLiteral True = new BoolLiteral(true); 14 | public static readonly BoolLiteral False = new BoolLiteral(false); 15 | #pragma warning restore CS0612 // 类型或成员已过时 16 | public static BoolLiteral ValueOf(bool x) => x ? True : False; 17 | public readonly bool Value; 18 | [Obsolete] 19 | public BoolLiteral(bool value) 20 | { 21 | this.Value = value; 22 | } 23 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 24 | { 25 | writer.Write(Value ? "真" : "假"); 26 | } 27 | internal override void WriteTo(MethodCodeDataWriterArgs a) 28 | { 29 | a.ExpressionData.Write((byte)0x18); 30 | a.ExpressionData.Write((short)(Value ? -1 : 0)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/ConstantExpression.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace QIQI.EProjectFile.Expressions 5 | { 6 | /// 7 | /// 访问常量 8 | /// 9 | public class ConstantExpression : Expression 10 | { 11 | public readonly short LibraryId; 12 | public readonly int ConstantId; 13 | public ConstantExpression(short libraryId, int constantId) 14 | { 15 | this.LibraryId = libraryId; 16 | this.ConstantId = constantId; 17 | } 18 | public ConstantExpression(int constantId) : this(-2, constantId) 19 | { 20 | } 21 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 22 | { 23 | writer.Write("#"); 24 | writer.Write(LibraryId == -2 ? nameMap.GetUserDefinedName(ConstantId) : nameMap.GetLibConstantName(LibraryId, ConstantId)); 25 | } 26 | internal override void WriteTo(MethodCodeDataWriterArgs a) 27 | { 28 | if (LibraryId == -2) 29 | { 30 | a.ConstantReference.Write(a.Offest); 31 | a.ExpressionData.Write((byte)0x1B); 32 | a.ExpressionData.Write(ConstantId); 33 | } 34 | else 35 | { 36 | a.ExpressionData.Write((byte)0x1C); 37 | a.ExpressionData.Write((short)(LibraryId + 1)); 38 | a.ExpressionData.Write((short)(ConstantId + 1)); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/DateTimeLiteral.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Expressions 6 | { 7 | /// 8 | /// 日期时间型字面量 9 | /// 10 | public class DateTimeLiteral : Expression 11 | { 12 | public readonly DateTime Value; 13 | 14 | public DateTimeLiteral(DateTime value) 15 | { 16 | this.Value = value; 17 | } 18 | 19 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 20 | { 21 | writer.Write("["); 22 | if (Value != null) 23 | { 24 | if (Value.TimeOfDay.TotalSeconds == 0) 25 | writer.Write(Value.ToString("yyyy年MM月dd日")); 26 | else 27 | writer.Write(Value.ToString("yyyy年MM月dd日HH时mm分ss秒")); 28 | } 29 | writer.Write("]"); 30 | 31 | } 32 | internal override void WriteTo(MethodCodeDataWriterArgs a) 33 | { 34 | a.ExpressionData.Write((byte)0x19); 35 | a.ExpressionData.Write(Value.ToOADate()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/DefaultValueExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Expressions 6 | { 7 | public class DefaultValueExpression : Expression 8 | { 9 | public static readonly DefaultValueExpression Instance = new DefaultValueExpression(); 10 | private DefaultValueExpression() 11 | { 12 | if (Instance != null) throw new NotSupportedException(); 13 | } 14 | internal override void WriteTo(MethodCodeDataWriterArgs a) => a.ExpressionData.Write((byte)0x16); 15 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 16 | { 17 | // Nothing need doing. 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/EmnuConstantExpression.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace QIQI.EProjectFile.Expressions 5 | { 6 | /// 7 | /// 访问枚举(支持库) 8 | /// 9 | public class EmnuConstantExpression : Expression 10 | { 11 | public readonly short LibraryId; 12 | public readonly short StructId; 13 | public readonly int MemberId; 14 | public EmnuConstantExpression(short structId, short libraryId, int memberId) 15 | { 16 | this.StructId = structId; 17 | this.LibraryId = libraryId; 18 | this.MemberId = memberId; 19 | } 20 | internal override void WriteTo(MethodCodeDataWriterArgs a) 21 | { 22 | a.ExpressionData.Write((byte)0x23); 23 | a.ExpressionData.Write((short)(StructId + 1)); 24 | a.ExpressionData.Write((short)(LibraryId + 1)); 25 | a.ExpressionData.Write(MemberId + 1); 26 | } 27 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 28 | { 29 | writer.Write("#"); 30 | writer.Write(nameMap.GetLibTypeName(LibraryId, StructId)); 31 | writer.Write("."); 32 | writer.Write(nameMap.GetLibTypeMemberName(LibraryId, StructId, MemberId)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/Expression.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace QIQI.EProjectFile.Expressions 5 | { 6 | /// 7 | /// 表达式 基类 8 | /// 9 | public abstract class Expression : IToTextCodeAble 10 | { 11 | internal abstract void WriteTo(MethodCodeDataWriterArgs a); 12 | public sealed override string ToString() => this.ToTextCode(IdToNameMap.Empty); 13 | public abstract void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/In0x38Expression.cs: -------------------------------------------------------------------------------- 1 | namespace QIQI.EProjectFile.Expressions 2 | { 3 | public abstract class In0x38Expression : Expression 4 | { 5 | internal abstract void WriteTo(MethodCodeDataWriterArgs a, bool need0x1DAnd0x37); 6 | internal override void WriteTo(MethodCodeDataWriterArgs a) 7 | { 8 | WriteTo(a, true); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/MethodPtrExpression.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace QIQI.EProjectFile.Expressions 5 | { 6 | /// 7 | /// 子程序取址 8 | /// 9 | public class MethodPtrExpression : Expression 10 | { 11 | public readonly int MethodId; 12 | public MethodPtrExpression(int methodId) 13 | { 14 | this.MethodId = methodId; 15 | } 16 | 17 | internal override void WriteTo(MethodCodeDataWriterArgs a) 18 | { 19 | a.MethodReference.Write(a.Offest); 20 | a.ExpressionData.Write((byte)0x1E); 21 | a.ExpressionData.Write(MethodId); 22 | } 23 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 24 | { 25 | writer.Write("&"); 26 | writer.Write(nameMap.GetUserDefinedName(MethodId)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/NumberLiteral.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace QIQI.EProjectFile.Expressions 5 | { 6 | /// 7 | /// 数值型字面量(易语言内部统一按double处理) 8 | /// 9 | public class NumberLiteral : Expression 10 | { 11 | public readonly double Value; 12 | public NumberLiteral(double value) 13 | { 14 | this.Value = value; 15 | } 16 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 17 | { 18 | writer.Write(Value); 19 | } 20 | internal override void WriteTo(MethodCodeDataWriterArgs a) 21 | { 22 | a.ExpressionData.Write((byte)0x17); 23 | a.ExpressionData.Write(Value); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/ParamListEnd.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Expressions 6 | { 7 | /// 8 | /// 解析时临时标记(参数列表结束标识) 9 | /// 10 | internal class ParamListEnd : Expression 11 | { 12 | public static readonly ParamListEnd Instance = new ParamListEnd(); 13 | private ParamListEnd() 14 | { 15 | if (Instance != null) throw new NotSupportedException(); 16 | } 17 | 18 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) => throw new NotImplementedException(); 19 | 20 | internal override void WriteTo(MethodCodeDataWriterArgs a) => a.ExpressionData.Write((byte)0x01); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/ParamListExpression.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace QIQI.EProjectFile.Expressions 8 | { 9 | /// 10 | /// 参数列表,配合CallExpression用 11 | /// 12 | public class ParamListExpression : Expression, IList 13 | { 14 | private readonly List parameters = new List(); 15 | 16 | public int Count => parameters.Count; 17 | 18 | public bool IsReadOnly => ((IList)parameters).IsReadOnly; 19 | 20 | public Expression this[int index] { get => parameters[index]; set => this.parameters[index] = value; } 21 | 22 | public void Add(Expression item) 23 | { 24 | parameters.Add(item ?? DefaultValueExpression.Instance); 25 | } 26 | internal override void WriteTo(MethodCodeDataWriterArgs a) 27 | { 28 | parameters.ForEach(x => x.WriteTo(a)); 29 | ParamListEnd.Instance.WriteTo(a); 30 | } 31 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 32 | { 33 | writer.Write("("); 34 | TextCodeUtils.JoinAndWriteCode(this, ", ", nameMap, writer, indent); 35 | writer.Write(")"); 36 | } 37 | 38 | public IEnumerator GetEnumerator() => ((IEnumerable)parameters).GetEnumerator(); 39 | IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)parameters).GetEnumerator(); 40 | 41 | public int IndexOf(Expression item) 42 | { 43 | return parameters.IndexOf(item ?? DefaultValueExpression.Instance); 44 | } 45 | 46 | public void Insert(int index, Expression item) 47 | { 48 | parameters.Insert(index, item ?? DefaultValueExpression.Instance); 49 | } 50 | 51 | public void RemoveAt(int index) 52 | { 53 | parameters.RemoveAt(index); 54 | } 55 | 56 | public void Clear() 57 | { 58 | parameters.Clear(); 59 | } 60 | 61 | public bool Contains(Expression item) 62 | { 63 | return parameters.Contains(item ?? DefaultValueExpression.Instance); 64 | } 65 | 66 | public void CopyTo(Expression[] array, int arrayIndex) 67 | { 68 | parameters.CopyTo(array, arrayIndex); 69 | } 70 | 71 | public bool Remove(Expression item) 72 | { 73 | return parameters.Remove(item ?? DefaultValueExpression.Instance); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/StringLiteral.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Expressions 6 | { 7 | /// 8 | /// 文本型字面量 9 | /// 10 | public class StringLiteral : Expression 11 | { 12 | public readonly string Value; 13 | public StringLiteral(string value) 14 | { 15 | this.Value = value; 16 | } 17 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 18 | { 19 | writer.Write("“"); 20 | writer.Write(Value ?? ""); 21 | writer.Write("”"); 22 | } 23 | internal override void WriteTo(MethodCodeDataWriterArgs a) 24 | { 25 | a.ExpressionData.Write((byte)0x1A); 26 | a.ExpressionData.WriteBStr(a.Encoding, Value ?? ""); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /EProjectFile/Expressions/VariableExpression.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace QIQI.EProjectFile.Expressions 5 | { 6 | /// 7 | /// 访问变量表达式 8 | /// 9 | public class VariableExpression : In0x38Expression 10 | { 11 | public readonly int Id; 12 | public VariableExpression(int id) 13 | { 14 | this.Id = id; 15 | } 16 | 17 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 18 | { 19 | writer.Write(nameMap.GetUserDefinedName(Id)); 20 | } 21 | internal override void WriteTo(MethodCodeDataWriterArgs a, bool need0x1DAnd0x37) 22 | { 23 | if (need0x1DAnd0x37) 24 | { 25 | a.VariableReference.Write(a.Offest); 26 | a.ExpressionData.Write((byte)0x1D); 27 | a.ExpressionData.Write((byte)0x38); 28 | } 29 | a.ExpressionData.Write(Id); 30 | if (need0x1DAnd0x37) a.ExpressionData.Write((byte)0x37); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /EProjectFile/FormElementInfo.cs: -------------------------------------------------------------------------------- 1 | using OpenEpl.ELibInfo; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | using System.Text.Json.Serialization; 8 | using System.Text.Json; 9 | 10 | namespace QIQI.EProjectFile 11 | { 12 | [JsonConverter(typeof(FormElementInfoJsonConverter))] 13 | public abstract class FormElementInfo : IHasId 14 | { 15 | private const int DataType_Menu = 65539; 16 | 17 | private class FormElementInfoJsonConverter : JsonConverter 18 | { 19 | public override FormElementInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 20 | { 21 | var element = JsonElement.ParseValue(ref reader); 22 | if (!element.TryGetProperty("DataType", out var jsonElement)) 23 | { 24 | throw new Exception($"Missing {nameof(FormElementInfo)}.{nameof(FormElementInfo.DataType)}"); 25 | } 26 | if (!jsonElement.TryGetInt32(out var dataType)) 27 | { 28 | throw new Exception($"Unsupported {nameof(FormElementInfo)}.{nameof(FormElementInfo.DataType)} = {jsonElement}"); 29 | } 30 | return dataType switch 31 | { 32 | DataType_Menu => element.Deserialize(options), 33 | _ => element.Deserialize(options), 34 | }; 35 | } 36 | 37 | public override void Write( 38 | Utf8JsonWriter writer, 39 | FormElementInfo value, 40 | JsonSerializerOptions options) 41 | { 42 | switch (value) 43 | { 44 | case FormMenuInfo menu: 45 | JsonSerializer.Serialize(writer, menu, options); 46 | break; 47 | case FormControlInfo control: 48 | JsonSerializer.Serialize(writer, control, options); 49 | break; 50 | default: 51 | throw new Exception($"Unknown sub class of {nameof(FormElementInfo)}"); 52 | } 53 | } 54 | } 55 | 56 | public abstract int Id { get; } 57 | public int DataType { get; set; } 58 | public string Name { get; set; } 59 | public bool Visible { get; set; } 60 | public bool Disable { get; set; } 61 | 62 | public static List ReadFormElements(BinaryReader r, Encoding encoding) 63 | { 64 | return r.ReadBlocksWithIdAndOffest((reader, id, length) => 65 | { 66 | var dataType = reader.ReadInt32(); 67 | FormElementInfo elem; 68 | if (dataType == DataType_Menu) 69 | { 70 | elem = FormMenuInfo.ReadWithoutDataType(r, encoding, id, length - 4); 71 | } 72 | else 73 | { 74 | elem = FormControlInfo.ReadWithoutDataType(r, encoding, id, length - 4); 75 | } 76 | elem.DataType = dataType; 77 | return elem; 78 | }); 79 | } 80 | public static void WriteFormElements(BinaryWriter w, Encoding encoding, List formElements) 81 | { 82 | w.WriteBlocksWithIdAndOffest( 83 | encoding, 84 | formElements, 85 | (writer, elem) => 86 | { 87 | elem.WriteWithoutId(writer, encoding); 88 | }); 89 | } 90 | 91 | protected abstract void WriteWithoutId(BinaryWriter writer, Encoding encoding); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /EProjectFile/FormInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.Json; 6 | using QIQI.EProjectFile.Internal; 7 | 8 | namespace QIQI.EProjectFile 9 | { 10 | public class FormInfo: IHasId, IHasMemoryAddress 11 | { 12 | public int Id { get; } 13 | 14 | public FormInfo(int id) 15 | { 16 | this.Id = id; 17 | } 18 | 19 | public int MemoryAddress { get; set; } 20 | public int UnknownBeforeClass { get; set; } 21 | /// 22 | /// 对应的窗口程序集 23 | /// 24 | public int Class { get; set; } 25 | public string Name { get; set; } 26 | public string Comment { get; set; } 27 | public List Elements { get; set; } 28 | public static List ReadForms(BinaryReader r, Encoding encoding) 29 | { 30 | return r.ReadBlocksWithIdAndMemoryAddress((reader, id, memoryAddress) => new FormInfo(id) 31 | { 32 | MemoryAddress = memoryAddress, 33 | UnknownBeforeClass = reader.ReadInt32(), 34 | Class = reader.ReadInt32(), 35 | Name = reader.ReadStringWithLengthPrefix(encoding), 36 | Comment = reader.ReadStringWithLengthPrefix(encoding), 37 | Elements = FormElementInfo.ReadFormElements(reader, encoding) 38 | }); 39 | } 40 | 41 | public static void WriteForms(BinaryWriter w, Encoding encoding, List forms) 42 | { 43 | w.WriteBlocksWithIdAndMemoryAddress(forms, (writer, elem) => 44 | { 45 | writer.Write(elem.UnknownBeforeClass); 46 | writer.Write(elem.Class); 47 | writer.WriteStringWithLengthPrefix(encoding, elem.Name); 48 | writer.WriteStringWithLengthPrefix(encoding, elem.Comment); 49 | FormElementInfo.WriteFormElements(writer, encoding, elem.Elements); 50 | }); 51 | } 52 | 53 | public override string ToString() 54 | { 55 | return JsonSerializer.Serialize(this, JsonUtils.Options); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /EProjectFile/FormMenuInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Immutable; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Text.Json.Serialization; 9 | 10 | namespace QIQI.EProjectFile 11 | { 12 | public class FormMenuInfo : FormElementInfo 13 | { 14 | private static readonly ImmutableArray Zero16Bytes = ImmutableArray.Create(new byte[16]); 15 | private static readonly ImmutableArray Zero20Bytes = ImmutableArray.Create(new byte[20]); 16 | 17 | public override int Id { get; } 18 | 19 | public FormMenuInfo(int id) 20 | { 21 | this.Id = id; 22 | } 23 | 24 | [JsonIgnore] 25 | public ImmutableArray UnknownBeforeName { get; set; } = Zero20Bytes; 26 | public int HotKey { get; set; } 27 | public int Level { get; set; } 28 | public bool Selected { get; set; } 29 | public string Text { get; set; } 30 | /// 31 | /// 仅EC有效 32 | /// 33 | public int ClickEvent { get; set; } 34 | [JsonIgnore] 35 | public ImmutableArray UnknownAfterClickEvent { get; set; } = Zero16Bytes; 36 | internal static FormMenuInfo ReadWithoutDataType(BinaryReader reader, Encoding encoding, int id, int length) 37 | { 38 | var startPosition = reader.BaseStream.Position; 39 | var elem = new FormMenuInfo(id); 40 | elem.UnknownBeforeName = reader.ReadImmutableBytes(20) switch 41 | { 42 | var x when x.SequenceEqual(Zero20Bytes) => Zero20Bytes, 43 | var x => x 44 | }; 45 | elem.Name = reader.ReadCStyleString(encoding); 46 | reader.ReadCStyleString(encoding); // 菜单没有Comment 47 | elem.HotKey = reader.ReadInt32(); 48 | elem.Level = reader.ReadInt32(); 49 | { 50 | int showStatus = reader.ReadInt32(); 51 | elem.Visible = (showStatus & 0x1) == 0; 52 | elem.Disable = (showStatus & 0x2) != 0; 53 | elem.Selected = (showStatus & 0x4) != 0; 54 | if ((showStatus & 0xFFFFFFF8) != 0) 55 | { 56 | throw new Exception($"Unknown flag for show status of the menu is found, value = 0x{showStatus:X8}"); 57 | } 58 | } 59 | elem.Text = reader.ReadCStyleString(encoding); 60 | elem.ClickEvent = reader.ReadInt32(); 61 | elem.UnknownAfterClickEvent = reader.ReadImmutableBytes(length - (int)(reader.BaseStream.Position - startPosition)) switch 62 | { 63 | var x when x.SequenceEqual(Zero16Bytes) => Zero16Bytes, 64 | var x => x 65 | }; ; 66 | return elem; 67 | } 68 | protected override void WriteWithoutId(BinaryWriter writer, Encoding encoding) 69 | { 70 | writer.Write(DataType); 71 | writer.Write(UnknownBeforeName); 72 | writer.WriteCStyleString(encoding, Name); 73 | writer.WriteCStyleString(encoding, ""); 74 | writer.Write(HotKey); 75 | writer.Write(Level); 76 | writer.Write((Visible ? 0 : 0x1) | (Disable ? 0x2 : 0) | (Selected ? 0x4 : 0)); 77 | writer.WriteCStyleString(encoding, Text); 78 | writer.Write(ClickEvent); 79 | writer.Write(UnknownAfterClickEvent); 80 | } 81 | public override string ToString() 82 | { 83 | return JsonSerializer.Serialize(this, JsonUtils.Options); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /EProjectFile/IHasId.cs: -------------------------------------------------------------------------------- 1 | namespace QIQI.EProjectFile 2 | { 3 | public interface IHasId 4 | { 5 | int Id { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /EProjectFile/IHasMemoryAddress.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile 6 | { 7 | public interface IHasMemoryAddress 8 | { 9 | /// 10 | /// 最后一次保存时易语言 IDE 储存相关结构使用的内存地址 11 | /// 在用于编辑文件的场景下,意义不大 12 | /// 13 | int MemoryAddress { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EProjectFile/IToTextCodeAble.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile 6 | { 7 | public interface IToTextCodeAble 8 | { 9 | /// 10 | /// 开头含缩进,结尾不含换行 11 | /// 12 | /// 13 | /// 14 | /// 15 | void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0); 16 | } 17 | public static class ExtensionForIToTextCodeAble 18 | { 19 | public static string ToTextCode(this IToTextCodeAble target, IdToNameMap nameMap, int indent = 0) 20 | { 21 | var writer = new StringWriter(); 22 | target.ToTextCode(nameMap, writer, indent); 23 | return writer.ToString(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /EProjectFile/IndexedEventInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile 6 | { 7 | public class IndexedEventInfo 8 | { 9 | public int FormId { get; set; } 10 | public int UnitId { get; set; } 11 | public int EventId { get; set; } 12 | public int MethodId { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /EProjectFile/Internal/ByteArrayHexConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.Json; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Text.Json.Serialization; 8 | 9 | namespace QIQI.EProjectFile.Internal 10 | { 11 | internal class ByteArrayHexConverter : JsonConverter 12 | { 13 | public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 14 | { 15 | return BytesUtils.HexToBytes(reader.GetString()); 16 | } 17 | 18 | public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) 19 | { 20 | writer.WriteStringValue(BytesUtils.BytesToHex(value)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /EProjectFile/Internal/BytesUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Internal 6 | { 7 | internal static class BytesUtils 8 | { 9 | public static byte[] HexToBytes(string src) 10 | { 11 | byte[] result = new byte[src.Length / 2]; 12 | for (int i = 0, c = 0; i < src.Length; i += 2, c++) 13 | { 14 | result[c] = Convert.ToByte(src.Substring(i, 2), 16); 15 | } 16 | return result; 17 | } 18 | 19 | public static string BytesToHex(byte[] data) 20 | { 21 | var sb = new StringBuilder(data.Length * 2); 22 | if (data != null) 23 | { 24 | for (int i = 0; i < data.Length; i++) 25 | { 26 | sb.Append(data[i].ToString("X2")); 27 | } 28 | } 29 | return sb.ToString(); 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /EProjectFile/Internal/CryptoECTransform.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Encryption; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | 7 | namespace QIQI.EProjectFile.Internal 8 | { 9 | sealed class CryptoECTransform : EStdCryptoTransform 10 | { 11 | public CryptoECTransform(EplSecret.EC secret, int lengthOfOvert = 0): base(secret, lengthOfOvert) 12 | { 13 | } 14 | 15 | protected override RC4Crypto NextBlockCrypto() 16 | { 17 | byte[] blockKey = new byte[40]; 18 | keyTable.Decode(blockKey, 0, 8); 19 | SecretId.CopyTo(0, blockKey, 8, 32); 20 | return new RC4Crypto(blockKey, EplSecret.EC.DefaultInitialStatus); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /EProjectFile/Internal/EStdCryptoTransform.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Encryption; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | 8 | namespace QIQI.EProjectFile.Internal 9 | { 10 | internal class EStdCryptoTransform : ICryptoTransform 11 | { 12 | private const int BlockLength = 4096; 13 | protected readonly RC4Crypto keyTable; 14 | 15 | public ImmutableArray SecretId { get; } 16 | private int lengthOfRemainedOvert = 0; 17 | 18 | public EStdCryptoTransform(EplSecret.EStd secret, int lengthOfOvert = 0): this((EplSecret)secret, lengthOfOvert) 19 | { 20 | } 21 | 22 | protected EStdCryptoTransform(EplSecret secret, int lengthOfOvert = 0) 23 | { 24 | if (secret is null) 25 | { 26 | throw new ArgumentNullException(nameof(secret)); 27 | } 28 | if (lengthOfOvert < 0) 29 | { 30 | throw new ArgumentException(nameof(lengthOfOvert)); 31 | } 32 | keyTable = new RC4Crypto(default, secret.IV); 33 | SecretId = secret.SecretId; 34 | lengthOfRemainedOvert = lengthOfOvert; 35 | } 36 | 37 | public bool CanReuseTransform => false; 38 | 39 | public bool CanTransformMultipleBlocks => false; 40 | 41 | public int InputBlockSize => BlockLength; 42 | 43 | public int OutputBlockSize => BlockLength; 44 | 45 | private int blockIndex = 0; 46 | 47 | public void Dispose() 48 | { 49 | } 50 | 51 | public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 52 | { 53 | Array.Copy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount); 54 | var crpyto = NextBlockCrypto(); 55 | if (inputCount > lengthOfRemainedOvert) 56 | { 57 | crpyto.Skip(lengthOfRemainedOvert); 58 | crpyto.Decode(outputBuffer, outputOffset + lengthOfRemainedOvert, inputCount - lengthOfRemainedOvert); 59 | lengthOfRemainedOvert = 0; 60 | } 61 | else 62 | { 63 | lengthOfRemainedOvert -= inputCount; 64 | } 65 | return inputCount; 66 | } 67 | 68 | protected virtual RC4Crypto NextBlockCrypto() 69 | { 70 | byte[] blockKey = new byte[40]; 71 | keyTable.Decode(blockKey, 0, 4); 72 | var nPrefix = blockKey[0] + (blockKey[1] << 8) + (blockKey[2] << 16) + (blockKey[3] << 24); 73 | var nSuffix = blockIndex ^ nPrefix; 74 | blockIndex++; 75 | SecretId.CopyTo(0, blockKey, 4, 32); 76 | blockKey[36] = unchecked((byte)(nSuffix & 0xFF)); 77 | blockKey[37] = unchecked((byte)((nSuffix >> 8) & 0xFF)); 78 | blockKey[38] = unchecked((byte)((nSuffix >> 16) & 0xFF)); 79 | blockKey[39] = unchecked((byte)((nSuffix >> 24) & 0xFF)); 80 | var crypto = new RC4Crypto(blockKey, 256); 81 | crypto.Skip(36); // Magic 82 | return crypto; 83 | } 84 | 85 | public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) 86 | { 87 | var buf = new byte[inputCount]; 88 | TransformBlock(inputBuffer, inputOffset, inputCount, buf, 0); 89 | return buf; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /EProjectFile/Internal/EditorTabInfoJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.EditorTabInfo; 2 | using System; 3 | using System.Linq; 4 | using System.Text.Json; 5 | using System.Text.Json.Nodes; 6 | using System.Text.Json.Serialization; 7 | 8 | namespace QIQI.EProjectFile.Internal 9 | { 10 | internal class EditorTabInfoJsonConverter : JsonConverter 11 | { 12 | public override IEditorTabInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 13 | { 14 | var root = JsonNode.Parse(ref reader).AsObject(); 15 | if (!root.TryGetPropertyValue(nameof(IEditorTabInfo.TypeId), out var typeIdNode)) 16 | { 17 | throw new Exception($"Missing {nameof(IEditorTabInfo.TypeId)}"); 18 | } 19 | var typeId = (byte)typeIdNode; 20 | if (PredefinedEditorTabInfos.Keys.TryGetValue(typeId, out var etik)) 21 | { 22 | var editorTabInfoType = etik.GetType() 23 | .GetInterfaces() 24 | .First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEditorTabInfoKey<>)) 25 | .GetGenericArguments() 26 | .Single(); 27 | return (IEditorTabInfo)JsonSerializer.Deserialize(root, editorTabInfoType, options); 28 | } 29 | else if (root.ContainsKey("Data")) 30 | { 31 | return JsonSerializer.Deserialize(root, options); 32 | } 33 | else 34 | { 35 | throw new Exception($"Failed to find a suitable JSON Deserializer of {nameof(IEditorTabInfo)} for Unknown[0x{typeId:X2}]"); 36 | } 37 | } 38 | 39 | public override void Write(Utf8JsonWriter writer, IEditorTabInfo value, JsonSerializerOptions options) 40 | { 41 | JsonSerializer.Serialize(writer, value, options); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /EProjectFile/Internal/EncodingJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace QIQI.EProjectFile.Internal 8 | { 9 | internal class EncodingJsonConverter : JsonConverter 10 | { 11 | public override Encoding Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 12 | { 13 | return Encoding.GetEncoding(reader.GetString()); 14 | } 15 | 16 | public override void Write(Utf8JsonWriter writer, Encoding value, JsonSerializerOptions options) 17 | { 18 | writer.WriteStringValue(value.WebName); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /EProjectFile/Internal/ImmutableByteArrayHexConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | 9 | namespace QIQI.EProjectFile.Internal 10 | { 11 | internal class ImmutableByteArrayHexConverter : JsonConverter> 12 | { 13 | public static ImmutableArray HexToBytes(string src) 14 | { 15 | var data = BytesUtils.HexToBytes(src); 16 | return Unsafe.As>(ref data); 17 | } 18 | 19 | public static string BytesToHex(ImmutableArray data) 20 | { 21 | return BytesUtils.BytesToHex(Unsafe.As, byte[]>(ref data)); 22 | } 23 | 24 | public override ImmutableArray Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 25 | { 26 | return HexToBytes(reader.GetString()); 27 | } 28 | 29 | public override void Write(Utf8JsonWriter writer, ImmutableArray value, JsonSerializerOptions options) 30 | { 31 | writer.WriteStringValue(BytesToHex(value)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /EProjectFile/Internal/JsonUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.Encodings.Web; 5 | using System.Text.Json; 6 | 7 | namespace QIQI.EProjectFile.Internal 8 | { 9 | internal class JsonUtils 10 | { 11 | public static JsonSerializerOptions Options = new JsonSerializerOptions 12 | { 13 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 14 | WriteIndented = true 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /EProjectFile/Internal/PrefixedStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace QIQI.EProjectFile 5 | { 6 | internal class PrefixedStream: Stream 7 | { 8 | public Stream BaseStream { get; } 9 | public byte[] Prefix { get; } 10 | private int offsetOfPrefix; 11 | public PrefixedStream(Stream stream, byte[] prefix) 12 | { 13 | this.BaseStream = stream; 14 | this.Prefix = prefix; 15 | } 16 | 17 | public override bool CanRead => BaseStream.CanRead; 18 | 19 | public override bool CanSeek => false; 20 | 21 | public override bool CanWrite => BaseStream.CanWrite; 22 | 23 | public override long Length => throw new NotSupportedException(); 24 | 25 | public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } 26 | 27 | protected override void Dispose(bool disposing) 28 | { 29 | base.Dispose(disposing); 30 | if (disposing) 31 | { 32 | BaseStream.Dispose(); 33 | } 34 | } 35 | 36 | public override void Flush() 37 | { 38 | BaseStream.Flush(); 39 | } 40 | 41 | public override int Read(byte[] buffer, int offset, int count) 42 | { 43 | int copiedLength = Math.Min(Prefix.Length - offsetOfPrefix, count); 44 | if (copiedLength > 0) 45 | { 46 | Array.Copy(Prefix, offsetOfPrefix, buffer, offset, copiedLength); 47 | offsetOfPrefix += copiedLength; 48 | offset += copiedLength; 49 | count -= copiedLength; 50 | if (count == 0) 51 | { 52 | return copiedLength; 53 | } 54 | } 55 | return BaseStream.Read(buffer, offset, count) + copiedLength; 56 | } 57 | 58 | public override long Seek(long offset, SeekOrigin origin) 59 | { 60 | throw new NotSupportedException(); 61 | } 62 | 63 | public override void SetLength(long value) 64 | { 65 | throw new NotSupportedException(); 66 | } 67 | 68 | public override void Write(byte[] buffer, int offset, int count) 69 | { 70 | int copiedLength = Math.Min(Prefix.Length - offsetOfPrefix, count); 71 | if (copiedLength > 0) 72 | { 73 | Array.Copy(buffer, offset, Prefix, offsetOfPrefix, copiedLength); 74 | offsetOfPrefix += copiedLength; 75 | offset += copiedLength; 76 | count -= copiedLength; 77 | if (count == 0) 78 | { 79 | return; 80 | } 81 | } 82 | BaseStream.Write(buffer, offset, count); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /EProjectFile/Internal/RC4Crypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace QIQI.EProjectFile.Internal 9 | { 10 | internal class RC4Crypto : ICloneable 11 | { 12 | private readonly byte[] status; 13 | private int i = 0; 14 | private int j = 0; 15 | /// It can be null 16 | /// It cannot be null 17 | /// 18 | public RC4Crypto(byte[] key, byte[] initialStatus) 19 | { 20 | status = (byte[])initialStatus?.Clone() ?? throw new ArgumentNullException(nameof(initialStatus)); 21 | EmitKey(key); 22 | } 23 | /// It can be null 24 | /// It cannot be null 25 | /// 26 | public RC4Crypto(ImmutableArray key, ImmutableArray initialStatus) 27 | { 28 | if (initialStatus.IsDefault) 29 | { 30 | throw new ArgumentException(nameof(initialStatus)); 31 | } 32 | status = initialStatus.ToArray(); 33 | EmitKey(key); 34 | } 35 | public RC4Crypto(byte[] key, int statusLength) 36 | { 37 | status = new byte[statusLength]; 38 | for (i = 0; i < statusLength; i++) 39 | { 40 | status[i] = unchecked((byte)i); 41 | } 42 | EmitKey(key); 43 | } 44 | public RC4Crypto(ImmutableArray key, int statusLength) 45 | { 46 | status = new byte[statusLength]; 47 | for (i = 0; i < statusLength; i++) 48 | { 49 | status[i] = unchecked((byte)i); 50 | } 51 | EmitKey(key); 52 | } 53 | private void EmitKey(byte[] key) => EmitKey(Unsafe.As>(ref key)); 54 | private void EmitKey(ImmutableArray key) 55 | { 56 | if (key.IsDefaultOrEmpty) 57 | { 58 | return; 59 | } 60 | int p2 = 0; 61 | for (int p1 = 0; p1 < status.Length; p1++) 62 | { 63 | p2 = (p2 + status[p1] + key[p1 % key.Length]) % status.Length; 64 | (status[p2], status[p1]) = (status[p1], status[p2]); 65 | } 66 | } 67 | public void Decode(byte[] data, long start, long length) 68 | { 69 | long end = start + length; 70 | for (var offset = start; offset < end; offset++) 71 | { 72 | i = (i + 1) % status.Length; 73 | j = (j + status[i]) % status.Length; 74 | (status[j], status[i]) = (status[i], status[j]); 75 | if (data != null) 76 | { 77 | data[offset] ^= status[(status[i] + status[j]) % status.Length]; 78 | } 79 | } 80 | } 81 | /// do not modify the return value 82 | internal byte[] UnsafeGetStatus() 83 | { 84 | return status; 85 | } 86 | public void Encode(byte[] data, long start, long length) => Decode(data, start, length); 87 | public void Decode(byte[] data) => Decode(data, 0, data.Length); 88 | public void Encode(byte[] data) => Encode(data, 0, data.Length); 89 | public void Skip(long length) => Decode(null, 0, length); 90 | public object Clone() => new RC4Crypto(null, status) { i = i, j = j }; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /EProjectFile/Internal/SectionJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Sections; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Text.Json.Nodes; 8 | using System.Text.Json.Serialization; 9 | using static System.Collections.Specialized.BitVector32; 10 | 11 | namespace QIQI.EProjectFile.Internal 12 | { 13 | internal class SectionJsonConverter : JsonConverter 14 | { 15 | public override ISection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 16 | { 17 | var root = JsonNode.Parse(ref reader).AsObject(); 18 | if (!root.TryGetPropertyValue(nameof(ISection.SectionKey), out var sectionKeyNode)) 19 | { 20 | throw new Exception($"Missing {nameof(ISection.SectionKey)}"); 21 | } 22 | var sectionKey = (int)sectionKeyNode; 23 | if (PredefinedSections.Keys.TryGetValue(sectionKey, out var sk)) 24 | { 25 | var sectionType = sk.GetType() 26 | .GetInterfaces() 27 | .First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ISectionKey<>)) 28 | .GetGenericArguments() 29 | .Single(); 30 | return (ISection)JsonSerializer.Deserialize(root, sectionType, options); 31 | } 32 | else if (root.ContainsKey("Data")) 33 | { 34 | return JsonSerializer.Deserialize(root, options); 35 | } 36 | else 37 | { 38 | if (root.TryGetPropertyValue(nameof(ISection.SectionName), out var sectionNameNode)) 39 | { 40 | throw new Exception($"Failed to find a suitable JSON Deserializer for {sectionNameNode}[0x{sectionKey:X8}]"); 41 | } 42 | else 43 | { 44 | throw new Exception($"Failed to find a suitable JSON Deserializer for Unknown[0x{sectionKey:X8}]"); 45 | } 46 | } 47 | } 48 | 49 | public override void Write(Utf8JsonWriter writer, ISection value, JsonSerializerOptions options) 50 | { 51 | JsonSerializer.Serialize(writer, value, options); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /EProjectFile/Internal/StreamWithImmutableExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.IO; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace QIQI.EProjectFile.Internal 9 | { 10 | internal static class StreamWithImmutableExtension 11 | { 12 | public static ImmutableArray ReadImmutableBytes(this BinaryReader reader, int count) 13 | { 14 | var data = reader.ReadBytes(count); 15 | return Unsafe.As>(ref data); 16 | } 17 | public static void Write(this BinaryWriter writer, ImmutableArray data) 18 | { 19 | writer.Write(Unsafe.As, byte[]>(ref data)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /EProjectFile/Internal/TextCodeUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Collections.Generic; 5 | using QIQI.EProjectFile.Sections; 6 | using System.IO; 7 | 8 | namespace QIQI.EProjectFile.Internal 9 | { 10 | internal class TextCodeUtils 11 | { 12 | private TextCodeUtils() => throw new NotSupportedException(); 13 | public static void WriteDefinitionCode(TextWriter writer, int indent, string type, params string[] items) 14 | { 15 | if (writer == null) 16 | throw new ArgumentNullException(nameof(writer)); 17 | if (string.IsNullOrEmpty(type)) 18 | throw new ArgumentException("声明类型不能为空", nameof(type)); 19 | for (int i = 0; i < indent; i++) 20 | writer.Write(" "); 21 | writer.Write("."); 22 | writer.Write(type); 23 | if (items != null & items.Length != 0) 24 | { 25 | var count = items.Length; 26 | while (count > 0 && string.IsNullOrEmpty(items[count - 1])) 27 | count--; 28 | if (count == 0) return; 29 | writer.Write(" "); 30 | writer.Write(string.Join(", ", items.Take(count))); 31 | } 32 | } 33 | private static void JoinAndWriteCode(IEnumerable items, string separator, TextWriter writer, Action writeTo) where T : IToTextCodeAble 34 | { 35 | if (items == null) return; 36 | bool first = true; 37 | foreach (var item in items) 38 | { 39 | if (first) 40 | { 41 | first = false; 42 | } 43 | else 44 | { 45 | writer.Write(separator); 46 | } 47 | writeTo(item); 48 | } 49 | } 50 | public static void JoinAndWriteCode(IEnumerable items, string separator, IdToNameMap nameMap, TextWriter writer, int indent) 51 | { 52 | JoinAndWriteCode(items, separator, writer, x => x.ToTextCode(nameMap, writer, indent)); 53 | } 54 | public static void JoinAndWriteCode(IEnumerable items, string separator, CodeSection codeSection, IdToNameMap nameMap, TextWriter writer, int indent, bool writeCode = true) 55 | { 56 | JoinAndWriteCode(items, separator, writer, x => x.ToTextCode(nameMap, writer, indent, codeSection, writeCode)); 57 | } 58 | public static void JoinAndWriteCode(IEnumerable items, string separator, IdToNameMap nameMap, TextWriter writer, int indent, bool writeCode) 59 | { 60 | JoinAndWriteCode(items, separator, writer, x => x.ToTextCode(nameMap, writer, indent, writeCode)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /EProjectFile/LibraryRefInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace QIQI.EProjectFile 9 | { 10 | public class LibraryRefInfo 11 | { 12 | public string FileName { get; set; } 13 | public string GuidString { get; set; } // 为了保证最大限度的准确还原,直接存储原始格式字符串 14 | public Version Version { get; set; } 15 | public string Name { get; set; } 16 | /// 17 | /// 用于支持库版本检查,当支持库文件所包含的 命令 数量少于此值时,IDE将发出 支持库版本不兼容 的警告 18 | /// 19 | public int MinRequiredCmd { get; set; } 20 | /// 21 | /// 用于支持库版本检查,当支持库文件所包含的 数据类型 数量少于此值时,IDE将发出 支持库版本不兼容 的警告 22 | /// 23 | public short MinRequiredDataType { get; set; } 24 | /// 25 | /// 用于支持库版本检查,当支持库文件所包含的 常量 数量少于此值时,IDE将发出 支持库版本不兼容 的警告 26 | /// 27 | public short MinRequiredConstant { get; set; } 28 | public static LibraryRefInfo[] ReadLibraries(BinaryReader reader, Encoding encoding) 29 | { 30 | return reader.ReadStringsWithMfcStyleCountPrefix(encoding).Select(x => 31 | { 32 | var array = x.Split('\r'); 33 | return new LibraryRefInfo() 34 | { 35 | FileName = array[0], 36 | GuidString = array[1], 37 | Version = new Version(int.Parse(array[2]), int.Parse(array[3])), 38 | Name = array[4] 39 | }; 40 | }).ToArray(); 41 | } 42 | public static void ApplyCompatibilityInfo(LibraryRefInfo[] infos, int[] minRequiredCmds, short[] minRequiredDataTypes, short[] minRequiredConstants) 43 | { 44 | for (int i = 0; i < infos.Length; i++) 45 | { 46 | infos[i].MinRequiredCmd = minRequiredCmds != null && i < minRequiredCmds.Length 47 | ? minRequiredCmds[i] : 0; 48 | infos[i].MinRequiredDataType = minRequiredDataTypes != null && i < minRequiredDataTypes.Length 49 | ? minRequiredDataTypes[i] : (short)0; 50 | infos[i].MinRequiredConstant = minRequiredConstants != null && i < minRequiredConstants.Length 51 | ? minRequiredConstants[i] : (short)0; 52 | } 53 | } 54 | public override string ToString() 55 | { 56 | return JsonSerializer.Serialize(this, JsonUtils.Options); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /EProjectFile/MethodCodeDataWriterArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile 6 | { 7 | internal struct MethodCodeDataWriterArgs 8 | { 9 | public Encoding Encoding; 10 | public BinaryWriter LineOffest; 11 | public BinaryWriter BlockOffest; 12 | public BinaryWriter MethodReference; 13 | public BinaryWriter VariableReference; 14 | public BinaryWriter ConstantReference; 15 | public BinaryWriter ExpressionData; 16 | public int Offest => (int)ExpressionData.BaseStream.Position; 17 | public IDisposable NewBlock(byte type) 18 | { 19 | return new BlockOffestHelper(this, type); 20 | } 21 | private struct BlockOffestHelper : IDisposable 22 | { 23 | private bool disposed; 24 | private MethodCodeDataWriterArgs a; 25 | private long posToFillEndOffest; 26 | public BlockOffestHelper(MethodCodeDataWriterArgs writers, byte type) 27 | { 28 | disposed = false; 29 | a = writers; 30 | a.BlockOffest.Write(type); 31 | a.BlockOffest.Write(a.Offest); 32 | posToFillEndOffest = a.BlockOffest.BaseStream.Position; 33 | a.BlockOffest.Write(0); 34 | } 35 | public void Dispose() 36 | { 37 | if (!disposed) 38 | { 39 | disposed = true; 40 | 41 | long curPos = a.BlockOffest.BaseStream.Position; 42 | a.BlockOffest.BaseStream.Position = posToFillEndOffest; 43 | a.BlockOffest.Write(a.Offest); 44 | a.BlockOffest.BaseStream.Position = curPos; 45 | } 46 | } 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /EProjectFile/ProjectFileWriter.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Encryption; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.IO; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | 8 | namespace QIQI.EProjectFile 9 | { 10 | public class ProjectFileWriter : IDisposable 11 | { 12 | private readonly BinaryWriter writer; 13 | public bool CryptEC { get; } = false; 14 | private int index; 15 | public ProjectFileWriter(Stream stream): this(stream, null) 16 | { 17 | 18 | } 19 | public ProjectFileWriter(Stream stream, EplEncryptionOptions encryptionOptions) 20 | { 21 | switch (encryptionOptions) 22 | { 23 | case EplEncryptionOptions.EStd eStd: 24 | writer = new BinaryWriter(new CryptoStream(stream, new EStdCryptoTransform(eStd.Password, 8), CryptoStreamMode.Write)); 25 | writer.Write(0x454C5457); //WTLE 26 | writer.Write(0x00000001); 27 | writer.Write(eStd.Password.SecretId); 28 | break; 29 | case EplEncryptionOptions.EC ec: 30 | { 31 | CryptEC = true; 32 | var hint_bytes = Encoding.GetEncoding("gbk").GetBytes(ec.PasswordHint); 33 | int lengthOfOvert = 4 /* [int]magic1 */ + 4 /* [int]magic2 */ + 4 /* [int]hint_length */ + hint_bytes.Length; 34 | writer = new BinaryWriter(new CryptoStream(stream, new CryptoECTransform(ec.Password, lengthOfOvert), CryptoStreamMode.Write)); 35 | writer.Write(0x454C5457); //WTLE 36 | writer.Write(0x00020001); 37 | writer.WriteBytesWithLengthPrefix(hint_bytes); 38 | writer.Write(ec.Password.SecretId); 39 | } 40 | break; 41 | case null: 42 | writer = new BinaryWriter(stream); 43 | break; 44 | default: 45 | throw new ArgumentException("unsupported encryption is required", nameof(encryptionOptions)); 46 | } 47 | writer.Write(0x4752504554574E43L); // CNWTEPRG 48 | } 49 | public void WriteSection(RawSectionInfo section) 50 | { 51 | index++; 52 | 53 | writer.Write(0x15117319); // Magic 54 | 55 | byte[] headerData; 56 | using (var headerWriter = new BinaryWriter(new MemoryStream())) 57 | { 58 | headerWriter.Write(section.Key); 59 | headerWriter.Write(EncodeName(section.Key, section.Name)); 60 | headerWriter.Write(new byte[2]); 61 | headerWriter.Write(index); // 从1开始 62 | headerWriter.Write(section.IsOptional ? 1 : 0); 63 | headerWriter.Write(GetCheckSum(section.Data)); 64 | headerWriter.Write(section.Data.Length); 65 | headerWriter.Write(new byte[40]); 66 | headerData = ((MemoryStream)headerWriter.BaseStream).ToArray(); 67 | } 68 | 69 | writer.Write(GetCheckSum(headerData)); 70 | if (CryptEC) 71 | { 72 | headerData[48] ^= 1; // mark it after calculating the checksum 73 | } 74 | writer.Write(headerData); 75 | writer.Write(section.Data); 76 | } 77 | 78 | private static byte[] EncodeName(int key, string name) 79 | { 80 | var r = new byte[30]; 81 | if (name != null) 82 | { 83 | Encoding.GetEncoding("gbk").GetBytes(name, 0, name.Length, r, 0); 84 | } 85 | if (key != 0x07007319) 86 | { 87 | var keyBytes = unchecked(new byte[] { (byte)key, (byte)(key >> 8), (byte)(key >> 16), (byte)(key >> 24) }); 88 | for (int i = 0; i < r.Length; i++) 89 | { 90 | r[i] ^= keyBytes[(i + 1) % 4]; 91 | } 92 | } 93 | return r; 94 | } 95 | 96 | private static int GetCheckSum(byte[] data) 97 | { 98 | var checkSum = new byte[4]; 99 | for (int i = 0; i < data.Length; i++) 100 | { 101 | checkSum[i & 0x3] ^= data[i]; 102 | } 103 | return checkSum[3] << 24 | checkSum[2] << 16 | checkSum[1] << 8 | checkSum[0]; 104 | } 105 | #region IDisposable Support 106 | private bool disposedValue = false; // 要检测冗余调用 107 | protected virtual void Dispose(bool disposing) 108 | { 109 | if (!disposedValue) 110 | { 111 | if (disposing) 112 | { 113 | writer.Dispose(); 114 | } 115 | disposedValue = true; 116 | } 117 | } 118 | 119 | public void Dispose() 120 | { 121 | Dispose(true); 122 | } 123 | #endregion 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /EProjectFile/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "EProjectFile": { 4 | "commandName": "Executable", 5 | "executablePath": "$(ProjectDir)$(OutDir)\\InjectedEComRepair.exe" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /EProjectFile/RawSectionInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QIQI.EProjectFile 4 | { 5 | /// 6 | /// 易语言项目文件由多个数据段组成 7 | /// 8 | public struct RawSectionInfo 9 | { 10 | /// 11 | /// 数据段的人类辨别名称 12 | /// 13 | public string Name; 14 | /// 15 | /// 用于决定如何对Name字段编码,同时也是易语言内部识别数据段的唯一标识(易语言内部并不靠Name字段识别数据段) 16 | /// 17 | public int Key; 18 | /// 19 | /// 数据段中的数据(不含段头) 20 | /// 21 | public byte[] Data; 22 | /// 23 | /// 如果易系统版本过低,无法识别本数据段,或本数据段已损坏,此字段将指定是否可以直接跳过。对于重要数据段,此字段通常设置为false,否则设置为true。 24 | /// 25 | public bool IsOptional; 26 | 27 | public RawSectionInfo(int key, string name, bool canSkip, byte[] data) 28 | { 29 | Key = key; 30 | Name = name ?? throw new ArgumentNullException(nameof(name)); 31 | IsOptional = canSkip; 32 | Data = data ?? throw new ArgumentNullException(nameof(data)); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /EProjectFile/RemovedDefinedItemInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace QIQI.EProjectFile 10 | { 11 | public class RemovedDefinedItemInfo : IHasId 12 | { 13 | public int Id { get; } 14 | public string Name { get; set; } 15 | public RemovedDefinedItemInfo(int id) 16 | { 17 | this.Id = id; 18 | } 19 | 20 | public static List ReadRemovedDefinedItems(BinaryReader reader, Encoding encoding) 21 | { 22 | var count = reader.ReadInt32(); 23 | var size = reader.ReadInt32(); 24 | var endPosition = reader.BaseStream.Position + size; 25 | var result = new List(count); 26 | var ids = reader.ReadInt32sWithFixedLength(count); 27 | var nameCode = reader.ReadInt32sWithFixedLength(count); 28 | var offsets = reader.ReadInt32sWithFixedLength(count); 29 | var startPosition = reader.BaseStream.Position; 30 | for (int i = 0; i < count; i++) 31 | { 32 | reader.BaseStream.Position = startPosition + offsets[i]; 33 | var name = reader.ReadCStyleString(encoding); 34 | result.Add(new RemovedDefinedItemInfo(ids[i]) 35 | { 36 | Name = name, 37 | }); 38 | var calculatedNameCode = CalculateNameCode(name, encoding); 39 | if (nameCode[i] != calculatedNameCode) 40 | { 41 | throw new Exception($"Invaild name code for \"{name}\" ({encoding.WebName}), " 42 | + $"calculated 0x{calculatedNameCode:X8} but got 0x{nameCode[i]:X8}"); 43 | } 44 | } 45 | reader.BaseStream.Position = endPosition; 46 | return result; 47 | } 48 | 49 | public static void WriteRemovedDefinedItems(BinaryWriter writer, Encoding encoding, List data) 50 | { 51 | if (data == null) 52 | { 53 | writer.Write(0); 54 | writer.Write(0); 55 | return; 56 | } 57 | var count = data.Count; 58 | var elem = new byte[count][]; 59 | for (int i = 0; i < count; i++) 60 | { 61 | var elemStream = new MemoryStream(); 62 | var elemBytes = encoding.GetBytes(data[i].Name); 63 | elemStream.Write(elemBytes, 0, elemBytes.Length); 64 | elemStream.WriteByte(0); 65 | elem[i] = elemStream.ToArray(); 66 | } 67 | 68 | var offsets = new int[count]; 69 | if (count > 0) 70 | { 71 | offsets[0] = 0; 72 | } 73 | for (int i = 1; i < count; i++) 74 | { 75 | offsets[i] = offsets[i - 1] + elem[i - 1].Length; 76 | } 77 | writer.Write(count); 78 | writer.Write((count * 12) + elem.Sum(x => x.Length)); 79 | foreach (var x in data) writer.Write(x.Id); 80 | foreach (var x in data) writer.Write(CalculateNameCode(x.Name, encoding)); 81 | writer.WriteInt32sWithoutLengthPrefix(offsets); 82 | Array.ForEach(elem, x => writer.Write(x)); 83 | } 84 | 85 | private static int CalculateNameCode(string name, Encoding encoding) 86 | { 87 | if (string.IsNullOrEmpty(name)) 88 | { 89 | return 0x01000000; 90 | } 91 | var bytes = encoding.GetBytes(name); 92 | byte composition1 = unchecked((byte)(bytes.Length + 1)); 93 | ushort composition2 = 0; 94 | foreach (var item in bytes) 95 | { 96 | unchecked 97 | { 98 | // 为了保持与易语言内部算法一致,不应当考虑双字节问题 99 | // 否则形如以下编码的字符将无法被计算出正确结果 100 | // 護 D76F (GB2312) 101 | // 譸 D770 (GB2312) 102 | if (item >= 'a' && item <= 'z') 103 | { 104 | composition2 -= 0x20; 105 | } 106 | composition2 += item; 107 | } 108 | } 109 | byte composition3 = bytes[0]; 110 | return (composition1 << 24) | (composition2 << 8) | composition3; 111 | } 112 | 113 | public override string ToString() 114 | { 115 | return JsonSerializer.Serialize(this, JsonUtils.Options); 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /EProjectFile/Sections/ClassPublicitySection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | using QIQI.EProjectFile.Internal; 7 | using QIQI.EProjectFile.Context; 8 | 9 | namespace QIQI.EProjectFile.Sections 10 | { 11 | public class ClassPublicitySection : ISection 12 | { 13 | private class KeyImpl : ISectionKey 14 | { 15 | public string SectionName => "辅助信息段2"; 16 | public int SectionKey => 0x0B007319; 17 | public bool IsOptional => true; 18 | 19 | public ClassPublicitySection Parse(BlockParserContext context) 20 | { 21 | return context.Consume(reader => 22 | { 23 | var that = new ClassPublicitySection(); 24 | var count = context.DataLength / 8; 25 | var publicities = new List(count); 26 | for (int i = 0; i < count; i++) 27 | { 28 | publicities.Add(new ClassPublicityInfo() 29 | { 30 | Class = reader.ReadInt32(), 31 | Flags = reader.ReadInt32() 32 | }); 33 | } 34 | that.ClassPublicities = publicities; 35 | return that; 36 | }); 37 | } 38 | } 39 | 40 | public static readonly ISectionKey Key = new KeyImpl(); 41 | public string SectionName => Key.SectionName; 42 | public int SectionKey => Key.SectionKey; 43 | public bool IsOptional => Key.IsOptional; 44 | 45 | public List ClassPublicities { get; set; } 46 | public byte[] ToBytes(BlockByteifierContext context) 47 | { 48 | return context.Collect(writer => 49 | { 50 | var encoding = context.Encoding; 51 | foreach (var publicity in ClassPublicities) 52 | { 53 | writer.Write(publicity.Class); 54 | writer.Write(publicity.Flags); 55 | } 56 | }); 57 | } 58 | public override string ToString() 59 | { 60 | return JsonSerializer.Serialize(this, JsonUtils.Options); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /EProjectFile/Sections/ConditionalCompilationSection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | using QIQI.EProjectFile.Internal; 7 | using System.Linq; 8 | using QIQI.EProjectFile.Context; 9 | 10 | namespace QIQI.EProjectFile.Sections 11 | { 12 | public class ConditionalCompilationSection : ISection 13 | { 14 | private class KeyImpl : ISectionKey 15 | { 16 | public string SectionName => "编译条件信息段"; 17 | public int SectionKey => 0x11007319; 18 | public bool IsOptional => true; 19 | 20 | public ConditionalCompilationSection Parse(BlockParserContext context) 21 | { 22 | return context.Consume(reader => 23 | { 24 | var encoding = context.Encoding; 25 | var that = new ConditionalCompilationSection(); 26 | that.ActivatedScheme = reader.ReadInt32(); 27 | var names = reader.ReadStringsWithMfcStyleCountPrefix(encoding); 28 | var allFeatures = reader.ReadStringsWithMfcStyleCountPrefix(encoding); 29 | that.Schemes = names.Zip(allFeatures, (name, features) => new CompilationSchemeInfo() 30 | { 31 | Name = name, 32 | Features = features 33 | }).ToList(); 34 | return that; 35 | }); 36 | } 37 | } 38 | 39 | public static readonly ISectionKey Key = new KeyImpl(); 40 | public string SectionName => Key.SectionName; 41 | public int SectionKey => Key.SectionKey; 42 | public bool IsOptional => Key.IsOptional; 43 | 44 | public int ActivatedScheme { get; set; } = -1; 45 | public List Schemes { get; set; } 46 | 47 | public byte[] ToBytes(BlockByteifierContext context) 48 | { 49 | return context.Collect(writer => 50 | { 51 | var encoding = context.Encoding; 52 | writer.Write(ActivatedScheme); 53 | writer.WriteStringsWithMfcStyleCountPrefix(encoding, Schemes, x => x.Name); 54 | writer.WriteStringsWithMfcStyleCountPrefix(encoding, Schemes, x => x.Features); 55 | }); 56 | } 57 | public override string ToString() 58 | { 59 | return JsonSerializer.Serialize(this, JsonUtils.Options); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /EProjectFile/Sections/ECDependenciesSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Linq; 6 | using System.Text.Json; 7 | using QIQI.EProjectFile.Internal; 8 | using QIQI.EProjectFile.Context; 9 | 10 | namespace QIQI.EProjectFile.Sections 11 | { 12 | public class ECDependenciesSection : ISection 13 | { 14 | private class KeyImpl : ISectionKey 15 | { 16 | public string SectionName => "易模块记录段"; 17 | public int SectionKey => 0x0C007319; 18 | public bool IsOptional => false; 19 | 20 | public ECDependenciesSection Parse(BlockParserContext context) 21 | { 22 | return context.Consume(reader => 23 | { 24 | var encoding = context.Encoding; 25 | var that = new ECDependenciesSection(); 26 | var count = reader.ReadInt32(); 27 | that.ECDependencies = new List(count); 28 | for (int i = 0; i < count; i++) 29 | { 30 | var dependency = new ECDependencyInfo(); 31 | dependency.InfoVersion = reader.ReadInt32(); 32 | if (dependency.InfoVersion > 2) 33 | { 34 | throw new Exception($"{nameof(ECDependencyInfo)}.{nameof(ECDependencyInfo.InfoVersion)} = {dependency.InfoVersion}, not supported"); 35 | } 36 | dependency.FileSize = reader.ReadInt32(); 37 | dependency.FileLastModifiedDate = DateTime.FromFileTime(reader.ReadInt64()); 38 | if (dependency.InfoVersion >= 2) 39 | { 40 | dependency.ReExport = reader.ReadInt32() != 0; 41 | } 42 | dependency.Name = reader.ReadStringWithLengthPrefix(encoding); 43 | dependency.Path = reader.ReadStringWithLengthPrefix(encoding); 44 | var starts = reader.ReadInt32sWithByteSizePrefix(); 45 | var counts = reader.ReadInt32sWithByteSizePrefix(); 46 | dependency.DefinedIds = new List(starts.Length); 47 | for (int indexOfids = 0; indexOfids < starts.Length; indexOfids++) 48 | { 49 | dependency.DefinedIds.Add(new ECDependencyInfo.PackedIds 50 | { 51 | Start = starts[indexOfids], 52 | Count = counts[indexOfids] 53 | }); 54 | } 55 | that.ECDependencies.Add(dependency); 56 | } 57 | return that; 58 | }); 59 | } 60 | } 61 | 62 | public static readonly ISectionKey Key = new KeyImpl(); 63 | public string SectionName => Key.SectionName; 64 | public int SectionKey => Key.SectionKey; 65 | public bool IsOptional => Key.IsOptional; 66 | 67 | public List ECDependencies { get; set; } 68 | public byte[] ToBytes(BlockByteifierContext context) 69 | { 70 | return context.Collect(writer => 71 | { 72 | var encoding = context.Encoding; 73 | writer.Write(ECDependencies.Count); 74 | foreach (var dependency in ECDependencies) 75 | { 76 | writer.Write(dependency.InfoVersion); 77 | writer.Write(dependency.FileSize); 78 | writer.Write(dependency.FileLastModifiedDate.ToFileTime()); 79 | if (dependency.InfoVersion >= 2) 80 | { 81 | writer.Write(dependency.ReExport ? 1 : 0); 82 | } 83 | else 84 | { 85 | if (dependency.ReExport) 86 | { 87 | throw new Exception($"Cannot re-export EC when {nameof(dependency.InfoVersion)} is 1"); 88 | } 89 | } 90 | writer.WriteStringWithLengthPrefix(encoding, dependency.Name); 91 | writer.WriteStringWithLengthPrefix(encoding, dependency.Path); 92 | writer.WriteInt32sWithByteSizePrefix(dependency.DefinedIds.Select(x => x.Start).ToArray()); 93 | writer.WriteInt32sWithByteSizePrefix(dependency.DefinedIds.Select(x => x.Count).ToArray()); 94 | } 95 | }); 96 | } 97 | public override string ToString() 98 | { 99 | return JsonSerializer.Serialize(this, JsonUtils.Options); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /EProjectFile/Sections/EPackageInfoSection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | using QIQI.EProjectFile.Context; 8 | 9 | namespace QIQI.EProjectFile.Sections 10 | { 11 | public class EPackageInfoSection : ISection 12 | { 13 | private class KeyImpl : ISectionKey 14 | { 15 | public string SectionName => "易包信息段1"; 16 | public int SectionKey => 0x0D007319; 17 | public bool IsOptional => true; 18 | 19 | public EPackageInfoSection Parse(BlockParserContext context) 20 | { 21 | return context.Consume(reader => 22 | { 23 | var encoding = context.Encoding; 24 | var packageInfo = new EPackageInfoSection(); 25 | var nameList = new List(); 26 | while (!(reader.BaseStream.Position == reader.BaseStream.Length)) 27 | { 28 | var name = reader.ReadStringWithLengthPrefix(encoding); 29 | if ("".Equals(name)) 30 | { 31 | name = null; 32 | } 33 | nameList.Add(name); 34 | } 35 | packageInfo.FileNames = nameList.ToArray(); 36 | return packageInfo; 37 | }); 38 | } 39 | } 40 | 41 | public static readonly ISectionKey Key = new KeyImpl(); 42 | public string SectionName => Key.SectionName; 43 | public int SectionKey => Key.SectionKey; 44 | public bool IsOptional => Key.IsOptional; 45 | 46 | /// 47 | /// 与每个子程序一一对应,null表示对应子程序非调用易包的子程序 48 | /// 49 | public string[] FileNames { get; set; } 50 | public byte[] ToBytes(BlockByteifierContext context) 51 | { 52 | return context.Collect(writer => 53 | { 54 | var encoding = context.Encoding; 55 | Array.ForEach(FileNames, x => writer.WriteStringWithLengthPrefix(encoding, x)); 56 | }); 57 | } 58 | public override string ToString() 59 | { 60 | return JsonSerializer.Serialize(this, JsonUtils.Options); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /EProjectFile/Sections/ESystemInfoSection.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Context; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | using System.Text.Json; 7 | 8 | namespace QIQI.EProjectFile.Sections 9 | { 10 | public class ESystemInfoSection : ISection 11 | { 12 | private class KeyImpl : ISectionKey 13 | { 14 | public string SectionName => "系统信息段"; 15 | public int SectionKey => 0x02007319; 16 | public bool IsOptional => false; 17 | 18 | public ESystemInfoSection Parse(BlockParserContext context) 19 | { 20 | return context.Consume(reader => 21 | { 22 | var systemInfo = new ESystemInfoSection(); 23 | systemInfo.ESystemVersion = new Version(reader.ReadInt16(), reader.ReadInt16()); 24 | reader.ReadInt32(); // Skip Unknown 25 | systemInfo.Language = reader.ReadInt32(); 26 | systemInfo.EProjectFormatVersion = new Version(reader.ReadInt16(), reader.ReadInt16()); 27 | systemInfo.FileType = reader.ReadInt32(); 28 | reader.ReadInt32(); // Skip Unknown 29 | systemInfo.ProjectType = reader.ReadInt32(); 30 | return systemInfo; 31 | }); 32 | } 33 | } 34 | public static readonly ISectionKey Key = new KeyImpl(); 35 | public string SectionName => Key.SectionName; 36 | public int SectionKey => Key.SectionKey; 37 | public bool IsOptional => Key.IsOptional; 38 | 39 | public Version ESystemVersion { get; set; } 40 | /// 41 | /// 1=中文(GBK),2=英语,3=中文(BIG5),4=日文(SJIS) 42 | /// 43 | public int Language { get; set; } = 1; 44 | 45 | public Encoding DetermineEncoding() 46 | { 47 | switch (Language) 48 | { 49 | case 2: 50 | return Encoding.ASCII; 51 | case 3: 52 | return Encoding.GetEncoding("big5"); 53 | case 4: 54 | return Encoding.GetEncoding("sjis"); 55 | default: 56 | return Encoding.GetEncoding("gbk"); 57 | } 58 | } 59 | 60 | public Version EProjectFormatVersion { get; set; } 61 | /// 62 | /// 1=源码,3=模块 63 | /// 64 | public int FileType { get; set; } = 1; 65 | 66 | public int ProjectType { get; set; } 67 | 68 | public byte[] ToBytes(BlockByteifierContext context) 69 | { 70 | return context.Collect(writer => 71 | { 72 | var encoding = context.Encoding; 73 | writer.Write((short)ESystemVersion.Major); 74 | writer.Write((short)ESystemVersion.Minor); 75 | writer.Write(1); 76 | writer.Write(Language); 77 | writer.Write((short)EProjectFormatVersion.Major); 78 | writer.Write((short)EProjectFormatVersion.Minor); 79 | writer.Write(FileType); 80 | writer.Write(0); 81 | writer.Write(ProjectType); 82 | writer.Write(new byte[32]); 83 | }); 84 | } 85 | 86 | public override string ToString() 87 | { 88 | return JsonSerializer.Serialize(this, JsonUtils.Options); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /EProjectFile/Sections/EditorInfoSection.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Context; 2 | using QIQI.EProjectFile.EditorTabInfo; 3 | using QIQI.EProjectFile.Internal; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Text.Json; 9 | 10 | namespace QIQI.EProjectFile.Sections 11 | { 12 | public class EditorInfoSection : ISection 13 | { 14 | private class KeyImpl : ISectionKey 15 | { 16 | public string SectionName => "编辑信息段2"; 17 | public int SectionKey => 0x09007319; 18 | public bool IsOptional => true; 19 | 20 | public EditorInfoSection Parse(BlockParserContext context) 21 | { 22 | return context.Consume(reader => 23 | { 24 | var encoding = context.Encoding; 25 | var that = new EditorInfoSection(); 26 | var count = reader.ReadInt32() + 1; 27 | that.Tabs = new List(count); 28 | for (int i = 0; i < count; i++) 29 | { 30 | var itemData = reader.ReadBytesWithLengthPrefix(); 31 | if (itemData.Length == 0) 32 | { 33 | that.Tabs.Add(null); 34 | } 35 | var typeId = itemData[0]; 36 | if (PredefinedEditorTabInfos.Keys.TryGetValue(typeId, out var key)) 37 | { 38 | that.Tabs.Add(key.Parse(new BlockParserContext(itemData, encoding, context.CryptEC))); 39 | } 40 | else 41 | { 42 | that.Tabs.Add(new GeneralEditorTabInfo(typeId, itemData.Skip(1).ToArray())); 43 | } 44 | } 45 | return that; 46 | }); 47 | } 48 | } 49 | public static readonly ISectionKey Key = new KeyImpl(); 50 | public string SectionName => Key.SectionName; 51 | public int SectionKey => Key.SectionKey; 52 | public bool IsOptional => Key.IsOptional; 53 | 54 | public List Tabs { get; set; } 55 | 56 | public byte[] ToBytes(BlockByteifierContext context) 57 | { 58 | return context.Collect(writer => 59 | { 60 | var encoding = context.Encoding; 61 | if (Tabs is null) 62 | { 63 | writer.Write(-1); 64 | return; 65 | } 66 | writer.Write(Tabs.Count - 1); 67 | foreach (var tab in Tabs) 68 | { 69 | if (tab is null) 70 | { 71 | writer.Write(0); 72 | continue; 73 | } 74 | tab.WriteTo(writer, encoding); 75 | } 76 | }); 77 | } 78 | 79 | public override string ToString() 80 | { 81 | return JsonSerializer.Serialize(this, JsonUtils.Options); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /EProjectFile/Sections/EndOfFileSection.cs: -------------------------------------------------------------------------------- 1 |  2 | using QIQI.EProjectFile.Context; 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | using System.Text.Json; 7 | 8 | namespace QIQI.EProjectFile.Sections 9 | { 10 | public class EndOfFileSection : ISection 11 | { 12 | private class KeyImpl : ISectionKey 13 | { 14 | public string SectionName => ""; 15 | public int SectionKey => 0x07007319; 16 | public bool IsOptional => false; 17 | 18 | public EndOfFileSection Parse(BlockParserContext context) 19 | { 20 | if (context.DataLength > 0) 21 | { 22 | throw new Exception("EndOfFileSection should be empty"); 23 | } 24 | return Instance; 25 | } 26 | } 27 | 28 | public static readonly ISectionKey Key = new KeyImpl(); 29 | 30 | public static EndOfFileSection Instance { get; } = new EndOfFileSection(); 31 | private EndOfFileSection() 32 | { 33 | } 34 | 35 | public string SectionName => Key.SectionName; 36 | public int SectionKey => Key.SectionKey; 37 | public bool IsOptional => Key.IsOptional; 38 | 39 | public byte[] ToBytes(BlockByteifierContext context) 40 | { 41 | return Array.Empty(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /EProjectFile/Sections/EventIndicesSection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Collections.Generic; 3 | using QIQI.EProjectFile.Internal; 4 | using QIQI.EProjectFile.Context; 5 | 6 | namespace QIQI.EProjectFile.Sections 7 | { 8 | public class EventIndicesSection : ISection 9 | { 10 | private class KeyImpl : ISectionKey 11 | { 12 | public string SectionName => "辅助信息段1"; 13 | public int SectionKey => 0x0A007319; 14 | public bool IsOptional => true; 15 | 16 | public EventIndicesSection Parse(BlockParserContext context) 17 | { 18 | return context.Consume(reader => 19 | { 20 | var that = new EventIndicesSection(); 21 | var count = context.DataLength / 16; 22 | var indices = new List(count); 23 | for (int i = 0; i < count; i++) 24 | { 25 | indices.Add(new IndexedEventInfo() 26 | { 27 | FormId = reader.ReadInt32(), 28 | UnitId = reader.ReadInt32(), 29 | EventId = reader.ReadInt32(), 30 | MethodId = reader.ReadInt32() 31 | }); 32 | } 33 | that.Indices = indices; 34 | return that; 35 | }); 36 | } 37 | } 38 | 39 | public static readonly ISectionKey Key = new KeyImpl(); 40 | public string SectionName => Key.SectionName; 41 | public int SectionKey => Key.SectionKey; 42 | public bool IsOptional => Key.IsOptional; 43 | 44 | public List Indices { get; set; } 45 | public byte[] ToBytes(BlockByteifierContext context) 46 | { 47 | return context.Collect(writer => 48 | { 49 | foreach (var x in Indices) 50 | { 51 | writer.Write(x.FormId); 52 | writer.Write(x.UnitId); 53 | writer.Write(x.EventId); 54 | writer.Write(x.MethodId); 55 | } 56 | }); 57 | } 58 | public override string ToString() 59 | { 60 | return JsonSerializer.Serialize(this, JsonUtils.Options); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /EProjectFile/Sections/FolderSection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | using QIQI.EProjectFile.Context; 8 | 9 | namespace QIQI.EProjectFile.Sections 10 | { 11 | public class FolderSection : ISection 12 | { 13 | private class KeyImpl : ISectionKey 14 | { 15 | public string SectionName => "编辑过滤器信息段"; 16 | public int SectionKey => 0x0E007319; 17 | public bool IsOptional => true; 18 | 19 | public FolderSection Parse(BlockParserContext context) 20 | { 21 | return context.Consume(reader => 22 | { 23 | var encoding = context.Encoding; 24 | var folderSectionInfo = new FolderSection(); 25 | folderSectionInfo.allocatedKey = reader.ReadInt32(); 26 | while (!(reader.BaseStream.Position == reader.BaseStream.Length)) 27 | { 28 | bool expand = reader.ReadInt32() != 0; 29 | folderSectionInfo.Folders.Add(new CodeFolderInfo(reader.ReadInt32()) 30 | { 31 | Expand = expand, 32 | ParentKey = reader.ReadInt32(), 33 | Name = reader.ReadStringWithLengthPrefix(encoding), 34 | Children = reader.ReadInt32sWithFixedLength(reader.ReadInt32() / 4) 35 | }); 36 | } 37 | return folderSectionInfo; 38 | }); 39 | } 40 | } 41 | 42 | public static readonly ISectionKey Key = new KeyImpl(); 43 | public string SectionName => Key.SectionName; 44 | public int SectionKey => Key.SectionKey; 45 | public bool IsOptional => Key.IsOptional; 46 | 47 | private int allocatedKey = 0; 48 | public List Folders { get; set; } = new List(); 49 | 50 | public int AllocKey() => ++allocatedKey; 51 | 52 | public byte[] ToBytes(BlockByteifierContext context) 53 | { 54 | return context.Collect(writer => 55 | { 56 | var encoding = context.Encoding; 57 | writer.Write(allocatedKey); 58 | foreach (var folder in Folders) 59 | { 60 | writer.Write(folder.Expand ? 1 : 0); 61 | writer.Write(folder.Key); 62 | writer.Write(folder.ParentKey); 63 | writer.WriteStringWithLengthPrefix(encoding, folder.Name); 64 | writer.Write(folder.Children.Length * 4); 65 | writer.WriteInt32sWithoutLengthPrefix(folder.Children); 66 | } 67 | }); 68 | } 69 | public override string ToString() 70 | { 71 | return JsonSerializer.Serialize(this, JsonUtils.Options); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /EProjectFile/Sections/GeneralSection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Text.Json.Serialization; 7 | using QIQI.EProjectFile.Context; 8 | 9 | namespace QIQI.EProjectFile.Sections 10 | { 11 | public class GeneralSection : ISection 12 | { 13 | private RawSectionInfo raw; 14 | 15 | public GeneralSection(RawSectionInfo raw) 16 | { 17 | this.raw = raw; 18 | } 19 | 20 | [JsonConstructor] 21 | public GeneralSection(string sectionName, int sectionKey, bool isOptional, byte[] data) 22 | { 23 | this.raw = new RawSectionInfo() 24 | { 25 | Name = sectionName, 26 | Key = sectionKey, 27 | IsOptional = isOptional, 28 | Data = data 29 | }; 30 | } 31 | 32 | public string SectionName => raw.Name; 33 | 34 | public int SectionKey => raw.Key; 35 | 36 | public bool IsOptional => raw.IsOptional; 37 | 38 | [JsonConverter(typeof(ByteArrayHexConverter))] 39 | public byte[] Data { get => raw.Data; set => raw.Data = value; } 40 | 41 | public byte[] ToBytes(BlockByteifierContext context) => (byte[])raw.Data.Clone(); 42 | 43 | public override string ToString() 44 | { 45 | return JsonSerializer.Serialize(this, JsonUtils.Options); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /EProjectFile/Sections/ISection.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Context; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | using System.Text.Json.Serialization; 8 | 9 | namespace QIQI.EProjectFile.Sections 10 | { 11 | [JsonConverter(typeof(SectionJsonConverter))] 12 | public interface ISection 13 | { 14 | string SectionName { get; } 15 | int SectionKey { get; } 16 | bool IsOptional { get; } 17 | byte[] ToBytes(BlockByteifierContext context); 18 | } 19 | 20 | /// 21 | /// 注意:不是所有的 ISection 都必须带有 Key 22 | /// 23 | /// 24 | public interface ISectionKey where TSection : ISection 25 | { 26 | string SectionName { get; } 27 | int SectionKey { get; } 28 | bool IsOptional { get; } 29 | TSection Parse(BlockParserContext context); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /EProjectFile/Sections/InitECSection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | using QIQI.EProjectFile.Context; 7 | 8 | namespace QIQI.EProjectFile.Sections 9 | { 10 | public class InitECSection : ISection 11 | { 12 | private class KeyImpl : ISectionKey 13 | { 14 | public string SectionName => "初始模块段"; 15 | public int SectionKey => 0x08007319; 16 | public bool IsOptional => false; 17 | 18 | public InitECSection Parse(Context.BlockParserContext context) 19 | { 20 | return context.Consume(reader => 21 | { 22 | var encoding = context.Encoding; 23 | var that = new InitECSection(); 24 | that.ECName = reader.ReadStringsWithMfcStyleCountPrefix(encoding); 25 | that.InitMethod = reader.ReadInt32sWithFixedLength(reader.ReadInt32() / 4); 26 | return that; 27 | }); 28 | } 29 | } 30 | 31 | public static readonly ISectionKey Key = new KeyImpl(); 32 | public string SectionName => Key.SectionName; 33 | public int SectionKey => Key.SectionKey; 34 | public bool IsOptional => Key.IsOptional; 35 | 36 | public string[] ECName { get; set; } 37 | public int[] InitMethod { get; set; } 38 | public byte[] ToBytes(BlockByteifierContext context) 39 | { 40 | return context.Collect(writer => 41 | { 42 | var encoding = context.Encoding; 43 | writer.WriteStringsWithMfcStyleCountPrefix(encoding, ECName); 44 | writer.Write(InitMethod.Length * 4); 45 | writer.WriteInt32sWithoutLengthPrefix(InitMethod); 46 | }); 47 | } 48 | public override string ToString() 49 | { 50 | return JsonSerializer.Serialize(this, JsonUtils.Options); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /EProjectFile/Sections/LosableSection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.Immutable; 6 | using System.ComponentModel; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Text.Json.Serialization; 11 | using QIQI.EProjectFile.Context; 12 | 13 | namespace QIQI.EProjectFile.Sections 14 | { 15 | public class LosableSection : ISection 16 | { 17 | private class KeyImpl : ISectionKey 18 | { 19 | public string SectionName => "可丢失程序段"; 20 | public int SectionKey => 0x05007319; 21 | public bool IsOptional => true; 22 | 23 | public LosableSection Parse(BlockParserContext context) 24 | { 25 | return context.Consume(reader => 26 | { 27 | var encoding = context.Encoding; 28 | var losableSectionInfo = new LosableSection(); 29 | losableSectionInfo.OutFile = reader.ReadStringWithLengthPrefix(encoding); 30 | losableSectionInfo.RemovedDefinedItems = RemovedDefinedItemInfo.ReadRemovedDefinedItems(reader, encoding); 31 | losableSectionInfo.UnknownAfterRemovedDefinedItem = reader.ReadImmutableBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position)) switch 32 | { 33 | // use shared object if it equals to the dafault value, which can reduce memory usage. 34 | var x when x.SequenceEqual(DefaultUnknownAfterRemovedDefinedItem) => DefaultUnknownAfterRemovedDefinedItem, 35 | var x => x 36 | }; 37 | return losableSectionInfo; 38 | }); 39 | } 40 | } 41 | public static readonly ISectionKey Key = new KeyImpl(); 42 | public string SectionName => Key.SectionName; 43 | public int SectionKey => Key.SectionKey; 44 | public bool IsOptional => Key.IsOptional; 45 | 46 | public string OutFile { get; set; } 47 | public List RemovedDefinedItems { get; set; } 48 | private static readonly ImmutableArray DefaultUnknownAfterRemovedDefinedItem 49 | = ImmutableArray.Create(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255 } ); 50 | [JsonIgnore] 51 | public ImmutableArray UnknownAfterRemovedDefinedItem { get; set; } = DefaultUnknownAfterRemovedDefinedItem; 52 | public byte[] ToBytes(BlockByteifierContext context) 53 | { 54 | return context.Collect(writer => 55 | { 56 | var encoding = context.Encoding; 57 | writer.WriteStringWithLengthPrefix(encoding, OutFile); 58 | RemovedDefinedItemInfo.WriteRemovedDefinedItems(writer, encoding, RemovedDefinedItems); 59 | writer.Write(UnknownAfterRemovedDefinedItem); 60 | }); 61 | } 62 | public override string ToString() 63 | { 64 | return JsonSerializer.Serialize(this, JsonUtils.Options); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /EProjectFile/Sections/PredefinedSections.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace QIQI.EProjectFile.Sections 6 | { 7 | public class PredefinedSections 8 | { 9 | public static Dictionary> Keys { get; } = new ISectionKey[]{ 10 | ESystemInfoSection.Key, 11 | ProjectConfigSection.Key, 12 | ResourceSection.Key, 13 | CodeSection.Key, 14 | EPackageInfoSection.Key, 15 | InitECSection.Key, 16 | LosableSection.Key, 17 | ClassPublicitySection.Key, 18 | FolderSection.Key, 19 | ECDependenciesSection.Key, 20 | ProjectConfigExSection.Key, 21 | ConditionalCompilationSection.Key, 22 | EditorInfoSection.Key, 23 | EventIndicesSection.Key, 24 | EndOfFileSection.Key 25 | }.ToDictionary(x => x.SectionKey); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /EProjectFile/Sections/ProjectConfigExSection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | using QIQI.EProjectFile.Internal; 7 | using QIQI.EProjectFile.Context; 8 | 9 | namespace QIQI.EProjectFile.Sections 10 | { 11 | public class ProjectConfigExSection : ISection 12 | { 13 | private class KeyImpl : ISectionKey 14 | { 15 | public string SectionName => "辅助信息段3"; 16 | public int SectionKey => 0x10007319; 17 | public bool IsOptional => true; 18 | 19 | public ProjectConfigExSection Parse(BlockParserContext context) 20 | { 21 | return context.Consume(reader => 22 | { 23 | var encoding = context.Encoding; 24 | var that = new ProjectConfigExSection(); 25 | that.ExternalFilePaths = reader.ReadStringsAsListWithMfcStyleCountPrefix(encoding); 26 | that.ECPassword = reader.ReadStringWithLengthPrefix(encoding); 27 | that.ECPasswordTips = reader.ReadStringWithLengthPrefix(encoding); 28 | return that; 29 | }); 30 | } 31 | } 32 | 33 | public static readonly ISectionKey Key = new KeyImpl(); 34 | public string SectionName => Key.SectionName; 35 | public int SectionKey => Key.SectionKey; 36 | public bool IsOptional => Key.IsOptional; 37 | 38 | /// 39 | /// 外部文件记录表 40 | /// 41 | public List ExternalFilePaths { get; set; } 42 | /// 43 | /// 编译易模块时,加密模块所使用的密码,留空表示不加密。 44 | /// 45 | /// 设置在模块源码中。 46 | public string ECPassword { get; set; } 47 | /// 48 | /// 编译易模块时,所输出加密模块的密码的提示文本。 49 | /// 50 | /// 设置在模块源码中。 51 | public string ECPasswordTips { get; set; } 52 | public byte[] ToBytes(BlockByteifierContext context) 53 | { 54 | return context.Collect(writer => 55 | { 56 | var encoding = context.Encoding; 57 | writer.WriteStringsWithMfcStyleCountPrefix(encoding, ExternalFilePaths); 58 | writer.WriteStringWithLengthPrefix(encoding, ECPassword); 59 | writer.WriteStringWithLengthPrefix(encoding, ECPasswordTips); 60 | }); 61 | } 62 | public override string ToString() 63 | { 64 | return JsonSerializer.Serialize(this, JsonUtils.Options); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /EProjectFile/Sections/ProjectConfigSection.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | using QIQI.EProjectFile.Context; 7 | 8 | namespace QIQI.EProjectFile.Sections 9 | { 10 | public class ProjectConfigSection : ISection 11 | { 12 | private class KeyImpl : ISectionKey 13 | { 14 | public string SectionName => "用户信息段"; 15 | public int SectionKey => 0x01007319; 16 | public bool IsOptional => false; 17 | 18 | public ProjectConfigSection Parse(BlockParserContext context) 19 | { 20 | return context.Consume(reader => 21 | { 22 | var encoding = context.Encoding; 23 | var projectConfig = new ProjectConfigSection(); 24 | projectConfig.Name = reader.ReadStringWithLengthPrefix(encoding); 25 | projectConfig.Description = reader.ReadStringWithLengthPrefix(encoding); 26 | projectConfig.Author = reader.ReadStringWithLengthPrefix(encoding); 27 | projectConfig.ZipCode = reader.ReadStringWithLengthPrefix(encoding); 28 | projectConfig.Address = reader.ReadStringWithLengthPrefix(encoding); 29 | projectConfig.TelephoneNumber = reader.ReadStringWithLengthPrefix(encoding); 30 | projectConfig.FaxNumber = reader.ReadStringWithLengthPrefix(encoding); 31 | projectConfig.Email = reader.ReadStringWithLengthPrefix(encoding); 32 | projectConfig.Homepage = reader.ReadStringWithLengthPrefix(encoding); 33 | projectConfig.Copyright = reader.ReadStringWithLengthPrefix(encoding); 34 | projectConfig.Version = new Version(reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32()); 35 | projectConfig.WriteVersion = reader.ReadInt32() == 0; 36 | projectConfig.CompilePlugins = reader.ReadStringWithFixedLength(encoding, 20); 37 | projectConfig.ExportPublicClassMethod = reader.ReadInt32() != 0; 38 | reader.ReadInt32(); // Unknown 39 | return projectConfig; 40 | }); 41 | } 42 | } 43 | 44 | public static readonly ISectionKey Key = new KeyImpl(); 45 | public string SectionName => Key.SectionName; 46 | public int SectionKey => Key.SectionKey; 47 | public bool IsOptional => Key.IsOptional; 48 | 49 | public string Name { get; set; } 50 | public string Description { get; set; } 51 | public string Author { get; set; } 52 | public string ZipCode { get; set; } 53 | public string Address { get; set; } 54 | public string TelephoneNumber { get; set; } 55 | public string FaxNumber { get; set; } 56 | public string Email { get; set; } 57 | public string Homepage { get; set; } 58 | public string Copyright { get; set; } 59 | public Version Version { get; set; } 60 | public bool WriteVersion { get; set; } 61 | public string CompilePlugins { get; set; } 62 | public bool ExportPublicClassMethod { get; set; } 63 | public byte[] ToBytes(BlockByteifierContext context) 64 | { 65 | return context.Collect(writer => 66 | { 67 | var encoding = context.Encoding; 68 | writer.WriteStringWithLengthPrefix(encoding, Name); 69 | writer.WriteStringWithLengthPrefix(encoding, Description); 70 | writer.WriteStringWithLengthPrefix(encoding, Author); 71 | writer.WriteStringWithLengthPrefix(encoding, ZipCode); 72 | writer.WriteStringWithLengthPrefix(encoding, Address); 73 | writer.WriteStringWithLengthPrefix(encoding, TelephoneNumber); 74 | writer.WriteStringWithLengthPrefix(encoding, FaxNumber); 75 | writer.WriteStringWithLengthPrefix(encoding, Email); 76 | writer.WriteStringWithLengthPrefix(encoding, Homepage); 77 | writer.WriteStringWithLengthPrefix(encoding, Copyright); 78 | writer.Write(Version.Major); 79 | writer.Write(Version.Minor); 80 | writer.Write(Version.Build); 81 | writer.Write(Version.Revision); 82 | writer.Write(WriteVersion ? 0 : 1); 83 | writer.WriteStringWithFixedLength(encoding, CompilePlugins, 20); 84 | writer.Write(ExportPublicClassMethod ? 1 : 0); 85 | writer.Write(0); // Unknown 86 | }); 87 | } 88 | public override string ToString() 89 | { 90 | return JsonSerializer.Serialize(this, JsonUtils.Options); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /EProjectFile/Sections/ResourceSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.Json; 6 | using QIQI.EProjectFile.Context; 7 | using QIQI.EProjectFile.Internal; 8 | using QIQI.EProjectFile.Sections; 9 | 10 | namespace QIQI.EProjectFile 11 | { 12 | public class ResourceSection : IToTextCodeAble, ISection 13 | { 14 | private class KeyImpl : ISectionKey 15 | { 16 | public string SectionName => "程序资源段"; 17 | public int SectionKey => 0x04007319; 18 | public bool IsOptional => false; 19 | 20 | public ResourceSection Parse(BlockParserContext context) 21 | { 22 | return context.Consume(reader => 23 | { 24 | var encoding = context.Encoding; 25 | ResourceSection resourceSectionInfo; 26 | resourceSectionInfo = new ResourceSection() 27 | { 28 | Forms = FormInfo.ReadForms(reader, encoding), 29 | Constants = ConstantInfo.ReadConstants(reader, encoding) 30 | }; 31 | return resourceSectionInfo; 32 | }); 33 | } 34 | } 35 | 36 | public static readonly ISectionKey Key = new KeyImpl(); 37 | public string SectionName => Key.SectionName; 38 | public int SectionKey => Key.SectionKey; 39 | public bool IsOptional => Key.IsOptional; 40 | 41 | public List Forms { get; set; } 42 | public List Constants { get; set; } 43 | public byte[] ToBytes(BlockByteifierContext context) 44 | { 45 | return context.Collect(writer => 46 | { 47 | var encoding = context.Encoding; 48 | FormInfo.WriteForms(writer, encoding, Forms); 49 | ConstantInfo.WriteConstants(writer, encoding, Constants); 50 | writer.Write(0); 51 | }); 52 | } 53 | public override string ToString() 54 | { 55 | return JsonSerializer.Serialize(this, JsonUtils.Options); 56 | } 57 | 58 | public void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 59 | { 60 | TextCodeUtils.JoinAndWriteCode(Constants, Environment.NewLine, nameMap, writer, indent); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /EProjectFile/Statements/CounterStatement.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using QIQI.EProjectFile.Expressions; 4 | namespace QIQI.EProjectFile.Statements 5 | { 6 | /// 7 | /// 计次循环 语句块 8 | /// 9 | public class CounterStatement : LoopStatement 10 | { 11 | public Expression Count { get; set; } 12 | public Expression Var { get; set; } 13 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 14 | { 15 | for (int i = 0; i < indent; i++) 16 | writer.Write(" "); 17 | if (MaskOnStart) 18 | writer.Write("' "); 19 | if (UnexaminedCode == null) 20 | { 21 | writer.Write(".计次循环首 ("); 22 | Count.ToTextCode(nameMap, writer, indent); 23 | writer.Write(", "); 24 | Var.ToTextCode(nameMap, writer, indent); 25 | writer.Write(")"); 26 | } 27 | else 28 | { 29 | writer.Write("."); 30 | writer.Write(UnexaminedCode); 31 | } 32 | if (CommentOnStart != null) 33 | { 34 | writer.Write(" ' "); 35 | writer.Write(CommentOnStart); 36 | } 37 | writer.WriteLine(); 38 | Block.ToTextCode(nameMap, writer, indent + 1); 39 | writer.WriteLine(); 40 | for (int i = 0; i < indent; i++) 41 | writer.Write(" "); 42 | if (MaskOnEnd) 43 | writer.Write("' "); 44 | writer.Write(".计次循环尾 ()"); 45 | if (CommentOnEnd != null) 46 | { 47 | writer.Write(" ' "); 48 | writer.Write(CommentOnEnd); 49 | } 50 | } 51 | 52 | internal override void WriteTo(MethodCodeDataWriterArgs a) 53 | { 54 | using (a.NewBlock(3)) 55 | { 56 | if (UnexaminedCode != null) 57 | new UnexaminedStatement(UnexaminedCode, MaskOnStart).WriteTo(a, 0x70, 0, 7); 58 | else 59 | new ExpressionStatement(new CallExpression(0, 7, new ParamListExpression() { Count, Var }), MaskOnStart, CommentOnStart).WriteTo(a, 0x70); 60 | Block.WriteTo(a); 61 | a.ExpressionData.Write((byte)0x55); 62 | } 63 | new ExpressionStatement(new CallExpression(0, 8, new ParamListExpression() { }), MaskOnEnd, CommentOnEnd).WriteTo(a, 0x71); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /EProjectFile/Statements/DoWhileStatement.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using QIQI.EProjectFile.Expressions; 4 | namespace QIQI.EProjectFile.Statements 5 | { 6 | /// 7 | /// 循环判断 语句块 8 | /// 9 | public class DoWhileStatement : LoopStatement 10 | { 11 | public Expression Condition { get; set; } 12 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 13 | { 14 | for (int i = 0; i < indent; i++) 15 | writer.Write(" "); 16 | if (MaskOnStart) 17 | writer.Write("' "); 18 | writer.Write(".循环判断首 ()"); 19 | if (CommentOnStart != null) 20 | { 21 | writer.Write(" ' "); 22 | writer.Write(CommentOnStart); 23 | } 24 | writer.WriteLine(); 25 | Block.ToTextCode(nameMap, writer, indent + 1); 26 | writer.WriteLine(); 27 | for (int i = 0; i < indent; i++) 28 | writer.Write(" "); 29 | if (MaskOnEnd) 30 | writer.Write("' "); 31 | if (UnexaminedCode == null) 32 | { 33 | writer.Write(".循环判断尾 ("); 34 | Condition.ToTextCode(nameMap, writer, indent); 35 | writer.Write(")"); 36 | } 37 | else 38 | { 39 | writer.Write("."); 40 | writer.Write(UnexaminedCode); 41 | } 42 | if (CommentOnEnd != null) 43 | { 44 | writer.Write(" ' "); 45 | writer.Write(CommentOnEnd); 46 | } 47 | } 48 | internal override void WriteTo(MethodCodeDataWriterArgs a) 49 | { 50 | using (a.NewBlock(3)) 51 | { 52 | new ExpressionStatement(new CallExpression(0, 5, new ParamListExpression() { }), MaskOnStart, CommentOnStart).WriteTo(a, 0x70); 53 | Block.WriteTo(a); 54 | a.ExpressionData.Write((byte)0x55); 55 | } 56 | if (UnexaminedCode != null) 57 | new UnexaminedStatement(UnexaminedCode, MaskOnEnd).WriteTo(a, 0x71, 0, 6); 58 | else 59 | new ExpressionStatement(new CallExpression(0, 6, new ParamListExpression() { Condition }), MaskOnEnd, CommentOnEnd).WriteTo(a, 0x71); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /EProjectFile/Statements/ExpressionStatement.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using QIQI.EProjectFile.Expressions; 4 | namespace QIQI.EProjectFile.Statements 5 | { 6 | /// 7 | /// 表达式语句 8 | /// 9 | public class ExpressionStatement : Statement 10 | { 11 | public CallExpression Expression { get; set; } 12 | public bool Mask { get; set; } 13 | 14 | public string Comment { get; set; } 15 | public ExpressionStatement() 16 | { 17 | 18 | } 19 | 20 | public ExpressionStatement(CallExpression expression, bool mask, string comment) 21 | { 22 | Expression = expression; 23 | Mask = mask; 24 | Comment = comment; 25 | } 26 | 27 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 28 | { 29 | for (int i = 0; i < indent; i++) 30 | writer.Write(" "); 31 | if (Mask) 32 | writer.Write("' "); 33 | Expression?.ToTextCode(nameMap, writer, indent); 34 | if (Comment != null) 35 | { 36 | writer.Write(Expression == null ? "' " : " ' "); 37 | writer.Write(Comment); 38 | } 39 | } 40 | 41 | internal void WriteTo(MethodCodeDataWriterArgs a, byte type) 42 | { 43 | a.LineOffest.Write(a.Offest); 44 | if (Expression != null) 45 | { 46 | Expression.WriteTo(a, type, Mask, Comment); 47 | } 48 | else 49 | { 50 | new CallExpression(-1, 0).WriteTo(a, type, Mask, Comment); 51 | } 52 | } 53 | internal override void WriteTo(MethodCodeDataWriterArgs a) 54 | { 55 | WriteTo(a, 0x6A); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /EProjectFile/Statements/ForStatement.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using QIQI.EProjectFile.Expressions; 4 | namespace QIQI.EProjectFile.Statements 5 | { 6 | /// 7 | /// 变量循环 语句块 8 | /// 9 | public class ForStatement : LoopStatement 10 | { 11 | public Expression Start { get; set; } 12 | public Expression End { get; set; } 13 | public Expression Step { get; set; } 14 | public Expression Var { get; set; } 15 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 16 | { 17 | for (int i = 0; i < indent; i++) 18 | writer.Write(" "); 19 | if (MaskOnStart) 20 | writer.Write("' "); 21 | if (UnexaminedCode == null) 22 | { 23 | writer.Write(".变量循环首 ("); 24 | Start.ToTextCode(nameMap, writer, indent); 25 | writer.Write(", "); 26 | End.ToTextCode(nameMap, writer, indent); 27 | writer.Write(", "); 28 | Step.ToTextCode(nameMap, writer, indent); 29 | writer.Write(", "); 30 | Var.ToTextCode(nameMap, writer, indent); 31 | writer.Write(")"); 32 | } 33 | else 34 | { 35 | writer.Write("."); 36 | writer.Write(UnexaminedCode); 37 | } 38 | if (CommentOnStart != null) 39 | { 40 | writer.Write(" ' "); 41 | writer.Write(CommentOnStart); 42 | } 43 | writer.WriteLine(); 44 | Block.ToTextCode(nameMap, writer, indent + 1); 45 | writer.WriteLine(); 46 | for (int i = 0; i < indent; i++) 47 | writer.Write(" "); 48 | if (MaskOnEnd) 49 | writer.Write("' "); 50 | writer.Write(".变量循环尾 ()"); 51 | if (CommentOnEnd != null) 52 | { 53 | writer.Write(" ' "); 54 | writer.Write(CommentOnEnd); 55 | } 56 | } 57 | internal override void WriteTo(MethodCodeDataWriterArgs a) 58 | { 59 | using (a.NewBlock(3)) 60 | { 61 | if (UnexaminedCode != null) 62 | new UnexaminedStatement(UnexaminedCode, MaskOnStart).WriteTo(a, 0x70, 0, 9); 63 | else 64 | new ExpressionStatement(new CallExpression(0, 9, new ParamListExpression() { Start, End, Step, Var }), MaskOnStart, CommentOnStart).WriteTo(a, 0x70); 65 | Block.WriteTo(a); 66 | a.ExpressionData.Write((byte)0x55); 67 | } 68 | new ExpressionStatement(new CallExpression(0, 10, new ParamListExpression() { }), MaskOnEnd, CommentOnEnd).WriteTo(a, 0x71); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /EProjectFile/Statements/IfElseStatement.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using QIQI.EProjectFile.Expressions; 4 | namespace QIQI.EProjectFile.Statements 5 | { 6 | /// 7 | /// 如果 语句块 8 | /// 9 | public class IfElseStatement : Statement 10 | { 11 | public Expression Condition { get; set; } 12 | /// 13 | /// 不为null时,应为null 14 | /// 15 | public string UnexaminedCode { get; set; } 16 | public StatementBlock BlockOnTrue { get; set; } 17 | public StatementBlock BlockOnFalse { get; set; } 18 | public string Comment { get; set; } 19 | public bool Mask { get; set; } 20 | 21 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 22 | { 23 | for (int i = 0; i < indent; i++) 24 | writer.Write(" "); 25 | if (Mask) 26 | writer.Write("' "); 27 | if (UnexaminedCode == null) 28 | { 29 | writer.Write(".如果 ("); 30 | Condition.ToTextCode(nameMap, writer, indent); 31 | writer.Write(")"); 32 | } 33 | else 34 | { 35 | writer.Write("."); 36 | writer.Write(UnexaminedCode); 37 | } 38 | if (Comment != null) 39 | { 40 | writer.Write(" ' "); 41 | writer.Write(Comment); 42 | } 43 | writer.WriteLine(); 44 | BlockOnTrue.ToTextCode(nameMap, writer, indent + 1); 45 | writer.WriteLine(); 46 | for (int i = 0; i < indent; i++) 47 | writer.Write(" "); 48 | if (Mask) 49 | writer.Write("' "); 50 | writer.WriteLine(".否则"); 51 | BlockOnFalse.ToTextCode(nameMap, writer, indent + 1); 52 | writer.WriteLine(); 53 | for (int i = 0; i < indent; i++) 54 | writer.Write(" "); 55 | if (Mask) 56 | writer.Write("' "); 57 | writer.Write(".如果结束"); 58 | } 59 | internal override void WriteTo(MethodCodeDataWriterArgs a) 60 | { 61 | using (a.NewBlock(1)) 62 | { 63 | if (UnexaminedCode != null) 64 | new UnexaminedStatement(UnexaminedCode, Mask).WriteTo(a, 0x6B, 0, 0); 65 | else 66 | new ExpressionStatement(new CallExpression(0, 0, new ParamListExpression() { Condition }), Mask, Comment).WriteTo(a, 0x6B); 67 | BlockOnTrue.WriteTo(a); 68 | a.ExpressionData.Write((byte)0x50); 69 | BlockOnFalse.WriteTo(a); 70 | a.ExpressionData.Write((byte)0x51); 71 | } 72 | a.ExpressionData.Write((byte)0x72); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /EProjectFile/Statements/IfStatement.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using QIQI.EProjectFile.Expressions; 4 | namespace QIQI.EProjectFile.Statements 5 | { 6 | /// 7 | /// 如果真 语句块 8 | /// 9 | public class IfStatement : Statement 10 | { 11 | public Expression Condition { get; set; } 12 | /// 13 | /// 不为null时,应为null 14 | /// 15 | public string UnexaminedCode { get; set; } 16 | public StatementBlock Block { get; set; } 17 | public string Comment { get; set; } 18 | public bool Mask { get; set; } 19 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 20 | { 21 | for (int i = 0; i < indent; i++) 22 | writer.Write(" "); 23 | if (Mask) 24 | writer.Write("' "); 25 | if (UnexaminedCode == null) 26 | { 27 | writer.Write(".如果真 ("); 28 | Condition.ToTextCode(nameMap, writer, indent); 29 | writer.Write(")"); 30 | } 31 | else 32 | { 33 | writer.Write("."); 34 | writer.Write(UnexaminedCode); 35 | } 36 | if (Comment != null) 37 | { 38 | writer.Write(" ' "); 39 | writer.Write(Comment); 40 | } 41 | writer.WriteLine(); 42 | Block.ToTextCode(nameMap, writer, indent + 1); 43 | writer.WriteLine(); 44 | for (int i = 0; i < indent; i++) 45 | writer.Write(" "); 46 | if (Mask) 47 | writer.Write("' "); 48 | writer.Write(".如果真结束"); 49 | } 50 | internal override void WriteTo(MethodCodeDataWriterArgs a) 51 | { 52 | using (a.NewBlock(2)) 53 | { 54 | if (UnexaminedCode != null) 55 | new UnexaminedStatement(UnexaminedCode, Mask).WriteTo(a, 0x6C, 0, 1); 56 | else 57 | new ExpressionStatement(new CallExpression(0, 1, new ParamListExpression() { Condition }), Mask, Comment).WriteTo(a, 0x6C); 58 | Block.WriteTo(a); 59 | a.ExpressionData.Write((byte)0x52); 60 | } 61 | a.ExpressionData.Write((byte)0x73); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /EProjectFile/Statements/LoopStatement.cs: -------------------------------------------------------------------------------- 1 | namespace QIQI.EProjectFile.Statements 2 | { 3 | /// 4 | /// 循环语句块 基类 5 | /// 6 | public abstract class LoopStatement : Statement 7 | { 8 | public StatementBlock Block { get; set; } 9 | /// 10 | /// 不为null时,其他循环参数应为null 11 | /// 12 | public string UnexaminedCode { get; set; } 13 | public string CommentOnStart { get; set; } 14 | public string CommentOnEnd { get; set; } 15 | public bool MaskOnStart { get; set; } 16 | public bool MaskOnEnd { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /EProjectFile/Statements/Statement.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Text; 4 | namespace QIQI.EProjectFile.Statements 5 | { 6 | /// 7 | /// 语句 基类 8 | /// 9 | public abstract class Statement : IToTextCodeAble 10 | { 11 | internal abstract void WriteTo(MethodCodeDataWriterArgs a); 12 | public abstract void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0); 13 | public sealed override string ToString() => this.ToTextCode(IdToNameMap.Empty); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EProjectFile/Statements/StatementBlock.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | namespace QIQI.EProjectFile.Statements 8 | { 9 | /// 10 | /// 语句块 11 | /// 12 | public class StatementBlock : IList, IToTextCodeAble 13 | { 14 | private List statements = new List(); 15 | 16 | public int Count => ((IList)statements).Count; 17 | 18 | public bool IsReadOnly => ((IList)statements).IsReadOnly; 19 | 20 | public Statement this[int index] { get => ((IList)statements)[index]; set => ((IList)statements)[index] = value; } 21 | 22 | public StatementBlock() 23 | { 24 | 25 | } 26 | internal void WriteTo(MethodCodeDataWriterArgs a) 27 | { 28 | statements.ForEach(x => x.WriteTo(a)); 29 | } 30 | [Obsolete] 31 | public MethodCodeData ToCodeData() => ToCodeData(Encoding.GetEncoding("gbk")); 32 | public MethodCodeData ToCodeData(Encoding encoding) 33 | { 34 | BinaryWriter newWriter() => new BinaryWriter(new MemoryStream(), encoding); 35 | byte[] getBytes(BinaryWriter x) => ((MemoryStream)x.BaseStream).ToArray(); 36 | using (BinaryWriter 37 | lineOffest = newWriter(), 38 | blockOffest = newWriter(), 39 | methodReference = newWriter(), 40 | variableReference = newWriter(), 41 | constantReference = newWriter(), 42 | expressionData = newWriter()) 43 | { 44 | var a = new MethodCodeDataWriterArgs 45 | { 46 | LineOffest = lineOffest, 47 | BlockOffest = blockOffest, 48 | MethodReference = methodReference, 49 | VariableReference = variableReference, 50 | ConstantReference = constantReference, 51 | ExpressionData = expressionData, 52 | Encoding = encoding 53 | }; 54 | WriteTo(a); 55 | return new MethodCodeData( 56 | getBytes(lineOffest), 57 | getBytes(blockOffest), 58 | getBytes(methodReference), 59 | getBytes(variableReference), 60 | getBytes(constantReference), 61 | getBytes(expressionData), 62 | encoding); 63 | } 64 | } 65 | public void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 66 | { 67 | TextCodeUtils.JoinAndWriteCode(this, Environment.NewLine, nameMap, writer, indent); 68 | } 69 | public sealed override string ToString() => this.ToTextCode(IdToNameMap.Empty); 70 | 71 | public int IndexOf(Statement item) 72 | { 73 | return ((IList)statements).IndexOf(item); 74 | } 75 | 76 | public void Insert(int index, Statement item) 77 | { 78 | ((IList)statements).Insert(index, item); 79 | } 80 | 81 | public void RemoveAt(int index) 82 | { 83 | ((IList)statements).RemoveAt(index); 84 | } 85 | 86 | public void Add(Statement item) 87 | { 88 | ((IList)statements).Add(item); 89 | } 90 | 91 | public void Clear() 92 | { 93 | ((IList)statements).Clear(); 94 | } 95 | 96 | public bool Contains(Statement item) 97 | { 98 | return ((IList)statements).Contains(item); 99 | } 100 | 101 | public void CopyTo(Statement[] array, int arrayIndex) 102 | { 103 | ((IList)statements).CopyTo(array, arrayIndex); 104 | } 105 | 106 | public bool Remove(Statement item) 107 | { 108 | return ((IList)statements).Remove(item); 109 | } 110 | 111 | public IEnumerator GetEnumerator() 112 | { 113 | return ((IList)statements).GetEnumerator(); 114 | } 115 | 116 | IEnumerator IEnumerable.GetEnumerator() 117 | { 118 | return ((IList)statements).GetEnumerator(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /EProjectFile/Statements/SwitchStatement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using QIQI.EProjectFile.Expressions; 6 | namespace QIQI.EProjectFile.Statements 7 | { 8 | /// 9 | /// 判断 语句块 10 | /// 11 | public class SwitchStatement : Statement 12 | { 13 | public class CaseInfo 14 | { 15 | public Expression Condition { get; set; } 16 | public string UnexaminedCode { get; set; } 17 | public StatementBlock Block { get; set; } 18 | public string Comment { get; set; } 19 | public bool Mask { get; set; } 20 | } 21 | public List Case { get; } = new List(); 22 | public StatementBlock DefaultBlock { get; set; } 23 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 24 | { 25 | if (Case.Count == 0) 26 | throw new Exception("Must hava a case"); 27 | for (int i = 0; i < indent; i++) 28 | writer.Write(" "); 29 | if (Case[0].Mask) 30 | writer.Write("' "); 31 | if (Case[0].UnexaminedCode == null) 32 | { 33 | writer.Write(".判断开始 ("); 34 | Case[0].Condition.ToTextCode(nameMap, writer, indent); 35 | writer.Write(")"); 36 | } 37 | else 38 | { 39 | writer.Write(".判断开始"); 40 | writer.Write(Case[0].UnexaminedCode.TrimStart().Substring("判断".Length)); 41 | } 42 | if (Case[0].Comment != null) 43 | { 44 | writer.Write(" ' "); 45 | writer.Write(Case[0].Comment); 46 | } 47 | writer.WriteLine(); 48 | Case[0].Block.ToTextCode(nameMap, writer, indent + 1); 49 | for (int i = 1; i < Case.Count; i++) 50 | { 51 | writer.WriteLine(); 52 | for (int x = 0; x < indent; x++) 53 | writer.Write(" "); 54 | if (Case[i].Mask) 55 | writer.Write("' "); 56 | if (Case[i].UnexaminedCode == null) 57 | { 58 | writer.Write(".判断 ("); 59 | Case[i].Condition.ToTextCode(nameMap, writer, indent); 60 | writer.Write(")"); 61 | } 62 | else 63 | { 64 | writer.Write("."); 65 | writer.Write(Case[i].UnexaminedCode); 66 | } 67 | if (Case[i].Comment != null) 68 | { 69 | writer.Write(" ' "); 70 | writer.Write(Case[i].Comment); 71 | } 72 | writer.WriteLine(); 73 | Case[i].Block.ToTextCode(nameMap, writer, indent + 1); 74 | } 75 | writer.WriteLine(); 76 | for (int i = 0; i < indent; i++) 77 | writer.Write(" "); 78 | if (Case[0].Mask) 79 | writer.Write("' "); 80 | writer.WriteLine(".默认"); 81 | DefaultBlock.ToTextCode(nameMap, writer, indent + 1); 82 | writer.WriteLine(); 83 | for (int i = 0; i < indent; i++) 84 | writer.Write(" "); 85 | if (Case[0].Mask) 86 | writer.Write("' "); 87 | writer.Write(".判断结束"); 88 | } 89 | internal override void WriteTo(MethodCodeDataWriterArgs a) 90 | { 91 | using (a.NewBlock(4)) 92 | { 93 | a.ExpressionData.Write((byte)0x6D); 94 | foreach (var curCase in Case) 95 | { 96 | if (curCase.UnexaminedCode != null) 97 | new UnexaminedStatement(curCase.UnexaminedCode, curCase.Mask).WriteTo(a, 0x6E, 0, 2); 98 | else 99 | new ExpressionStatement(new CallExpression(0, 2, new ParamListExpression() { curCase.Condition }), curCase.Mask, curCase.Comment).WriteTo(a, 0x6E); 100 | curCase.Block.WriteTo(a); 101 | a.ExpressionData.Write((byte)0x53); 102 | } 103 | a.ExpressionData.Write((byte)0x6F); 104 | DefaultBlock.WriteTo(a); 105 | a.ExpressionData.Write((byte)0x54); 106 | } 107 | a.ExpressionData.Write((byte)0x74); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /EProjectFile/Statements/UnexaminedStatement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using QIQI.EProjectFile.Expressions; 5 | using QIQI.EProjectFile.Internal; 6 | 7 | namespace QIQI.EProjectFile.Statements 8 | { 9 | /// 10 | /// 未验证代码语句 11 | /// 12 | public class UnexaminedStatement : Statement 13 | { 14 | private string unexaminedCode; 15 | public bool Mask { get; set; } 16 | 17 | public string UnexaminedCode { get => unexaminedCode; set => unexaminedCode = value ?? throw new ArgumentNullException(nameof(UnexaminedCode)); } 18 | 19 | public UnexaminedStatement() 20 | { 21 | } 22 | 23 | public UnexaminedStatement(string unexaminedCode, bool mask) 24 | { 25 | UnexaminedCode = unexaminedCode; 26 | Mask = mask; 27 | } 28 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 29 | { 30 | for (int i = 0; i < indent; i++) 31 | writer.Write(" "); 32 | if (Mask) 33 | writer.Write("' "); 34 | writer.Write(unexaminedCode); 35 | } 36 | internal void WriteTo(MethodCodeDataWriterArgs a, byte type, short libraryId, int methodId) 37 | { 38 | a.LineOffest.Write(a.Offest); 39 | a.ExpressionData.Write(type); 40 | a.ExpressionData.Write(methodId); 41 | a.ExpressionData.Write(libraryId); 42 | a.ExpressionData.Write((short)(Mask ? 0 : 0x40)); 43 | a.ExpressionData.WriteBStr(a.Encoding, UnexaminedCode); 44 | a.ExpressionData.WriteBStr(a.Encoding, null); 45 | a.ExpressionData.Write((byte)0x36); 46 | ParamListEnd.Instance.WriteTo(a); 47 | 48 | } 49 | internal override void WriteTo(MethodCodeDataWriterArgs a) 50 | { 51 | WriteTo(a, 0x6A, -1, 0); 52 | 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /EProjectFile/Statements/WhileStatement.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using QIQI.EProjectFile.Expressions; 4 | namespace QIQI.EProjectFile.Statements 5 | { 6 | /// 7 | /// 判断循环 语句块 8 | /// 9 | public class WhileStatement : LoopStatement 10 | { 11 | public Expression Condition { get; set; } 12 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 13 | { 14 | for (int i = 0; i < indent; i++) 15 | writer.Write(" "); 16 | if (MaskOnStart) 17 | writer.Write("' "); 18 | if (UnexaminedCode == null) 19 | { 20 | writer.Write(".判断循环首 ("); 21 | Condition.ToTextCode(nameMap, writer, indent); 22 | writer.Write(")"); 23 | } 24 | else 25 | { 26 | writer.Write("."); 27 | writer.Write(UnexaminedCode); 28 | } 29 | if (CommentOnStart != null) 30 | { 31 | writer.Write(" ' "); 32 | writer.Write(CommentOnStart); 33 | } 34 | writer.WriteLine(); 35 | Block.ToTextCode(nameMap, writer, indent + 1); 36 | writer.WriteLine(); 37 | for (int i = 0; i < indent; i++) 38 | writer.Write(" "); 39 | if (MaskOnEnd) 40 | writer.Write("' "); 41 | writer.Write(".判断循环尾 ()"); 42 | if (CommentOnEnd != null) 43 | { 44 | writer.Write(" ' "); 45 | writer.Write(CommentOnEnd); 46 | } 47 | } 48 | internal override void WriteTo(MethodCodeDataWriterArgs a) 49 | { 50 | using (a.NewBlock(3)) 51 | { 52 | if (UnexaminedCode != null) 53 | new UnexaminedStatement(UnexaminedCode, MaskOnStart).WriteTo(a, 0x70, 0, 3); 54 | else 55 | new ExpressionStatement(new CallExpression(0, 3, new ParamListExpression() { Condition }), MaskOnStart, CommentOnStart).WriteTo(a, 0x70); 56 | Block.WriteTo(a); 57 | a.ExpressionData.Write((byte)0x55); 58 | } 59 | new ExpressionStatement(new CallExpression(0, 4, new ParamListExpression() { }), MaskOnEnd, CommentOnEnd).WriteTo(a, 0x71); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /EProjectFile/StructInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using QIQI.EProjectFile.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace QIQI.EProjectFile 9 | { 10 | public class StructInfo : IHasId, IHasMemoryAddress, IToTextCodeAble 11 | { 12 | public int Id { get; } 13 | 14 | public StructInfo(int id) 15 | { 16 | this.Id = id; 17 | } 18 | 19 | public int MemoryAddress { get; set; } 20 | public int Flags { get; set; } 21 | public bool Public { get => (Flags & 0x1) != 0; set => Flags = (Flags & ~0x1) | (value ? 0x1 : 0); } 22 | public bool Hidden { get => (Flags & 0x2) != 0; set => Flags = (Flags & ~0x2) | (value ? 0x2 : 0); } 23 | public string Name { get; set; } 24 | public string Comment { get; set; } 25 | public List Members { get; set; } 26 | public static List ReadStructs(BinaryReader r, Encoding encoding) 27 | { 28 | return r.ReadBlocksWithIdAndMemoryAddress((reader, id, memoryAddress) => 29 | new StructInfo(id) 30 | { 31 | MemoryAddress = memoryAddress, 32 | Flags = reader.ReadInt32(), 33 | Name = reader.ReadStringWithLengthPrefix(encoding), 34 | Comment = reader.ReadStringWithLengthPrefix(encoding), 35 | Members = AbstractVariableInfo.ReadVariables(reader, encoding, x => new StructMemberInfo(x)) 36 | } 37 | ); 38 | } 39 | public static void WriteStructs(BinaryWriter w, Encoding encoding, List structs) 40 | { 41 | w.WriteBlocksWithIdAndMemoryAddress(structs, (writer, elem) => 42 | { 43 | writer.Write(elem.Flags); 44 | writer.WriteStringWithLengthPrefix(encoding, elem.Name); 45 | writer.WriteStringWithLengthPrefix(encoding, elem.Comment); 46 | AbstractVariableInfo.WriteVariables(writer, encoding, elem.Members); 47 | }); 48 | } 49 | public override string ToString() 50 | { 51 | return JsonSerializer.Serialize(this, JsonUtils.Options); 52 | } 53 | 54 | public void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent) 55 | { 56 | TextCodeUtils.WriteDefinitionCode(writer, indent, "数据类型", nameMap.GetUserDefinedName(Id), Public ? "公开" : "", Comment); 57 | writer.WriteLine(); 58 | TextCodeUtils.JoinAndWriteCode(Members, Environment.NewLine, nameMap, writer, indent + 1); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /EProjectFile/VariableInfo/AbstractVariableInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.Json; 6 | using QIQI.EProjectFile.Internal; 7 | 8 | namespace QIQI.EProjectFile 9 | { 10 | public abstract class AbstractVariableInfo : IHasId, IToTextCodeAble 11 | { 12 | public int Id { get; } 13 | public virtual int[] UBound { get ; set ; } 14 | 15 | public AbstractVariableInfo(int id) 16 | { 17 | this.Id = id; 18 | } 19 | public int DataType { get; set; } 20 | public int Flags { get; set; } 21 | public string Name { get; set; } 22 | public string Comment { get; set; } 23 | internal static List ReadVariables(BinaryReader r, Encoding encoding, Func newFunction) where TElem : AbstractVariableInfo 24 | { 25 | return r.ReadBlocksWithIdAndOffest((reader, id) => 26 | { 27 | var x = newFunction(id); 28 | x.DataType = reader.ReadInt32(); 29 | x.Flags = reader.ReadInt16(); 30 | x.UBound = reader.ReadInt32sWithFixedLength(reader.ReadByte()); 31 | x.Name = reader.ReadCStyleString(encoding); 32 | x.Comment = reader.ReadCStyleString(encoding); 33 | return x; 34 | }); 35 | } 36 | internal static void WriteVariables(BinaryWriter w, Encoding encoding, List variables) where TElem : AbstractVariableInfo 37 | { 38 | w.WriteBlocksWithIdAndOffest( 39 | encoding, 40 | variables, 41 | (writer, elem) => 42 | { 43 | writer.Write(elem.DataType); 44 | writer.Write((short)elem.Flags); 45 | if (elem.UBound == null) 46 | { 47 | writer.Write((byte)0); 48 | } 49 | else 50 | { 51 | writer.Write((byte)elem.UBound.Length); 52 | writer.WriteInt32sWithoutLengthPrefix(elem.UBound); 53 | } 54 | writer.WriteCStyleString(encoding, elem.Name); 55 | writer.WriteCStyleString(encoding, elem.Comment); 56 | }); 57 | } 58 | public abstract void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /EProjectFile/VariableInfo/ClassVariableInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.Json; 8 | 9 | namespace QIQI.EProjectFile 10 | { 11 | public class ClassVariableInfo : AbstractVariableInfo 12 | { 13 | public ClassVariableInfo(int id) : base(EplSystemId.MakeSureIsSpecifiedType(id, EplSystemId.Type_ClassMember)) 14 | { 15 | } 16 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 17 | { 18 | string strForUBound; 19 | if (UBound.Length == 0) 20 | strForUBound = ""; 21 | else if (UBound.Length == 1 && UBound[0] == 0) 22 | strForUBound = "\"0\""; 23 | else 24 | strForUBound = "\"" + string.Join(",", UBound.Select(x => x == 0 ? "" : x.ToString())) + "\""; 25 | TextCodeUtils.WriteDefinitionCode(writer, indent, "程序集变量", nameMap.GetUserDefinedName(Id), nameMap.GetDataTypeName(DataType), "", strForUBound, Comment); 26 | } 27 | public override string ToString() 28 | { 29 | return JsonSerializer.Serialize(this, JsonUtils.Options); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /EProjectFile/VariableInfo/DllParameterInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.Json; 8 | 9 | namespace QIQI.EProjectFile 10 | { 11 | public class DllParameterInfo : AbstractVariableInfo 12 | { 13 | public bool ArrayParameter { get => (Flags & 0x8) != 0; set => Flags = (Flags & ~0x8) | (value ? 0x8 : 0); } 14 | public bool ByRef { get => (Flags & 0x2) != 0; set => Flags = (Flags & ~0x2) | (value ? 0x2 : 0); } 15 | public DllParameterInfo(int id) : base(EplSystemId.MakeSureIsSpecifiedType(id, EplSystemId.Type_DllParameter)) 16 | { 17 | } 18 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 19 | { 20 | string strForFlags = string.Join(" ", new string[] { ByRef ? "传址" : null, ArrayParameter ? "数组" : null }.Where(x => x != null)); 21 | TextCodeUtils.WriteDefinitionCode(writer, indent, "参数", nameMap.GetUserDefinedName(Id), nameMap.GetDataTypeName(DataType), strForFlags, Comment); 22 | } 23 | public override string ToString() 24 | { 25 | return JsonSerializer.Serialize(this, JsonUtils.Options); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /EProjectFile/VariableInfo/GlobalVariableInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.Json; 8 | 9 | namespace QIQI.EProjectFile 10 | { 11 | public class GlobalVariableInfo : AbstractVariableInfo 12 | { 13 | public bool Public { get => (Flags & 0x100) != 0; set => Flags = (Flags & ~0x100) | (value ? 0x100 : 0); } 14 | public bool Hidden { get => (Flags & 0x200) != 0; set => Flags = (Flags & ~0x200) | (value ? 0x200 : 0); } 15 | public GlobalVariableInfo(int id) : base(EplSystemId.MakeSureIsSpecifiedType(id, EplSystemId.Type_Global)) 16 | { 17 | } 18 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 19 | { 20 | string strForUBound; 21 | if (UBound.Length == 0) 22 | strForUBound = ""; 23 | else if (UBound.Length == 1 && UBound[0] == 0) 24 | strForUBound = "\"0\""; 25 | else 26 | strForUBound = "\"" + string.Join(",", UBound.Select(x => x == 0 ? "" : x.ToString())) + "\""; 27 | TextCodeUtils.WriteDefinitionCode(writer, indent, "全局变量", nameMap.GetUserDefinedName(Id), nameMap.GetDataTypeName(DataType), Public ? "公开" : "", strForUBound, Comment); 28 | } 29 | public override string ToString() 30 | { 31 | return JsonSerializer.Serialize(this, JsonUtils.Options); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /EProjectFile/VariableInfo/LocalVariableInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.Json; 8 | 9 | namespace QIQI.EProjectFile 10 | { 11 | public class LocalVariableInfo : AbstractVariableInfo 12 | { 13 | public bool Static { get => (Flags & 0x1) != 0; set => Flags = (Flags & ~0x1) | (value ? 0x1 : 0); } 14 | public LocalVariableInfo(int id) : base(EplSystemId.MakeSureIsSpecifiedType(id, EplSystemId.Type_Local)) 15 | { 16 | } 17 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 18 | { 19 | string strForUBound; 20 | if (UBound.Length == 0) 21 | strForUBound = ""; 22 | else if (UBound.Length == 1 && UBound[0] == 0) 23 | strForUBound = "\"0\""; 24 | else 25 | strForUBound = "\"" + string.Join(",", UBound.Select(x => x == 0 ? "" : x.ToString())) + "\""; 26 | TextCodeUtils.WriteDefinitionCode(writer, indent, "局部变量", nameMap.GetUserDefinedName(Id), nameMap.GetDataTypeName(DataType), Static ? "静态" : "", strForUBound, Comment); 27 | } 28 | public override string ToString() 29 | { 30 | return JsonSerializer.Serialize(this, JsonUtils.Options); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /EProjectFile/VariableInfo/MethodParameterInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.Json; 8 | 9 | namespace QIQI.EProjectFile 10 | { 11 | public class MethodParameterInfo : AbstractVariableInfo 12 | { 13 | public bool OptionalParameter { get => (Flags & 0x4) != 0; set => Flags = (Flags & ~0x4) | (value ? 0x4 : 0); } 14 | public bool ArrayParameter { get => (Flags & 0x8) != 0; set => Flags = (Flags & ~0x8) | (value ? 0x8 : 0); } 15 | public bool ByRef { get => (Flags & 0x2) != 0; set => Flags = (Flags & ~0x2) | (value ? 0x2 : 0); } 16 | public MethodParameterInfo(int id) : base(EplSystemId.MakeSureIsSpecifiedType(id, EplSystemId.Type_Local)) 17 | { 18 | } 19 | 20 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 21 | { 22 | string strForFlags = string.Join(" ", new string[] { ByRef ? "参考" : null, OptionalParameter ? "可空" : null, ArrayParameter ? "数组" : null }.Where(x => x != null)); 23 | TextCodeUtils.WriteDefinitionCode(writer, indent, "参数", nameMap.GetUserDefinedName(Id), nameMap.GetDataTypeName(DataType), strForFlags, Comment); 24 | } 25 | public override string ToString() 26 | { 27 | return JsonSerializer.Serialize(this, JsonUtils.Options); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /EProjectFile/VariableInfo/StructMemberInfo.cs: -------------------------------------------------------------------------------- 1 | using QIQI.EProjectFile.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.Json; 8 | 9 | namespace QIQI.EProjectFile 10 | { 11 | public class StructMemberInfo : AbstractVariableInfo 12 | { 13 | public bool ByRef { get => (Flags & 0x2) != 0; set => Flags = (Flags & ~0x2) | (value ? 0x2 : 0); } 14 | public StructMemberInfo(int id) : base(EplSystemId.MakeSureIsSpecifiedType(id, EplSystemId.Type_StructMember)) 15 | { 16 | } 17 | public override void ToTextCode(IdToNameMap nameMap, TextWriter writer, int indent = 0) 18 | { 19 | string strForUBound; 20 | if (UBound.Length == 0) 21 | strForUBound = ""; 22 | else if (UBound.Length == 1 && UBound[0] == 0) 23 | strForUBound = "\"0\""; 24 | else 25 | strForUBound = "\"" + string.Join(",", UBound.Select(x => x == 0 ? "" : x.ToString())) + "\""; 26 | TextCodeUtils.WriteDefinitionCode(writer, indent, "成员", nameMap.GetUserDefinedName(Id), nameMap.GetDataTypeName(DataType), ByRef ? "传址" : "", strForUBound, Comment); 27 | } 28 | public override string ToString() 29 | { 30 | return JsonSerializer.Serialize(this, JsonUtils.Options); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***Only Chinese document is available.*** 2 | 3 | # EProjectFile 4 | [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) 5 | [![NuGet](https://img.shields.io/nuget/v/QIQI.EProjectFile.svg)](https://www.nuget.org/packages/QIQI.EProjectFile) 6 | 一个用于编辑易语言项目文件的第三方库(C#) 7 | 命名空间:`QIQI.EProjectFile` 8 | 9 | ## 安装方式 10 | Package Manager `Install-Package QIQI.EProjectFile` 11 | .NET CLI `dotnet add package QIQI.EProjectFile` 12 | 使用 VS 的 NuGet 包管理器(GUI)搜索 `QIQI.EProjectFile` 13 | 支持:.NET Core 2.0 或更高(已经不再支持在 XP 环境下运行) 14 | 15 | ## 版权相关 16 | - 本项目是贡献到 **公共领域(Public Domain)** 的自由、无限制的程序库 17 | - 在法律许可的范围内,任何人可以出于任何用途(非商业用途或商业用途等)、以任何形式(源代码或编译好的二进制文件等)、任何使用方式(复制、修改、发布、使用、分发、售卖等)使用本项目而不受到限制。作者自愿永久放弃一切(现有的或未来的)由各国版权法与用于版权保护的国际公约提供的权利,将本项目奉献于公共领域,并清楚地认识到这可能会损害作者及其继承者本可获得的利益。**但请注意,对于因您的不当使用而可能产生的对其他主体的侵权行为(包括但不限于因使用本项目所依赖的第三方程序库而可能造成的风险、将本项目用于侵害他人合法权益的用途而产生的纠纷等),需由您自行承担相关责任** 18 | - 本项目以原样提供,不作任何形式(明示或暗示)的保证,包括但不限于对适销性、特定用途适用性和非侵权性的保证。在任何情况下,如合同诉讼、侵权诉讼或其他诉讼中,作者或版权持有人**均不承担**因本项目或本项目的使用、交易或其他行为而产生的、引起的或因其它任何原因与之相关的任何责任(包括但不限于索赔、损害等)。 19 | 20 | ## 项目状态 21 | 本项目属于**实验性项目**,所有 API 随时可能发生破坏性的变更(breaking changes) 22 | 由于可能存在的程序错误,本项目可能造成被操作文件的数据丢失、损坏,请在进行操作前提前备份。 23 | 当前项目的完成度可用于从 *.e 文件中提取出所有代码信息和少量编辑信息,并进行代码编辑, 24 | 可用于 文本代码互转、EplDiff、甚至自行实现编译器等场景。 25 | ### 数据段支持情况 26 | 27 | | 段标识 | 段名称 | 本项目使用的类名 | 备注 | 28 | | ---- | ---- | ---- | ---- | 29 | | `0x01007319` | 用户信息段 | `ProjectConfigSection` | 30 | | `0x02007319` | 系统信息段 | `ESystemInfoSection` | 31 | | `0x03007319` | 程序段 | `CodeSection` | 32 | | `0x04007319` | 程序资源段 | `ResourceSection` | 33 | | `0x05007319` | 可丢失程序段 | `LosableSection` | 只支持解析部分数据 | 34 | | `0x06007319` | *\* | *Not supported* | 新版易语言已经不会写出此段 | 35 | | `0x07007319` | *(empty)* | `EndOfFileSection` | 36 | | `0x08007319` | 初始模块段 | `InitECSection` | 37 | | `0x09007319` | 编辑信息段2 | `EditorInfoSection` | 38 | | `0x0A007319` | 辅助信息段1 | `EventIndicesSection` | 39 | | `0x0B007319` | 辅助信息段2 | `ClassPublicitySection` | 40 | | `0x0C007319` | 易模块记录段 | `ECDependenciesSection` | 41 | | `0x0D007319` | 易包信息段1 | `EPackageInfoSection` | 42 | | `0x0E007319` | 编辑过滤器信息段 | `FolderSection` | 43 | | `0x0F007319` | 易模块记录段2 | *Not supported* | 当引用加密模块时,此段用于记忆密码 | 44 | | `0x10007319` | 辅助信息段3 | `ProjectConfigExSection` | 45 | | `0x11007319` | 编译条件信息段 | `ConditionalCompilationSection` | 46 | 47 | ### 文件格式支持情况 48 | 49 | | 格式 | 读取 | 写出 | 50 | | ---- | :----: | :----: | 51 | | `*.e` \(未加密\) | ✔️ | ✔️ | 52 | | `*.e` \(加密\) | ✔️ | ✔️ | 53 | | `*.ec` \(未加密\) | ✔️ | ✔️ | 54 | | `*.ec` \(加密\) | ✔️ | ✔️ | 55 | 56 | ### 代码解析与生成 57 | 58 | | 特性名称 | 完成情况 | 59 | | ---- | :----: | 60 | | 代码数据中语义部分的解析与生成 | ✔️ | 61 | | 代码数据中注释部分的解析与生成 | ✔️ | 62 | | 处理 屏蔽 标志 | ✔️ | 63 | | 处理 书签 标志 | ❌ | 64 | | 处理 折叠 标志 | ❌ | 65 | | 处理 展开 标志 | ❌ | 66 | 67 | ## 交流途径 68 | 一般的 bug 反馈 与 feature 请求,请用 GitHub 的 Issues 模块反馈 69 | 如果您希望对本项目做出贡献,请使用标准 GitHub 工作流:Fork + Pull request 70 | 进一步的快速讨论:请加入 QQ 群 605310933 *(注意不要在群中反馈 bug,这很可能导致反馈没有被记录。聊天消息较 Issues 模块比较混乱)* 71 | 72 | ## 编译方式 73 | 使用 NuGet 安装缺失包,然后按照一般流程编译即可 74 | 注意:**必须 VS2017 或更高版本,使用了最新语言特性** 75 | 76 | ## 使用例程 77 | 可以参考 [OpenEpl/InjectedEComRepair](https://github.com/OpenEpl/InjectedEComRepair) 项目 78 | 79 | ## 特别感谢 80 | 本项目的发展得益于开源社区中许多贡献者的支持,这里仅列举部分人员用于表示感谢,*请注意该列表并非全部贡献者* 81 | 82 | | 用户 | 成就 | 83 | | ---- | ---- | 84 | | 东灿 | [最早开源的项目文件结构分析代码](https://bbs.125.la/forum.php?mod=viewthread&tid=13751690) | 85 | | 曙光 | 最早开源的代码数据解析软件 | 86 | | 为你芯冻 | [e.net](https://github.com/wnxd/e.net)(包含一定的项目解析代码) | 87 | | JimStone(谢栋) | 支持库兼容性信息分析 [`80348c3`](https://github.com/OpenEpl/EProjectFile/commit/80348c3e42d775c1b2f2c45af699356c46b3503d) | 88 | | @clhhz | 发现 MemoryAddress 字段 \([#2](https://github.com/OpenEpl/EProjectFile/pull/2)\) | --------------------------------------------------------------------------------