├── .gitattributes ├── .github └── workflows │ ├── Marathon.yml │ └── Patch-Version.ps1 ├── .gitignore ├── Build.ps1 ├── COPYRIGHT ├── LICENSE ├── Marathon.CLI ├── Marathon.CLI.csproj ├── Program.cs ├── Properties │ └── PublishProfiles │ │ ├── linux-x64.pubxml │ │ ├── osx-x64.pubxml │ │ ├── win-x64.pubxml │ │ └── win-x86.pubxml └── Resources │ └── Icon.ico ├── Marathon.Shared ├── Extensions │ ├── AssemblyExtensions.cs │ └── ExceptionExtensions.cs ├── Marathon.Shared.projitems ├── Marathon.Shared.shproj └── Resources │ └── Images │ └── Logos │ ├── CLI.png │ └── Marathon.png ├── Marathon.sln ├── Marathon ├── Exceptions │ ├── InvalidSetParameterType.cs │ └── InvalidSignatureException.cs ├── Formats │ ├── Archive │ │ └── U8Archive.cs │ ├── Audio │ │ └── SoundBank.cs │ ├── Event │ │ ├── EventPlaybook.cs │ │ └── TimeEvent.cs │ ├── Mesh │ │ ├── Collision.cs │ │ ├── Ninja │ │ │ ├── NinjaCamera.cs │ │ │ ├── NinjaEffectList.cs │ │ │ ├── NinjaFlags.cs │ │ │ ├── NinjaKeyframe.cs │ │ │ ├── NinjaLight.cs │ │ │ ├── NinjaMaterial.cs │ │ │ ├── NinjaMaterialColours.cs │ │ │ ├── NinjaMaterialLogic.cs │ │ │ ├── NinjaMotion.cs │ │ │ ├── NinjaNext.cs │ │ │ ├── NinjaNode.cs │ │ │ ├── NinjaNodeNameList.cs │ │ │ ├── NinjaObject.cs │ │ │ ├── NinjaPrimitiveList.cs │ │ │ ├── NinjaSubMotion.cs │ │ │ ├── NinjaSubObject.cs │ │ │ ├── NinjaTextureList.cs │ │ │ ├── NinjaTextureMap.cs │ │ │ └── NinjaVertexList.cs │ │ ├── PathSpline.cs │ │ └── ReflectionZone.cs │ ├── Package │ │ ├── AssetPackage.cs │ │ ├── CommonPackage.cs │ │ ├── ExplosionPackage.cs │ │ ├── PathPackage.cs │ │ ├── ScriptPackage.cs │ │ └── ShotPackage.cs │ ├── Particle │ │ ├── ParticleContainer.cs │ │ ├── ParticleEffectBank.cs │ │ ├── ParticleGenerationSystem.cs │ │ └── ParticleTextureBank.cs │ ├── Placement │ │ ├── PropertyDatabase.cs │ │ ├── SetData.cs │ │ └── SetDataType.cs │ ├── Save │ │ ├── SonicNextEpisode.cs │ │ ├── SonicNextFlags.cs │ │ ├── SonicNextOptions.cs │ │ ├── SonicNextRank.cs │ │ ├── SonicNextSaveData.cs │ │ └── SonicNextTrial.cs │ ├── Script │ │ ├── IndentationType.cs │ │ ├── Lua │ │ │ ├── Decompiler │ │ │ │ ├── Blocks │ │ │ │ │ ├── AlwaysLoop.cs │ │ │ │ │ ├── Block.cs │ │ │ │ │ ├── BooleanIndicator.cs │ │ │ │ │ ├── Break.cs │ │ │ │ │ ├── CompareBlock.cs │ │ │ │ │ ├── DoEndBlock.cs │ │ │ │ │ ├── ElseEndBlock.cs │ │ │ │ │ ├── ForBlock.cs │ │ │ │ │ ├── IfThenElseBlock.cs │ │ │ │ │ ├── IfThenEndBlock.cs │ │ │ │ │ ├── OuterBlock.cs │ │ │ │ │ ├── RepeatBlock.cs │ │ │ │ │ ├── SetBlock.cs │ │ │ │ │ ├── TForBlock.cs │ │ │ │ │ └── WhileBlock.cs │ │ │ │ ├── Branches │ │ │ │ │ ├── AndBranch.cs │ │ │ │ │ ├── AssignNode.cs │ │ │ │ │ ├── Branch.cs │ │ │ │ │ ├── EQNode.cs │ │ │ │ │ ├── LENode.cs │ │ │ │ │ ├── LTNode.cs │ │ │ │ │ ├── NotBranch.cs │ │ │ │ │ ├── OrBranch.cs │ │ │ │ │ ├── TestNode.cs │ │ │ │ │ ├── TestSetNode.cs │ │ │ │ │ └── TrueNode.cs │ │ │ │ ├── Code.cs │ │ │ │ ├── Constant.cs │ │ │ │ ├── Declaration.cs │ │ │ │ ├── Decompiler.cs │ │ │ │ ├── Expressions │ │ │ │ │ ├── Associativity.cs │ │ │ │ │ ├── BinaryExpression.cs │ │ │ │ │ ├── ClosureExpression.cs │ │ │ │ │ ├── ConstantExpression.cs │ │ │ │ │ ├── Expression.cs │ │ │ │ │ ├── FunctionCall.cs │ │ │ │ │ ├── GlobalExpression.cs │ │ │ │ │ ├── LocalVariable.cs │ │ │ │ │ ├── Precedence.cs │ │ │ │ │ ├── TableLiteral.cs │ │ │ │ │ ├── TableReference.cs │ │ │ │ │ ├── UnaryExpression.cs │ │ │ │ │ ├── UpvalueExpression.cs │ │ │ │ │ └── Vararg.cs │ │ │ │ ├── Function.cs │ │ │ │ ├── ICodeExtract.cs │ │ │ │ ├── IOutputProvider.cs │ │ │ │ ├── Op.cs │ │ │ │ ├── OpcodeFormat.cs │ │ │ │ ├── OpcodeMap.cs │ │ │ │ ├── Operations │ │ │ │ │ ├── CallOperation.cs │ │ │ │ │ ├── GlobalSet.cs │ │ │ │ │ ├── Operation.cs │ │ │ │ │ ├── RegisterSet.cs │ │ │ │ │ ├── ReturnOperation.cs │ │ │ │ │ ├── TableSet.cs │ │ │ │ │ └── UpvalueSet.cs │ │ │ │ ├── Output.cs │ │ │ │ ├── Registers.cs │ │ │ │ ├── Statements │ │ │ │ │ ├── Assignment.cs │ │ │ │ │ ├── Declare.cs │ │ │ │ │ ├── FunctionCallStatement.cs │ │ │ │ │ ├── Return.cs │ │ │ │ │ └── Statement.cs │ │ │ │ ├── Targets │ │ │ │ │ ├── GlobalTarget.cs │ │ │ │ │ ├── TableTarget.cs │ │ │ │ │ ├── Target.cs │ │ │ │ │ ├── UpvalueTarget.cs │ │ │ │ │ └── VariableTarget.cs │ │ │ │ └── Upvalues.cs │ │ │ ├── LuaBinary.cs │ │ │ ├── Types │ │ │ │ ├── BHeader.cs │ │ │ │ ├── BInteger.cs │ │ │ │ ├── BIntegerType.cs │ │ │ │ ├── BList.cs │ │ │ │ ├── BObject.cs │ │ │ │ ├── BObjectType.cs │ │ │ │ ├── BSizeT.cs │ │ │ │ ├── BSizeTType.cs │ │ │ │ ├── LBoolean.cs │ │ │ │ ├── LBooleanType.cs │ │ │ │ ├── LConstantType.cs │ │ │ │ ├── LFunction.cs │ │ │ │ ├── LFunctionType.cs │ │ │ │ ├── LLocal.cs │ │ │ │ ├── LLocalType.cs │ │ │ │ ├── LNil.cs │ │ │ │ ├── LNumber.cs │ │ │ │ ├── LNumberType.cs │ │ │ │ ├── LObject.cs │ │ │ │ ├── LSourceLines.cs │ │ │ │ ├── LString.cs │ │ │ │ ├── LStringType.cs │ │ │ │ ├── LUpvalue.cs │ │ │ │ └── LUpvalueType.cs │ │ │ └── Version.cs │ │ └── MAB.cs │ └── Text │ │ ├── MessageTable.cs │ │ └── PictureFont.cs ├── Helpers │ ├── ArchiveHelper.cs │ ├── BinaryHelper.cs │ ├── Converters │ │ └── ByteArrayConverter.cs │ ├── StringHelper.cs │ └── VectorHelper.cs ├── IO │ ├── FileBase.cs │ ├── Interfaces │ │ ├── IArchive.cs │ │ ├── IArchiveData.cs │ │ ├── IArchiveDirectory.cs │ │ ├── IArchiveFile.cs │ │ └── IHeader.cs │ ├── Streams │ │ ├── BinaryExtended.cs │ │ └── ZlibStream.cs │ └── Types │ │ └── BINA │ │ ├── BINA.cs │ │ └── BINAHeader.cs ├── Interfaces │ └── ILogger.cs ├── Logger.cs └── Marathon.csproj └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ps1 linguist-vendored -------------------------------------------------------------------------------- /.github/workflows/Patch-Version.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | [String]$CommitID, 4 | [String]$ProjectPath, 5 | [Switch]$UseFullCommitID, 6 | [String]$Version 7 | ) 8 | 9 | $project = [System.IO.File]::ReadAllLines($ProjectPath) 10 | 11 | if (!$UseFullCommitID -and $CommitID.Length -ne 0) 12 | { 13 | $CommitID = $CommitID.Substring(0, 7) 14 | } 15 | 16 | $i = 0 17 | foreach ($line in $project) 18 | { 19 | $lineSplit = $line.Split(">") 20 | 21 | if ($lineSplit[0].Contains("", $lineSplit) 38 | 39 | $i++ 40 | } 41 | 42 | [System.IO.File]::WriteAllLines($ProjectPath, $project) -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | [Switch]$Archive, 4 | [Switch]$BuildAll, 5 | [String]$CommitID, 6 | [String]$Configuration = "Release", 7 | [Switch]$Clean, 8 | [Switch]$Help, 9 | [String]$Profile, 10 | [Switch]$UseFullCommitID, 11 | [String]$Version 12 | ) 13 | 14 | $work = $pwd 15 | $profiles = @("win-x86", "win-x64", "linux-x64", "osx-x64") 16 | $buildPaths = @("Marathon.CLI\bin\Publish\") 17 | $patchVersion = ".github\workflows\Patch-Version.ps1" 18 | 19 | if ($Help) 20 | { 21 | echo "Marathon Build Script" 22 | echo "" 23 | echo "All your platforms are belong to us." 24 | echo "" 25 | echo "Usage:" 26 | echo "-Archive - archives the build artifacts." 27 | echo "-BuildAll - build Marathon with all available profiles." 28 | echo "-CommitID [id] - set the commit ID to use from GitHub for the version number." 29 | echo "-Configuration [name] - build Marathon with a specific configuration." 30 | echo "-Clean - cleans the solution before building Marathon." 31 | echo "-Help - display help." 32 | echo "-Profile [name] - build Marathon with a specific profile." 33 | echo "-UseFullCommitID - use the full 40 character commit ID for the version number." 34 | echo "-Version [major].[minor].[revision] - set the version number for this build of Marathon." 35 | exit 36 | } 37 | 38 | # Check if the .NET SDK is installed. 39 | if (!(Get-Command -Name dotnet -ErrorAction SilentlyContinue)) 40 | { 41 | echo ".NET SDK is required to build Marathon." 42 | echo "You can install the required .NET SDK for Windows from here: https://dotnet.microsoft.com/en-us/download/dotnet/8.0" 43 | exit 44 | } 45 | 46 | if ([System.String]::IsNullOrEmpty($Version)) 47 | { 48 | foreach ($project in [System.IO.Directory]::EnumerateFiles(".", "*.csproj", [System.IO.SearchOption]::AllDirectories)) 49 | { 50 | & "${patchVersion}" -ProjectPath "${project}" -Version "1.0.0" 51 | } 52 | } 53 | 54 | if ($Clean) 55 | { 56 | dotnet clean 57 | } 58 | 59 | function PatchVersionInformation([String]$commitID, [Boolean]$useFullCommitID, [String]$version) 60 | { 61 | # Patch the version number for all projects. 62 | if (![System.String]::IsNullOrEmpty($version)) 63 | { 64 | foreach ($project in [System.IO.Directory]::EnumerateFiles(".", "*.csproj", [System.IO.SearchOption]::AllDirectories)) 65 | { 66 | & "${patchVersion}" -CommitID $commitID -ProjectPath "${project}" -Version $version 67 | } 68 | } 69 | } 70 | 71 | function Build([String]$configuration, [String]$profile) 72 | { 73 | # Patch version number before building. 74 | PatchVersionInformation $CommitID $UseFullCommitID $Version 75 | 76 | dotnet publish /p:Configuration="${configuration}" /p:PublishProfile="${profile}" 77 | 78 | # Restore default version number. 79 | PatchVersionInformation "" $false "1.0.0" 80 | 81 | if ($Archive) 82 | { 83 | foreach ($buildPath in $buildPaths) 84 | { 85 | foreach ($artifact in [System.IO.Directory]::EnumerateDirectories($buildPath)) 86 | { 87 | # Creates a full path to the artifact using the current build path and profile name. 88 | $artifactPath = [System.IO.Path]::Combine([System.IO.Directory]::CreateDirectory([System.IO.Path]::Combine($buildPath, "artifacts")).FullName, $buildPath.Split('\')[0] + "-${profile}") 89 | 90 | # We only want to archive the most recent build. 91 | if ([System.IO.Path]::GetFileName($artifact) -ne $profile) 92 | { 93 | continue 94 | } 95 | 96 | # Enter artifact directory. 97 | cd $artifact 98 | 99 | if ($profile.StartsWith("win")) 100 | { 101 | # Use *.zip files for Windows. 102 | Compress-Archive -Force * "${artifactPath}.zip" 103 | } 104 | else 105 | { 106 | # Use *.tar.gz files for Linux and macOS. 107 | tar -c -z -f "${artifactPath}.tar.gz" * 108 | } 109 | 110 | # Return to working directory. 111 | cd $work 112 | } 113 | } 114 | } 115 | } 116 | 117 | if (![System.String]::IsNullOrEmpty($Profile)) 118 | { 119 | if ([System.Array]::IndexOf($profiles, $Profile) -eq -1) 120 | { 121 | echo "No such profile exists: ${Profile}" 122 | exit 123 | } 124 | 125 | Build $Configuration $Profile 126 | 127 | if (!$BuildAll) 128 | { 129 | exit 130 | } 131 | } 132 | 133 | if ($BuildAll) 134 | { 135 | foreach ($currentProfile in $profiles) 136 | { 137 | # Skip profile if it was already specified and built. 138 | if ($currentProfile -eq $Profile) 139 | { 140 | continue 141 | } 142 | 143 | Build $Configuration $currentProfile 144 | } 145 | } 146 | else 147 | { 148 | Build $Configuration "win-x86" 149 | Build $Configuration "win-x64" 150 | } -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | ------ 2 | unluac 3 | ------ 4 | 5 | MIT License 6 | 7 | Copyright (c) 2011-2016 tehtmi 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 HyperBE32 4 | Copyright (c) 2022 Knuxfan24 5 | Copyright (c) 2020 David Korth 6 | Copyright (c) 2020 Radfordhound 7 | 8 | Copyright and license terms of Unluac (unlub): 9 | Copyright (c) 2011-2016 tehtmi 10 | With Portions Copyright (c) 2014 Thomas Klaeger 11 | With Portions Copyright (c) 2015 Shadow LAG 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | -------------------------------------------------------------------------------- /Marathon.CLI/Marathon.CLI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 1.0.0 7 | 1.0.0 8 | Hyper, Knuxfan24 9 | Marathon Command Line 10 | Resources\Icon.ico 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Marathon.CLI/Properties/PublishProfiles/linux-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Publish\linux-x64\ 10 | FileSystem 11 | net8.0 12 | linux-x64 13 | true 14 | true 15 | true 16 | 17 | -------------------------------------------------------------------------------- /Marathon.CLI/Properties/PublishProfiles/osx-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Publish\osx-x64\ 10 | FileSystem 11 | net8.0 12 | true 13 | osx-x64 14 | true 15 | true 16 | 17 | -------------------------------------------------------------------------------- /Marathon.CLI/Properties/PublishProfiles/win-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Publish\win-x64\ 10 | FileSystem 11 | net8.0 12 | win-x64 13 | true 14 | true 15 | true 16 | true 17 | 18 | -------------------------------------------------------------------------------- /Marathon.CLI/Properties/PublishProfiles/win-x86.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Publish\win-x86\ 10 | FileSystem 11 | net8.0 12 | win-x86 13 | true 14 | true 15 | true 16 | true 17 | 18 | -------------------------------------------------------------------------------- /Marathon.CLI/Resources/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperbx/Marathon/43238ad9aea77a0a6d7602e68e5556449f01ae10/Marathon.CLI/Resources/Icon.ico -------------------------------------------------------------------------------- /Marathon.Shared/Extensions/AssemblyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Marathon.Shared 4 | { 5 | public class AssemblyExtensions 6 | { 7 | /// 8 | /// Returns the assembly informational version from the entry assembly. 9 | /// 10 | public static string GetInformationalVersion() 11 | => Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion; 12 | 13 | /// 14 | /// Returns the current assembly name. 15 | /// 16 | public static string GetAssemblyName() 17 | => Assembly.GetEntryAssembly().GetName().Name; 18 | 19 | /// 20 | /// Returns the current assembly version. 21 | /// 22 | public static string GetAssemblyVersion() 23 | => Assembly.GetEntryAssembly().GetName().Version.ToString(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Marathon.Shared/Extensions/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace Marathon.Shared 5 | { 6 | public static class ExceptionExtensions 7 | { 8 | /// 9 | /// Builds a text log of the exception. 10 | /// 11 | /// Enables markdown for a better preview with services that use it. 12 | public static string CreateLog(this Exception ex, bool markdown = false) 13 | { 14 | StringBuilder exception = new(); 15 | 16 | if (markdown) 17 | exception.AppendLine("```"); 18 | 19 | exception.AppendLine("Marathon " + $"({AssemblyExtensions.GetInformationalVersion()})"); 20 | 21 | if (!string.IsNullOrEmpty(ex.GetType().Name)) 22 | exception.AppendLine($"\nType: {ex.GetType().Name}"); 23 | 24 | if (!string.IsNullOrEmpty(ex.Message)) 25 | exception.AppendLine($"Message: {ex.Message}"); 26 | 27 | if (!string.IsNullOrEmpty(ex.Source)) 28 | exception.AppendLine($"Source: {ex.Source}"); 29 | 30 | if (ex.TargetSite != null) 31 | exception.AppendLine($"Function: {ex.TargetSite}"); 32 | 33 | if (!string.IsNullOrEmpty(ex.StackTrace)) 34 | exception.AppendLine($"\nStack Trace: \n{ex.StackTrace}"); 35 | 36 | if (ex.InnerException != null) 37 | exception.AppendLine($"\nInner Exception: \n{ex.InnerException}"); 38 | 39 | if (markdown) 40 | exception.AppendLine("```"); 41 | 42 | return exception.ToString(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Marathon.Shared/Marathon.Shared.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | da94bae1-9d6a-4e00-8dff-10af9fd6aac1 7 | 8 | 9 | Marathon.Shared 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Marathon.Shared/Marathon.Shared.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | da94bae1-9d6a-4e00-8dff-10af9fd6aac1 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Marathon.Shared/Resources/Images/Logos/CLI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperbx/Marathon/43238ad9aea77a0a6d7602e68e5556449f01ae10/Marathon.Shared/Resources/Images/Logos/CLI.png -------------------------------------------------------------------------------- /Marathon.Shared/Resources/Images/Logos/Marathon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperbx/Marathon/43238ad9aea77a0a6d7602e68e5556449f01ae10/Marathon.Shared/Resources/Images/Logos/Marathon.png -------------------------------------------------------------------------------- /Marathon.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31423.177 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marathon", "Marathon\Marathon.csproj", "{A240D10A-776F-4132-B876-F505B003789E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marathon.CLI", "Marathon.CLI\Marathon.CLI.csproj", "{6B6B3325-EF17-4304-87C1-FA2CDCA4B6B4}" 9 | EndProject 10 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Marathon.Shared", "Marathon.Shared\Marathon.Shared.shproj", "{DA94BAE1-9D6A-4E00-8DFF-10AF9FD6AAC1}" 11 | EndProject 12 | Global 13 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 14 | Marathon.Shared\Marathon.Shared.projitems*{6b6b3325-ef17-4304-87c1-fa2cdca4b6b4}*SharedItemsImports = 5 15 | Marathon.Shared\Marathon.Shared.projitems*{da94bae1-9d6a-4e00-8dff-10af9fd6aac1}*SharedItemsImports = 13 16 | EndGlobalSection 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {A240D10A-776F-4132-B876-F505B003789E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {A240D10A-776F-4132-B876-F505B003789E}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {A240D10A-776F-4132-B876-F505B003789E}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {A240D10A-776F-4132-B876-F505B003789E}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {6B6B3325-EF17-4304-87C1-FA2CDCA4B6B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {6B6B3325-EF17-4304-87C1-FA2CDCA4B6B4}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {6B6B3325-EF17-4304-87C1-FA2CDCA4B6B4}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {6B6B3325-EF17-4304-87C1-FA2CDCA4B6B4}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {3A078EC9-6D13-4437-90E2-B9ECB276C638} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /Marathon/Exceptions/InvalidSetParameterType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Exceptions 2 | { 3 | public class InvalidSetParameterType : Exception 4 | { 5 | static readonly new string Message = "Got invalid data type {0} in Object Parameter at position {1}..."; 6 | 7 | public InvalidSetParameterType(uint invalidType, long position) : base(string.Format(Message, invalidType, position)) { } 8 | 9 | public InvalidSetParameterType(string invalidType, long position) : base(string.Format(Message, invalidType, position)) { } 10 | } 11 | } -------------------------------------------------------------------------------- /Marathon/Exceptions/InvalidSignatureException.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Exceptions 2 | { 3 | public class InvalidSignatureException : Exception 4 | { 5 | static readonly new string Message = "The signature read from the stream is incorrect! Expected {0}, got {1}..."; 6 | 7 | public InvalidSignatureException(string expectedSignature, string receivedSignature) : base(string.Format(Message, expectedSignature, receivedSignature)) { } 8 | } 9 | } -------------------------------------------------------------------------------- /Marathon/Formats/Mesh/Ninja/NinjaCamera.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Mesh.Ninja 2 | { 3 | /// 4 | /// Structure of the main Ninja Camera. 5 | /// TODO (Knuxfan24): This is all wild guessing which I'm 100% sure is all sorts of bollocks. 6 | /// 7 | public class NinjaCamera 8 | { 9 | public CameraType Type { get; set; } 10 | 11 | /// 12 | /// TODO: unknown - always 0. 13 | /// 14 | public uint UnknownUInt32_1 { get; set; } 15 | 16 | /// 17 | /// TODO: unknown - flags? 18 | /// 19 | public uint UnknownUInt32_2 { get; set; } 20 | 21 | public Vector3 UnknownVector3_1 { get; set; } 22 | 23 | public Vector3 UnknownVector3_2 { get; set; } 24 | 25 | /// 26 | /// TODO (Knuxfan24): Seems like it ends up as NaN a lot? Maybe a coincidence that it's occasionally sensible numbers??? 27 | /// 28 | public float UnknownFloat_1 { get; set; } 29 | 30 | /// 31 | /// TODO (Knuxfan24): Seems like it ends up as NaN a lot? Maybe a coincidence that it's occasionally sensible numbers??? 32 | /// 33 | public float UnknownFloat_2 { get; set; } 34 | 35 | /// 36 | /// TODO: unknown - could be either a float or uint maybe? 37 | /// 38 | public float UnknownFloat_3 { get; set; } 39 | 40 | public float UnknownFloat_4 { get; set; } 41 | 42 | /// 43 | /// Reads the Ninja Camera from a file. 44 | /// 45 | /// The binary reader for this SegaNN file. 46 | public void Read(BinaryReaderEx reader) 47 | { 48 | // Read the offset to the actual Ninja Camera. 49 | uint dataOffset = reader.ReadUInt32(); 50 | 51 | // Jump to the actual Ninja Camera. 52 | reader.JumpTo(dataOffset, true); 53 | 54 | // Read the Type of this Camera and the offset to the camera data. 55 | Type = (CameraType)reader.ReadUInt32(); 56 | uint CameraOffset = reader.ReadUInt32(); 57 | 58 | // Jump to and read the data for this Camera. 59 | reader.JumpTo(CameraOffset, true); 60 | UnknownUInt32_1 = reader.ReadUInt32(); 61 | UnknownUInt32_2 = reader.ReadUInt32(); 62 | UnknownVector3_1 = reader.ReadVector3(); 63 | UnknownVector3_2 = reader.ReadVector3(); 64 | UnknownFloat_1 = reader.ReadSingle(); 65 | UnknownFloat_2 = reader.ReadSingle(); 66 | UnknownFloat_3 = reader.ReadSingle(); 67 | UnknownFloat_4 = reader.ReadSingle(); 68 | } 69 | 70 | /// 71 | /// Write the Ninja Camera to a file. 72 | /// 73 | /// The binary writer for this SegaNN file. 74 | public void Write(BinaryWriterEx writer) 75 | { 76 | // Write NXCA header. 77 | writer.Write("NXCA"); 78 | writer.Write("SIZE"); // Temporary entry, is filled in later once we know this chunk's size. 79 | long HeaderSizePosition = writer.BaseStream.Position; 80 | writer.AddOffset("dataOffset"); 81 | writer.FixPadding(0x10); 82 | 83 | // Write camera data. 84 | uint CameraPosition = (uint)writer.BaseStream.Position; 85 | writer.Write(UnknownUInt32_1); 86 | writer.Write(UnknownUInt32_2); 87 | writer.Write(UnknownVector3_1); 88 | writer.Write(UnknownVector3_2); 89 | writer.Write(UnknownFloat_1); 90 | writer.Write(UnknownFloat_2); 91 | writer.Write(UnknownFloat_3); 92 | writer.Write(UnknownFloat_4); 93 | 94 | // Write chunk data. 95 | writer.FillOffset("dataOffset", true); 96 | writer.Write((uint)Type); 97 | writer.AddOffset($"CameraData", 0); 98 | writer.Write(CameraPosition - 0x20); 99 | 100 | // Alignment. 101 | writer.FixPadding(0x10); 102 | 103 | // Write chunk size. 104 | long ChunkEndPosition = writer.BaseStream.Position; 105 | uint ChunkSize = (uint)(ChunkEndPosition - HeaderSizePosition); 106 | writer.BaseStream.Position = HeaderSizePosition - 4; 107 | writer.Write(ChunkSize); 108 | writer.BaseStream.Position = ChunkEndPosition; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Marathon/Formats/Mesh/Ninja/NinjaKeyframe.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Mesh.Ninja 2 | { 3 | /// 4 | /// Different structures for different keyframe types. 5 | /// 6 | public class NinjaKeyframe 7 | { 8 | /// 9 | /// Keyframe that consists of a single floating point value. 10 | /// 11 | public class NNS_MOTION_KEY_FLOAT 12 | { 13 | public float Frame { get; set; } 14 | 15 | public float Value { get; set; } 16 | 17 | public void Read(BinaryReaderEx reader) 18 | { 19 | Frame = reader.ReadSingle(); 20 | Value = reader.ReadSingle(); 21 | } 22 | } 23 | 24 | /// 25 | /// Keyframe that consists of a single short, usually using the Binary Angle Measurement System. 26 | /// 27 | public class NNS_MOTION_KEY_SINT16 28 | { 29 | public short Frame { get; set; } 30 | 31 | public short Value { get; set; } 32 | 33 | public void Read(BinaryReaderEx reader) 34 | { 35 | Frame = reader.ReadInt16(); 36 | Value = reader.ReadInt16(); 37 | } 38 | } 39 | 40 | /// 41 | /// Keyframe that consists of a Vector3. 42 | /// 43 | public class NNS_MOTION_KEY_VECTOR 44 | { 45 | public float Frame { get; set; } 46 | 47 | public Vector3 Value { get; set; } 48 | 49 | public void Read(BinaryReaderEx reader) 50 | { 51 | Frame = reader.ReadSingle(); 52 | Value = reader.ReadVector3(); 53 | } 54 | } 55 | 56 | /// 57 | /// Keyframe that consists of three shorts, usually using the Binary Angle Measurement System. 58 | /// 59 | public class NNS_MOTION_KEY_ROTATE_A16 60 | { 61 | public short Frame { get; set; } 62 | 63 | public short Value1 { get; set; } 64 | 65 | public short Value2 { get; set; } 66 | 67 | public short Value3 { get; set; } 68 | 69 | public void Read(BinaryReaderEx reader) 70 | { 71 | Frame = reader.ReadInt16(); 72 | Value1 = reader.ReadInt16(); 73 | Value2 = reader.ReadInt16(); 74 | Value3 = reader.ReadInt16(); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Marathon/Formats/Mesh/Ninja/NinjaLight.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Mesh.Ninja 2 | { 3 | /// 4 | /// Structure of the main Ninja Light. 5 | /// TODO (Knuxfan24): This is all wild guessing which I'm 100% sure is all sorts of bollocks. 6 | /// 7 | public class NinjaLight 8 | { 9 | public LightType Type { get; set; } 10 | 11 | public uint UnknownUInt32_1 { get; set; } 12 | 13 | public Vector3 UnknownVector3_1 { get; set; } 14 | 15 | public Vector3 UnknownVector3_2 { get; set; } 16 | 17 | public Vector3 UnknownVector3_3 { get; set; } 18 | 19 | public float UnknownFloat_1 { get; set; } 20 | 21 | /// 22 | /// Reads the Ninja Light from a file. 23 | /// 24 | /// The binary reader for this SegaNN file. 25 | public void Read(BinaryReaderEx reader) 26 | { 27 | // Read the offset to the actual Ninja Light. 28 | uint dataOffset = reader.ReadUInt32(); 29 | 30 | // Jump to the actual Ninja Light. 31 | reader.JumpTo(dataOffset, true); 32 | 33 | // Read the Type of this Light and the offset to the Light data. 34 | Type = (LightType)reader.ReadUInt32(); 35 | uint LightOffset = reader.ReadUInt32(); 36 | 37 | // Jump to and read the data for this Light. 38 | reader.JumpTo(LightOffset, true); 39 | UnknownUInt32_1 = reader.ReadUInt32(); 40 | UnknownVector3_1 = reader.ReadVector3(); 41 | UnknownVector3_2 = reader.ReadVector3(); 42 | UnknownVector3_3 = reader.ReadVector3(); 43 | UnknownFloat_1 = reader.ReadSingle(); 44 | } 45 | 46 | /// 47 | /// Write the Ninja Light to a file. 48 | /// 49 | /// The binary writer for this SegaNN file. 50 | public void Write(BinaryWriterEx writer) 51 | { 52 | // Write NXLI header. 53 | writer.Write("NXLI"); 54 | writer.Write("SIZE"); // Temporary entry, is filled in later once we know this chunk's size. 55 | long HeaderSizePosition = writer.BaseStream.Position; 56 | writer.AddOffset("dataOffset"); 57 | writer.FixPadding(0x10); 58 | 59 | // Write light data. 60 | uint LightPosition = (uint)writer.BaseStream.Position; 61 | writer.Write(UnknownUInt32_1); 62 | writer.Write(UnknownVector3_1); 63 | writer.Write(UnknownVector3_2); 64 | writer.Write(UnknownVector3_3); 65 | writer.Write(UnknownFloat_1); 66 | 67 | // Write chunk data. 68 | writer.FillOffset("dataOffset", true); 69 | writer.Write((uint)Type); 70 | writer.AddOffset($"LightData", 0); 71 | writer.Write(LightPosition - 0x20); 72 | 73 | // Alignment. 74 | writer.FixPadding(0x10); 75 | 76 | // Write chunk size. 77 | long ChunkEndPosition = writer.BaseStream.Position; 78 | uint ChunkSize = (uint)(ChunkEndPosition - HeaderSizePosition); 79 | writer.BaseStream.Position = HeaderSizePosition - 4; 80 | writer.Write(ChunkSize); 81 | writer.BaseStream.Position = ChunkEndPosition; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Marathon/Formats/Mesh/Ninja/NinjaMaterialColours.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Mesh.Ninja 2 | { 3 | /// 4 | /// Structure of a Ninja Object Material's Material Colours. 5 | /// This is kept seperate from Materials as multiple materials can use the same colours. 6 | /// 7 | public class NinjaMaterialColours 8 | { 9 | public Vector4 Diffuse { get; set; } 10 | 11 | public Vector4 Ambient { get; set; } 12 | 13 | public Vector4 Specular { get; set; } 14 | 15 | public Vector4 Emissive { get; set; } 16 | 17 | public float Power { get; set; } 18 | 19 | public uint Reserved0 { get; set; } 20 | 21 | public uint Reserved1 { get; set; } 22 | 23 | public uint Reserved2 { get; set; } 24 | 25 | /// 26 | /// This offset is stored by us purely for the writing process. 27 | /// 28 | public uint Offset { get; set; } 29 | 30 | /// 31 | /// Reads a material's colours from a file. 32 | /// 33 | /// The binary reader for this SegaNN file. 34 | public void Read(BinaryReaderEx reader) 35 | { 36 | // Skip over the material's Type. 37 | reader.JumpAhead(4); 38 | 39 | // Jump to the material's main data. 40 | reader.JumpTo(reader.ReadUInt32(), true); 41 | 42 | // Skip over the material's Flag and User Defined data. 43 | reader.JumpAhead(8); 44 | 45 | // Save the offset for the writing process then jump to it. 46 | Offset = reader.ReadUInt32(); 47 | reader.JumpTo(Offset, true); 48 | 49 | // Save the colour data for this material. 50 | Diffuse = reader.ReadVector4(); 51 | Ambient = reader.ReadVector4(); 52 | Specular = reader.ReadVector4(); 53 | Emissive = reader.ReadVector4(); 54 | Power = reader.ReadSingle(); 55 | Reserved0 = reader.ReadUInt32(); 56 | Reserved1 = reader.ReadUInt32(); 57 | Reserved2 = reader.ReadUInt32(); 58 | } 59 | 60 | /// 61 | /// Writes this material colour entry to a file. 62 | /// 63 | /// The binary writer for this SegaNN file. 64 | /// The number of this material colour entry in a linear list. 65 | /// The list of offsets this Object chunk uses. 66 | public void Write(BinaryWriterEx writer, int index, Dictionary ObjectOffsets) 67 | { 68 | // Add an entry for this material colour entry into the offset list so we know where it is. 69 | ObjectOffsets.Add($"ColourOffset{index}", (uint)writer.BaseStream.Position); 70 | 71 | // Write the material colour data. 72 | writer.Write(Diffuse); 73 | writer.Write(Ambient); 74 | writer.Write(Specular); 75 | writer.Write(Emissive); 76 | writer.Write(Power); 77 | writer.Write(Reserved0); 78 | writer.Write(Reserved1); 79 | writer.Write(Reserved2); 80 | } 81 | 82 | public override bool Equals(object obj) 83 | { 84 | if (obj is NinjaMaterialColours) 85 | return Equals((NinjaMaterialColours)obj); 86 | 87 | return false; 88 | } 89 | 90 | public bool Equals(NinjaMaterialColours obj) 91 | { 92 | if (obj == null) 93 | return false; 94 | 95 | if (!EqualityComparer.Default.Equals(Offset, obj.Offset)) 96 | return false; 97 | 98 | return true; 99 | } 100 | 101 | public override int GetHashCode() => EqualityComparer.Default.GetHashCode(Offset); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Marathon/Formats/Mesh/Ninja/NinjaNode.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Mesh.Ninja 2 | { 3 | /// 4 | /// Structure of a Ninja Object Node entry. 5 | /// 6 | public class NinjaNode 7 | { 8 | /// 9 | /// Not actually part of the Node, used purely so we don't keep having to go back 10 | /// and forth between the Node Name List and the Nodes themselves. 11 | /// 12 | public string Name { get; set; } 13 | 14 | public NodeType Type { get; set; } 15 | 16 | public short MatrixIndex { get; set; } = -1; 17 | 18 | public short ParentIndex { get; set; } = -1; 19 | 20 | public short ChildIndex { get; set; } = -1; 21 | 22 | public short SiblingIndex { get; set; } = -1; 23 | 24 | public Vector3 Translation { get; set; } 25 | 26 | public Vector3 Rotation { get; set; } 27 | 28 | public Vector3 Scaling { get; set; } 29 | 30 | public Matrix4x4 InvInitMatrix { get; set; } 31 | 32 | public Vector3 Center { get; set; } 33 | 34 | public float Radius { get; set; } 35 | 36 | public uint UserDefined { get; set; } 37 | 38 | public Vector3 BoundingBox { get; set; } 39 | 40 | public override string ToString() => Name; 41 | 42 | /// 43 | /// Reads a Ninja Object Node from a file. 44 | /// 45 | /// The binary reader for this SegaNN file. 46 | public void Read(BinaryReaderEx reader) 47 | { 48 | Type = (NodeType)reader.ReadUInt32(); 49 | MatrixIndex = reader.ReadInt16(); 50 | ParentIndex = reader.ReadInt16(); 51 | ChildIndex = reader.ReadInt16(); 52 | SiblingIndex = reader.ReadInt16(); 53 | Translation = reader.ReadVector3(); 54 | Rotation = reader.ReadVector3(); 55 | Scaling = reader.ReadVector3(); 56 | InvInitMatrix = reader.ReadMatrix(); 57 | Center = reader.ReadVector3(); 58 | Radius = reader.ReadSingle(); 59 | UserDefined = reader.ReadUInt32(); 60 | BoundingBox = reader.ReadVector3(); 61 | } 62 | 63 | /// 64 | /// Writes this Ninja Object Node to a file. 65 | /// 66 | /// The binary writer for this SegaNN file. 67 | public void Write(BinaryWriterEx writer) 68 | { 69 | writer.Write((uint)Type); 70 | writer.Write(MatrixIndex); 71 | writer.Write(ParentIndex); 72 | writer.Write(ChildIndex); 73 | writer.Write(SiblingIndex); 74 | writer.Write(Translation); 75 | writer.Write(Rotation); 76 | writer.Write(Scaling); 77 | writer.Write(InvInitMatrix); 78 | writer.Write(Center); 79 | writer.Write(Radius); 80 | writer.Write(UserDefined); 81 | writer.Write(BoundingBox); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Marathon/Formats/Mesh/Ninja/NinjaNodeNameList.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Mesh.Ninja 2 | { 3 | /// 4 | /// Structure of the main Ninja Node Name List. 5 | /// 6 | public class NinjaNodeNameList 7 | { 8 | public NodeNameSortType Type { get; set; } 9 | 10 | public List NinjaNodeNames { get; set; } = new(); 11 | 12 | /// 13 | /// Reads the Ninja Node Name List from a file. 14 | /// 15 | /// The binary reader for this SegaNN file. 16 | public void Read(BinaryReaderEx reader) 17 | { 18 | // Read the offset to the main data of this Node Name List chunk. 19 | uint dataOffset = reader.ReadUInt32(); 20 | 21 | // Jump to the main data of this Node Name List chunk. 22 | reader.JumpTo(dataOffset, true); 23 | 24 | // Read the type of this Node Name List chunk, the amount of node names and the offset to the table of them. 25 | Type = (NodeNameSortType)reader.ReadUInt32(); 26 | uint NodeCount = reader.ReadUInt32(); 27 | uint NodeListOffset = reader.ReadUInt32(); 28 | 29 | // Jump to the first node name in this Node Name List chunk. 30 | reader.JumpTo(NodeListOffset, true); 31 | 32 | // Loop through based on the count of node names in this chunk. 33 | for (int i = 0; i < NodeCount; i++) 34 | { 35 | // Read this Node's index (always linear) and the offset to this node's actual name. 36 | uint NodeID = reader.ReadUInt32(); 37 | uint NodeName_NameOffset = reader.ReadUInt32(); 38 | 39 | // Save our current position so we can jump back after reading. 40 | long pos = reader.BaseStream.Position; 41 | 42 | // Jump to the NameOffset, store the name of this node then jump back. 43 | reader.JumpTo(NodeName_NameOffset, true); 44 | NinjaNodeNames.Add(reader.ReadNullTerminatedString()); 45 | reader.JumpTo(pos); 46 | } 47 | } 48 | 49 | /// 50 | /// Write the Ninja Node Name List to a file. 51 | /// 52 | /// The binary writer for this SegaNN file. 53 | public void Write(BinaryWriterEx writer) 54 | { 55 | // Write NXNN header. 56 | writer.Write("NXNN"); 57 | writer.Write("SIZE"); // Temporary entry, is filled in later once we know this chunk's size. 58 | long HeaderSizePosition = writer.BaseStream.Position; 59 | writer.AddOffset("dataOffset"); 60 | writer.FixPadding(0x10); 61 | 62 | // Write Node Names. 63 | uint NodeNamesOffset = (uint)writer.BaseStream.Position - writer.Offset; 64 | for (int i = 0; i < NinjaNodeNames.Count; i++) 65 | { 66 | writer.Write(i); 67 | writer.AddOffset($"NodeName{i}_NameOffset"); 68 | } 69 | 70 | // Write chunk data. 71 | writer.FillOffset("dataOffset", true, false); 72 | writer.Write((uint)Type); 73 | writer.Write(NinjaNodeNames.Count); 74 | writer.AddOffset($"NodeName", 0); 75 | writer.Write(NodeNamesOffset); 76 | 77 | // Write chunk string table. 78 | for (int i = 0; i < NinjaNodeNames.Count; i++) 79 | { 80 | writer.FillOffset($"NodeName{i}_NameOffset", true, false); 81 | writer.WriteNullTerminatedString(NinjaNodeNames[i]); 82 | } 83 | 84 | // Alignment. 85 | writer.FixPadding(0x10); 86 | 87 | // Write chunk size. 88 | long ChunkEndPosition = writer.BaseStream.Position; 89 | uint ChunkSize = (uint)(ChunkEndPosition - HeaderSizePosition); 90 | writer.BaseStream.Position = HeaderSizePosition - 4; 91 | writer.Write(ChunkSize); 92 | writer.BaseStream.Position = ChunkEndPosition; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Marathon/Formats/Mesh/Ninja/NinjaSubMotion.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Mesh.Ninja 2 | { 3 | /// 4 | /// Structure of a Ninja Sub Motion entry. 5 | /// 6 | public class NinjaSubMotion 7 | { 8 | public SubMotionType Type { get; set; } 9 | 10 | public SubMotionInterpolationType InterpolationType { get; set; } 11 | 12 | public int NodeIndex { get; set; } 13 | 14 | public float StartFrame { get; set; } 15 | 16 | public float EndFrame { get; set; } 17 | 18 | public float StartKeyframe { get; set; } 19 | 20 | public float EndKeyframe { get; set; } 21 | 22 | public List Keyframes { get; set; } = new(); 23 | 24 | /// 25 | /// Reads a Ninja Sub Motion entry from a file. 26 | /// 27 | /// The binary reader for this SegaNN file. 28 | public void Read(BinaryReaderEx reader) 29 | { 30 | // Read the main data for this Sub Motion. 31 | Type = (SubMotionType)reader.ReadUInt32(); 32 | InterpolationType = (SubMotionInterpolationType)reader.ReadUInt32(); 33 | NodeIndex = reader.ReadInt32(); 34 | StartFrame = reader.ReadSingle(); 35 | EndFrame = reader.ReadSingle(); 36 | StartKeyframe = reader.ReadSingle(); 37 | EndKeyframe = reader.ReadSingle(); 38 | uint KeyFrameCount = reader.ReadUInt32(); 39 | uint KeyFrameSize = reader.ReadUInt32(); 40 | uint KeyFrameOffset = reader.ReadUInt32(); 41 | 42 | // Save our current position so we can jump back afterwards. 43 | long pos = reader.BaseStream.Position; 44 | 45 | // Jump to the list of Keyframes for this sub motion. 46 | reader.JumpTo(KeyFrameOffset, true); 47 | 48 | // Loop through and read the keyframes based on the Type flag. 49 | for (int i = 0; i < KeyFrameCount; i++) 50 | { 51 | if 52 | ( 53 | Type.HasFlag(SubMotionType.NND_SMOTTYPE_TRANSLATION_MASK) || 54 | Type.HasFlag(SubMotionType.NND_SMOTTYPE_SCALING_MASK) || 55 | Type.HasFlag(SubMotionType.NND_SMOTTYPE_AMBIENT_MASK) || 56 | Type.HasFlag(SubMotionType.NND_SMOTTYPE_DIFFUSE_MASK) || 57 | Type.HasFlag(SubMotionType.NND_SMOTTYPE_SPECULAR_MASK) || 58 | Type.HasFlag(SubMotionType.NND_SMOTTYPE_LIGHT_COLOR_MASK) 59 | ) 60 | { 61 | NinjaKeyframe.NNS_MOTION_KEY_VECTOR Keyframe = new(); 62 | Keyframe.Read(reader); 63 | Keyframes.Add(Keyframe); 64 | } 65 | else if (Type.HasFlag(SubMotionType.NND_SMOTTYPE_ROTATION_XYZ)) 66 | { 67 | NinjaKeyframe.NNS_MOTION_KEY_ROTATE_A16 Keyframe = new(); 68 | Keyframe.Read(reader); 69 | Keyframes.Add(Keyframe); 70 | } 71 | 72 | /* (Knuxfan24): Generic Handling, these could go tits up. */ 73 | 74 | else if (Type.HasFlag(SubMotionType.NND_SMOTTYPE_FRAME_FLOAT) && KeyFrameSize == 8) 75 | { 76 | NinjaKeyframe.NNS_MOTION_KEY_FLOAT Keyframe = new(); 77 | Keyframe.Read(reader); 78 | Keyframes.Add(Keyframe); 79 | } 80 | else if(Type.HasFlag(SubMotionType.NND_SMOTTYPE_FRAME_SINT16) && KeyFrameSize == 4) 81 | { 82 | NinjaKeyframe.NNS_MOTION_KEY_SINT16 Keyframe = new(); 83 | Keyframe.Read(reader); 84 | Keyframes.Add(Keyframe); 85 | } 86 | else 87 | { 88 | // All else has failed, give up. 89 | throw new NotImplementedException(); 90 | } 91 | } 92 | 93 | // Jump back to where we were. 94 | reader.JumpTo(pos); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Marathon/Formats/Package/PathPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Marathon.Formats.Package 4 | { 5 | /// 6 | /// File base for the PathObj.bin format. 7 | /// Used in SONIC THE HEDGEHOG for defining valid objects for common_path_obj. 8 | /// 9 | public class PathPackage : FileBase 10 | { 11 | public PathPackage() { } 12 | 13 | public PathPackage(string file, bool serialise = false) 14 | { 15 | switch (Path.GetExtension(file)) 16 | { 17 | case ".json": 18 | { 19 | PathObjects = JsonDeserialise>(file); 20 | 21 | // Save extension-less JSON (exploiting .NET weirdness, because it doesn't omit all extensions). 22 | if (serialise) 23 | Save(Path.GetFileNameWithoutExtension(file)); 24 | 25 | break; 26 | } 27 | 28 | default: 29 | { 30 | Load(file); 31 | 32 | if (serialise) 33 | JsonSerialise(PathObjects); 34 | 35 | break; 36 | } 37 | } 38 | } 39 | 40 | public override string Extension { get; } = ".bin"; 41 | 42 | public List PathObjects { get; set; } = []; 43 | 44 | public override void Load(Stream stream) 45 | { 46 | BINAReader reader = new(stream); 47 | 48 | // Read the first entry's name to get the string table's position. 49 | uint stringTablePosition = reader.ReadUInt32(); 50 | 51 | reader.JumpBehind(4); 52 | 53 | // Loop through and read entries. 54 | while (reader.BaseStream.Position < (stringTablePosition - 4) + 0x20) 55 | { 56 | var entry = new PathObject(); 57 | 58 | // Read string offsets. 59 | uint nameOffset = reader.ReadUInt32(); 60 | uint modelOffset = reader.ReadUInt32(); 61 | uint animOffset = reader.ReadUInt32(); 62 | uint textOffset = reader.ReadUInt32(); 63 | uint matAnimOffset = reader.ReadUInt32(); 64 | 65 | long position = reader.BaseStream.Position; 66 | 67 | if (nameOffset != 0) 68 | entry.Name = reader.ReadNullTerminatedString(false, nameOffset, true); 69 | 70 | if (modelOffset != 0) 71 | entry.Model = reader.ReadNullTerminatedString(false, modelOffset, true); 72 | 73 | if (animOffset != 0) 74 | entry.Animation = reader.ReadNullTerminatedString(false, animOffset, true); 75 | 76 | if (textOffset != 0) 77 | entry.Text = reader.ReadNullTerminatedString(false, textOffset, true); 78 | 79 | if (matAnimOffset != 0) 80 | entry.MaterialAnimation = reader.ReadNullTerminatedString(false, matAnimOffset, true); 81 | 82 | PathObjects.Add(entry); 83 | 84 | reader.JumpTo(position); 85 | } 86 | } 87 | 88 | public override void Save(Stream stream) 89 | { 90 | BINAWriter writer = new(stream); 91 | 92 | for (int i = 0; i < PathObjects.Count; i++) 93 | { 94 | var pathObj = PathObjects[i]; 95 | 96 | writer.AddString($"Name{i}", pathObj.Name); 97 | writer.AddString($"Model{i}", pathObj.Model); 98 | writer.AddString($"Animation{i}", pathObj.Animation); 99 | writer.AddString($"Text{i}", pathObj.Text); 100 | writer.AddString($"MaterialAnimation{i}", pathObj.MaterialAnimation); 101 | } 102 | 103 | writer.WriteNulls(4); 104 | writer.FinishWrite(); 105 | } 106 | } 107 | 108 | public class PathObject 109 | { 110 | /// 111 | /// Name of this object. 112 | /// 113 | public string Name { get; set; } 114 | 115 | /// 116 | /// Model this object should use. 117 | /// 118 | public string Model { get; set; } 119 | 120 | /// 121 | /// Animation this object should use. 122 | /// 123 | public string Animation { get; set; } 124 | 125 | /// 126 | /// TODO: Unknown. 127 | /// 128 | public string Text { get; set; } 129 | 130 | /// 131 | /// Material animation this object should use. 132 | /// 133 | public string MaterialAnimation { get; set; } 134 | 135 | public PathObject() { } 136 | 137 | public PathObject(string in_name, string in_model, string in_animation, string in_text, string in_materialAnimation) 138 | { 139 | Name = in_name; 140 | Model = in_model; 141 | Animation = in_animation; 142 | Text = in_text; 143 | MaterialAnimation = in_materialAnimation; 144 | } 145 | 146 | public override string ToString() => Name; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Marathon/Formats/Particle/ParticleTextureBank.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Particle 2 | { 3 | /// 4 | /// File base for the *.ptb format. 5 | /// Used in SONIC THE HEDGEHOG for defining textures for particle effects. 6 | /// 7 | public class ParticleTextureBank : FileBase 8 | { 9 | public ParticleTextureBank() { } 10 | 11 | public ParticleTextureBank(string file, bool serialise = false) 12 | { 13 | switch (Path.GetExtension(file)) 14 | { 15 | case ".json": 16 | { 17 | Data = JsonDeserialise(file); 18 | 19 | // Save extension-less JSON (exploiting .NET weirdness, because it doesn't omit all extensions). 20 | if (serialise) 21 | Save(Path.GetFileNameWithoutExtension(file)); 22 | 23 | break; 24 | } 25 | 26 | default: 27 | { 28 | Load(file); 29 | 30 | if (serialise) 31 | JsonSerialise(Data); 32 | 33 | break; 34 | } 35 | } 36 | } 37 | 38 | public override string Signature { get; } = "BTEP"; 39 | 40 | public override string Extension { get; } = ".ptb"; 41 | 42 | public class FormatData 43 | { 44 | public string Name { get; set; } 45 | 46 | public List ParticleTextures { get; set; } = []; 47 | 48 | public override string ToString() => Name; 49 | } 50 | 51 | public FormatData Data { get; set; } = new(); 52 | 53 | public override void Load(Stream stream) 54 | { 55 | BINAReader reader = new(stream); 56 | 57 | reader.ReadSignature(4, Signature); 58 | reader.JumpAhead(0x8); // Always Null. 59 | uint EntryCount = reader.ReadUInt32(); 60 | Data.Name = reader.ReadNullPaddedString(0x20); 61 | uint OffsetTable = reader.ReadUInt32(); 62 | 63 | reader.JumpTo(OffsetTable, true); // Should already be here but just to be safe. 64 | 65 | // Particle Texture Entries. 66 | for (int i = 0; i < EntryCount; i++) 67 | { 68 | ParticleTexture particle = new() 69 | { 70 | Name = reader.ReadNullPaddedString(0x20), 71 | Path = reader.ReadNullPaddedString(0x80), 72 | Width = reader.ReadUInt32(), 73 | Height = reader.ReadUInt32() 74 | }; 75 | 76 | // Save particle texture entry into the ParticleTextures list. 77 | Data.ParticleTextures.Add(particle); 78 | } 79 | } 80 | 81 | public override void Save(Stream stream) 82 | { 83 | BINAWriter writer = new(stream); 84 | 85 | // Header 86 | writer.WriteSignature(Signature); 87 | writer.WriteNulls(0x8); 88 | writer.Write(Data.ParticleTextures.Count); 89 | writer.WriteNullPaddedString(Data.Name, 0x20); 90 | writer.AddOffset("OffsetTable"); 91 | 92 | writer.FillOffset("OffsetTable", true); 93 | 94 | // Particle Texture Entries 95 | for (int i = 0; i < Data.ParticleTextures.Count; i++) 96 | { 97 | writer.WriteNullPaddedString(Data.ParticleTextures[i].Name, 0x20); 98 | writer.WriteNullPaddedString(Data.ParticleTextures[i].Path, 0x80); 99 | writer.Write(Data.ParticleTextures[i].Width); 100 | writer.Write(Data.ParticleTextures[i].Height); 101 | } 102 | 103 | // Write the footer. 104 | writer.FinishWrite(); 105 | } 106 | } 107 | 108 | public class ParticleTexture 109 | { 110 | /// 111 | /// Name of this particle texture. 112 | /// 113 | public string Name { get; set; } 114 | 115 | /// 116 | /// Path to the texture used by this particle. 117 | /// 118 | public string Path { get; set; } 119 | 120 | public uint Width { get; set; } 121 | 122 | public uint Height { get; set; } 123 | 124 | public ParticleTexture() { } 125 | 126 | public ParticleTexture(string in_name, string in_path, uint in_width, uint in_height) 127 | { 128 | Name = in_name; 129 | Path = in_path; 130 | Width = in_width; 131 | Height = in_height; 132 | } 133 | 134 | public override string ToString() => Name; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Marathon/Formats/Placement/SetDataType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Placement 2 | { 3 | [JsonConverter(typeof(StringEnumConverter))] 4 | public enum SetDataType 5 | { 6 | Boolean, 7 | Int32, 8 | Single, 9 | String, 10 | Vector3, 11 | UInt32 = 6 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Marathon/Formats/Save/SonicNextEpisode.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Save 2 | { 3 | public class SonicNextEpisode 4 | { 5 | /// 6 | /// The number of total lives pertaining to this episode. 7 | /// 8 | public int Lives { get; set; } 9 | 10 | /// 11 | /// The number of total rings pertaining to this episode. 12 | /// 13 | public int Rings { get; set; } 14 | 15 | /// 16 | /// The path to this mission's Lua script. 17 | /// 18 | public string Lua { get; set; } 19 | 20 | /// 21 | /// The name of the MST entry for the loading screen text. 22 | /// 23 | public string Objective { get; set; } 24 | 25 | /// 26 | /// This mission's area code defined in game.lub. 27 | /// 28 | public string Area { get; set; } 29 | 30 | /// 31 | /// The path to this mission's terrain data. 32 | /// 33 | public string Terrain { get; set; } 34 | 35 | /// 36 | /// The path to this mission's SET data. 37 | /// 38 | public string SET { get; set; } 39 | 40 | /// 41 | /// The path to this mission's PATH data. 42 | /// 43 | public string PATH { get; set; } 44 | 45 | /// 46 | /// The path to this mission's MST data containing . 47 | /// 48 | public string MST { get; set; } 49 | 50 | /// 51 | /// The percent of story completion for this episode. 52 | /// 53 | public int Progress { get; set; } 54 | 55 | /// 56 | /// The year this save data was created on. 57 | /// 58 | public short Year { get; set; } 59 | 60 | /// 61 | /// The month of the year this save data was created on. 62 | /// 63 | public sbyte Month { get; set; } 64 | 65 | /// 66 | /// The day of the month this save data was created on. 67 | /// 68 | public sbyte Day { get; set; } 69 | 70 | /// 71 | /// The hour of the day this save data was created on. 72 | /// 73 | public sbyte Hour { get; set; } 74 | 75 | /// 76 | /// The minute of the hour this save data was created on. 77 | /// 78 | public sbyte Minute { get; set; } 79 | 80 | /// 81 | /// The name of the MST entry for the location text. 82 | /// 83 | public string Location { get; set; } 84 | 85 | public override string ToString() => Lua; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Marathon/Formats/Save/SonicNextOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Save 2 | { 3 | public class SonicNextOptions 4 | { 5 | /// 6 | /// Determines whether or not subtitles are enabled. 7 | /// 8 | public bool Subtitles { get; set; } 9 | 10 | /// 11 | /// The volume of the music. 12 | /// 13 | public float Music { get; set; } 14 | 15 | /// 16 | /// The volume of the sound effects. 17 | /// 18 | public float Effects { get; set; } 19 | 20 | public SonicNextOptions() { } 21 | 22 | public SonicNextOptions(bool in_isSubtitles, float in_music, float in_effects) 23 | { 24 | Subtitles = in_isSubtitles; 25 | Music = in_music; 26 | Effects = in_effects; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Marathon/Formats/Save/SonicNextRank.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Save 2 | { 3 | public enum SonicNextRank : int 4 | { 5 | S, 6 | A, 7 | B, 8 | C, 9 | D 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Marathon/Formats/Save/SonicNextTrial.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Save 2 | { 3 | public class SonicNextTrial 4 | { 5 | /// 6 | /// The ID for this trial set in the Lua score table. 7 | /// 8 | public int ID { get; set; } 9 | 10 | /// 11 | /// The rank rewarded for this trial. 12 | /// 13 | public SonicNextRank Rank { get; set; } 14 | 15 | /// 16 | /// The time taken to complete this trial. 17 | /// 18 | public int Time { get; set; } 19 | 20 | /// 21 | /// The total score achieved in this trial. 22 | /// 23 | public int Score { get; set; } 24 | 25 | /// 26 | /// The total rings collected in this trial. 27 | /// 28 | public int Rings { get; set; } 29 | 30 | public SonicNextTrial() { } 31 | 32 | public SonicNextTrial(int in_id, SonicNextRank in_rank, int in_time, int in_score, int in_rings) 33 | { 34 | ID = in_id; 35 | Rank = in_rank; 36 | Time = in_time; 37 | Score = in_score; 38 | Rings = in_rings; 39 | } 40 | 41 | public override string ToString() => ID == -1 ? "Incomplete" : ID.ToString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/IndentationType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script 2 | { 3 | public enum IndentationType 4 | { 5 | Spaces, 6 | Tabs 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/AlwaysLoop.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 5 | { 6 | public class AlwaysLoop : Block 7 | { 8 | private readonly List _statements; 9 | 10 | public AlwaysLoop(LFunction function, int begin, int end) : base(function, begin, end) => _statements = new List(); 11 | 12 | public override int ScopeEnd() => End - 2; 13 | 14 | public override bool Breakable() => true; 15 | 16 | public override bool IsContainer() => true; 17 | 18 | public override bool IsUnprotected() => true; 19 | 20 | public override int GetLoopback() => Begin; 21 | 22 | public override void Write(Output @out) 23 | { 24 | @out.WriteLine("while true do"); 25 | @out.Indent(); 26 | 27 | WriteSequence(@out, _statements); 28 | 29 | @out.Dedent(); 30 | @out.Write("end"); 31 | } 32 | 33 | public override void AddStatement(Statement statement) 34 | => _statements.Add(statement); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/Block.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | using Marathon.Formats.Script.Lua.Decompiler.Operations; 4 | 5 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 6 | { 7 | public abstract class Block : Statement, IComparable 8 | { 9 | protected readonly LFunction _function; 10 | 11 | public int Begin, End; 12 | public bool LoopRedirectAdjustment = false; 13 | 14 | public Block(LFunction function, int begin, int end) 15 | { 16 | _function = function; 17 | Begin = begin; 18 | End = end; 19 | } 20 | 21 | public abstract void AddStatement(Statement statement); 22 | 23 | public bool Contains(Block block) => Begin <= block.Begin && End >= block.End; 24 | 25 | public bool Contains(int line) => Begin <= line && line < End; 26 | 27 | public virtual int ScopeEnd() => End - 1; 28 | 29 | /// 30 | /// An unprotected block is one that ends in a JMP instruction. 31 | /// If this is the case, any inner statement that tries to jump to the end of this block will be redirected. 32 | /// One of the Lua compiler's few optimizations is that is changes any JMP that targets another JMP to the ultimate target. 33 | /// This is what I call redirection. 34 | /// 35 | public abstract bool IsUnprotected(); 36 | 37 | public abstract int GetLoopback(); 38 | 39 | public abstract bool Breakable(); 40 | 41 | public abstract bool IsContainer(); 42 | 43 | public int CompareTo(Block block) 44 | { 45 | if (Begin < block.Begin) 46 | { 47 | return -1; 48 | } 49 | else if (Begin == block.Begin) 50 | { 51 | if (End < block.End) 52 | { 53 | return 1; 54 | } 55 | else if (End == block.End) 56 | { 57 | if (IsContainer() && !block.IsContainer()) 58 | { 59 | return -1; 60 | } 61 | else if (!IsContainer() && block.IsContainer()) 62 | { 63 | return 1; 64 | } 65 | else 66 | { 67 | return 0; 68 | } 69 | } 70 | else 71 | { 72 | return -1; 73 | } 74 | } 75 | else 76 | { 77 | return 1; 78 | } 79 | } 80 | 81 | public virtual Operation Process(Decompiler d) 82 | { 83 | Statement statement = this; 84 | 85 | return new OperationAnonymousInnerClass(this, statement); 86 | } 87 | 88 | private class OperationAnonymousInnerClass : Operation 89 | { 90 | private readonly Block _outerInstance; 91 | private Statement _statement; 92 | 93 | public OperationAnonymousInnerClass(Block outerInstance, Statement statement) : base(outerInstance.End - 1) 94 | { 95 | _outerInstance = outerInstance; 96 | _statement = statement; 97 | } 98 | 99 | public override Statement Process(Registers r, Block block) => _statement; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/BooleanIndicator.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 5 | { 6 | public class BooleanIndicator : Block 7 | { 8 | public BooleanIndicator(LFunction function, int line) : base(function, line, line) { } 9 | 10 | public override void AddStatement(Statement statement) { } 11 | 12 | public override bool IsContainer() => false; 13 | 14 | public override bool IsUnprotected() => false; 15 | 16 | public override bool Breakable() => false; 17 | 18 | public override int GetLoopback() => throw new Exception(); 19 | 20 | public override void Write(Output @out) 21 | => @out.Write("-- Unhandled boolean indicator..."); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/Break.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 5 | { 6 | public class Break : Block 7 | { 8 | public readonly int Target; 9 | 10 | public Break(LFunction function, int line, int target) : base(function, line, line) => Target = target; 11 | 12 | public override void AddStatement(Statement statement) 13 | => throw new Exception(); 14 | 15 | public override bool IsContainer() => false; 16 | 17 | public override bool Breakable() => false; 18 | 19 | /// 20 | /// This *is* unprotected, but isn't really a block. 21 | /// 22 | public override bool IsUnprotected() => false; 23 | 24 | public override int GetLoopback() => throw new Exception(); 25 | 26 | public override void Write(Output @out) 27 | => @out.Write("do break end"); 28 | 29 | public override void WriteTail(Output @out) 30 | => @out.Write("break"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/CompareBlock.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Branches; 3 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 4 | using Marathon.Formats.Script.Lua.Decompiler.Operations; 5 | 6 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 7 | { 8 | public class CompareBlock : Block 9 | { 10 | public int Target; 11 | public Branch Branch; 12 | 13 | public CompareBlock(LFunction function, int begin, int end, int target, Branch branch) : base(function, begin, end) 14 | { 15 | Target = target; 16 | Branch = branch; 17 | } 18 | 19 | public override bool IsContainer() => false; 20 | 21 | public override bool Breakable() => false; 22 | 23 | public override void AddStatement(Statement statement) { } 24 | 25 | public override bool IsUnprotected() => false; 26 | 27 | public override int GetLoopback() => throw new Exception(); 28 | 29 | public override void Write(Output @out) 30 | => @out.Write("-- Unhandled compare assign..."); 31 | 32 | public override Operation Process(Decompiler d) => new OperationAnonymousInnerClass(this); 33 | 34 | private class OperationAnonymousInnerClass : Operation 35 | { 36 | private readonly CompareBlock _outerInstance; 37 | 38 | public OperationAnonymousInnerClass(CompareBlock outerInstance) : base(outerInstance.End - 1) 39 | => _outerInstance = outerInstance; 40 | 41 | public override Statement Process(Registers r, Block block) 42 | => new RegisterSet(_outerInstance.End - 1, _outerInstance.Target, _outerInstance.Branch.AsExpression(r)).Process(r, block); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/DoEndBlock.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 5 | { 6 | public class DoEndBlock : Block 7 | { 8 | private readonly List _statements; 9 | 10 | public DoEndBlock(LFunction function, int begin, int end) : base(function, begin, end) => _statements = new List(end - begin + 1); 11 | 12 | public override void AddStatement(Statement statement) => _statements.Add(statement); 13 | 14 | public override bool Breakable() => false; 15 | 16 | public override bool IsContainer() => true; 17 | 18 | public override bool IsUnprotected() => false; 19 | 20 | public override int GetLoopback() => throw new Exception(); 21 | 22 | public override void Write(Output @out) 23 | { 24 | @out.WriteLine("do"); 25 | @out.Indent(); 26 | 27 | WriteSequence(@out, _statements); 28 | 29 | @out.Dedent(); 30 | @out.Write("end"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/ElseEndBlock.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 5 | { 6 | public class ElseEndBlock : Block, IComparable 7 | { 8 | private readonly List _statements; 9 | public IfThenElseBlock Partner; 10 | 11 | public ElseEndBlock(LFunction function, int begin, int end) : base(function, begin, end) => _statements = new List(end - begin + 1); 12 | 13 | public new int CompareTo(Block block) 14 | { 15 | if (block == Partner) 16 | { 17 | return 1; 18 | } 19 | else 20 | { 21 | return base.CompareTo(block); 22 | } 23 | } 24 | 25 | public override bool Breakable() => false; 26 | 27 | public override bool IsContainer() => true; 28 | 29 | public override void AddStatement(Statement statement) 30 | => _statements.Add(statement); 31 | 32 | public override bool IsUnprotected() => false; 33 | 34 | public override int GetLoopback() => throw new Exception(); 35 | 36 | public override void Write(Output @out) 37 | { 38 | if (_statements.Count == 1 && _statements[0] is IfThenEndBlock) 39 | { 40 | @out.Write("else"); 41 | 42 | _statements[0].Write(@out); 43 | } 44 | else if (_statements.Count == 2 && _statements[0] is IfThenElseBlock && _statements[1] is ElseEndBlock) 45 | { 46 | @out.Write("else"); 47 | 48 | _statements[0].Write(@out); 49 | _statements[1].Write(@out); 50 | } 51 | else 52 | { 53 | @out.Write("else"); 54 | @out.WriteLine(); 55 | @out.Indent(); 56 | 57 | WriteSequence(@out, _statements); 58 | 59 | @out.Dedent(); 60 | @out.Write("end"); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/ForBlock.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 4 | 5 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 6 | { 7 | public class ForBlock : Block 8 | { 9 | private readonly int _register; 10 | private readonly Registers _r; 11 | private readonly List _statements; 12 | 13 | public ForBlock(LFunction function, int begin, int end, int register, Registers r) : base(function, begin, end) 14 | { 15 | _register = register; 16 | _r = r; 17 | _statements = new List(end - begin + 1); 18 | } 19 | 20 | public override int ScopeEnd() => End - 2; 21 | 22 | public override void AddStatement(Statement statement) 23 | => _statements.Add(statement); 24 | 25 | public override bool Breakable() => true; 26 | 27 | public override bool IsContainer() => true; 28 | 29 | public override bool IsUnprotected() => false; 30 | 31 | public override int GetLoopback() => throw new Exception(); 32 | 33 | public override void Write(Output @out) 34 | { 35 | @out.Write("for "); 36 | 37 | if (_function.Header.Version == Version.LUA50) 38 | { 39 | _r.GetTarget(_register, Begin - 1).Write(@out); 40 | } 41 | else 42 | { 43 | _r.GetTarget(_register + 3, Begin - 1).Write(@out); 44 | } 45 | 46 | @out.Write(" = "); 47 | 48 | if (_function.Header.Version == Version.LUA50) 49 | { 50 | _r.GetValue(_register, Begin - 2).Write(@out); 51 | } 52 | else 53 | { 54 | _r.GetValue(_register, Begin - 1).Write(@out); 55 | } 56 | 57 | @out.Write(", "); 58 | 59 | _r.GetValue(_register + 1, Begin - 1).Write(@out); 60 | 61 | Expression step = _r.GetValue(_register + 2, Begin - 1); 62 | 63 | if (!step.IsInteger() || step.AsInteger() != 1) 64 | { 65 | @out.Write(", "); 66 | step.Write(@out); 67 | } 68 | 69 | @out.Write(" do"); 70 | @out.WriteLine(); 71 | @out.Indent(); 72 | 73 | WriteSequence(@out, _statements); 74 | 75 | @out.Dedent(); 76 | @out.Write("end"); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/IfThenElseBlock.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Branches; 3 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 4 | 5 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 6 | { 7 | public class IfThenElseBlock : Block, IComparable 8 | { 9 | private readonly Branch _branch; 10 | private readonly int _loopback; 11 | private readonly Registers _r; 12 | private readonly List _statements; 13 | private readonly bool _emptyElse; 14 | public ElseEndBlock Partner; 15 | 16 | public IfThenElseBlock(LFunction function, Branch branch, int loopback, bool emptyElse, Registers r) : base(function, branch.Begin, branch.End) 17 | { 18 | _branch = branch; 19 | _loopback = loopback; 20 | _emptyElse = emptyElse; 21 | _r = r; 22 | _statements = new List(branch.End - branch.Begin + 1); 23 | } 24 | 25 | public new int CompareTo(Block block) 26 | { 27 | if (block == Partner) 28 | { 29 | return -1; 30 | } 31 | 32 | return base.CompareTo(block); 33 | } 34 | 35 | public override bool Breakable() => false; 36 | 37 | public override bool IsContainer() => true; 38 | 39 | public override void AddStatement(Statement statement) 40 | => _statements.Add(statement); 41 | 42 | public override int ScopeEnd() => End - 2; 43 | 44 | public override bool IsUnprotected() => true; 45 | 46 | public override int GetLoopback() => _loopback; 47 | 48 | public override void Write(Output @out) 49 | { 50 | @out.Write("if "); 51 | 52 | _branch.AsExpression(_r).Write(@out); 53 | 54 | @out.Write(" then"); 55 | @out.WriteLine(); 56 | @out.Indent(); 57 | 58 | /* Handle the case where the "then" is empty in if-then-else. 59 | The jump over the else block is falsely detected as a break. */ 60 | if (_statements.Count == 1 && _statements[0] is Break @break) 61 | { 62 | if (@break.Target == _loopback) 63 | { 64 | @out.Dedent(); 65 | return; 66 | } 67 | } 68 | 69 | WriteSequence(@out, _statements); 70 | 71 | @out.Dedent(); 72 | 73 | if (_emptyElse) 74 | { 75 | @out.WriteLine("else"); 76 | @out.WriteLine("end"); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/OuterBlock.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 5 | { 6 | public class OuterBlock : Block 7 | { 8 | private readonly List _statements; 9 | 10 | public OuterBlock(LFunction function, int length) : base(function, 0, length + 1) => _statements = new List(length); 11 | 12 | public override void AddStatement(Statement statement) 13 | => _statements.Add(statement); 14 | 15 | public override bool Breakable() => false; 16 | 17 | public override bool IsContainer() => true; 18 | 19 | public override bool IsUnprotected() => false; 20 | 21 | public override int GetLoopback() => throw new Exception(); 22 | 23 | public override int ScopeEnd() => (End - 1) + _function.Header.Version.GetOuterBlockScopeAdjustment(); 24 | 25 | public override void Write(Output @out) 26 | { 27 | // Extra return statement. 28 | int last = _statements.Count - 1; 29 | 30 | if (last < 0 || _statements[last] is not Return) 31 | throw new Exception(_statements[last].ToString()); 32 | 33 | _statements.RemoveAt(last); 34 | 35 | WriteSequence(@out, _statements); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/RepeatBlock.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Branches; 3 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 4 | 5 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 6 | { 7 | public class RepeatBlock : Block 8 | { 9 | private readonly Branch _branch; 10 | private readonly Registers _r; 11 | private readonly List _statements; 12 | 13 | public RepeatBlock(LFunction function, Branch branch, Registers r) : base(function, branch.End, branch.Begin) 14 | { 15 | _branch = branch; 16 | _r = r; 17 | _statements = new List(branch.Begin - branch.End + 1); 18 | } 19 | 20 | public override bool Breakable() => true; 21 | 22 | public override bool IsContainer() => true; 23 | 24 | public override void AddStatement(Statement statement) 25 | => _statements.Add(statement); 26 | 27 | public override bool IsUnprotected() => false; 28 | 29 | public override int GetLoopback() => throw new Exception(); 30 | 31 | public override void Write(Output @out) 32 | { 33 | @out.Write("repeat"); 34 | @out.WriteLine(); 35 | @out.Indent(); 36 | 37 | WriteSequence(@out, _statements); 38 | 39 | @out.Dedent(); 40 | @out.Write("until "); 41 | 42 | _branch.AsExpression(_r).Write(@out); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/TForBlock.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 4 | 5 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 6 | { 7 | public class TForBlock : Block 8 | { 9 | private readonly int _register, _length; 10 | private readonly Registers _r; 11 | private readonly List _statements; 12 | 13 | public TForBlock(LFunction function, int begin, int end, int register, int length, Registers r) : base(function, begin, end) 14 | { 15 | _register = register; 16 | _length = length; 17 | _r = r; 18 | _statements = new List(end - begin + 1); 19 | } 20 | 21 | public override int ScopeEnd() => End - 3; 22 | 23 | public override bool Breakable() => true; 24 | 25 | public override bool IsContainer() => true; 26 | 27 | public override void AddStatement(Statement statement) 28 | => _statements.Add(statement); 29 | 30 | public override bool IsUnprotected() => false; 31 | 32 | public override int GetLoopback() => throw new Exception(); 33 | 34 | public override void Write(Output @out) 35 | { 36 | @out.Write("for "); 37 | 38 | if (_function.Header.Version == Version.LUA50) 39 | { 40 | _r.GetTarget(_register + 2, Begin - 1).Write(@out); 41 | 42 | for (int r1 = _register + 3; r1 <= _register + 2 + _length; r1++) 43 | { 44 | @out.Write(", "); 45 | 46 | _r.GetTarget(r1, Begin - 1).Write(@out); 47 | } 48 | } 49 | else 50 | { 51 | _r.GetTarget(_register + 3, Begin - 1).Write(@out); 52 | 53 | for (int r1 = _register + 4; r1 <= _register + 2 + _length; r1++) 54 | { 55 | @out.Write(", "); 56 | 57 | _r.GetTarget(r1, Begin - 1).Write(@out); 58 | } 59 | } 60 | 61 | @out.Write(" in "); 62 | 63 | Expression value; 64 | value = _r.GetValue(_register, Begin - 1); 65 | value.Write(@out); 66 | 67 | if (!value.IsMultiple()) 68 | { 69 | @out.Write(", "); 70 | 71 | value = _r.GetValue(_register + 1, Begin - 1); 72 | value.Write(@out); 73 | 74 | if (!value.IsMultiple()) 75 | { 76 | @out.Write(", "); 77 | 78 | value = _r.GetValue(_register + 2, Begin - 1); 79 | value.Write(@out); 80 | } 81 | } 82 | 83 | @out.Write(" do"); 84 | @out.WriteLine(); 85 | @out.Indent(); 86 | 87 | WriteSequence(@out, _statements); 88 | 89 | @out.Dedent(); 90 | @out.Write("end"); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Blocks/WhileBlock.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Branches; 3 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 4 | 5 | namespace Marathon.Formats.Script.Lua.Decompiler.Blocks 6 | { 7 | public class WhileBlock : Block 8 | { 9 | private readonly Branch _branch; 10 | private readonly int _loopback; 11 | private readonly Registers _r; 12 | private readonly List _statements; 13 | 14 | public WhileBlock(LFunction function, Branch branch, int loopback, Registers r) : base(function, branch.Begin, branch.End) 15 | { 16 | _branch = branch; 17 | _loopback = loopback; 18 | _r = r; 19 | _statements = new List(branch.End - branch.Begin + 1); 20 | } 21 | 22 | public override int ScopeEnd() => End - 2; 23 | 24 | public override bool Breakable() => true; 25 | 26 | public override bool IsContainer() => true; 27 | 28 | public override void AddStatement(Statement statement) 29 | => _statements.Add(statement); 30 | 31 | public override bool IsUnprotected() => true; 32 | 33 | public override int GetLoopback() => _loopback; 34 | 35 | public override void Write(Output @out) 36 | { 37 | @out.Write("while "); 38 | 39 | _branch.AsExpression(_r).Write(@out); 40 | 41 | @out.Write(" do"); 42 | @out.WriteLine(); 43 | @out.Indent(); 44 | 45 | WriteSequence(@out, _statements); 46 | 47 | @out.Dedent(); 48 | @out.Write("end"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/AndBranch.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | public class AndBranch : Branch 6 | { 7 | private readonly Branch _left, _right; 8 | 9 | public AndBranch(Branch left, Branch right) : base(right.Line, right.Begin, right.End) 10 | { 11 | _left = left; 12 | _right = right; 13 | } 14 | 15 | public override Branch Invert() => new OrBranch(_left.Invert(), _right.Invert()); 16 | 17 | public override int GetRegister() 18 | { 19 | int rleft = _left.GetRegister(), 20 | rright = _right.GetRegister(); 21 | 22 | return rleft == rright ? rleft : -1; 23 | } 24 | 25 | public override Expression AsExpression(Registers r) 26 | => new BinaryExpression("and", _left.AsExpression(r), _right.AsExpression(r), Precedence.AND, Associativity.NONE); 27 | 28 | public override void UseExpression(Expression expression) 29 | { 30 | _left.UseExpression(expression); 31 | _right.UseExpression(expression); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/AssignNode.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | public class AssignNode : Branch 6 | { 7 | private Expression _expression; 8 | 9 | public AssignNode(int line, int begin, int end) : base(line, begin, end) { } 10 | 11 | public override Branch Invert() => throw new Exception(); 12 | 13 | public override int GetRegister() => throw new Exception(); 14 | 15 | public override Expression AsExpression(Registers r) => _expression; 16 | 17 | public override void UseExpression(Expression expression) 18 | => _expression = expression; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/Branch.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | public abstract class Branch 6 | { 7 | public readonly int Line; 8 | 9 | public int Begin, 10 | End, 11 | SetTarget = -1; 12 | 13 | public bool IsSet = false, 14 | IsCompareSet = false, 15 | IsTest = false; 16 | 17 | public Branch(int line, int begin, int end) 18 | { 19 | Line = line; 20 | Begin = begin; 21 | End = end; 22 | } 23 | 24 | public abstract Branch Invert(); 25 | 26 | public abstract int GetRegister(); 27 | 28 | public abstract Expression AsExpression(Registers r); 29 | 30 | public abstract void UseExpression(Expression expression); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/EQNode.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | public class EQNode : Branch 6 | { 7 | private readonly int _left, _right; 8 | private readonly bool _invert; 9 | 10 | public EQNode(int left, int right, bool invert, int line, int begin, int end) : base(line, begin, end) 11 | { 12 | _left = left; 13 | _right = right; 14 | _invert = invert; 15 | } 16 | 17 | public override Branch Invert() => new EQNode(_left, _right, !_invert, Line, End, Begin); 18 | 19 | public override int GetRegister() => -1; 20 | 21 | public override Expression AsExpression(Registers r) 22 | { 23 | bool transpose = false; 24 | 25 | return new BinaryExpression 26 | ( 27 | _invert ? "~=" : "==", 28 | r.GetConstantExpression(!transpose ? _left : _right, Line), 29 | r.GetConstantExpression(!transpose ? _right : _left, Line), 30 | Precedence.COMPARE, 31 | Associativity.LEFT 32 | ); 33 | } 34 | 35 | public override void UseExpression(Expression expression) { } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/LENode.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | class LENode : Branch 6 | { 7 | private readonly int _left, _right; 8 | private readonly bool _invert; 9 | 10 | public LENode(int left, int right, bool invert, int line, int begin, int end) : base(line, begin, end) 11 | { 12 | _left = left; 13 | _right = right; 14 | _invert = invert; 15 | } 16 | 17 | public override Branch Invert() => new LENode(_left, _right, !_invert, Line, End, Begin); 18 | 19 | public override int GetRegister() => -1; 20 | 21 | public override Expression AsExpression(Registers r) 22 | { 23 | bool transpose = false; 24 | 25 | Expression leftExpression = r.GetConstantExpression(_left, Line), 26 | rightExpression = r.GetConstantExpression(_right, Line); 27 | 28 | if (!leftExpression.IsConstant() && !rightExpression.IsConstant()) 29 | { 30 | transpose = r.GetUpdated(_left, Line) > r.GetUpdated(_right, Line); 31 | } 32 | else 33 | { 34 | transpose = rightExpression.GetConstantIndex() < leftExpression.GetConstantIndex(); 35 | } 36 | 37 | Expression result = new BinaryExpression 38 | ( 39 | !transpose ? "<=" : ">=", 40 | !transpose ? leftExpression : rightExpression, 41 | !transpose ? rightExpression : leftExpression, 42 | Precedence.COMPARE, 43 | Associativity.LEFT 44 | ); 45 | 46 | if (_invert) 47 | result = new UnaryExpression("not ", result, Precedence.UNARY); 48 | 49 | return result; 50 | } 51 | 52 | public override void UseExpression(Expression expression) { } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/LTNode.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | public class LTNode : Branch 6 | { 7 | private readonly int _left, _right; 8 | private readonly bool _invert; 9 | 10 | public LTNode(int left, int right, bool invert, int line, int begin, int end) : base(line, begin, end) 11 | { 12 | _left = left; 13 | _right = right; 14 | _invert = invert; 15 | } 16 | 17 | public override Branch Invert() => new LTNode(_left, _right, !_invert, Line, End, Begin); 18 | 19 | public override int GetRegister() => -1; 20 | 21 | public override Expression AsExpression(Registers r) 22 | { 23 | bool transpose = false; 24 | 25 | Expression leftExpression = r.GetConstantExpression(_left, Line), 26 | rightExpression = r.GetConstantExpression(_right, Line); 27 | 28 | if (!leftExpression.IsConstant() && !rightExpression.IsConstant()) 29 | { 30 | transpose = r.GetUpdated(_left, Line) > r.GetUpdated(_right, Line); 31 | } 32 | else 33 | { 34 | transpose = rightExpression.GetConstantIndex() < leftExpression.GetConstantIndex(); 35 | } 36 | 37 | Expression result = new BinaryExpression 38 | ( 39 | !transpose ? "<" : ">", 40 | !transpose ? leftExpression : rightExpression, 41 | !transpose ? rightExpression : leftExpression, 42 | Precedence.COMPARE, 43 | Associativity.LEFT 44 | ); 45 | 46 | return result; 47 | } 48 | 49 | public override void UseExpression(Expression expression) { } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/NotBranch.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | public class NotBranch : Branch 6 | { 7 | private readonly Branch _branch; 8 | 9 | public NotBranch(Branch branch) : base(branch.Line, branch.Begin, branch.End) => _branch = branch; 10 | 11 | public override Branch Invert() => _branch; 12 | 13 | public override int GetRegister() => _branch.GetRegister(); 14 | 15 | public override Expression AsExpression(Registers r) => new UnaryExpression("not ", _branch.AsExpression(r), Precedence.UNARY); 16 | 17 | public override void UseExpression(Expression expression) { } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/OrBranch.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | public class OrBranch : Branch 6 | { 7 | private readonly Branch _left, _right; 8 | 9 | public OrBranch(Branch left, Branch right) : base(right.Line, right.Begin, right.End) 10 | { 11 | _left = left; 12 | _right = right; 13 | } 14 | 15 | public override Branch Invert() => new AndBranch(_left.Invert(), _right.Invert()); 16 | 17 | public override int GetRegister() 18 | { 19 | int rleft = _left.GetRegister(), 20 | rright = _right.GetRegister(); 21 | 22 | return rleft == rright ? rleft : -1; 23 | } 24 | 25 | public override Expression AsExpression(Registers r) => new BinaryExpression("or", _left.AsExpression(r), _right.AsExpression(r), Precedence.OR, Associativity.NONE); 26 | 27 | public override void UseExpression(Expression expression) 28 | { 29 | _left.UseExpression(expression); 30 | _right.UseExpression(expression); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/TestNode.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | public class TestNode : Branch 6 | { 7 | public readonly int Test; 8 | public readonly bool _Invert; 9 | 10 | public TestNode(int test, bool invert, int line, int begin, int end) : base(line, begin, end) 11 | { 12 | Test = test; 13 | _Invert = invert; 14 | IsTest = true; 15 | } 16 | 17 | public override Branch Invert() => new TestNode(Test, !_Invert, Line, End, Begin); 18 | 19 | public override int GetRegister() => Test; 20 | 21 | public override Expression AsExpression(Registers r) 22 | { 23 | if (_Invert) 24 | { 25 | return new NotBranch(Invert()).AsExpression(r); 26 | } 27 | else 28 | { 29 | return r.GetExpression(Test, Line); 30 | } 31 | } 32 | 33 | public override void UseExpression(Expression expression) { } 34 | 35 | public override string ToString() => $"TestNode[test={Test};invert={_Invert};line={Line};begin={Begin};end={End}]"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/TestSetNode.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 4 | { 5 | public class TestSetNode : Branch 6 | { 7 | public readonly int Test; 8 | public readonly bool _Invert; 9 | 10 | public TestSetNode(int target, int test, bool invert, int line, int begin, int end) : base(line, begin, end) 11 | { 12 | Test = test; 13 | _Invert = invert; 14 | SetTarget = target; 15 | } 16 | 17 | public override Branch Invert() => new TestSetNode(SetTarget, Test, !_Invert, Line, End, Begin); 18 | 19 | public override int GetRegister() => SetTarget; 20 | 21 | public override Expression AsExpression(Registers r) => r.GetExpression(Test, Line); 22 | 23 | public override void UseExpression(Expression expression) { } 24 | 25 | public override string ToString() => $"TestSetNode[target={SetTarget};test={Test};invert={_Invert};line={Line};begin={Begin};end={End}]"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Branches/TrueNode.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Branches 5 | { 6 | public class TrueNode : Branch 7 | { 8 | public readonly int Register; 9 | private readonly bool _invert; 10 | 11 | public TrueNode(int register, bool invert, int line, int begin, int end) : base(line, begin, end) 12 | { 13 | Register = register; 14 | _invert = invert; 15 | SetTarget = register; 16 | } 17 | 18 | public override Branch Invert() => new TrueNode(Register, !_invert, Line, End, Begin); 19 | 20 | public override int GetRegister() => Register; 21 | 22 | public override Expression AsExpression(Registers r) => new ConstantExpression(new Constant(_invert ? LBoolean.LTRUE : LBoolean.LFALSE), -1); 23 | 24 | public override void UseExpression(Expression expression) { } 25 | 26 | public override string ToString() => $"TrueNode[invert={_invert};line={Line};begin={Begin};end={End}]"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Code.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler 4 | { 5 | public class Code 6 | { 7 | private readonly ICodeExtract _extractor; 8 | private readonly OpcodeMap _map; 9 | private readonly int[] _code; 10 | 11 | public Code(LFunction function) 12 | { 13 | _code = function.Code; 14 | _map = function.Header.Version.GetOpcodeMap(); 15 | _extractor = function.Header.Extractor; 16 | } 17 | 18 | public virtual Op.Opcode Op(int line) => _map.Get(_code[line - 1] & 0x0000003F); 19 | 20 | public int A(int line) => _extractor.ExtractA(_code[line - 1]); 21 | 22 | public int B(int line) => _extractor.ExtractB(_code[line - 1]); 23 | 24 | public int Bx(int line) => _extractor.ExtractBx(_code[line - 1]); 25 | 26 | public int C(int line) => _extractor.ExtractC(_code[line - 1]); 27 | 28 | public int sBx(int line) => _extractor.ExtractsBx(_code[line - 1]); 29 | 30 | public int Codepoint(int line) => _code[line - 1]; 31 | } 32 | 33 | public class Code50 : ICodeExtract 34 | { 35 | private int _shiftA, 36 | _shiftC, 37 | _shiftB, 38 | _shiftBx, 39 | _maskOp, 40 | _maskA, 41 | _maskB, 42 | _maskBx, 43 | _maskC, 44 | _excessK; 45 | 46 | public Code50(int sizeOp, int sizeA, int sizeB, int sizeC) 47 | { 48 | _shiftA = sizeB + sizeC + sizeOp; 49 | _shiftB = sizeC + sizeOp; 50 | _shiftBx = sizeOp; 51 | _shiftC = sizeOp; 52 | 53 | _maskOp = (1 << sizeOp) - 1; 54 | _maskA = (1 << sizeA) - 1; 55 | _maskB = (1 << sizeB) - 1; 56 | _maskBx = (1 << (sizeB + sizeC)) - 1; 57 | _maskC = (1 << sizeC) - 1; 58 | 59 | _excessK = _maskBx / 2; 60 | } 61 | 62 | public int ExtractA(int codepoint) => (codepoint >> _shiftA) & _maskA; 63 | 64 | public int ExtractB(int codepoint) => (codepoint >> _shiftB) & _maskB; 65 | 66 | public int ExtractBx(int codepoint) => (codepoint >> _shiftBx) & _maskBx; 67 | 68 | public int ExtractC(int codepoint) => (codepoint >> _shiftC) & _maskC; 69 | 70 | public int ExtractsBx(int codepoint) => ((codepoint >> _shiftBx) & _maskBx) - _excessK; 71 | 72 | public int ExtractOp(int codepoint) => codepoint & _maskOp; 73 | } 74 | 75 | public class Code51 : ICodeExtract 76 | { 77 | public int ExtractA(int codepoint) => (codepoint >> 6) & 0x0000000FF; 78 | 79 | public int ExtractC(int codepoint) => (codepoint >> 14) & 0x000001FF; 80 | 81 | public int ExtractB(int codepoint) => (int)((uint)codepoint >> 23); 82 | 83 | public int ExtractBx(int codepoint) => (int)((uint)codepoint >> 14); 84 | 85 | public int ExtractsBx(int codepoint) => (int)((uint)codepoint >> 14) - 131071; 86 | 87 | public int ExtractOp(int codepoint) => codepoint & 0x0000003F; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Declaration.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler 4 | { 5 | public class Declaration 6 | { 7 | public readonly string Name; 8 | public readonly int Begin, End; 9 | public int Register; 10 | 11 | /// 12 | /// Whether this is an invisible for loop book-keeping variable. 13 | /// 14 | public bool ForLoop = false; 15 | 16 | /// 17 | /// Whether this is an explicit for loop declared variable. 18 | /// 19 | public bool ForLoopExplicit = false; 20 | 21 | public Declaration(LLocal local) 22 | { 23 | Name = local.ToString(); 24 | Begin = local.Start; 25 | End = local.End; 26 | } 27 | 28 | public Declaration(string name, int begin, int end) 29 | { 30 | Name = name; 31 | Begin = begin; 32 | End = end; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/Associativity.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public enum Associativity 4 | { 5 | NONE, 6 | LEFT, 7 | RIGHT 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/BinaryExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class BinaryExpression : Expression 4 | { 5 | private readonly string _op; 6 | private readonly Expression _left, _right; 7 | private readonly Associativity _associativity; 8 | 9 | public BinaryExpression(string op, Expression left, Expression right, Precedence precedence, Associativity associativity) : base(precedence) 10 | { 11 | _op = op; 12 | _left = left; 13 | _right = right; 14 | _associativity = associativity; 15 | } 16 | 17 | public override int GetConstantIndex() => Math.Max(_left.GetConstantIndex(), _right.GetConstantIndex()); 18 | 19 | public override bool BeginsWithParent() => LeftGroup() || _left.BeginsWithParent(); 20 | 21 | public override void Write(Output @out) 22 | { 23 | bool leftGroup = LeftGroup(); 24 | bool rightGroup = RightGroup(); 25 | 26 | if (leftGroup) 27 | @out.Write("("); 28 | 29 | _left.Write(@out); 30 | 31 | if (leftGroup) 32 | @out.Write(")"); 33 | 34 | @out.Write(" "); 35 | @out.Write(_op); 36 | @out.Write(" "); 37 | 38 | if (rightGroup) 39 | @out.Write("("); 40 | 41 | _right.Write(@out); 42 | 43 | if (rightGroup) 44 | @out.Write(")"); 45 | } 46 | 47 | private bool LeftGroup() 48 | => Precedence > _left.Precedence || (Precedence == _left.Precedence && _associativity == Associativity.RIGHT); 49 | 50 | private bool RightGroup() 51 | => Precedence > _right.Precedence || (Precedence == _right.Precedence && _associativity == Associativity.LEFT); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/ClosureExpression.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Targets; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 5 | { 6 | public class ClosureExpression : Expression 7 | { 8 | private readonly LFunction _function; 9 | private int _upvalueLine; 10 | 11 | public ClosureExpression(LFunction function, int upvalueLine) : base(Precedence.ATOMIC) 12 | { 13 | _function = function; 14 | _upvalueLine = upvalueLine; 15 | } 16 | 17 | public override int GetConstantIndex() => -1; 18 | 19 | public override bool IsClosure() => true; 20 | 21 | public override bool IsUpvalueOf(int register) 22 | { 23 | for (int i = 0; i < _function.Upvalues.Length; i++) 24 | { 25 | LUpvalue upvalue = _function.Upvalues[i]; 26 | 27 | if (upvalue.InStack && upvalue.Index == register) 28 | return true; 29 | } 30 | 31 | return false; 32 | } 33 | 34 | public override int ClosureUpvalueLine() => _upvalueLine; 35 | 36 | public override void Write(Output @out) 37 | { 38 | Decompiler d = new(_function); 39 | 40 | @out.Write("function"); 41 | 42 | PrintMain(@out, d, true); 43 | } 44 | 45 | public override void WriteClosure(Output @out, Target name) 46 | { 47 | Decompiler d = new(_function); 48 | 49 | @out.Write("function "); 50 | 51 | if (_function.NumParams >= 1 && d.DeclarationList[0].Name.Equals("self") && name is TableTarget) 52 | { 53 | name.WriteMethod(@out); 54 | PrintMain(@out, d, false); 55 | } 56 | else 57 | { 58 | name.Write(@out); 59 | PrintMain(@out, d, true); 60 | } 61 | } 62 | 63 | private void PrintMain(Output @out, Decompiler d, bool includeFirst) 64 | { 65 | @out.Write("("); 66 | 67 | int start = includeFirst ? 0 : 1; 68 | 69 | if (_function.NumParams > start) 70 | { 71 | new VariableTarget(d.DeclarationList[start]).Write(@out); 72 | 73 | for (int i = start + 1; i < _function.NumParams; i++) 74 | { 75 | @out.Write(", "); 76 | 77 | new VariableTarget(d.DeclarationList[i]).Write(@out); 78 | } 79 | } 80 | 81 | if ((_function.Vararg & 1) == 1) 82 | { 83 | if (_function.NumParams > start) 84 | { 85 | @out.Write(", ..."); 86 | } 87 | else 88 | { 89 | @out.Write("..."); 90 | } 91 | } 92 | 93 | @out.Write(")"); 94 | @out.WriteLine(); 95 | @out.Indent(); 96 | 97 | d.Decompile(); 98 | d.Write(@out); 99 | 100 | @out.Dedent(); 101 | @out.Write("end"); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/ConstantExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class ConstantExpression : Expression 4 | { 5 | private readonly Constant _constant; 6 | private readonly int _index; 7 | 8 | public ConstantExpression(Constant constant, int index) : base(Precedence.ATOMIC) 9 | { 10 | _constant = constant; 11 | _index = index; 12 | } 13 | 14 | public override int GetConstantIndex() => _index; 15 | 16 | public override void Write(Output @out) 17 | => _constant.Write(@out); 18 | 19 | public override bool IsConstant() => true; 20 | 21 | public override bool IsNil() => _constant.IsNil(); 22 | 23 | public override bool IsBoolean() => _constant.IsBoolean(); 24 | 25 | public override bool IsInteger() => _constant.IsInteger(); 26 | 27 | public override int AsInteger() => _constant.AsInteger(); 28 | 29 | public override bool IsString() => _constant.IsString(); 30 | 31 | public override bool IsIdentifier() => _constant.IsIdentifier(); 32 | 33 | public override string AsName() => _constant.AsName(); 34 | 35 | public override bool IsBrief() => !_constant.IsString() || _constant.AsName().Length <= 10; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/FunctionCall.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class FunctionCall : Expression 4 | { 5 | private readonly Expression _function; 6 | private readonly Expression[] _arguments; 7 | private readonly bool _multiple; 8 | 9 | public FunctionCall(Expression function, Expression[] arguments, bool multiple) : base(Precedence.ATOMIC) 10 | { 11 | _function = function; 12 | _arguments = arguments; 13 | _multiple = multiple; 14 | } 15 | 16 | public override int GetConstantIndex() 17 | { 18 | int index = _function.GetConstantIndex(); 19 | 20 | foreach (Expression argument in _arguments) 21 | index = Math.Max(argument.GetConstantIndex(), index); 22 | 23 | return index; 24 | } 25 | 26 | public override bool IsMultiple() => _multiple; 27 | 28 | public void PrintMultiple(Output @out) 29 | { 30 | if (!_multiple) 31 | @out.Write("("); 32 | 33 | Write(@out); 34 | 35 | if (!_multiple) 36 | @out.Write(")"); 37 | } 38 | 39 | private bool IsMethodCall() => _function.IsMemberAccess() && _arguments.Length > 0 && _function.GetTable() == _arguments[0]; 40 | 41 | public override bool BeginsWithParent() 42 | { 43 | if (IsMethodCall()) 44 | { 45 | Expression obj = _function.GetTable(); 46 | 47 | return obj.IsClosure() || obj.IsConstant() || obj.BeginsWithParent(); 48 | } 49 | else 50 | { 51 | return _function.IsClosure() || _function.IsConstant() || _function.BeginsWithParent(); 52 | } 53 | } 54 | 55 | public override void Write(Output @out) 56 | { 57 | List args = new(_arguments.Length); 58 | 59 | if (IsMethodCall()) 60 | { 61 | Expression obj = _function.GetTable(); 62 | 63 | if (obj.IsClosure() || obj.IsConstant()) 64 | { 65 | @out.Write("("); 66 | obj.Write(@out); 67 | @out.Write(")"); 68 | } 69 | else 70 | { 71 | obj.Write(@out); 72 | } 73 | 74 | @out.Write(":"); 75 | @out.Write(_function.GetField()); 76 | 77 | for (int i = 1; i < _arguments.Length; i++) 78 | args.Add(_arguments[i]); 79 | } 80 | else 81 | { 82 | if (_function.IsClosure() || _function.IsConstant()) 83 | { 84 | @out.Write("("); 85 | _function.Write(@out); 86 | @out.Write(")"); 87 | } 88 | else 89 | { 90 | _function.Write(@out); 91 | } 92 | 93 | for (int i = 0; i < _arguments.Length; i++) 94 | args.Add(_arguments[i]); 95 | } 96 | 97 | @out.Write("("); 98 | 99 | WriteSequence(@out, args, false, true); 100 | 101 | @out.Write(")"); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/GlobalExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class GlobalExpression : Expression 4 | { 5 | private readonly string _name; 6 | private readonly int _index; 7 | 8 | public GlobalExpression(string name, int index) : base(Precedence.ATOMIC) 9 | { 10 | _name = name; 11 | _index = index; 12 | } 13 | 14 | public override int GetConstantIndex() => _index; 15 | 16 | public override bool IsDotChain() => true; 17 | 18 | public override void Write(Output @out) 19 | => @out.Write(_name); 20 | 21 | public override bool IsBrief() => true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/LocalVariable.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class LocalVariable : Expression 4 | { 5 | private readonly Declaration _decl; 6 | 7 | public LocalVariable(Declaration decl) : base(Precedence.ATOMIC) => _decl = decl; 8 | 9 | public override int GetConstantIndex() => -1; 10 | 11 | public override bool IsDotChain() => true; 12 | 13 | public override void Write(Output @out) 14 | => @out.Write(_decl.Name); 15 | 16 | public override bool IsBrief() => true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/Precedence.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public enum Precedence 4 | { 5 | OR = 1, 6 | AND = 2, 7 | COMPARE = 3, 8 | CONCAT = 4, 9 | ADD = 5, 10 | MUL = 6, 11 | UNARY = 7, 12 | POW = 8, 13 | ATOMIC = 9 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/TableLiteral.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class TableLiteral : Expression 4 | { 5 | public class Entry : IComparable 6 | { 7 | public readonly Expression Key, Value; 8 | public readonly bool IsList; 9 | public readonly int Timestamp; 10 | 11 | public Entry(Expression key, Expression value, bool isList, int timestamp) 12 | { 13 | Key = key; 14 | Value = value; 15 | IsList = isList; 16 | Timestamp = timestamp; 17 | } 18 | 19 | public int CompareTo(Entry e) => Timestamp.CompareTo(e.Timestamp); 20 | } 21 | 22 | private List _entries; 23 | 24 | private bool _isObject = true, 25 | _isList = true; 26 | 27 | private int listLength = 1; 28 | 29 | public TableLiteral() : this(5, 5) { } 30 | 31 | public TableLiteral(int arraySize, int hashSize) : base(Precedence.ATOMIC) => _entries = new List(arraySize + hashSize); 32 | 33 | public override int GetConstantIndex() 34 | { 35 | int index = -1; 36 | 37 | foreach (Entry entry in _entries) 38 | { 39 | index = Math.Max(entry.Key.GetConstantIndex(), index); 40 | index = Math.Max(entry.Value.GetConstantIndex(), index); 41 | } 42 | 43 | return index; 44 | } 45 | 46 | public override void Write(Output @out) 47 | { 48 | _entries.Sort(); 49 | 50 | listLength = 1; 51 | 52 | if (_entries.Count == 0) 53 | { 54 | @out.Write("{}"); 55 | } 56 | else 57 | { 58 | bool lineBreak = _isList && _entries.Count > 5 || _isObject && _entries.Count > 2 || !_isObject; 59 | 60 | if (!lineBreak) 61 | { 62 | foreach (Entry entry in _entries) 63 | { 64 | Expression value = entry.Value; 65 | 66 | if (!value.IsBrief()) 67 | { 68 | lineBreak = true; 69 | 70 | break; 71 | } 72 | } 73 | } 74 | 75 | @out.Write("{"); 76 | 77 | if (lineBreak) 78 | { 79 | @out.WriteLine(); 80 | @out.Indent(); 81 | } 82 | 83 | WriteEntry(0, @out); 84 | 85 | if (!_entries[0].Value.IsMultiple()) 86 | { 87 | for (int index = 1; index < _entries.Count; index++) 88 | { 89 | @out.Write(","); 90 | 91 | if (lineBreak) 92 | { 93 | @out.WriteLine(); 94 | } 95 | else 96 | { 97 | @out.Write(" "); 98 | } 99 | 100 | WriteEntry(index, @out); 101 | 102 | if (_entries[index].Value.IsMultiple()) 103 | break; 104 | } 105 | } 106 | 107 | if (lineBreak) 108 | { 109 | @out.WriteLine(); 110 | @out.Dedent(); 111 | } 112 | 113 | @out.Write("}"); 114 | } 115 | } 116 | 117 | private void WriteEntry(int index, Output @out) 118 | { 119 | Entry entry = _entries[index]; 120 | 121 | Expression key = entry.Key, 122 | value = entry.Value; 123 | 124 | bool isList = entry.IsList, 125 | multiple = index + 1 >= _entries.Count || value.IsMultiple(); 126 | 127 | if (isList && key.IsInteger() && listLength == key.AsInteger()) 128 | { 129 | if (multiple) 130 | { 131 | value.WriteMultiple(@out); 132 | } 133 | else 134 | { 135 | value.Write(@out); 136 | } 137 | 138 | listLength++; 139 | } 140 | else if (_isObject && key.IsIdentifier()) 141 | { 142 | @out.Write(key.AsName()); 143 | @out.Write(" = "); 144 | value.Write(@out); 145 | } 146 | else 147 | { 148 | @out.Write("["); 149 | key.Write(@out); 150 | @out.Write("] = "); 151 | value.Write(@out); 152 | } 153 | } 154 | 155 | public override bool IsTableLiteral() => true; 156 | 157 | public override void AddEntry(Entry entry) 158 | { 159 | _entries.Add(entry); 160 | 161 | _isObject = _isObject && (entry.IsList || entry.Key.IsIdentifier()); 162 | 163 | _isList = _isList && entry.IsList; 164 | } 165 | 166 | public override bool IsBrief() => false; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/TableReference.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class TableReference : Expression 4 | { 5 | private readonly Expression _table, _index; 6 | 7 | public TableReference(Expression table, Expression index) : base(Precedence.ATOMIC) 8 | { 9 | _table = table; 10 | _index = index; 11 | } 12 | 13 | public override int GetConstantIndex() => Math.Max(_table.GetConstantIndex(), _index.GetConstantIndex()); 14 | 15 | public override void Write(Output @out) 16 | { 17 | _table.Write(@out); 18 | 19 | if (_index.IsIdentifier()) 20 | { 21 | @out.Write("."); 22 | @out.Write(_index.AsName()); 23 | } 24 | else 25 | { 26 | @out.Write("["); 27 | _index.Write(@out); 28 | @out.Write("]"); 29 | } 30 | } 31 | 32 | public override bool IsDotChain() => _index.IsIdentifier() && _table.IsDotChain(); 33 | 34 | public override bool IsMemberAccess() => _index.IsIdentifier(); 35 | 36 | public override Expression GetTable() => _table; 37 | 38 | public override string GetField() => _index.AsName(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/UnaryExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class UnaryExpression : Expression 4 | { 5 | private readonly string _op; 6 | private readonly Expression _expression; 7 | 8 | public UnaryExpression(string op, Expression expression, Precedence precedence) : base(precedence) 9 | { 10 | _op = op; 11 | _expression = expression; 12 | } 13 | 14 | public override int GetConstantIndex() => _expression.GetConstantIndex(); 15 | 16 | public override void Write(Output @out) 17 | { 18 | @out.Write(_op); 19 | 20 | if (Precedence > _expression.Precedence) 21 | @out.Write("("); 22 | 23 | _expression.Write(@out); 24 | 25 | if (Precedence > _expression.Precedence) 26 | @out.Write(")"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/UpvalueExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class UpvalueExpression : Expression 4 | { 5 | private readonly string _name; 6 | 7 | public UpvalueExpression(string name) : base(Precedence.ATOMIC) => _name = name; 8 | 9 | public override int GetConstantIndex() => -1; 10 | 11 | public override bool IsDotChain() => true; 12 | 13 | public override void Write(Output @out) 14 | => @out.Write(_name); 15 | 16 | public override bool IsBrief() => true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Expressions/Vararg.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Expressions 2 | { 3 | public class Vararg : Expression 4 | { 5 | public readonly int Length; 6 | public readonly bool Multiple; 7 | 8 | public Vararg(int length, bool multiple) : base(Precedence.ATOMIC) 9 | { 10 | Length = length; 11 | Multiple = multiple; 12 | } 13 | 14 | public override int GetConstantIndex() => -1; 15 | 16 | public override void Write(Output @out) 17 | => @out.Write(Multiple ? "..." : "(...)"); 18 | 19 | public void PrintMultiple(Output @out) 20 | => @out.Write(Multiple ? "..." : "(...)"); 21 | 22 | public override bool IsMultiple() => Multiple; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Function.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler 5 | { 6 | public class Function 7 | { 8 | private Constant[] _constants; 9 | private readonly int _constantsOffset; 10 | 11 | public Function(LFunction function) 12 | { 13 | _constants = new Constant[function.Constants.Length]; 14 | 15 | for (int i = 0; i < _constants.Length; i++) 16 | _constants[i] = new Constant(function.Constants[i]); 17 | 18 | if (function.Header.Version == Version.LUA50) 19 | { 20 | _constantsOffset = 250; 21 | } 22 | else 23 | { 24 | _constantsOffset = 256; 25 | } 26 | } 27 | 28 | public bool IsConstant(int register) => register >= _constantsOffset; 29 | 30 | public int ConstantIndex(int register) => register - _constantsOffset; 31 | 32 | public string GetGlobalName(int constantIndex) => _constants[constantIndex].AsName(); 33 | 34 | public ConstantExpression GetConstantExpression(int constantIndex) => new(_constants[constantIndex], constantIndex); 35 | 36 | public GlobalExpression GetGlobalExpression(int constantIndex) => new(GetGlobalName(constantIndex), constantIndex); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/ICodeExtract.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler 2 | { 3 | public interface ICodeExtract 4 | { 5 | int ExtractA(int codepoint); 6 | 7 | int ExtractB(int codepoint); 8 | 9 | int ExtractBx(int codepoint); 10 | 11 | int ExtractC(int codepoint); 12 | 13 | int ExtractsBx(int codepoint); 14 | 15 | int ExtractOp(int codepoint); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/IOutputProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler 2 | { 3 | public interface IOutputProvider 4 | { 5 | void Write(string str); 6 | 7 | void WriteLine(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Op.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler 2 | { 3 | public sealed class Op 4 | { 5 | public enum Opcode 6 | { 7 | MOVE, 8 | LOADK, 9 | LOADBOOL, 10 | LOADNIL, 11 | GETUPVAL, 12 | GETGLOBAL, 13 | GETTABLE, 14 | SETGLOBAL, 15 | SETUPVAL, 16 | SETTABLE, 17 | NEWTABLE, 18 | SELF, 19 | ADD, 20 | SUB, 21 | MUL, 22 | DIV, 23 | MOD, 24 | POW, 25 | UNM, 26 | NOT, 27 | LEN, 28 | CONCAT, 29 | JMP, 30 | EQ, 31 | LT, 32 | LE, 33 | TEST, 34 | TESTSET, 35 | CALL, 36 | TAILCALL, 37 | RETURN, 38 | FORLOOP, 39 | FORPREP, 40 | TFORLOOP, 41 | SETLIST, 42 | CLOSE, 43 | CLOSURE, 44 | VARARG, 45 | LOADKX, 46 | GETTABUP, 47 | SETTABUP, 48 | TFORCALL, 49 | EXTRAARG, 50 | SETLIST50, 51 | SETLISTO, 52 | TFORPREP, 53 | TEST50, 54 | NULL 55 | } 56 | 57 | private readonly OpcodeFormat _format; 58 | 59 | private Op(OpcodeFormat format) => _format = format; 60 | 61 | public string CodePointToString(int codepoint, ICodeExtract ex) 62 | { 63 | switch (_format) 64 | { 65 | case OpcodeFormat.A: 66 | return $"{ToString()} {ex.ExtractA(codepoint)}"; 67 | 68 | case OpcodeFormat.A_B: 69 | return $"{ToString()} {ex.ExtractA(codepoint)} {ex.ExtractB(codepoint)}"; 70 | 71 | case OpcodeFormat.A_C: 72 | return $"{ToString()} {ex.ExtractA(codepoint)} {ex.ExtractC(codepoint)}"; 73 | 74 | case OpcodeFormat.A_B_C: 75 | return $"{ToString()} {ex.ExtractA(codepoint)} {ex.ExtractB(codepoint)} {ex.ExtractC(codepoint)}"; 76 | 77 | case OpcodeFormat.A_Bx: 78 | return $"{ToString()} {ex.ExtractA(codepoint)} {ex.ExtractBx(codepoint)}"; 79 | 80 | case OpcodeFormat.A_sBx: 81 | return $"{ToString()} {ex.ExtractA(codepoint)} {ex.ExtractsBx(codepoint)}"; 82 | 83 | case OpcodeFormat.Ax: 84 | return $"{ToString()} "; 85 | 86 | case OpcodeFormat.sBx: 87 | return $"{ToString()} {ex.ExtractsBx(codepoint)}"; 88 | 89 | default: 90 | return ToString(); 91 | } 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/OpcodeFormat.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler 2 | { 3 | public enum OpcodeFormat 4 | { 5 | A, 6 | A_B, 7 | A_C, 8 | A_B_C, 9 | A_Bx, 10 | A_sBx, 11 | Ax, 12 | sBx 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/OpcodeMap.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler 2 | { 3 | public class OpcodeMap 4 | { 5 | private Op.Opcode[] _map; 6 | 7 | public OpcodeMap(int version) 8 | { 9 | if (version == 0x50) 10 | { 11 | _map = new Op.Opcode[35]; 12 | _map[0] = Op.Opcode.MOVE; 13 | _map[1] = Op.Opcode.LOADK; 14 | _map[2] = Op.Opcode.LOADBOOL; 15 | _map[3] = Op.Opcode.LOADNIL; 16 | _map[4] = Op.Opcode.GETUPVAL; 17 | _map[5] = Op.Opcode.GETGLOBAL; 18 | _map[6] = Op.Opcode.GETTABLE; 19 | _map[7] = Op.Opcode.SETGLOBAL; 20 | _map[8] = Op.Opcode.SETUPVAL; 21 | _map[9] = Op.Opcode.SETTABLE; 22 | _map[10] = Op.Opcode.NEWTABLE; 23 | _map[11] = Op.Opcode.SELF; 24 | _map[12] = Op.Opcode.ADD; 25 | _map[13] = Op.Opcode.SUB; 26 | _map[14] = Op.Opcode.MUL; 27 | _map[15] = Op.Opcode.DIV; 28 | _map[16] = Op.Opcode.POW; 29 | _map[17] = Op.Opcode.UNM; 30 | _map[18] = Op.Opcode.NOT; 31 | _map[19] = Op.Opcode.CONCAT; 32 | _map[20] = Op.Opcode.JMP; 33 | _map[21] = Op.Opcode.EQ; 34 | _map[22] = Op.Opcode.LT; 35 | _map[23] = Op.Opcode.LE; 36 | _map[24] = Op.Opcode.TEST50; 37 | _map[25] = Op.Opcode.CALL; 38 | _map[26] = Op.Opcode.TAILCALL; 39 | _map[27] = Op.Opcode.RETURN; 40 | _map[28] = Op.Opcode.FORLOOP; 41 | _map[29] = Op.Opcode.TFORLOOP; 42 | _map[30] = Op.Opcode.TFORPREP; 43 | _map[31] = Op.Opcode.SETLIST50; 44 | _map[32] = Op.Opcode.SETLISTO; 45 | _map[33] = Op.Opcode.CLOSE; 46 | _map[34] = Op.Opcode.CLOSURE; 47 | } 48 | else if (version == 0x51) 49 | { 50 | _map = new Op.Opcode[38]; 51 | _map[0] = Op.Opcode.MOVE; 52 | _map[1] = Op.Opcode.LOADK; 53 | _map[2] = Op.Opcode.LOADBOOL; 54 | _map[3] = Op.Opcode.LOADNIL; 55 | _map[4] = Op.Opcode.GETUPVAL; 56 | _map[5] = Op.Opcode.GETGLOBAL; 57 | _map[6] = Op.Opcode.GETTABLE; 58 | _map[7] = Op.Opcode.SETGLOBAL; 59 | _map[8] = Op.Opcode.SETUPVAL; 60 | _map[9] = Op.Opcode.SETTABLE; 61 | _map[10] = Op.Opcode.NEWTABLE; 62 | _map[11] = Op.Opcode.SELF; 63 | _map[12] = Op.Opcode.ADD; 64 | _map[13] = Op.Opcode.SUB; 65 | _map[14] = Op.Opcode.MUL; 66 | _map[15] = Op.Opcode.DIV; 67 | _map[16] = Op.Opcode.MOD; 68 | _map[17] = Op.Opcode.POW; 69 | _map[18] = Op.Opcode.UNM; 70 | _map[19] = Op.Opcode.NOT; 71 | _map[20] = Op.Opcode.LEN; 72 | _map[21] = Op.Opcode.CONCAT; 73 | _map[22] = Op.Opcode.JMP; 74 | _map[23] = Op.Opcode.EQ; 75 | _map[24] = Op.Opcode.LT; 76 | _map[25] = Op.Opcode.LE; 77 | _map[26] = Op.Opcode.TEST; 78 | _map[27] = Op.Opcode.TESTSET; 79 | _map[28] = Op.Opcode.CALL; 80 | _map[29] = Op.Opcode.TAILCALL; 81 | _map[30] = Op.Opcode.RETURN; 82 | _map[31] = Op.Opcode.FORLOOP; 83 | _map[32] = Op.Opcode.FORPREP; 84 | _map[33] = Op.Opcode.TFORLOOP; 85 | _map[34] = Op.Opcode.SETLIST; 86 | _map[35] = Op.Opcode.CLOSE; 87 | _map[36] = Op.Opcode.CLOSURE; 88 | _map[37] = Op.Opcode.VARARG; 89 | } 90 | else 91 | { 92 | _map = new Op.Opcode[40]; 93 | _map[0] = Op.Opcode.MOVE; 94 | _map[1] = Op.Opcode.LOADK; 95 | _map[2] = Op.Opcode.LOADKX; 96 | _map[3] = Op.Opcode.LOADBOOL; 97 | _map[4] = Op.Opcode.LOADNIL; 98 | _map[5] = Op.Opcode.GETUPVAL; 99 | _map[6] = Op.Opcode.GETTABUP; 100 | _map[7] = Op.Opcode.GETTABLE; 101 | _map[8] = Op.Opcode.SETTABUP; 102 | _map[9] = Op.Opcode.SETUPVAL; 103 | _map[10] = Op.Opcode.SETTABLE; 104 | _map[11] = Op.Opcode.NEWTABLE; 105 | _map[12] = Op.Opcode.SELF; 106 | _map[13] = Op.Opcode.ADD; 107 | _map[14] = Op.Opcode.SUB; 108 | _map[15] = Op.Opcode.MUL; 109 | _map[16] = Op.Opcode.DIV; 110 | _map[17] = Op.Opcode.MOD; 111 | _map[18] = Op.Opcode.POW; 112 | _map[19] = Op.Opcode.UNM; 113 | _map[20] = Op.Opcode.NOT; 114 | _map[21] = Op.Opcode.LEN; 115 | _map[22] = Op.Opcode.CONCAT; 116 | _map[23] = Op.Opcode.JMP; 117 | _map[24] = Op.Opcode.EQ; 118 | _map[25] = Op.Opcode.LT; 119 | _map[26] = Op.Opcode.LE; 120 | _map[27] = Op.Opcode.TEST; 121 | _map[28] = Op.Opcode.TESTSET; 122 | _map[29] = Op.Opcode.CALL; 123 | _map[30] = Op.Opcode.TAILCALL; 124 | _map[31] = Op.Opcode.RETURN; 125 | _map[32] = Op.Opcode.FORLOOP; 126 | _map[33] = Op.Opcode.FORPREP; 127 | _map[34] = Op.Opcode.TFORCALL; 128 | _map[35] = Op.Opcode.TFORLOOP; 129 | _map[36] = Op.Opcode.SETLIST; 130 | _map[37] = Op.Opcode.CLOSURE; 131 | _map[38] = Op.Opcode.VARARG; 132 | _map[39] = Op.Opcode.EXTRAARG; 133 | } 134 | } 135 | 136 | public virtual Op.Opcode Get(int opNumber) 137 | { 138 | if (opNumber >= 0 && opNumber < _map.Length) 139 | { 140 | return _map[opNumber]; 141 | } 142 | else 143 | { 144 | return Op.Opcode.NULL; 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Operations/CallOperation.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Blocks; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 4 | 5 | namespace Marathon.Formats.Script.Lua.Decompiler.Operations 6 | { 7 | public class CallOperation : Operation 8 | { 9 | private FunctionCall _call; 10 | 11 | public CallOperation(int line, FunctionCall call) : base(line) => _call = call; 12 | 13 | public override Statement Process(Registers r, Block block) => new FunctionCallStatement(_call); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Operations/GlobalSet.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Blocks; 2 | using Marathon.Formats.Script.Lua.Decompiler.Targets; 3 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 4 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 5 | 6 | namespace Marathon.Formats.Script.Lua.Decompiler.Operations 7 | { 8 | public class GlobalSet : Operation 9 | { 10 | private string _global; 11 | private Expression _value; 12 | 13 | public GlobalSet(int line, string global, Expression value) : base(line) 14 | { 15 | _global = global; 16 | _value = value; 17 | } 18 | 19 | public override Statement Process(Registers r, Block block) => new Assignment(new GlobalTarget(_global), _value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Operations/Operation.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Blocks; 2 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Operations 5 | { 6 | public abstract class Operation 7 | { 8 | public readonly int Line; 9 | 10 | public Operation(int line) => Line = line; 11 | 12 | public abstract Statement Process(Registers r, Block block); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Operations/RegisterSet.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Blocks; 2 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 3 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 4 | 5 | namespace Marathon.Formats.Script.Lua.Decompiler.Operations 6 | { 7 | public class RegisterSet : Operation 8 | { 9 | public readonly int Register; 10 | public readonly Expression Value; 11 | 12 | public RegisterSet(int line, int register, Expression value) : base(line) 13 | { 14 | Register = register; 15 | Value = value; 16 | } 17 | 18 | public override Statement Process(Registers r, Block block) 19 | { 20 | r.SetValue(Register, Line, Value); 21 | 22 | if (r.IsAssignable(Register, Line)) 23 | { 24 | return new Assignment(r.GetTarget(Register, Line), Value); 25 | } 26 | else 27 | { 28 | return null; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Operations/ReturnOperation.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Blocks; 2 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 3 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 4 | 5 | namespace Marathon.Formats.Script.Lua.Decompiler.Operations 6 | { 7 | public class ReturnOperation : Operation 8 | { 9 | private Expression[] _values; 10 | 11 | public ReturnOperation(int line, Expression value) : base(line) 12 | { 13 | _values = new Expression[1]; 14 | _values[0] = value; 15 | } 16 | 17 | public ReturnOperation(int line, Expression[] values) : base(line) => _values = values; 18 | 19 | public override Statement Process(Registers r, Block block) => new Return(_values); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Operations/TableSet.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Blocks; 2 | using Marathon.Formats.Script.Lua.Decompiler.Targets; 3 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 4 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 5 | 6 | namespace Marathon.Formats.Script.Lua.Decompiler.Operations 7 | { 8 | public class TableSet : Operation 9 | { 10 | private Expression _table, 11 | _index, 12 | _value; 13 | 14 | private bool _isTable; 15 | 16 | private int _timestamp; 17 | 18 | public TableSet(int line, Expression table, Expression index, Expression value, bool isTable, int timestamp) : base(line) 19 | { 20 | _table = table; 21 | _index = index; 22 | _value = value; 23 | _isTable = isTable; 24 | _timestamp = timestamp; 25 | } 26 | 27 | public override Statement Process(Registers r, Block block) 28 | { 29 | if (_table.IsTableLiteral()) 30 | { 31 | _table.AddEntry(new TableLiteral.Entry(_index, _value, !_isTable, _timestamp)); 32 | 33 | return null; 34 | } 35 | else 36 | { 37 | return new Assignment(new TableTarget(_table, _index), _value); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Operations/UpvalueSet.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Blocks; 2 | using Marathon.Formats.Script.Lua.Decompiler.Targets; 3 | using Marathon.Formats.Script.Lua.Decompiler.Statements; 4 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 5 | 6 | namespace Marathon.Formats.Script.Lua.Decompiler.Operations 7 | { 8 | public class UpvalueSet : Operation 9 | { 10 | private UpvalueTarget _target; 11 | private Expression _value; 12 | 13 | public UpvalueSet(int line, string upvalue, Expression value) : base(line) 14 | { 15 | _target = new UpvalueTarget(upvalue); 16 | _value = value; 17 | } 18 | 19 | public override Statement Process(Registers r, Block block) => new Assignment(_target, _value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Output.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler 2 | { 3 | public class Output 4 | { 5 | private IndentationType _indentationType; 6 | private IOutputProvider _out; 7 | private int _indentationLevel = 0; 8 | private int _position = 0; 9 | 10 | public Output() => new OutputProviderAnonymousInnerClass(this); 11 | 12 | public Output(IOutputProvider @out, IndentationType indentationType = IndentationType.Spaces) 13 | { 14 | _out = @out; 15 | _indentationType = indentationType; 16 | } 17 | 18 | private class OutputProviderAnonymousInnerClass : IOutputProvider 19 | { 20 | private readonly Output _outerInstance; 21 | 22 | public OutputProviderAnonymousInnerClass(Output outerInstance) => _outerInstance = outerInstance; 23 | 24 | public void Write(string str) 25 | => Console.Write(str); 26 | 27 | public void WriteLine() 28 | => Console.WriteLine(); 29 | } 30 | 31 | public void Indent() 32 | => _indentationLevel += _indentationType == IndentationType.Spaces ? 4 : 1; 33 | 34 | public void Dedent() 35 | => _indentationLevel -= _indentationType == IndentationType.Spaces ? 4 : 1; 36 | 37 | public int GetIndentationLevel() => _indentationLevel; 38 | 39 | public int GetPosition() => _position; 40 | 41 | public void SetIndentationLevel(int indentationLevel) => _indentationLevel = indentationLevel; 42 | 43 | private void Start() 44 | { 45 | if (_position == 0) 46 | { 47 | for (int i = _indentationLevel; i != 0; i--) 48 | { 49 | _out.Write(_indentationType == IndentationType.Spaces ? " " : "\t"); 50 | _position++; 51 | } 52 | } 53 | } 54 | 55 | public void Write(string str) 56 | { 57 | Start(); 58 | _out.Write(str); 59 | _position += str.Length; 60 | } 61 | 62 | public void WriteLine() 63 | { 64 | Start(); 65 | _out.WriteLine(); 66 | _position = 0; 67 | } 68 | 69 | public void WriteLine(string str) 70 | { 71 | Write(str); 72 | WriteLine(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Statements/Assignment.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | using Marathon.Formats.Script.Lua.Decompiler.Targets; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler.Statements 5 | { 6 | public class Assignment : Statement 7 | { 8 | private readonly List _targets = new(5); 9 | private readonly List _values = new(5); 10 | 11 | private bool _allNil = true, 12 | _declare = false; 13 | 14 | private int _declareStart = 0; 15 | 16 | public Assignment() { } 17 | 18 | public Target GetFirstTarget() => _targets[0]; 19 | 20 | public Expression GetFirstValue() => _values[0]; 21 | 22 | public bool AssignsTarget(Declaration decl) 23 | { 24 | foreach (Target target in _targets) 25 | { 26 | if (target.IsDeclaration(decl)) 27 | return true; 28 | } 29 | 30 | return false; 31 | } 32 | 33 | public int GetArity() => _targets.Count; 34 | 35 | public Assignment(Target target, Expression value) 36 | { 37 | _targets.Add(target); 38 | _values.Add(value); 39 | _allNil = _allNil && value.IsNil(); 40 | } 41 | 42 | public void AddFirst(Target target, Expression value) 43 | { 44 | _targets.Insert(0, target); 45 | _values.Insert(0, value); 46 | _allNil = _allNil && value.IsNil(); 47 | } 48 | 49 | public void AddLast(Target target, Expression value) 50 | { 51 | if (_targets.Contains(target)) 52 | { 53 | int index = _targets.IndexOf(target); 54 | _targets.RemoveAt(index); 55 | value = _values[index]; 56 | } 57 | 58 | _targets.Add(target); 59 | _values.Add(value); 60 | _allNil = _allNil && value.IsNil(); 61 | } 62 | 63 | public bool AssignListEquals(List decls) 64 | { 65 | if (decls.Count != _targets.Count) 66 | return false; 67 | 68 | foreach (Target target in _targets) 69 | { 70 | bool found = false; 71 | 72 | foreach (Declaration decl in decls) 73 | { 74 | if (target.IsDeclaration(decl)) 75 | { 76 | found = true; 77 | 78 | break; 79 | } 80 | } 81 | 82 | if (!found) 83 | return false; 84 | } 85 | 86 | return true; 87 | } 88 | 89 | public void Declare(int declareStart) 90 | { 91 | _declare = true; 92 | _declareStart = declareStart; 93 | } 94 | 95 | public override void Write(Output @out) 96 | { 97 | if (_targets.Count != 0) 98 | { 99 | if (_declare) 100 | @out.Write("local "); 101 | 102 | bool functionSugar = false; 103 | 104 | if (_targets.Count == 1 && _values.Count == 1 && _values[0].IsClosure() && _targets[0].IsFunctionName()) 105 | { 106 | Expression closure = _values[0]; 107 | 108 | // This check only works in Lua 5.1. 109 | if (!_declare || _declareStart >= closure.ClosureUpvalueLine()) 110 | functionSugar = true; 111 | 112 | if (_targets[0].IsLocal() && closure.IsUpvalueOf(_targets[0].GetIndex())) 113 | functionSugar = true; 114 | } 115 | 116 | if (!functionSugar) 117 | { 118 | _targets[0].Write(@out); 119 | 120 | for (int i = 1; i < _targets.Count; i++) 121 | { 122 | @out.Write(", "); 123 | _targets[i].Write(@out); 124 | } 125 | 126 | if (!_declare || !_allNil) 127 | { 128 | @out.Write(" = "); 129 | Expression.WriteSequence(@out, _values, false, false); 130 | } 131 | } 132 | else 133 | { 134 | _values[0].WriteClosure(@out, _targets[0]); 135 | } 136 | 137 | if (Comment != null) 138 | { 139 | @out.Write(" -- "); 140 | @out.Write(Comment); 141 | } 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Statements/Declare.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Statements 2 | { 3 | public class Declare : Statement 4 | { 5 | private readonly List _decls; 6 | 7 | public Declare(List decls) => _decls = decls; 8 | 9 | public override void Write(Output @out) 10 | { 11 | @out.Write("local "); 12 | @out.Write(_decls[0].Name); 13 | 14 | for (int i = 1; i < _decls.Count; i++) 15 | { 16 | @out.Write(", "); 17 | @out.Write(_decls[i].Name); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Statements/FunctionCallStatement.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Statements 4 | { 5 | public class FunctionCallStatement : Statement 6 | { 7 | private FunctionCall _call; 8 | 9 | public FunctionCallStatement(FunctionCall call) => _call = call; 10 | 11 | public override void Write(Output @out) 12 | => _call.Write(@out); 13 | 14 | public override bool BeginsWithParent() => _call.BeginsWithParent(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Statements/Return.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Statements 4 | { 5 | public class Return : Statement 6 | { 7 | private Expression[] _values; 8 | 9 | public Return() => _values = Array.Empty(); 10 | 11 | public Return(Expression value) 12 | { 13 | _values = new Expression[1]; 14 | _values[0] = value; 15 | } 16 | 17 | public Return(Expression[] values) => _values = values; 18 | 19 | public override void Write(Output @out) 20 | { 21 | @out.Write("do "); 22 | WriteTail(@out); 23 | @out.Write(" end"); 24 | } 25 | 26 | public override void WriteTail(Output @out) 27 | { 28 | @out.Write("return"); 29 | 30 | if (_values.Length > 0) 31 | { 32 | @out.Write(" "); 33 | 34 | List returns = new(_values.Length); 35 | 36 | foreach (Expression value in _values) 37 | returns.Add(value); 38 | 39 | Expression.WriteSequence(@out, returns, false, true); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Statements/Statement.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Blocks; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Statements 4 | { 5 | public abstract class Statement 6 | { 7 | public string Comment; 8 | 9 | /// 10 | /// Prints out a sequences of statements on separate lines. 11 | /// Correctly informs the last statement that it is last in a block. 12 | /// 13 | public static void WriteSequence(Output @out, List statements) 14 | { 15 | int n = statements.Count; 16 | 17 | for (int i = 0; i < n; i++) 18 | { 19 | bool last = i + 1 == n; 20 | 21 | Statement statement = statements[i], 22 | nextStatement = last ? null : statements[i + 1]; 23 | 24 | if (last) 25 | { 26 | statement.WriteTail(@out); 27 | } 28 | else 29 | { 30 | statement.Write(@out); 31 | } 32 | 33 | if (nextStatement != null && statement is FunctionCallStatement && nextStatement.BeginsWithParent()) 34 | @out.Write(";"); 35 | 36 | if (statement is not IfThenElseBlock) 37 | @out.WriteLine(); 38 | } 39 | } 40 | 41 | public abstract void Write(Output @out); 42 | 43 | public virtual void WriteTail(Output @out) 44 | => Write(@out); 45 | 46 | public void AddComment(string comment) 47 | => Comment = comment; 48 | 49 | public virtual bool BeginsWithParent() => false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Targets/GlobalTarget.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Targets 2 | { 3 | public class GlobalTarget : Target 4 | { 5 | private readonly string _name; 6 | 7 | public GlobalTarget(string name) => _name = name; 8 | 9 | public override void Write(Output @out) 10 | => @out.Write(_name); 11 | 12 | public override void WriteMethod(Output @out) 13 | => throw new Exception(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Targets/TableTarget.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 2 | 3 | namespace Marathon.Formats.Script.Lua.Decompiler.Targets 4 | { 5 | public class TableTarget : Target 6 | { 7 | private readonly Expression _table, _index; 8 | 9 | public TableTarget(Expression table, Expression index) 10 | { 11 | _table = table; 12 | _index = index; 13 | } 14 | 15 | public override void Write(Output @out) 16 | => new TableReference(_table, _index).Write(@out); 17 | 18 | public override void WriteMethod(Output @out) 19 | { 20 | _table.Write(@out); 21 | @out.Write(":"); 22 | @out.Write(_index.AsName()); 23 | } 24 | 25 | public override bool IsFunctionName() 26 | { 27 | if (!_index.IsIdentifier()) 28 | return false; 29 | 30 | if (!_table.IsDotChain()) 31 | return false; 32 | 33 | return true; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Targets/Target.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Targets 2 | { 3 | public abstract class Target 4 | { 5 | public abstract void Write(Output @out); 6 | 7 | public abstract void WriteMethod(Output @out); 8 | 9 | public virtual bool IsDeclaration(Declaration decl) => false; 10 | 11 | public virtual bool IsLocal() => false; 12 | 13 | public virtual int GetIndex() => throw new Exception(); 14 | 15 | public virtual bool IsFunctionName() => true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Targets/UpvalueTarget.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Targets 2 | { 3 | public class UpvalueTarget : Target 4 | { 5 | private readonly string _name; 6 | 7 | public UpvalueTarget(string name) => _name = name; 8 | 9 | public override void Write(Output @out) 10 | => @out.Write(_name); 11 | 12 | public override void WriteMethod(Output @out) 13 | => throw new Exception(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Targets/VariableTarget.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Decompiler.Targets 2 | { 3 | public class VariableTarget : Target 4 | { 5 | public readonly Declaration Declaration; 6 | 7 | public VariableTarget(Declaration decl) => Declaration = decl; 8 | 9 | public override void Write(Output @out) 10 | => @out.Write(Declaration.Name); 11 | 12 | public override void WriteMethod(Output @out) 13 | => throw new Exception(); 14 | 15 | public override bool IsDeclaration(Declaration decl) => Declaration == decl; 16 | 17 | public override bool IsLocal() => true; 18 | 19 | public override int GetIndex() => Declaration.Register; 20 | 21 | public override bool Equals(object obj) 22 | { 23 | if (obj is VariableTarget t) 24 | { 25 | return Declaration == t.Declaration; 26 | } 27 | else 28 | { 29 | return false; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Decompiler/Upvalues.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler.Expressions; 3 | 4 | namespace Marathon.Formats.Script.Lua.Decompiler 5 | { 6 | public class Upvalues 7 | { 8 | private readonly LUpvalue[] _upvalues; 9 | 10 | public Upvalues(LUpvalue[] upvalues) => _upvalues = upvalues; 11 | 12 | public string GetName(int index) 13 | { 14 | if (index < _upvalues.Length && _upvalues[index].Name != null) 15 | { 16 | return _upvalues[index].Name; 17 | } 18 | else 19 | { 20 | // TODO: Set error. 21 | return $"v{index}"; 22 | } 23 | } 24 | 25 | public UpvalueExpression GetExpression(int index) => new(GetName(index)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/LuaBinary.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler; 3 | 4 | namespace Marathon.Formats.Script.Lua 5 | { 6 | /// 7 | /// File base for the *.lub format. 8 | /// Used in SONIC THE HEDGEHOG for compiled Lua scripts. 9 | /// 10 | public class LuaBinary : FileBase 11 | { 12 | public IndentationType IndentationType { get; set; } = IndentationType.Spaces; 13 | 14 | public LuaBinary() { } 15 | 16 | public LuaBinary(string file, bool decompile = false) 17 | { 18 | switch (Path.GetExtension(file)) 19 | { 20 | case ".lua": 21 | case ".lub": 22 | { 23 | Load(file); 24 | 25 | if (decompile) 26 | Save(file); 27 | 28 | break; 29 | } 30 | } 31 | } 32 | 33 | private LFunction _main; 34 | public LFunction Main 35 | { 36 | get => _main; 37 | 38 | private set 39 | { 40 | _main = value; 41 | DecompileCache = null; 42 | } 43 | } 44 | 45 | private string DecompileCache { get; set; } 46 | 47 | public override void Load(Stream stream) 48 | { 49 | BinaryReaderEx reader = new(stream); 50 | 51 | BHeader header = new(reader); 52 | Main = header.Function.Parse(reader, header); 53 | } 54 | 55 | public string Decompile() 56 | { 57 | if (string.IsNullOrEmpty(DecompileCache)) 58 | { 59 | Decompiler.Decompiler unlub = new(Main); 60 | unlub.Decompile(); 61 | 62 | var provider = new OutputProviderString(); 63 | unlub.Write(new Output(provider, IndentationType)); 64 | 65 | DecompileCache = provider.ToString(); 66 | } 67 | 68 | return DecompileCache; 69 | } 70 | 71 | public override void Save(Stream stream) 72 | { 73 | Decompiler.Decompiler unlub = new(Main); 74 | unlub.Decompile(); 75 | 76 | using var provider = new OutputProviderStream(stream); 77 | unlub.Write(new Output(provider, IndentationType)); 78 | } 79 | 80 | private class OutputProviderString : IOutputProvider 81 | { 82 | private StringBuilder _pout = new(); 83 | 84 | public void Write(string str) 85 | => _pout.Append(str); 86 | 87 | public void WriteLine() 88 | => _pout.AppendLine(); 89 | 90 | public override string ToString() => _pout.ToString(); 91 | } 92 | 93 | private class OutputProviderStream : IOutputProvider, IDisposable 94 | { 95 | private StreamWriter _out; 96 | 97 | public OutputProviderStream(Stream stream) => _out = new StreamWriter(stream, leaveOpen: true); 98 | 99 | public void Write(string str) 100 | => _out.Write(str); 101 | 102 | public void WriteLine() 103 | => _out.WriteLine(); 104 | 105 | public void Dispose() 106 | => _out.Dispose(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/BHeader.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Decompiler; 2 | 3 | namespace Marathon.Formats.Script.Lua.Types 4 | { 5 | public class BHeader 6 | { 7 | public static readonly byte[] Signature = { 0x1B, 0x4C, 0x75, 0x61 }; 8 | public static readonly byte[] LuaTail = { 0x19, 0x93, 0x0D, 0x0A, 0x1A, 0x0A }; 9 | 10 | public readonly Version Version; 11 | public readonly BIntegerType Integer; 12 | public readonly BSizeTType SizeT; 13 | public readonly LBooleanType Bool; 14 | public readonly LNumberType Number; 15 | public readonly LStringType String; 16 | public readonly LConstantType Constant; 17 | public readonly LLocalType Local; 18 | public readonly LUpvalueType Upvalue; 19 | public readonly LFunctionType Function; 20 | public readonly ICodeExtract Extractor; 21 | 22 | public BHeader(BinaryReaderEx reader) 23 | { 24 | // Read script signature. 25 | reader.ReadSignature(4, Signature); 26 | 27 | int versionNumber = reader.ReadByte(); 28 | 29 | switch (versionNumber) 30 | { 31 | case 0x50: 32 | { 33 | Version = Version.LUA50; 34 | break; 35 | } 36 | 37 | case 0x51: 38 | { 39 | Version = Version.LUA51; 40 | break; 41 | } 42 | 43 | case 0x52: 44 | { 45 | Version = Version.LUA52; 46 | break; 47 | } 48 | 49 | default: 50 | { 51 | int major = versionNumber >> 4; 52 | int minor = versionNumber & 0x0F; 53 | 54 | throw new Exception($"The input chunk's Lua version is {major}.{minor}; Marathon can only handle Lua 5.0, Lua 5.1 and Lua 5.2."); 55 | } 56 | } 57 | 58 | if (Version.HasFormat()) 59 | { 60 | int format = reader.ReadByte(); 61 | 62 | if (format != 0) 63 | throw new Exception($"The input chunk reports a non-standard Lua format: {format}"); 64 | } 65 | 66 | int endianness = reader.ReadByte(); 67 | 68 | switch (endianness) 69 | { 70 | case 0: 71 | reader.IsBigEndian = true; 72 | break; 73 | 74 | case 1: 75 | reader.IsBigEndian = false; 76 | break; 77 | 78 | default: 79 | throw new Exception($"The input chunk reports an invalid endianness: {endianness}"); 80 | } 81 | 82 | int intSize = reader.ReadByte(); 83 | Integer = new BIntegerType(intSize); 84 | 85 | int sizeTSize = reader.ReadByte(); 86 | SizeT = new BSizeTType(sizeTSize); 87 | 88 | int instructionSize = reader.ReadByte(); 89 | if (instructionSize != 4) 90 | throw new Exception($"The input chunk reports an unsupported instruction size: {instructionSize} bytes"); 91 | 92 | if (Version == Version.LUA50) 93 | { 94 | Extractor = new Code50(reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte()); 95 | } 96 | else 97 | { 98 | Extractor = new Code51(); 99 | } 100 | 101 | int lNumberSize = reader.ReadByte(); 102 | if (Version == Version.LUA50) 103 | { 104 | Number = new LNumberType(lNumberSize, false); 105 | reader.ReadInt64(); 106 | } 107 | else 108 | { 109 | int lNumberIntegralCode = reader.ReadByte(); 110 | 111 | if (lNumberIntegralCode > 1) 112 | throw new Exception($"The input chunk reports an invalid code for lua number integralness: {lNumberIntegralCode}"); 113 | 114 | bool lNumberIntegral = lNumberIntegralCode == 1; 115 | 116 | Number = new LNumberType(lNumberSize, lNumberIntegral); 117 | } 118 | 119 | Bool = new LBooleanType(); 120 | String = new LStringType(); 121 | Constant = new LConstantType(); 122 | Local = new LLocalType(); 123 | Upvalue = new LUpvalueType(); 124 | Function = Version.GetLFunctionType(); 125 | 126 | if (Version.HasHeaderTail()) 127 | reader.ReadSignature(6, LuaTail); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/BInteger.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class BInteger : BObject 4 | { 5 | private readonly BigInteger? _big; 6 | private readonly int _n; 7 | 8 | private static BigInteger? _max; 9 | private static BigInteger? _min; 10 | 11 | public BInteger(BInteger b) 12 | { 13 | _big = b._big; 14 | _n = b._n; 15 | } 16 | 17 | public BInteger(int n) 18 | { 19 | _big = null; 20 | _n = n; 21 | } 22 | 23 | public BInteger(BigInteger big) 24 | { 25 | _big = big; 26 | _n = 0; 27 | 28 | if (_max == 0) 29 | { 30 | _max = int.MaxValue; 31 | _min = int.MinValue; 32 | } 33 | } 34 | 35 | public int AsInt() 36 | { 37 | if (_big == null) 38 | { 39 | return _n; 40 | } 41 | else if (_big.Value.CompareTo(_max) > 0 || _big.Value.CompareTo(_min) < 0) 42 | { 43 | throw new Exception("The size of an integer is outside the range that unluac can handle."); 44 | } 45 | else 46 | { 47 | return (int)_big.Value; 48 | } 49 | } 50 | 51 | public void Iterate(Action thunk) 52 | { 53 | if (_big == null) 54 | { 55 | int i = _n; 56 | 57 | while (i-- != 0) 58 | thunk(); 59 | } 60 | else 61 | { 62 | BigInteger i = _big.Value; 63 | 64 | while (_big.Value.Sign > 0) 65 | { 66 | thunk(); 67 | 68 | i -= BigInteger.One; 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/BIntegerType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class BIntegerType : BObjectType 4 | { 5 | public readonly int IntSize; 6 | 7 | public BIntegerType(int intSize) => IntSize = intSize; 8 | 9 | public BInteger RawParse(BinaryReaderEx reader, BHeader header) 10 | { 11 | BInteger value; 12 | 13 | switch (IntSize) 14 | { 15 | case 0: 16 | value = new BInteger(0); 17 | break; 18 | 19 | case 1: 20 | value = new BInteger(reader.ReadByte()); 21 | break; 22 | 23 | case 2: 24 | value = new BInteger(reader.ReadInt16()); 25 | break; 26 | 27 | case 4: 28 | value = new BInteger(reader.ReadInt32()); 29 | break; 30 | 31 | default: 32 | { 33 | byte[] bytes = new byte[IntSize]; 34 | 35 | int start = 0, delta = 1; 36 | 37 | if (!reader.IsBigEndian) 38 | { 39 | start = IntSize - 1; 40 | delta = -1; 41 | } 42 | 43 | for (int i = start; i >= 0 && i < IntSize; i += delta) 44 | bytes[i] = reader.ReadByte(); 45 | 46 | value = new BInteger(new BigInteger(bytes)); 47 | 48 | break; 49 | } 50 | } 51 | 52 | return value; 53 | } 54 | 55 | public override BInteger Parse(BinaryReaderEx reader, BHeader header) => RawParse(reader, header); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/BList.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class BList : BObject where T : BObject 4 | { 5 | public readonly BInteger Length; 6 | 7 | private readonly List _values; 8 | 9 | public BList(BInteger length, List values) 10 | { 11 | Length = length; 12 | _values = values; 13 | } 14 | 15 | public T Get(int index) => _values[index]; 16 | 17 | public T[] AsArray(T[] array) 18 | { 19 | int i = 0; 20 | 21 | Length.Iterate(Run); 22 | 23 | void Run() 24 | { 25 | array[i] = _values[i]; 26 | i++; 27 | } 28 | 29 | return array; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/BObject.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public abstract class BObject { } 4 | } 5 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/BObjectType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public abstract class BObjectType where T : BObject 4 | { 5 | public abstract T Parse(BinaryReaderEx reader, BHeader header); 6 | 7 | public BList ParseList(BinaryReaderEx reader, BHeader header) 8 | { 9 | BInteger length = header.Integer.Parse(reader, header); 10 | List values = new(); 11 | 12 | length.Iterate(Run); 13 | 14 | void Run() 15 | => values.Add(Parse(reader, header)); 16 | 17 | return new BList(length, values); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/BSizeT.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class BSizeT : BInteger 4 | { 5 | public BSizeT(BInteger b) : base(b) { } 6 | 7 | public BSizeT(int n) : base(n) { } 8 | 9 | public BSizeT(BigInteger n) : base(n) { } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/BSizeTType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class BSizeTType : BObjectType 4 | { 5 | public readonly int SizeTSize; 6 | 7 | private BIntegerType _integerType; 8 | 9 | public BSizeTType(int sizeTSize) 10 | { 11 | SizeTSize = sizeTSize; 12 | _integerType = new BIntegerType(sizeTSize); 13 | } 14 | 15 | public override BSizeT Parse(BinaryReaderEx reader, BHeader header) => new(_integerType.RawParse(reader, header)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LBoolean.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LBoolean : LObject 4 | { 5 | public static readonly LBoolean LTRUE = new(true); 6 | public static readonly LBoolean LFALSE = new(false); 7 | 8 | private readonly bool _value; 9 | 10 | private LBoolean(bool value) => _value = value; 11 | 12 | public override string ToString() => _value.ToString(); 13 | 14 | public override bool Equals(object o) => this == o; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LBooleanType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LBooleanType : BObjectType 4 | { 5 | public override LBoolean Parse(BinaryReaderEx reader, BHeader header) 6 | { 7 | int value = reader.ReadByte(); 8 | 9 | if ((value & 0xFFFFFFFE) != 0) 10 | { 11 | throw new Exception(); 12 | } 13 | else 14 | { 15 | return value == 0 ? LBoolean.LFALSE : LBoolean.LTRUE; 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LConstantType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LConstantType : BObjectType 4 | { 5 | public override LObject Parse(BinaryReaderEx reader, BHeader header) 6 | { 7 | int type = reader.ReadByte(); 8 | 9 | switch (type) 10 | { 11 | case 0: 12 | return LNil.NIL; 13 | 14 | case 1: 15 | return header.Bool.Parse(reader, header); 16 | 17 | case 3: 18 | return header.Number.Parse(reader, header); 19 | 20 | case 4: 21 | return header.String.Parse(reader, header); 22 | 23 | default: 24 | throw new Exception(); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LFunction.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LFunction : BObject 4 | { 5 | public BHeader Header; 6 | public int[] Code; 7 | public LLocal[] Locals; 8 | public LObject[] Constants; 9 | public LUpvalue[] Upvalues; 10 | public LFunction[] Functions; 11 | 12 | public int MaximumStackSize, 13 | NumUpvalues, 14 | NumParams, 15 | Vararg; 16 | 17 | public LFunction(BHeader header, int[] code, LLocal[] locals, LObject[] constants, LUpvalue[] upvalues, LFunction[] functions, 18 | int maximumStackSize, int numUpvalues, int numParams, int vararg) 19 | { 20 | Header = header; 21 | Code = code; 22 | Locals = locals; 23 | Constants = constants; 24 | Upvalues = upvalues; 25 | Functions = functions; 26 | MaximumStackSize = maximumStackSize; 27 | NumUpvalues = numUpvalues; 28 | NumParams = numParams; 29 | Vararg = vararg; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LLocal.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LLocal : BObject 4 | { 5 | public readonly LString Name; 6 | public readonly int Start, End; 7 | 8 | /// 9 | /// Used by the decompiler for annotation. 10 | /// 11 | public bool ForLoop = false; 12 | 13 | public LLocal(LString name, BInteger start, BInteger end) 14 | { 15 | Name = name; 16 | Start = start.AsInt(); 17 | End = end.AsInt(); 18 | } 19 | 20 | public override string ToString() => Name.Dereference(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LLocalType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LLocalType : BObjectType 4 | { 5 | public override LLocal Parse(BinaryReaderEx reader, BHeader header) 6 | { 7 | LString name = header.String.Parse(reader, header); 8 | 9 | BInteger start = header.Integer.Parse(reader, header), 10 | end = header.Integer.Parse(reader, header); 11 | 12 | return new LLocal(name, start, end); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LNil.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LNil : LObject 4 | { 5 | public static readonly LNil NIL = new(); 6 | 7 | private LNil() { } 8 | 9 | public override string Dereference() => throw new NotImplementedException(); 10 | 11 | public override bool Equals(object o) => this == o; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LNumber.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public abstract class LNumber : LObject 4 | { 5 | public static LNumber MakeInteger(int number) => new LIntNumber(number); 6 | 7 | public abstract new string ToString(); 8 | 9 | public abstract double Value(); 10 | } 11 | 12 | class LFloatNumber : LNumber 13 | { 14 | public readonly float Number; 15 | 16 | public LFloatNumber(float number) => Number = number; 17 | 18 | public override string ToString() 19 | { 20 | if (Number == (float)Math.Round(Number)) 21 | { 22 | return ((int)Number).ToString(); 23 | } 24 | else 25 | { 26 | return Number.ToString(); 27 | } 28 | } 29 | 30 | public override string Dereference() => throw new NotImplementedException(); 31 | 32 | public override bool Equals(object o) 33 | { 34 | if (o is LFloatNumber lFloatNumber) 35 | { 36 | return Number == lFloatNumber.Number; 37 | } 38 | else if (o is LNumber lNumber) 39 | { 40 | return Value() == lNumber.Value(); 41 | } 42 | 43 | return false; 44 | } 45 | 46 | public override double Value() => Number; 47 | } 48 | 49 | class LDoubleNumber : LNumber 50 | { 51 | public readonly double Number; 52 | 53 | public LDoubleNumber(double number) => Number = number; 54 | 55 | public override string ToString() 56 | { 57 | if (Number == (double)Math.Round(Number)) 58 | { 59 | return ((long)Number).ToString(); 60 | } 61 | else 62 | { 63 | return Number.ToString(); 64 | } 65 | } 66 | 67 | public override string Dereference() => throw new NotImplementedException(); 68 | 69 | public override bool Equals(object o) 70 | { 71 | if (o is LDoubleNumber lDoubleNumber) 72 | { 73 | return Number == lDoubleNumber.Number; 74 | } 75 | else if (o is LNumber lNumber) 76 | { 77 | return Value() == lNumber.Value(); 78 | } 79 | 80 | return false; 81 | } 82 | 83 | public override double Value() => Number; 84 | } 85 | 86 | class LIntNumber : LNumber 87 | { 88 | public readonly int Number; 89 | 90 | public LIntNumber(int number) => Number = number; 91 | 92 | public override string ToString() => Number.ToString(); 93 | 94 | public override string Dereference() => throw new NotImplementedException(); 95 | 96 | public override bool Equals(object o) 97 | { 98 | if (o is LIntNumber lIntNumber) 99 | { 100 | return Number == lIntNumber.Number; 101 | } 102 | else if (o is LNumber lNumber) 103 | { 104 | return Value() == lNumber.Value(); 105 | } 106 | 107 | return false; 108 | } 109 | 110 | public override double Value() => Number; 111 | } 112 | 113 | class LLongNumber : LNumber 114 | { 115 | public readonly long Number; 116 | 117 | public LLongNumber(long number) => Number = number; 118 | 119 | public override string ToString() => Number.ToString(); 120 | 121 | public override string Dereference() => throw new NotImplementedException(); 122 | 123 | public override bool Equals(object o) 124 | { 125 | if (o is LLongNumber lLongNumber) 126 | { 127 | return Number == lLongNumber.Number; 128 | } 129 | else if (o is LNumber lNumber) 130 | { 131 | return Value() == lNumber.Value(); 132 | } 133 | 134 | return false; 135 | } 136 | 137 | public override double Value() => Number; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LNumberType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LNumberType : BObjectType 4 | { 5 | public readonly int Size; 6 | public readonly bool Integral; 7 | 8 | public LNumberType(int size, bool integral) 9 | { 10 | Size = size; 11 | Integral = integral; 12 | 13 | if (!(size == 4 || size == 8)) 14 | throw new Exception($"The input chunk has an unsupported Lua number size: {size}"); 15 | } 16 | 17 | public override LNumber Parse(BinaryReaderEx reader, BHeader header) 18 | { 19 | LNumber value = null; 20 | 21 | if (Integral) 22 | { 23 | switch (Size) 24 | { 25 | case 4: 26 | value = new LIntNumber(reader.ReadInt32()); 27 | break; 28 | 29 | case 8: 30 | value = new LLongNumber(reader.ReadInt64()); 31 | break; 32 | } 33 | } 34 | else 35 | { 36 | switch (Size) 37 | { 38 | case 4: 39 | value = new LFloatNumber(reader.ReadSingle()); 40 | break; 41 | 42 | case 8: 43 | value = new LDoubleNumber(reader.ReadDouble()); 44 | break; 45 | } 46 | } 47 | 48 | if (value == null) 49 | throw new Exception("The input chunk has an unsupported Lua number format"); 50 | 51 | return value; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LObject.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public abstract class LObject : BObject 4 | { 5 | public virtual string Dereference() => throw new Exception(); 6 | 7 | public abstract new bool Equals(object o); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LSourceLines.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LSourceLines 4 | { 5 | public static LSourceLines Parse(BinaryReaderEx reader) 6 | { 7 | int number = reader.ReadInt32(); 8 | 9 | while (number-- > 0) 10 | reader.ReadInt32(); 11 | 12 | return null; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LString.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LString : LObject 4 | { 5 | public readonly BSizeT Size; 6 | public readonly string Value; 7 | 8 | public LString(BSizeT size, string value) 9 | { 10 | Size = size; 11 | Value = value.Length == 0 ? "" : value[0..^1]; 12 | } 13 | 14 | public override string Dereference() => Value; 15 | 16 | public override string ToString() => $"\"{Value}\""; 17 | 18 | public override bool Equals(object o) 19 | { 20 | if (o is LString lString) 21 | return lString.Value.Equals(Value); 22 | 23 | return false; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LStringType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LStringType : BObjectType 4 | { 5 | protected StringBuilder InitialValue() => new(); 6 | 7 | public override LString Parse(BinaryReaderEx reader, BHeader header) 8 | { 9 | BSizeT sizeT = header.SizeT.Parse(reader, header); 10 | StringBuilder b = new(); 11 | 12 | sizeT.Iterate(Run); 13 | 14 | void Run() 15 | => b.Append((char)reader.ReadByte()); 16 | 17 | return new LString(sizeT, b.ToString()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LUpvalue.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LUpvalue : BObject 4 | { 5 | public bool InStack; 6 | public int Index; 7 | public string Name; 8 | 9 | public override bool Equals(object obj) 10 | { 11 | if (obj is LUpvalue lUpvalue) 12 | { 13 | if (!(InStack == lUpvalue.InStack && Index == lUpvalue.Index)) 14 | return false; 15 | 16 | if (Name == lUpvalue.Name) 17 | return true; 18 | 19 | return Name != null && Name.Equals(lUpvalue.Name); 20 | } 21 | else 22 | { 23 | return false; 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Types/LUpvalueType.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script.Lua.Types 2 | { 3 | public class LUpvalueType : BObjectType 4 | { 5 | public override LUpvalue Parse(BinaryReaderEx reader, BHeader header) 6 | { 7 | LUpvalue upvalue = new() 8 | { 9 | InStack = reader.ReadByte() != 0, 10 | Index = reader.ReadByte() 11 | }; 12 | 13 | return upvalue; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/Lua/Version.cs: -------------------------------------------------------------------------------- 1 | using Marathon.Formats.Script.Lua.Types; 2 | using Marathon.Formats.Script.Lua.Decompiler; 3 | 4 | namespace Marathon.Formats.Script.Lua 5 | { 6 | public abstract class Version 7 | { 8 | public static readonly Version LUA50 = new Version50(); 9 | public static readonly Version LUA51 = new Version51(); 10 | public static readonly Version LUA52 = new Version52(); 11 | 12 | protected readonly int _versionNumber; 13 | 14 | protected Version(int versionNumber) => _versionNumber = versionNumber; 15 | 16 | public abstract bool HasHeaderTail(); 17 | 18 | public abstract bool HasFormat(); 19 | 20 | public abstract LFunctionType GetLFunctionType(); 21 | 22 | public OpcodeMap GetOpcodeMap() => new(_versionNumber); 23 | 24 | public abstract int GetOuterBlockScopeAdjustment(); 25 | 26 | public abstract bool UsesOldLoadNilEncoding(); 27 | 28 | public abstract bool UsesInlineUpvalueDeclarations(); 29 | 30 | public abstract Op.Opcode GetTForTarget(); 31 | 32 | public abstract Op.Opcode GetForTarget(); 33 | 34 | public abstract bool IsBreakableLoopEnd(Op.Opcode op); 35 | } 36 | 37 | public class Version50 : Version 38 | { 39 | public Version50() : base(0x50) { } 40 | 41 | public override bool HasHeaderTail() => false; 42 | 43 | public override bool HasFormat() => false; 44 | 45 | public override LFunctionType GetLFunctionType() => LFunctionType.TYPE50; 46 | 47 | public override int GetOuterBlockScopeAdjustment() => -1; 48 | 49 | public override bool UsesOldLoadNilEncoding() => true; 50 | 51 | public override bool UsesInlineUpvalueDeclarations() => true; 52 | 53 | public override Op.Opcode GetTForTarget() => Op.Opcode.TFORLOOP; 54 | 55 | public override Op.Opcode GetForTarget() => Op.Opcode.FORLOOP; 56 | 57 | public override bool IsBreakableLoopEnd(Op.Opcode op) => op == Op.Opcode.JMP || op == Op.Opcode.FORLOOP; 58 | } 59 | 60 | public class Version51 : Version 61 | { 62 | public Version51() : base(0x51) { } 63 | 64 | public override bool HasHeaderTail() => false; 65 | 66 | public override bool HasFormat() => true; 67 | 68 | public override LFunctionType GetLFunctionType() => LFunctionType.TYPE51; 69 | 70 | public override int GetOuterBlockScopeAdjustment() => -1; 71 | 72 | public override bool UsesOldLoadNilEncoding() => true; 73 | 74 | public override bool UsesInlineUpvalueDeclarations() => true; 75 | 76 | public override Op.Opcode GetTForTarget() => Op.Opcode.TFORLOOP; 77 | 78 | public override Op.Opcode GetForTarget() => Op.Opcode.NULL; 79 | 80 | public override bool IsBreakableLoopEnd(Op.Opcode op) => op == Op.Opcode.JMP || op == Op.Opcode.FORLOOP; 81 | } 82 | 83 | public class Version52 : Version 84 | { 85 | public Version52() : base(0x52) { } 86 | 87 | public override bool HasHeaderTail() => true; 88 | 89 | public override bool HasFormat() => true; 90 | 91 | public override LFunctionType GetLFunctionType() => LFunctionType.TYPE52; 92 | 93 | public override int GetOuterBlockScopeAdjustment() => 0; 94 | 95 | public override bool UsesOldLoadNilEncoding() => false; 96 | 97 | public override bool UsesInlineUpvalueDeclarations() => false; 98 | 99 | public override Op.Opcode GetTForTarget() => Op.Opcode.TFORCALL; 100 | 101 | public override Op.Opcode GetForTarget() => Op.Opcode.NULL; 102 | 103 | public override bool IsBreakableLoopEnd(Op.Opcode op) => op == Op.Opcode.JMP || op == Op.Opcode.FORLOOP || op == Op.Opcode.TFORLOOP; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Marathon/Formats/Script/MAB.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Script 2 | { 3 | public class MAB : FileBase // TODO: Identify what *.mab is... 4 | { 5 | public MAB() { } 6 | 7 | public MAB(string file, bool serialise = false) 8 | { 9 | switch (Path.GetExtension(file)) 10 | { 11 | default: 12 | { 13 | Load(file); 14 | 15 | break; 16 | } 17 | } 18 | } 19 | 20 | public override string Signature { get; } = "MRAB"; 21 | 22 | public override string Extension { get; } = ".mab"; 23 | 24 | public override void Load(Stream stream) 25 | { 26 | BinaryReaderEx reader = new(stream, true); 27 | 28 | reader.ReadSignature(4, Signature); 29 | 30 | // MRAB Chunk, contains a BINA Header for some reason. 31 | uint mrabChunkSize = reader.ReadUInt32(); 32 | uint fileSize = reader.ReadUInt32(); 33 | uint UnknownUInt32_1 = reader.ReadUInt32(); // Always zero. 34 | 35 | BINAHeader bina = new(); 36 | bina.Read(reader); 37 | 38 | reader.Offset = mrabChunkSize + 0x20; // MRAB chunk size + BINA header size 39 | 40 | uint abdaOffset = reader.ReadUInt32(); // Always 0x20. 41 | uint dataLength = reader.ReadUInt32(); // Seems to be the length from the end of the BINA Header to the start of the ABRS chunk? 42 | reader.JumpTo(abdaOffset, true); 43 | 44 | // ABDA Chunk 45 | string ChunkID = new(reader.ReadChars(0x04)); 46 | uint UnknownUInt32_2 = reader.ReadUInt32(); // Seemed like the length of the ABDA chunk and the ABDT chunk, but isn't? 47 | uint Length = reader.ReadUInt32(); 48 | uint UnknownUInt32_3 = reader.ReadUInt32(); // Always zero. 49 | uint Version = reader.ReadUInt32(); // Should always be 2006020901 (probably a timestamp - requires match by game executable). 50 | uint ABDTCount = reader.ReadUInt32(); // How many ABDT chunks this file contains. 51 | uint SizeUntilPOF0 = reader.ReadUInt32(); // Includes the header of the ABDA chunk and goes down to the first POF0 chunk. 52 | uint UnknownUInt32_4 = reader.ReadUInt32(); // Always zero. 53 | uint Length2ElectricBoogaloo = reader.ReadUInt32(); // Same as Length, for some reason. 54 | int HasExtraData = reader.ReadInt32(); 55 | 56 | if (HasExtraData == -1) 57 | { 58 | // TODO: Read extra data after this flag... 59 | } 60 | 61 | // TODO: Finish this? 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Marathon/Formats/Text/PictureFont.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Formats.Text 2 | { 3 | /// 4 | /// File base for the *.pft format. 5 | /// Used in SONIC THE HEDGEHOG for defining placeholder images for the format. 6 | /// 7 | public class PictureFont : FileBase 8 | { 9 | public PictureFont() { } 10 | 11 | public PictureFont(string file, bool serialise = false) 12 | { 13 | switch (Path.GetExtension(file)) 14 | { 15 | case ".json": 16 | { 17 | Data = JsonDeserialise(file); 18 | 19 | // Save extension-less JSON (exploiting .NET weirdness, because it doesn't omit all extensions). 20 | if (serialise) 21 | Save(Path.GetFileNameWithoutExtension(file)); 22 | 23 | break; 24 | } 25 | 26 | default: 27 | { 28 | Load(file); 29 | 30 | if (serialise) 31 | JsonSerialise(Data); 32 | 33 | break; 34 | } 35 | } 36 | } 37 | 38 | public override string Signature { get; } = "FNTP"; 39 | 40 | public override string Extension { get; } = ".pft"; 41 | 42 | public class FormatData 43 | { 44 | public string Texture; 45 | 46 | public List Entries { get; set; } = []; 47 | 48 | public override string ToString() => Texture; 49 | } 50 | 51 | public FormatData Data { get; set; } = new(); 52 | 53 | public override void Load(Stream stream) 54 | { 55 | BINAReader reader = new(stream); 56 | 57 | reader.ReadSignature(4, Signature); 58 | 59 | uint texturePos = reader.ReadUInt32(); 60 | uint placeholderEntries = reader.ReadUInt32(); 61 | 62 | reader.JumpTo(reader.ReadUInt32(), true); 63 | long position = reader.BaseStream.Position; 64 | 65 | Data.Texture = reader.ReadNullTerminatedString(false, texturePos, true); 66 | 67 | reader.JumpTo(position, false); 68 | for (uint i = 0; i < placeholderEntries; i++) 69 | { 70 | uint placeholderEntry = reader.ReadUInt32(); 71 | 72 | Picture fontPicture = new() 73 | { 74 | X = reader.ReadUInt16(), 75 | Y = reader.ReadUInt16(), 76 | Width = reader.ReadUInt16(), 77 | Height = reader.ReadUInt16() 78 | }; 79 | 80 | position = reader.BaseStream.Position; 81 | 82 | reader.JumpTo(placeholderEntry, true); 83 | fontPicture.Name = reader.ReadNullTerminatedString(); 84 | reader.JumpTo(position, false); 85 | 86 | Data.Entries.Add(fontPicture); 87 | } 88 | } 89 | 90 | public override void Save(Stream stream) 91 | { 92 | BINAWriter writer = new(stream); 93 | 94 | writer.WriteSignature(Signature); 95 | 96 | writer.AddString("textureName", Data.Texture); 97 | 98 | writer.Write((uint)Data.Entries.Count); 99 | writer.AddOffset("placeholderEntriesPos"); 100 | writer.FillOffset("placeholderEntriesPos", true); 101 | 102 | for (int i = 0; i < Data.Entries.Count; i++) 103 | { 104 | writer.AddString($"placeholderName{i}", Data.Entries[i].Name); 105 | writer.Write(Data.Entries[i].X); 106 | writer.Write(Data.Entries[i].Y); 107 | writer.Write(Data.Entries[i].Width); 108 | writer.Write(Data.Entries[i].Height); 109 | } 110 | 111 | writer.FinishWrite(); 112 | } 113 | } 114 | 115 | public class Picture 116 | { 117 | /// 118 | /// The name of the picture. 119 | /// 120 | public string Name { get; set; } 121 | 122 | /// 123 | /// The X position of the top-left corner of the picture. 124 | /// 125 | public ushort X { get; set; } 126 | 127 | /// 128 | /// The Y position of the top-left corner of the picture. 129 | /// 130 | public ushort Y { get; set; } 131 | 132 | /// 133 | /// The width of the picture. 134 | /// 135 | public ushort Width { get; set; } 136 | 137 | /// 138 | /// The height of the picture. 139 | /// 140 | public ushort Height { get; set; } 141 | 142 | public Picture() { } 143 | 144 | public Picture(string in_name, ushort in_x, ushort in_y, ushort in_width, ushort in_height) 145 | { 146 | Name = in_name; 147 | X = in_x; 148 | Y = in_y; 149 | Width = in_width; 150 | Height = in_height; 151 | } 152 | 153 | public override string ToString() => Name; 154 | } 155 | } -------------------------------------------------------------------------------- /Marathon/Helpers/BinaryHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Helpers 2 | { 3 | public class BinaryHelper 4 | { 5 | /// 6 | /// Converts a string of hexadecimal characters to a byte array. 7 | /// 8 | /// Hexadecimal string. 9 | public static byte[] StringToByteArray(string hex) 10 | { 11 | // Remove any spaces in case the string is formatted as such. 12 | hex = hex.Replace(" ", ""); 13 | 14 | return Enumerable.Range(0, hex.Length) 15 | .Where(x => x % 2 == 0) 16 | .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) 17 | .ToArray(); 18 | } 19 | 20 | /// 21 | /// Converts byte length to a Windows-like suffix string. 22 | /// 23 | /// Byte length. 24 | public static string ByteLengthToDecimalString(long length) 25 | { 26 | // Get absolute value. 27 | long absolute_i = length < 0 ? -length : length; 28 | 29 | // Determine the suffix and readable value. 30 | string suffix; 31 | double readable; 32 | 33 | // Exabyte 34 | if (absolute_i >= 0x1000000000000000) 35 | { 36 | suffix = "EB"; 37 | readable = length >> 50; 38 | } 39 | 40 | // Petabyte 41 | else if (absolute_i >= 0x4000000000000) 42 | { 43 | suffix = "PB"; 44 | readable = length >> 40; 45 | } 46 | 47 | // Terabyte 48 | else if (absolute_i >= 0x10000000000) 49 | { 50 | suffix = "TB"; 51 | readable = length >> 30; 52 | } 53 | 54 | // Gigabyte 55 | else if (absolute_i >= 0x40000000) 56 | { 57 | suffix = "GB"; 58 | readable = length >> 20; 59 | } 60 | 61 | // Megabyte 62 | else if (absolute_i >= 0x100000) 63 | { 64 | suffix = "MB"; 65 | readable = length >> 10; 66 | } 67 | 68 | // Kilobyte 69 | else if (absolute_i >= 0x400) 70 | { 71 | suffix = "KB"; 72 | readable = length; 73 | } 74 | 75 | // Byte 76 | else 77 | { 78 | suffix = "KB"; 79 | readable = length % 1024 >= 1 ? length + 1024 - length % 1024 : length - length % 1024; 80 | } 81 | 82 | // Divide by 1024 to get fractional value. 83 | readable /= 1024; 84 | 85 | // Return formatted number with suffix. 86 | return $"{readable:0} {suffix}"; 87 | } 88 | 89 | /// 90 | /// Converts byte length to a rounded up variant. 91 | /// 92 | /// Byte length. 93 | public static double ByteLengthToDecimal(long length, DataLengthType lengthType) 94 | { 95 | double readable = 0; 96 | 97 | switch (lengthType) 98 | { 99 | case DataLengthType.B: 100 | readable = length % 1024 >= 1 ? length + 1024 - length % 1024 : length - length % 1024; 101 | break; 102 | 103 | case DataLengthType.KB: 104 | readable = length; 105 | break; 106 | 107 | case DataLengthType.MB: 108 | readable = length >> 10; 109 | break; 110 | 111 | case DataLengthType.GB: 112 | readable = length >> 20; 113 | break; 114 | 115 | case DataLengthType.TB: 116 | readable = length >> 30; 117 | break; 118 | 119 | case DataLengthType.PB: 120 | readable = length >> 40; 121 | break; 122 | 123 | case DataLengthType.EB: 124 | readable = length >> 50; 125 | break; 126 | } 127 | 128 | return readable / 1024; 129 | } 130 | 131 | public enum DataLengthType 132 | { 133 | B, 134 | KB, 135 | MB, 136 | GB, 137 | TB, 138 | PB, 139 | EB 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Marathon/Helpers/Converters/ByteArrayConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.Helpers.Converters 2 | { 3 | public class ByteArrayConverter : JsonConverter 4 | { 5 | public override bool CanConvert(Type objectType) => objectType == typeof(byte[]); 6 | 7 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 8 | { 9 | if (reader.TokenType == JsonToken.String) 10 | { 11 | string hex = serializer.Deserialize(reader); 12 | 13 | if (!string.IsNullOrEmpty(hex)) 14 | return BinaryHelper.StringToByteArray(hex); 15 | } 16 | 17 | return Array.Empty(); 18 | } 19 | 20 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 21 | => serializer.Serialize(writer, BitConverter.ToString((byte[])value).Replace("-", " ")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Marathon/Helpers/StringHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Marathon.Helpers 4 | { 5 | public class StringHelper 6 | { 7 | /// 8 | /// Returns the full extension from the file name (with a dot prefix). 9 | /// 10 | public static string GetFullExtension(string fileName) 11 | => Regex.Match(Path.GetFileName(fileName), @"\..*").Value; 12 | 13 | /// 14 | /// Returns the file name completely extension-less. 15 | /// 16 | public static string RemoveFullExtension(string fileName) 17 | => Regex.Replace(Path.GetFileName(fileName), @"\..*", string.Empty); 18 | 19 | /// 20 | /// Appends text to the file name before the extension. 21 | /// 22 | /// Full file path. 23 | /// Text to append. 24 | public static string AppendToFileName(string filePath, string text) 25 | => Path.GetFileNameWithoutExtension(filePath) + text + GetFullExtension(filePath); 26 | 27 | /// 28 | /// Returns a new path with the specified filename. 29 | /// 30 | public static string ReplaceFilename(string path, string newFile) 31 | => Path.Combine(Path.GetDirectoryName(path), Path.GetFileName(newFile)); 32 | 33 | /// 34 | /// Parses all line breaks from a string and returns an array of lines. 35 | /// 36 | /// String to parse line breaks from. 37 | public static string[] ParseLineBreaks(string input) 38 | => input.Split(new[] { '\r', '\n' }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Marathon/Helpers/VectorHelper.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace Marathon.Helpers 4 | { 5 | public class VectorHelper 6 | { 7 | /// 8 | /// Parses Vector3 data from a JObject - returns the input object if it's just a Vector3. 9 | /// 10 | public static Vector3 ParseVector3(object data) 11 | { 12 | if (data.GetType().Equals(typeof(JObject))) 13 | { 14 | JObject jsonVector3 = (JObject)data; 15 | 16 | return new Vector3(jsonVector3["X"].Value(), jsonVector3["Y"].Value(), jsonVector3["Z"].Value()); 17 | } 18 | 19 | return (Vector3)data; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Marathon/IO/Interfaces/IArchive.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.IO.Interfaces 2 | { 3 | public interface IArchive 4 | { 5 | IArchiveDirectory Root { get; } 6 | 7 | CompressionLevel CompressionLevel { get; set; } 8 | 9 | void Extract(string location); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Marathon/IO/Interfaces/IArchiveData.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.IO.Interfaces 2 | { 3 | public interface IArchiveData 4 | { 5 | string Name { get; set; } 6 | 7 | string Path { get; internal set; } 8 | 9 | IArchiveData Parent { get; } 10 | 11 | void Extract(string location); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Marathon/IO/Interfaces/IArchiveDirectory.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.IO.Interfaces 2 | { 3 | public interface IArchiveDirectory : IArchiveData, IEnumerable 4 | { 5 | void Add(IArchiveData data, bool overwrite = false); 6 | 7 | bool RemoveFile(string name); 8 | 9 | bool RemoveDirectory(string name); 10 | 11 | bool FileExists(string name); 12 | 13 | bool DirectoryExists(string name); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Marathon/IO/Interfaces/IArchiveFile.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.IO.Interfaces 2 | { 3 | public interface IArchiveFile : IArchiveData 4 | { 5 | uint Offset { get; set; } 6 | 7 | uint Length { get; set; } 8 | 9 | uint UncompressedSize { get; set; } 10 | 11 | byte[] Data { get; set; } 12 | 13 | void Compress(CompressionLevel compressionLevel); 14 | 15 | void Decompress(); 16 | 17 | bool IsCompressed(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Marathon/IO/Interfaces/IHeader.cs: -------------------------------------------------------------------------------- 1 | namespace Marathon.IO.Interfaces 2 | { 3 | public interface IHeader 4 | { 5 | /// 6 | /// Reads the header. 7 | /// 8 | void Read(BinaryReaderEx reader); 9 | 10 | /// 11 | /// Writes null bytes to be filled in later with header data by FinishWrite and sets the offset of the given writer if necessary. 12 | /// This should be used before writing anything else to the file. 13 | /// 14 | void PrepareWrite(BinaryWriterEx writer); 15 | 16 | /// 17 | /// Writes the actual header data. 18 | /// This should be used after all other data has been written to the file - including the footer. 19 | /// This does not automatically jump back to the beginning of the stream! 20 | /// 21 | void FinishWrite(BinaryWriterEx writer); 22 | } 23 | } -------------------------------------------------------------------------------- /Marathon/Interfaces/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Marathon.Interfaces 4 | { 5 | public enum LogLevel 6 | { 7 | /// 8 | /// Unimportant logging - usually reserved for debugging. 9 | /// Colour: White 10 | /// 11 | None, 12 | 13 | /// 14 | /// General logging - used for user output. 15 | /// Colour: Green 16 | /// 17 | Utility, 18 | 19 | /// 20 | /// Warnings - reserved for minor issues that aren't critical. 21 | /// Colour: Yellow 22 | /// 23 | Warning, 24 | 25 | /// 26 | /// Errors - reserved for fatal issues. 27 | /// Colour: Red 28 | /// 29 | Error 30 | } 31 | 32 | public interface ILogger 33 | { 34 | void Log(string message, LogLevel level, [CallerMemberName] string caller = null); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Marathon/Logger.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Marathon.Interfaces; 3 | 4 | namespace Marathon 5 | { 6 | public class ConsoleLogger : ILogger 7 | { 8 | /// 9 | /// Logs information to the console output. 10 | /// 11 | /// Message body. 12 | /// Level of importance for logging. 13 | /// Function that called the log. 14 | public void Log(string message, LogLevel level, string caller) 15 | { 16 | // Store original console colour. 17 | var oldColour = Console.ForegroundColor; 18 | 19 | switch (level) 20 | { 21 | case LogLevel.Warning: 22 | Console.ForegroundColor = ConsoleColor.Yellow; 23 | break; 24 | 25 | case LogLevel.Error: 26 | Console.ForegroundColor = ConsoleColor.Red; 27 | break; 28 | 29 | case LogLevel.Utility: 30 | Console.ForegroundColor = ConsoleColor.Green; 31 | break; 32 | } 33 | 34 | Console.WriteLine(string.IsNullOrEmpty(caller) ? message : $"[{caller}] {message}"); 35 | 36 | // Restore original console colour. 37 | Console.ForegroundColor = oldColour; 38 | } 39 | } 40 | 41 | public static class Logger 42 | { 43 | private static List _handlers = new() { new ConsoleLogger() }; 44 | 45 | public static void Add(ILogger logger) 46 | => _handlers.Add(logger); 47 | 48 | public static bool Remove(ILogger logger) 49 | => _handlers.Remove(logger); 50 | 51 | /// 52 | /// Logs information to the console output. 53 | /// 54 | /// Message body. 55 | /// Level of importance for logging. 56 | /// Function that called the log. 57 | public static void Log(string message, LogLevel level = LogLevel.Utility, [CallerMemberName] string caller = null) 58 | { 59 | foreach (var logger in _handlers) 60 | logger.Log(message, level, caller); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Marathon/Marathon.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | true 6 | 1.0.0 7 | Hyper, Knuxfan24 8 | 9 | A library for SONIC THE HEDGEHOG (2006) file formats. 10 | https://github.com/hyperbx/Marathon 11 | https://github.com/hyperbx/Marathon 12 | api;marathon;sonic-the-hedgehog;modding-tools;sonic-06;sonicnext 13 | MIT 14 | Marathon.png 15 | BE32.$(AssemblyName) 16 | 17 | 18 | 19 | 20 | True 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------