diff --git a/Dockerfile b/Dockerfile
index 0ff6728..ec7796f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,9 +1,28 @@
+# Build stage
FROM rust:latest AS builder
WORKDIR /app
+
+# Install nightly Rust
+RUN rustup install nightly
+
+# Copy source
COPY . .
-RUN cargo build --release --bin medal
-RUN strip target/release/medal
+# Build with nightly and strip binary
+RUN cargo +nightly build --release --bin medal && \
+ strip target/release/medal
+
+# Runtime stage (minimal size)
FROM debian:12-slim AS runtime
-COPY --from=builder /app/target/release/medal /bin/medal
-ENTRYPOINT ["/bin/medal"]
\ No newline at end of file
+
+# Install ca-certificates for HTTPS requests
+RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
+
+# Copy binary from builder
+COPY --from=builder /app/target/release/medal /usr/local/bin/medal
+
+# Expose port
+EXPOSE 3000
+
+# Run the web server
+ENTRYPOINT ["/usr/local/bin/medal", "serve", "--port", "3000"]
diff --git a/bot/.gitattributes b/bot/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/bot/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/bot/.gitignore b/bot/.gitignore
new file mode 100644
index 0000000..9491a2f
--- /dev/null
+++ b/bot/.gitignore
@@ -0,0 +1,363 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
\ No newline at end of file
diff --git a/bot/Dockerfile b/bot/Dockerfile
new file mode 100644
index 0000000..14ee12b
--- /dev/null
+++ b/bot/Dockerfile
@@ -0,0 +1,43 @@
+# Stage 1: Build MoonsecDeobfuscator
+FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS moonsec-builder
+WORKDIR /build
+COPY . .
+RUN dotnet publish -c Release -o /app
+
+# Stage 2: Clone & Build Medal (Rust)
+FROM rust:alpine AS medal-builder
+WORKDIR /build
+RUN apk add --no-cache git build-base
+RUN rustup install nightly
+RUN git clone https://github.com/xConixyCEO/medal.git
+WORKDIR /build/medal
+RUN cargo +nightly build --release --bin medal
+
+# Stage 3: Runtime (with Lua!)
+FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine
+WORKDIR /app
+
+# Install dependencies and fix Lua library loading
+RUN apk add --no-cache \
+ curl \
+ lua5.4 \
+ lua5.4-dev \
+ icu-libs && \
+ # Create symlinks for NLua
+ ln -sf /usr/lib/liblua5.4.so /usr/lib/liblua54.so && \
+ # Ensure library path includes /usr/lib
+ echo "/usr/lib" > /etc/ld-musl-x86_64.path
+
+# Enable globalization support
+ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=0
+
+# Copy bot files
+COPY --from=moonsec-builder /app/* ./
+
+# Copy Medal binary
+COPY --from=medal-builder /build/medal/target/release/medal ./medal
+
+# Make executables runnable
+RUN chmod +x ./MoonsecDeobfuscator ./medal
+
+CMD ["dotnet", "MoonsecDeobfuscator.dll"]
diff --git a/bot/LICENSE b/bot/LICENSE
new file mode 100644
index 0000000..e72bfdd
--- /dev/null
+++ b/bot/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
\ No newline at end of file
diff --git a/bot/MoonsecDeobfuscator.csproj b/bot/MoonsecDeobfuscator.csproj
new file mode 100644
index 0000000..0080d39
--- /dev/null
+++ b/bot/MoonsecDeobfuscator.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+ 0200;8600;8618;8625;8632;3021
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bot/MoonsecDeobfuscator.sln b/bot/MoonsecDeobfuscator.sln
new file mode 100644
index 0000000..3a18d33
--- /dev/null
+++ b/bot/MoonsecDeobfuscator.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36705.20 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonsecDeobfuscator", "MoonsecDeobfuscator.csproj", "{9C5F7678-617D-4024-BB68-8C64DE6FF97F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9C5F7678-617D-4024-BB68-8C64DE6FF97F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9C5F7678-617D-4024-BB68-8C64DE6FF97F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9C5F7678-617D-4024-BB68-8C64DE6FF97F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9C5F7678-617D-4024-BB68-8C64DE6FF97F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4ED42D1F-1B4B-4B22-91AB-890F2C631550}
+ EndGlobalSection
+EndGlobal
diff --git a/bot/README.md b/bot/README.md
new file mode 100644
index 0000000..452658a
--- /dev/null
+++ b/bot/README.md
@@ -0,0 +1,82 @@
+# Moonsec Deobfuscator
+This project aims to deobfuscate Lua scripts obfuscated using **MoonSec V3**. The deobfuscation process produces a **Lua 5.1 bytecode file**, which you can then decompile with your favorite Lua decompiler. Optionally, the tool can also generate a **disassembly** of the bytecode.
+
+## Usage
+### Installation & Building
+To install and build run the following commands.
+```bash
+git clone https://github.com/tupsutumppu/MoonsecDeobfuscator.git
+cd MoonsecDeobfuscator
+dotnet build -c Release
+```
+### Command-Line
+```plaintext
+Devirtualize and dump bytecode to file:
+ -dev -i -o
+
+Devirtualize and dump bytecode disassembly to file:
+ -dis -i -o
+```
+## Examples
+Here we have a Lua script. Let's see what it looks like after deobfuscation.
+```Lua
+local chars = { "H", "e", "l", "l", "o ", "W", "o", "r", "l", "d", "!" }
+local result = ""
+for i = 1, #chars do
+ result = result .. chars[i]
+end
+print(result)
+```
+After deobfuscation
+```Lua
+local v0 = {
+ "H",
+ "e",
+ "l",
+ "l",
+ "o ",
+ "W",
+ "o",
+ "r",
+ "l",
+ "d",
+ "!"
+};
+local v1 = "";
+for v2 = 1, #v0 do
+ v1 = v1 .. v0[v2];
+end;
+print(v1);
+```
+Disassembly
+```Lua
+function func_f99e02d1(...)
+ [Slots: 12, Upvalues: 0, Constants: 12]
+ [ 0] NewTable | 0 | 11 | 0 | R0 = {}
+ [ 1] LoadK | 1 | 0 | 0 | R1 = "H"
+ [ 2] LoadK | 2 | 1 | 0 | R2 = "e"
+ [ 3] LoadK | 3 | 2 | 0 | R3 = "l"
+ [ 4] LoadK | 4 | 2 | 0 | R4 = "l"
+ [ 5] LoadK | 5 | 3 | 0 | R5 = "o "
+ [ 6] LoadK | 6 | 4 | 0 | R6 = "W"
+ [ 7] LoadK | 7 | 5 | 0 | R7 = "o"
+ [ 8] LoadK | 8 | 6 | 0 | R8 = "r"
+ [ 9] LoadK | 9 | 2 | 0 | R9 = "l"
+ [ 10] LoadK | 10 | 7 | 0 | R10 = "d"
+ [ 11] LoadK | 11 | 8 | 0 | R11 = "!"
+ [ 12] SetList | 0 | 11 | 1 |
+ [ 13] LoadK | 1 | 9 | 0 | R1 = ""
+ [ 14] LoadK | 2 | 10 | 0 | R2 = 1
+ [ 15] Len | 3 | 0 | 0 | R3 = #R0
+ [ 16] LoadK | 4 | 10 | 0 | R4 = 1
+ [ 17] ForPrep | 2 | 3 | 0 | R2 -= R4; PC += 3
+ [ 18] Move | 6 | 1 | 0 | R6 = R1
+ [ 19] GetTable | 7 | 0 | 5 | R7 = R0[R5]
+ [ 20] Concat | 1 | 6 | 7 | R1 = R6 .. R7
+ [ 21] ForLoop | 2 | -4 | 0 | R2 += R4; if loop continues then PC += -4; R5 = R2;
+ [ 22] GetGlobal | 2 | 11 | 0 | R2 = print
+ [ 23] Move | 3 | 1 | 0 | R3 = R1
+ [ 24] Call | 2 | 2 | 1 | R2(R3)
+ [ 25] Return | 0 | 1 | 0 | return
+end
+```
diff --git a/bot/render.yaml b/bot/render.yaml
new file mode 100644
index 0000000..6fc8b85
--- /dev/null
+++ b/bot/render.yaml
@@ -0,0 +1,15 @@
+services:
+ - type: web
+ name: moonsec-deobfuscator-bot
+ env: docker
+ dockerfilePath: ./Dockerfile
+ plan: free
+ branch: main
+ autoDeploy: true
+ dockerBuildTimeoutSeconds: 1800
+ healthCheckPath: /
+ envVars:
+ - key: DISCORD_TOKEN
+ sync: false
+ - key: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
+ value: "1"
diff --git a/bot/src/Deobfuscation/Bytecode/Deserializer.cs b/bot/src/Deobfuscation/Bytecode/Deserializer.cs
new file mode 100644
index 0000000..d9983d9
--- /dev/null
+++ b/bot/src/Deobfuscation/Bytecode/Deserializer.cs
@@ -0,0 +1,142 @@
+using MoonsecDeobfuscator.Bytecode.Models;
+using MoonsecDeobfuscator.Deobfuscation.Utils;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Bytecode;
+
+public enum ProtoStep
+{
+ Instructions,
+ Constants,
+ Functions,
+ NumParams,
+
+ StringConstant,
+ NumberConstant,
+ BooleanConstant
+}
+
+public class Deserializer(byte[] bytes, Context ctx) : BinaryReader(new MemoryStream(bytes))
+{
+ private List ReadInstructions(Function function)
+ {
+ var size = ReadInt32();
+ var instructions = new List(size);
+
+ for (var i = 0; i < size; i++)
+ {
+ var descriptor = ReadByte();
+
+ if (GetBits(descriptor, 1, 1) != 0)
+ continue;
+
+ var type = GetBits(descriptor, 2, 3);
+ var mask = GetBits(descriptor, 4, 6);
+ var instruction = new Instruction
+ {
+ OpNum = ReadInt16(),
+ A = ReadInt16(),
+ PC = i,
+ Function = function
+ };
+
+ switch (type)
+ {
+ case 0:
+ instruction.B = ReadInt16();
+ instruction.C = ReadInt16();
+ break;
+ case 1:
+ instruction.B = ReadInt32();
+ break;
+ case 2:
+ instruction.B = ReadInt32() - (1 << 16);
+ break;
+ case 3:
+ instruction.B = ReadInt32() - (1 << 16);
+ instruction.C = ReadInt16();
+ break;
+ }
+
+ instruction.IsKA = GetBits(mask, 1, 1) == 1;
+ instruction.IsKB = GetBits(mask, 2, 2) == 1;
+ instruction.IsKC = GetBits(mask, 3, 3) == 1;
+
+ instructions.Add(instruction);
+ }
+
+ return instructions;
+ }
+
+ private List ReadPrototypes()
+ {
+ var size = ReadInt32();
+ var prototypes = new List(size);
+
+ for (var i = 0; i < size; i++)
+ prototypes.Add(ReadFunction());
+
+ return prototypes;
+ }
+
+ private List ReadConstants()
+ {
+ var size = ReadInt32();
+ var constants = new List(size);
+
+ for (var i = 0; i < size; i++)
+ {
+ var typeFlag = ReadByte();
+
+ if (!ctx.ConstantFormat.TryGetValue(typeFlag, out var type))
+ {
+ constants.Add(new NilConstant());
+ continue;
+ }
+
+ switch (type)
+ {
+ case ProtoStep.BooleanConstant:
+ constants.Add(new BooleanConstant(ReadBoolean()));
+ break;
+ case ProtoStep.NumberConstant:
+ constants.Add(new NumberConstant(ReadDouble()));
+ break;
+ case ProtoStep.StringConstant:
+ var str = StringDecoding.DecodeConstant(ctx.ConstantKey, ReadBytes(ReadInt32()));
+ constants.Add(new StringConstant(str));
+ break;
+ }
+ }
+
+ return constants;
+ }
+
+ public Function ReadFunction()
+ {
+ var function = new Function();
+
+ foreach (var step in ctx.ProtoFormat)
+ {
+ switch (step)
+ {
+ case ProtoStep.Constants:
+ function.Constants = ReadConstants();
+ break;
+ case ProtoStep.Instructions:
+ function.Instructions = ReadInstructions(function);
+ break;
+ case ProtoStep.NumParams:
+ function.NumParams = ReadByte();
+ break;
+ case ProtoStep.Functions:
+ function.Functions = ReadPrototypes();
+ break;
+ }
+ }
+
+ return function;
+ }
+
+ private static int GetBits(int source, int start, int end) =>
+ (source >> (start - 1)) & ((1 << (end - start + 1)) - 1);
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Bytecode/Disassembler.cs b/bot/src/Deobfuscation/Bytecode/Disassembler.cs
new file mode 100644
index 0000000..f75338b
--- /dev/null
+++ b/bot/src/Deobfuscation/Bytecode/Disassembler.cs
@@ -0,0 +1,212 @@
+using System.Text;
+using MoonsecDeobfuscator.Bytecode.Models;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Bytecode;
+
+public class Disassembler(Function rootFunction)
+{
+ private readonly StringBuilder _builder = new();
+
+ public string Disassemble()
+ {
+ _builder.AppendLine("-- wsg hello join galactic services rn!");
+ DisassembleFunction(rootFunction);
+ return _builder.ToString();
+ }
+
+ private void DisassembleFunction(Function function)
+ {
+ _builder.Append($"function {function.Name}(");
+
+ for (var i = 0; i < function.NumParams; i++)
+ {
+ _builder.Append($"R{i}");
+
+ if (i + 1 < function.NumParams)
+ _builder.Append(", ");
+ }
+
+ if (function.IsVarArgFlag == 2)
+ _builder.Append(function.NumParams > 0 ? ", ..." : "...");
+
+ _builder.AppendLine(")");
+ _builder.AppendLine(
+ $"\t[Slots: {function.MaxStackSize}, Upvalues: {function.NumUpvalues}, Constants: {function.Constants.Count}]");
+
+ var instructions = function.Instructions;
+
+ for (var i = 0; i < instructions.Count; i++)
+ {
+ _builder.Append($"\t[{i,4}]\t");
+ DisassembleInstruction(instructions[i]);
+ }
+
+ _builder.AppendLine("end");
+
+ foreach (var childFunction in function.Functions)
+ DisassembleFunction(childFunction);
+ }
+
+ private void DisassembleInstruction(Instruction instruction)
+ {
+ var A = instruction.A;
+ var B = instruction.B;
+ var C = instruction.C;
+
+ if (instruction.OpCode != OpCode.Unknown)
+ _builder.Append($"{instruction.OpCode,12}\t| {A,4} | {B,4} | {C,4} |");
+ else if (instruction.IsDead)
+ _builder.Append($"{"DEAD",12}\t| {"-",4} | {"-",4} | {"-",4} |");
+ else
+ _builder.Append($"{instruction.OpNum,12}\t| {A,4} | {B,4} | {C,4} |");
+
+ if (instruction.OpCode != OpCode.Unknown)
+ {
+ _builder.Append('\t');
+ _builder.Append(GenAnnotation(instruction));
+ }
+
+ _builder.AppendLine();
+ }
+
+ private static string GenAnnotation(Instruction instruction)
+ {
+ var A = instruction.A;
+ var B = instruction.B;
+ var C = instruction.C;
+ var function = instruction.Function;
+
+ switch (instruction.OpCode)
+ {
+ case OpCode.GetGlobal:
+ return $"R{A} = {((StringConstant) function.Constants[B]).Value}";
+ case OpCode.SetGlobal:
+ return $"{((StringConstant) function.Constants[B]).Value} = R{A}";
+ case OpCode.LoadK:
+ return $"R{A} = {function.Constants.ElementAtOrDefault(B)}";
+ case OpCode.LoadNil:
+ return B - A == 0 ? $"R{A} = nil" : $"R{A}->R{B} = nil";
+ case OpCode.LoadBool:
+ var value = B != 0 ? "true" : "false";
+ var skip = C != 0 ? "; PC += 1" : "";
+ return $"R{A} = {value}{skip}";
+ case OpCode.Move:
+ return $"R{A} = R{B}";
+ case OpCode.Jmp:
+ return $"PC += {B}";
+ case OpCode.GetUpval:
+ return $"R{A} = UPVALUE_{B}";
+ case OpCode.SetUpval:
+ return $"UPVALUE_{B} = R{A}";
+ case OpCode.GetTable:
+ return $"R{A} = R{B}[{RegisterOrConstant(C, function)}]";
+ case OpCode.SetTable:
+ return $"R{A}[{RegisterOrConstant(B, function)}] = {RegisterOrConstant(C, function)}";
+ case OpCode.NewTable:
+ return $"R{A} = {{}}";
+ case OpCode.Self:
+ return $"R{A + 1} = R{B}; R{A} = R{B}[{RegisterOrConstant(C, function)}]";
+ case OpCode.Add:
+ return $"R{A} = {RegisterOrConstant(B, function)} + {RegisterOrConstant(C, function)}";
+ case OpCode.Sub:
+ return $"R{A} = {RegisterOrConstant(B, function)} - {RegisterOrConstant(C, function)}";
+ case OpCode.Mul:
+ return $"R{A} = {RegisterOrConstant(B, function)} * {RegisterOrConstant(C, function)}";
+ case OpCode.Div:
+ return $"R{A} = {RegisterOrConstant(B, function)} / {RegisterOrConstant(C, function)}";
+ case OpCode.Mod:
+ return $"R{A} = {RegisterOrConstant(B, function)} % {RegisterOrConstant(C, function)}";
+ case OpCode.Pow:
+ return $"R{A} = {RegisterOrConstant(B, function)} ^ {RegisterOrConstant(C, function)}";
+ case OpCode.Unm:
+ return $"R{A} = -R{B}";
+ case OpCode.Not:
+ return $"R{A} = not R{B}";
+ case OpCode.Len:
+ return $"R{A} = #R{B}";
+ case OpCode.Concat:
+ var result = new StringBuilder();
+ result.Append($"R{A} = ");
+
+ for (var i = B; i <= C; i++)
+ {
+ result.Append($"R{i}");
+
+ if (i + 1 <= C)
+ result.Append(" .. ");
+ }
+
+ return result.ToString();
+ case OpCode.Eq:
+ case OpCode.Lt:
+ case OpCode.Le:
+ var op = instruction.OpCode switch
+ {
+ OpCode.Eq => A == 0 ? "==" : "~=",
+ OpCode.Lt => A == 0 ? "<" : ">",
+ OpCode.Le => A == 0 ? "<=" : ">=",
+ _ => " ?? "
+ };
+ return $"if {RegisterOrConstant(B, function)} {op} {RegisterOrConstant(C, function)} then PC += 1";
+ case OpCode.Test:
+ return $"if {(C == 0 ? $"not R{A}" : $"R{A}")} then PC += 1";
+ case OpCode.TestSet:
+ return $"if {(C == 0 ? $"not R{B}" : $"R{B}")} then R{A} = R{B} else PC += 1";
+ case OpCode.Call:
+ var lhs = C switch
+ {
+ 0 => $"R{A}->top = ",
+ 1 => "",
+ _ => string.Join(", ", Enumerable.Range(0, C - 1).Select(i => $"R{A + i}")) + " = "
+ };
+ var args = B switch
+ {
+ 0 => $"R{A + 1}->top",
+ 1 => "",
+ _ => string.Join(", ", Enumerable.Range(1, B - 1).Select(i => $"R{A + i}"))
+ };
+
+ var r = $"{lhs}R{A}({args})";
+ return r;
+ case OpCode.TailCall:
+ return B switch
+ {
+ > 1 => $"return R{A}({string.Join(", ", Enumerable.Range(1, B - 1).Select(i => $"R{A + i}"))})",
+ 0 => $"return R{A}()",
+ 1 => $"return R{A}(R{A + 1})",
+ _ => $"return R{A}()"
+ };
+ case OpCode.Return:
+ return B switch
+ {
+ 0 => $"return R{A}->top",
+ 1 => "return",
+ _ => $"return {string.Join(", ", Enumerable.Range(0, B - 1).Select(i => $"R{A + i}"))}"
+ };
+ case OpCode.ForLoop:
+ return $"R{A} += R{A + 2}; if loop continues then PC += {B}; R{A + 3} = R{A};";
+ case OpCode.ForPrep:
+ return $"R{A} -= R{A + 2}; PC += {B}";
+ case OpCode.TForLoop:
+ var targets = string.Join(", ", Enumerable.Range(3, C).Select(i => $"R{A + i}"));
+ var call = $"R{A}(R{A + 1}, R{A + 2})";
+ var body = $"if R{A + 3} ~= nil then R{A + 2} = R{A + 3} else PC += 1 end";
+ return $"{targets} = {call}; {body}";
+ case OpCode.Closure:
+ return $"R{A} = {function.Functions[B].Name}";
+ case OpCode.VarArg:
+ var a = B switch
+ {
+ 0 => $"R{A}->top",
+ 1 => "",
+ _ => string.Join(", ", Enumerable.Range(0, B - 1).Select(i => $"R{A + i}"))
+ };
+ return $"{a} = ...";
+ }
+
+ return "";
+ }
+
+ private static string RegisterOrConstant(int register, Function function) =>
+ register > 255 ? function.Constants[register - 256].ToString()! : $"R{register}";
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Bytecode/Models/Constant.cs b/bot/src/Deobfuscation/Bytecode/Models/Constant.cs
new file mode 100644
index 0000000..e7b9428
--- /dev/null
+++ b/bot/src/Deobfuscation/Bytecode/Models/Constant.cs
@@ -0,0 +1,29 @@
+namespace MoonsecDeobfuscator.Bytecode.Models;
+
+public abstract class Constant;
+
+public class StringConstant(string value) : Constant
+{
+ public readonly string Value = value;
+
+ public override string ToString() => $"\"{Value}\"";
+}
+
+public class NumberConstant(double value) : Constant
+{
+ public readonly double Value = value;
+
+ public override string ToString() => Value.ToString();
+}
+
+public class BooleanConstant(bool value) : Constant
+{
+ public readonly bool Value = value;
+
+ public override string ToString() => Value.ToString().ToLower();
+}
+
+public class NilConstant : Constant
+{
+ public override string ToString() => "nil";
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Bytecode/Models/Function.cs b/bot/src/Deobfuscation/Bytecode/Models/Function.cs
new file mode 100644
index 0000000..bbc4b3c
--- /dev/null
+++ b/bot/src/Deobfuscation/Bytecode/Models/Function.cs
@@ -0,0 +1,11 @@
+namespace MoonsecDeobfuscator.Bytecode.Models;
+
+public class Function
+{
+ public List Instructions = null!;
+ public List Constants = null!;
+ public List Functions = null!;
+
+ public byte MaxStackSize, NumParams, NumUpvalues, IsVarArgFlag;
+ public readonly string Name = $"func_{Guid.NewGuid().ToString("N")[..8]}";
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Bytecode/Models/Instruction.cs b/bot/src/Deobfuscation/Bytecode/Models/Instruction.cs
new file mode 100644
index 0000000..283e1ad
--- /dev/null
+++ b/bot/src/Deobfuscation/Bytecode/Models/Instruction.cs
@@ -0,0 +1,75 @@
+namespace MoonsecDeobfuscator.Bytecode.Models;
+
+public class Instruction
+{
+ public int A, B, C, OpNum, PC;
+ public bool IsKA, IsKB, IsKC, IsDead;
+
+ public Function Function = null!;
+ public OpCode OpCode = OpCode.Unknown;
+
+ public override string ToString()
+ {
+ if (OpCode != OpCode.Unknown)
+ return $"{OpCode,12}\t| {A,4} | {B,4} | {C,4} |";
+
+ if (IsDead)
+ return $"{"DEAD",12}\t| {"-",4} | {"-",4} | {"-",4} |";
+
+ return $"{OpNum,12}\t| {A,4} | {B,4} | {C,4} |";
+ }
+
+ public OpType GetOpType()
+ {
+ switch (OpCode)
+ {
+ case OpCode.Move:
+ case OpCode.LoadNil:
+ case OpCode.GetUpval:
+ case OpCode.SetUpval:
+ case OpCode.Unm:
+ case OpCode.Not:
+ case OpCode.Len:
+ case OpCode.Return:
+ case OpCode.VarArg:
+ return OpType.AB;
+ case OpCode.LoadK:
+ case OpCode.GetGlobal:
+ case OpCode.SetGlobal:
+ case OpCode.Closure:
+ return OpType.ABx;
+ case OpCode.LoadBool:
+ case OpCode.GetTable:
+ case OpCode.SetTable:
+ case OpCode.Add:
+ case OpCode.Sub:
+ case OpCode.Mul:
+ case OpCode.Div:
+ case OpCode.Mod:
+ case OpCode.Pow:
+ case OpCode.Concat:
+ case OpCode.Call:
+ case OpCode.TailCall:
+ case OpCode.Self:
+ case OpCode.Eq:
+ case OpCode.Lt:
+ case OpCode.Le:
+ case OpCode.TestSet:
+ case OpCode.NewTable:
+ case OpCode.SetList:
+ return OpType.ABC;
+ case OpCode.Jmp:
+ return OpType.sBx;
+ case OpCode.Test:
+ case OpCode.TForLoop:
+ return OpType.AC;
+ case OpCode.ForPrep:
+ case OpCode.ForLoop:
+ return OpType.AsBx;
+ case OpCode.Close:
+ return OpType.A;
+ }
+
+ throw new Exception("Invalid opcode in GetOpType");
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Bytecode/Models/OpCode.cs b/bot/src/Deobfuscation/Bytecode/Models/OpCode.cs
new file mode 100644
index 0000000..eb4aa98
--- /dev/null
+++ b/bot/src/Deobfuscation/Bytecode/Models/OpCode.cs
@@ -0,0 +1,132 @@
+namespace MoonsecDeobfuscator.Bytecode.Models;
+
+public enum OpCode
+{
+ // MOVE A B R(A) := R(B)
+ Move,
+
+ // LOADK A Bx R(A) := Kst(Bx)
+ LoadK,
+
+ // LOADBOOL A B C R(A) := (Bool)B; if (C) PC++
+ LoadBool,
+
+ // LOADNIL A B R(A) := ... := R(B) := nil
+ LoadNil,
+
+ // GETUPVAL A B R(A) := UpValue[B]
+ GetUpval,
+
+ // GETGLOBAL A Bx R(A) := Gbl[Kst(Bx)]
+ GetGlobal,
+
+ // GETTABLE A B C R(A) := R(B)[RK(C)]
+ GetTable,
+
+ // SETGLOBAL A Bx Gbl[Kst(Bx)] := R(A)
+ SetGlobal,
+
+ // SETUPVAL A B UpValue[B] := R(A)
+ SetUpval,
+
+ // SETTABLE A B C R(A)[RK(B)] := RK(C)
+ SetTable,
+
+ // NEWTABLE A B C R(A) := {} (size = B,C)
+ NewTable,
+
+ // SELF A B C R(A+1) := R(B); R(A) := R(B)[RK(C)]
+ Self,
+
+ // ADD A B C R(A) := RK(B) + RK(C)
+ Add,
+
+ // SUB A B C R(A) := RK(B) – RK(C)
+ Sub,
+
+ // MUL A B C R(A) := RK(B) * RK(C)
+ Mul,
+
+ // DIV A B C R(A) := RK(B) / RK(C)
+ Div,
+
+ // MOD A B C R(A) := RK(B) % RK(C)
+ Mod,
+
+ // POW A B C R(A) := RK(B) ^ RK(C)
+ Pow,
+
+ // UNM A B R(A) := -R(B)
+ Unm,
+
+ // NOT A B R(A) := not R(B)
+ Not,
+
+ // LEN A B R(A) := length of R(B)
+ Len,
+
+ // CONCAT A B C R(A) := R(B).. ... ..R(C)
+ Concat,
+
+ // JMP sBx PC += sBx
+ Jmp,
+
+ // EQ A B C if ((RK(B) == RK(C)) ~= A) then PC++
+ Eq,
+
+ // LT A B C if ((RK(B) < RK(C)) ~= A) then PC++
+ Lt,
+
+ // LE A B C if ((RK(B) <= RK(C)) ~= A) then PC++
+ Le,
+
+ // TEST A C if not (R(A) <=> C) then PC++
+ Test,
+
+ // TESTSET A B C if (R(B) <=> C) then R(A) := R(B) else PC++
+ TestSet,
+
+ // CALL A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
+ Call,
+
+ // TAILCALL A B C return R(A)(R(A+1), ... ,R(A+B-1))
+ TailCall,
+
+ // RETURN A B return R(A), ... ,R(A+B-2)
+ Return,
+
+ /*
+ FORLOOP A sBx R(A) += R(A+2)
+ if R(A) = R(A+1) then {
+ PC += sBx; R(A+3) = R(A)
+ }
+ */
+ ForLoop,
+
+ // FORPREP A sBx R(A) -= R(A+2); PC += sBx
+ ForPrep,
+
+ /*
+ TFORLOOP A C R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));
+ if R(A+3) ~= nil then {
+ R(A+2) = R(A+3);
+ } else {
+ PC++;
+ }
+ */
+ TForLoop,
+
+ // SETLIST A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B
+ SetList,
+
+ // CLOSE A close all variables in the stack up to (>=) R(A)
+ Close,
+
+ // CLOSURE A Bx R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))
+ Closure,
+
+ // VARARG A B R(A), R(A+1), ..., R(A+B-1) = vararg
+ VarArg,
+
+ Unknown
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Bytecode/Models/OpType.cs b/bot/src/Deobfuscation/Bytecode/Models/OpType.cs
new file mode 100644
index 0000000..e6a595f
--- /dev/null
+++ b/bot/src/Deobfuscation/Bytecode/Models/OpType.cs
@@ -0,0 +1,13 @@
+namespace MoonsecDeobfuscator.Bytecode.Models;
+
+public enum OpType
+{
+ AB,
+ ABx,
+ ABC,
+ sBx,
+ AC,
+ AsBx,
+ A,
+ None
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Bytecode/Serializer.cs b/bot/src/Deobfuscation/Bytecode/Serializer.cs
new file mode 100644
index 0000000..da194f6
--- /dev/null
+++ b/bot/src/Deobfuscation/Bytecode/Serializer.cs
@@ -0,0 +1,139 @@
+using System.Text;
+using MoonsecDeobfuscator.Bytecode.Models;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Bytecode;
+
+public class Serializer(Stream stream) : BinaryWriter(stream)
+{
+ private void WriteString(string data)
+ {
+ var bytes = Encoding.UTF8.GetBytes(data);
+
+ Write((ulong) (bytes.Length + 1));
+ Write(bytes);
+ Write((byte) 0);
+ }
+
+ private void WriteFunction(Function function)
+ {
+ WriteString("");
+ Write(0);
+ Write(0);
+
+ Write(function.NumUpvalues);
+ Write(function.NumParams);
+ Write(function.IsVarArgFlag);
+ Write(function.MaxStackSize);
+
+ WriteInstructions(function.Instructions);
+ WriteConstants(function.Constants);
+ WriteFunctions(function.Functions);
+
+ Write(0);
+ Write(0);
+ Write(0);
+ }
+
+ private void WriteFunctions(List functions)
+ {
+ Write(functions.Count);
+
+ foreach (var function in functions)
+ WriteFunction(function);
+ }
+
+ private void WriteConstants(List constants)
+ {
+ Write(constants.Count);
+
+ foreach (var constant in constants)
+ {
+ switch (constant)
+ {
+ case StringConstant sc:
+ Write((byte) 4);
+ WriteString(sc.Value);
+ break;
+ case NumberConstant nc:
+ Write((byte) 3);
+ Write(nc.Value);
+ break;
+ case BooleanConstant bc:
+ Write((byte) 1);
+ Write((byte) (bc.Value ? 1 : 0));
+ break;
+ default:
+ Write((byte) 0);
+ break;
+ }
+ }
+ }
+
+ private void WriteInstructions(List instructions)
+ {
+ Write(instructions.Count);
+
+ foreach (var instruction in instructions)
+ {
+ var data = 0u;
+ data |= (uint) instruction.OpCode;
+
+ switch (instruction.GetOpType())
+ {
+ case OpType.A:
+ data |= Mask(instruction.A, 8) << 6;
+ break;
+ case OpType.AB:
+ data |= Mask(instruction.A, 8) << 6;
+ data |= Mask(instruction.B, 9) << 23;
+ break;
+ case OpType.AC:
+ data |= Mask(instruction.A, 8) << 6;
+ data |= Mask(instruction.C, 9) << 14;
+ break;
+ case OpType.ABC:
+ data |= Mask(instruction.A, 8) << 6;
+ data |= Mask(instruction.B, 9) << 23;
+ data |= Mask(instruction.C, 9) << 14;
+ break;
+ case OpType.ABx:
+ data |= Mask(instruction.A, 8) << 6;
+ data |= Mask(instruction.B, 18) << 14;
+ break;
+ case OpType.AsBx:
+ data |= Mask(instruction.A, 8) << 6;
+ data |= Mask(instruction.B + 131_071, 18) << 14;
+ break;
+ case OpType.sBx:
+ data |= Mask(instruction.B + 131_071, 18) << 14;
+ break;
+ }
+
+ Write(data);
+ }
+ }
+
+ private void WriteHeader()
+ {
+ Write(
+ [
+ 0x1B, 0x4C, 0x75, 0x61,
+ 0x51,
+ 0,
+ 1,
+ 4,
+ 8,
+ 4,
+ 8,
+ 0
+ ]);
+ }
+
+ public void Serialize(Function function)
+ {
+ WriteHeader();
+ WriteFunction(function);
+ }
+
+ private static uint Mask(int value, int bits) => (uint) value & ((1u << bits) - 1);
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/BytecodeDeobfuscator.cs b/bot/src/Deobfuscation/BytecodeDeobfuscator.cs
new file mode 100644
index 0000000..360e29a
--- /dev/null
+++ b/bot/src/Deobfuscation/BytecodeDeobfuscator.cs
@@ -0,0 +1,359 @@
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Bytecode.Models;
+using MoonsecDeobfuscator.Deobfuscation.Rewriters;
+using MoonsecDeobfuscator.Deobfuscation.Utils;
+using MoonsecDeobfuscator.Deobfuscation.Walkers;
+using Function = MoonsecDeobfuscator.Bytecode.Models.Function;
+
+namespace MoonsecDeobfuscator.Deobfuscation;
+
+public class Handler(Block body, string fingerprint)
+{
+ public readonly Block Body = body;
+ public readonly string Fingerprint = fingerprint;
+}
+
+public class BytecodeDeobfuscator(Function function, Context ctx)
+{
+ private readonly Dictionary> _handlerMapping = [];
+ private readonly Function _rootFunction = function;
+
+ public Function Deobfuscate()
+ {
+ CreateHandlerMapping();
+ DeobfuscateOpcodes(_rootFunction);
+ DeobfuscateControlFlow(_rootFunction);
+ FixProgramEntry();
+ RebuildConstantPool(_rootFunction);
+ SetFlags(_rootFunction);
+ _rootFunction.IsVarArgFlag = 2;
+ return _rootFunction;
+ }
+
+ private static void DeobfuscateControlFlow(Function function)
+ {
+ var jumpReferences = ComputeJumpReferences(function);
+
+ RemoveDeadCode(function);
+ RemoveTestFlip(function);
+ FixTailCall(function);
+ FixJumpOffsets(function, jumpReferences);
+
+ foreach (var childFunction in function.Functions)
+ DeobfuscateControlFlow(childFunction);
+ }
+
+ private static void RebuildConstantPool(Function function)
+ {
+ var remapping = new Dictionary(function.Constants.Count);
+ var newOrder = new List(function.Constants.Count);
+ var next = 0;
+
+ foreach (var instruction in function.Instructions)
+ {
+ switch (instruction.OpCode)
+ {
+ case OpCode.LoadK:
+ case OpCode.GetGlobal:
+ case OpCode.SetGlobal:
+ instruction.B = Remap(instruction.B);
+ break;
+ case OpCode.SetTable:
+ case OpCode.Eq:
+ case OpCode.Lt:
+ case OpCode.Le:
+ case OpCode.Add:
+ case OpCode.Sub:
+ case OpCode.Mul:
+ case OpCode.Div:
+ case OpCode.Mod:
+ case OpCode.Pow:
+ instruction.B = RemapRK(instruction.B);
+ instruction.C = RemapRK(instruction.C);
+ break;
+ case OpCode.GetTable:
+ case OpCode.Self:
+ instruction.C = RemapRK(instruction.C);
+ break;
+ }
+ }
+
+ function.Constants = newOrder;
+ function.Functions.ForEach(RebuildConstantPool);
+ return;
+
+ int Remap(int oldIdx)
+ {
+ if (!remapping.TryGetValue(oldIdx, out var newIdx))
+ {
+ newIdx = next++;
+ remapping[oldIdx] = newIdx;
+ newOrder.Add(function.Constants[oldIdx]);
+ }
+
+ return newIdx;
+ }
+
+ int RemapRK(int operand) => operand >= 256 ? Remap(operand - 256) + 256 : operand;
+ }
+
+ private static void RemoveTestFlip(Function function)
+ {
+ var instructions = function.Instructions;
+
+ for (var i = 0; i < instructions.Count; i++)
+ {
+ var instruction = instructions[i];
+
+ if (instruction.OpCode is not (OpCode.Eq or OpCode.Lt or OpCode.Le or OpCode.Test))
+ continue;
+
+ var next1 = instructions[i + 1];
+ var next2 = instructions[i + 2];
+
+ if (!(next1.OpCode == OpCode.Jmp && next2.OpCode == OpCode.Jmp))
+ continue;
+
+ switch (instruction.OpCode)
+ {
+ case OpCode.Eq:
+ case OpCode.Lt:
+ case OpCode.Le:
+ instruction.A = instruction.A == 1 ? 0 : 1;
+ break;
+ case OpCode.Test:
+ instruction.C = instruction.C == 1 ? 0 : 1;
+ break;
+ }
+
+ instructions.RemoveAt(i + 1);
+ }
+ }
+
+ /*
+ Insert Return after TailCall if not present
+ */
+ private static void FixTailCall(Function function)
+ {
+ var instructions = function.Instructions;
+
+ for (var i = 0; i < instructions.Count; i++)
+ {
+ var instruction = instructions[i];
+
+ if (instruction.OpCode != OpCode.TailCall)
+ continue;
+
+ if (i + 1 < instructions.Count && instructions[i + 1].OpCode == OpCode.Return)
+ continue;
+
+ instructions.Insert(i + 1, new Instruction
+ {
+ OpCode = OpCode.Return,
+ A = instruction.A
+ });
+ }
+ }
+
+ /*
+ The actual program starts after this check and anything before is to be removed.
+
+ if GLOBAL ~= "This file was protected with MoonSec V3" then
+ return
+ end
+ */
+ private void FixProgramEntry()
+ {
+ var check = _rootFunction.Instructions
+ .FirstOrDefault(instr => instr is { OpCode: OpCode.Eq, C: > 255 }
+ && instr.Function.Constants[instr.C - 256] is StringConstant sc
+ && sc.Value.StartsWith("This file was protected with MoonSec V3"));
+
+ if (check == null)
+ return;
+
+ var idx = _rootFunction.Instructions.IndexOf(check);
+
+ for (var i = idx; i < _rootFunction.Instructions.Count; i++)
+ {
+ var instruction = _rootFunction.Instructions[i];
+
+ if (instruction.OpCode == OpCode.Return)
+ {
+ _rootFunction.Instructions.RemoveRange(0, i + 1);
+ break;
+ }
+ }
+
+ RemoveUnusedFunctions();
+ }
+
+ /*
+ FixProgramEntry might leave some unused functions so we remove them here
+ */
+ private void RemoveUnusedFunctions()
+ {
+ var functionReferences = new Dictionary();
+
+ foreach (var instruction in _rootFunction.Instructions)
+ {
+ if (instruction.OpCode == OpCode.Closure)
+ functionReferences[instruction] = _rootFunction.Functions[instruction.B];
+ }
+
+ _rootFunction.Functions.RemoveAll(f => !functionReferences.ContainsValue(f));
+
+ foreach (var (instr, function) in functionReferences)
+ instr.B = _rootFunction.Functions.IndexOf(function);
+ }
+
+ private static void RemoveDeadCode(Function function)
+ {
+ function.Instructions.RemoveAll(instruction => instruction.IsDead);
+ }
+
+ private static void FixJumpOffsets(Function function, Dictionary jumpReferences)
+ {
+ var instructions = function.Instructions;
+ var pos = new Dictionary();
+
+ for (var i = 0; i < instructions.Count; i++)
+ pos[instructions[i]] = i;
+
+ for (var i = 0; i < instructions.Count; i++)
+ {
+ var instruction = instructions[i];
+
+ if (jumpReferences.TryGetValue(instruction, out var target))
+ instruction.B = pos[target] - (i + 1);
+ }
+ }
+
+ private static void SetFlags(Function function)
+ {
+ function.IsVarArgFlag = 0;
+
+ var maxA = 0;
+
+ foreach (var instruction in function.Instructions)
+ {
+ var opcode = instruction.OpCode;
+ var A = instruction.A;
+
+ if (A > maxA)
+ maxA = A;
+
+ if (opcode == OpCode.Closure)
+ function.Functions[instruction.B].NumUpvalues = (byte) instruction.C;
+ else if (opcode == OpCode.VarArg)
+ function.IsVarArgFlag = 2;
+ }
+
+ function.MaxStackSize = (byte) (maxA + 1);
+ function.Functions.ForEach(SetFlags);
+ }
+
+ private static Dictionary ComputeJumpReferences(Function function)
+ {
+ var jumpReferences = new Dictionary();
+ var instructions = function.Instructions;
+
+ for (var i = 0; i < instructions.Count; i++)
+ {
+ var instruction = instructions[i];
+
+ if (instruction.OpCode is OpCode.Jmp or OpCode.ForLoop or OpCode.ForPrep)
+ jumpReferences[instruction] = instructions[(i + instruction.B) + 1];
+ }
+
+ return jumpReferences;
+ }
+
+ private void DeobfuscateOpcodes(Function function)
+ {
+ var instructions = function.Instructions;
+ var jmpSet = new HashSet();
+
+ for (var i = 0; i < instructions.Count; i++)
+ {
+ var instruction = instructions[i];
+ var handlers = _handlerMapping[instruction.OpNum];
+
+ foreach (var handler in handlers)
+ {
+ DeobfuscateOpcode(instruction, handler);
+
+ if (instruction.OpCode == OpCode.Jmp)
+ jmpSet.Add(i + instruction.B + 1);
+
+ if (instruction.OpCode is OpCode.Return or OpCode.TailCall)
+ {
+ SkipDeadCode(instructions, jmpSet, ref i);
+ break;
+ }
+
+ if (handler != handlers.Last() && i + 1 < instructions.Count)
+ instruction = instructions[++i];
+ }
+ }
+
+ function.Functions.ForEach(DeobfuscateOpcodes);
+ }
+
+ private static void SkipDeadCode(List instructions, HashSet jmpSet, ref int i)
+ {
+ while (++i < instructions.Count)
+ {
+ var nextIndex = i;
+
+ if (jmpSet.Contains(nextIndex))
+ {
+ i--;
+ break;
+ }
+
+ instructions[i].IsDead = true;
+ }
+ }
+
+ private static void DeobfuscateOpcode(Instruction instruction, Handler handler)
+ {
+ if (OpCodes.StaticOpcodes.TryGetValue(handler.Fingerprint, out var it))
+ {
+ instruction.OpCode = it.Item1;
+ it.Item2?.Invoke(instruction);
+ }
+ else
+ {
+ Console.Error.WriteLine($"Could not identify handler: {handler.Fingerprint}");
+ Console.Error.WriteLine(PrettyPrinter.AsString(handler.Body));
+ }
+ }
+
+ private void CreateHandlerMapping()
+ {
+ var solvedVmTree = new TreeSolver(ctx.VmTree, "enum").Solve();
+
+ foreach (var entry in solvedVmTree)
+ CreateHandler(entry.Key, entry.Value);
+ }
+
+ private void CreateHandler(int opcode, Block body)
+ {
+ var handlers = body.Statements
+ .WindowedByDiscardedPairs((a, b) => Matching.IsPcIncrement(a) && Matching.IsInstAssign(b))
+ .Select(stats =>
+ {
+ var block = new Block
+ {
+ Statements = [..stats]
+ }.Clone();
+ new HandlerRewriter().Rewrite(block, Order.PostOrder, symbols: true, fixedPoint: true);
+ return block;
+ })
+ .Select(block => new Handler(block, FingerprintGenerator.Generate(block)))
+ .ToList();
+
+ _handlerMapping[opcode] = handlers;
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Context.cs b/bot/src/Deobfuscation/Context.cs
new file mode 100644
index 0000000..0bd8eb3
--- /dev/null
+++ b/bot/src/Deobfuscation/Context.cs
@@ -0,0 +1,20 @@
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Deobfuscation.Bytecode;
+
+namespace MoonsecDeobfuscator.Deobfuscation;
+
+public class Context
+{
+ public readonly Dictionary IdentifiedNames = [];
+ public readonly List KeyExpressions = [];
+
+ public readonly List ProtoFormat = [];
+ public readonly Dictionary ConstantFormat = [];
+
+ public LocalFunction Wrapper;
+ public If VmTree;
+ public string BytecodeString;
+
+ public int BytecodeKey, ConstantKey;
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Deobfuscator.cs b/bot/src/Deobfuscation/Deobfuscator.cs
new file mode 100644
index 0000000..27d90e2
--- /dev/null
+++ b/bot/src/Deobfuscation/Deobfuscator.cs
@@ -0,0 +1,103 @@
+using System.Text;
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Bytecode.Models;
+using MoonsecDeobfuscator.Deobfuscation.Bytecode;
+using MoonsecDeobfuscator.Deobfuscation.Rewriters;
+using MoonsecDeobfuscator.Deobfuscation.Utils;
+using MoonsecDeobfuscator.Deobfuscation.Walkers;
+using NLua;
+using Function = MoonsecDeobfuscator.Bytecode.Models.Function;
+
+namespace MoonsecDeobfuscator.Deobfuscation;
+
+public class Deobfuscator
+{
+ private Context _ctx = new();
+ private Block _root = null!;
+
+ public Function Deobfuscate(string code, bool antiTamper = false)
+ {
+ _root = Block.FromString(code);
+
+ new Renamer().Walk(_root);
+ new ConstantReplacer(DecodeConstants()).Rewrite(_root, Order.PreOrder);
+ new ConstantFolder().Rewrite(_root, Order.PostOrder);
+ new Analyzer(_ctx).Walk(_root, symbols: true);
+
+ if (_root.Statements.Count > 3 && !antiTamper)
+ return Deobfuscate(HandleAntiTamper(), true);
+
+ new ControlFlowSolver().Rewrite(_ctx.Wrapper, Order.PostOrder);
+ new Renamer(_ctx.IdentifiedNames).Walk(_root);
+
+ var function = DeserializeBytecode();
+ var bytecodeDeobfuscator = new BytecodeDeobfuscator(function, _ctx);
+
+ function = bytecodeDeobfuscator.Deobfuscate();
+
+ return function;
+ }
+
+ private Dictionary DecodeConstants()
+ {
+ var constants = StringCollector.Collect(_root);
+
+ return constants
+ .Select(it => it.Item1 != null
+ ? StringDecoding.Decode(it.Item2, (int) it.Item1)
+ : StringDecoding.DecodeEscape(it.Item2))
+ .Select(StringDecoding.DecodeConstants)
+ .SelectMany(map => map)
+ .ToDictionary(entry => entry.Key, entry => entry.Value);
+ }
+
+ private string HandleAntiTamper()
+ {
+ var function = DeserializeBytecode();
+ var instructions = function.Instructions
+ .Where(instr => instr is { IsKA: false, IsKB: true, IsKC: false })
+ .ToList();
+
+ // why tf is this static
+ var constant = function.Constants[instructions[^2].B - 1];
+ var key = ((NumberConstant) constant).Value;
+
+ var bytecodeString = ((StringLiteral) ((Assign) _root.Statements[2]).Values[0]).Value[1..^1];
+ var scriptString = ((StringLiteral) ((Assign) _root.Statements[3]).Values[0]).Value[1..^1];
+
+ _ctx = new Context
+ {
+ BytecodeString = bytecodeString
+ };
+
+ return Encoding.UTF8.GetString(StringDecoding.Decode(scriptString, (int) key));
+ }
+
+ private Function DeserializeBytecode()
+ {
+ SolveKeys();
+
+ var bytecode = StringDecoding.Decode(_ctx.BytecodeString, _ctx.BytecodeKey);
+ var deserializer = new Deserializer(bytecode, _ctx);
+
+ return deserializer.ReadFunction();
+ }
+
+ private void SolveKeys()
+ {
+ using var L = new Lua(openLibs: false);
+
+ if (_ctx.KeyExpressions.Count == 2)
+ {
+ var code = $"return {PrettyPrinter.AsString(_ctx.KeyExpressions.First())}";
+ _ctx.ConstantKey = Convert.ToInt32(L.DoString(code)[0]);
+ }
+
+ {
+ var code = $"return {PrettyPrinter.AsString(_ctx.KeyExpressions.Last())}";
+ _ctx.BytecodeKey = Convert.ToInt32(L.DoString(code)[0]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/OpCodes.cs b/bot/src/Deobfuscation/OpCodes.cs
new file mode 100644
index 0000000..e2f2ad7
--- /dev/null
+++ b/bot/src/Deobfuscation/OpCodes.cs
@@ -0,0 +1,651 @@
+using MoonsecDeobfuscator.Bytecode.Models;
+
+namespace MoonsecDeobfuscator.Deobfuscation;
+
+public static class OpCodes
+{
+ // https://github.com/Trollicus/ironbrew-2/tree/master/IronBrew2/Obfuscator/Opcodes
+
+ public static readonly Dictionary?)> StaticOpcodes = new()
+ {
+ // MOVE A B R(A) := R(B)
+ // stk[inst[OP_A]] = stk[inst[OP_B]]
+ ["91909190"] = (OpCode.Move, null),
+
+ // LOADK A Bx R(A) := Kst(Bx)
+ // stk(inst[OP_A], inst[OP_B])
+ ["1419090"] = (OpCode.LoadK, instruction => instruction.B--),
+
+ // LOADBOOL A B C R(A) := (Bool)B; if (C) PC++
+ // stk[inst[OP_A]] = inst[OP_B] ~= 0
+ ["91902690"] = (OpCode.LoadBool, null),
+ // stk[inst[OP_A]] = inst[OP_B] ~= 0; pc = pc + 1
+ ["91902690291529"] = (OpCode.LoadBool, instruction => instruction.C = 1),
+
+ // LOADNIL A B R(A) := ... := R(B) := nil
+ // for _302 = inst[OP_A], inst[OP_B] do stk[_302] = nil end
+ ["13909091"] = (OpCode.LoadNil, null),
+
+ // GETUPVAL A B R(A) := UpValue[B]
+ // stk[inst[OP_A]] = upv[inst[OP_B]]
+ ["91909290"] = (OpCode.GetUpval, null),
+
+ // GETGLOBAL A Bx R(A) := Gbl[Kst(Bx)]
+ // stk[inst[OP_A]] = env[inst[OP_B]]
+ ["91909390"] = (OpCode.GetGlobal, instruction => instruction.B--),
+
+ // GETTABLE A B C R(A) := R(B)[RK(C)]
+ //stk[inst[OP_A]] = stk[inst[OP_B]][stk[inst[OP_C]]]
+ ["9190991909190"] = (OpCode.GetTable, null),
+ // stk[inst[OP_A]] = stk[inst[OP_B]][inst[OP_C]]
+ ["91909919090"] = (OpCode.GetTable, instruction => instruction.C += 255),
+
+ // SETGLOBAL A Bx Gbl[Kst(Bx)] := R(A)
+ // env[inst[OP_B]] = stk[inst[OP_A]]
+ ["93909190"] = (OpCode.SetGlobal, instruction => instruction.B--),
+
+ // SETUPVAL A B UpValue[B] := R(A)
+ // upv[inst[OP_B]] = stk[inst[OP_A]]
+ ["92909190"] = (OpCode.SetUpval, null),
+
+ // SETTABLE A B C R(A)[RK(B)] := RK(C)
+ // stk[inst[OP_A]][stk[inst[OP_B]]] = stk[inst[OP_C]]
+ ["9919091909190"] = (OpCode.SetTable, null),
+ // stk[inst[OP_A]][stk[inst[OP_B]]] = inst[OP_C]
+ ["99190919090"] = (OpCode.SetTable, instruction => instruction.C += 255),
+ // stk[inst[OP_A]][inst[OP_B]] = stk[inst[OP_C]]
+ ["99190909190"] = (OpCode.SetTable, instruction => instruction.B += 255),
+ // stk[inst[OP_A]][inst[OP_B]] = inst[OP_C]
+ ["991909090"] = (OpCode.SetTable, instruction =>
+ {
+ instruction.B += 255;
+ instruction.C += 255;
+ }),
+
+ // NEWTABLE A B C R(A) := {} (size = B,C)
+ // stk[inst[OP_A]] = {}
+ ["919033"] = (OpCode.NewTable, null),
+
+ // SELF A B C R(A+1) := R(B); R(A) := R(B)[RK(C)]
+ /*
+ local _17 = inst[OP_A]
+ local _18 = stk[inst[OP_B]]
+ stk[_17 + 1] = _18
+ stk[_17] = _18[inst[OP_C]]
+ */
+ ["909190911591990"] = (OpCode.Self, instruction => instruction.C += 255),
+ /*
+ local _511 = inst[OP_A]
+ local _512 = stk[inst[OP_B]]
+ stk[_511 + 1] = _512
+ stk[_511] = _512[stk[inst[OP_C]]]
+ */
+ ["90919091159199190"] = (OpCode.Self, null),
+
+ // ADD A B C R(A) := RK(B) + RK(C)
+ // stk[inst[OP_A]] = stk[inst[OP_B]] + stk[inst[OP_C]]
+ ["91901591909190"] = (OpCode.Add, null),
+ // stk[inst[OP_A]] = stk[inst[OP_B]] + inst[OP_C]
+ ["919015919090"] = (OpCode.Add, instruction => instruction.C += 255),
+ // stk[inst[OP_A]] = inst[OP_B] + stk[inst[OP_C]]
+ ["919015909190"] = (OpCode.Add, instruction => instruction.B += 255),
+ // stk[inst[OP_A]] = inst[OP_B] + inst[OP_C]
+ ["9190159090"] = (OpCode.Add, instruction =>
+ {
+ instruction.B += 255;
+ instruction.C += 255;
+ }),
+
+ // SUB A B C R(A) := RK(B) – RK(C)
+ // stk[inst[OP_A]] = stk[inst[OP_B]] - stk[inst[OP_C]]
+ ["91901691909190"] = (OpCode.Sub, null),
+ // stk[inst[OP_A]] = stk[inst[OP_B]] - inst[OP_C]
+ ["919016919090"] = (OpCode.Sub, instruction => instruction.C += 255),
+ // stk[inst[OP_A]] = inst[OP_B] - stk[inst[OP_C]]
+ ["919016909190"] = (OpCode.Sub, instruction => instruction.B += 255),
+ // stk[inst[OP_A]] = inst[OP_B] - inst[OP_C]
+ ["9190169090"] = (OpCode.Sub, instruction =>
+ {
+ instruction.B += 255;
+ instruction.C += 255;
+ }),
+
+ // MUL A B C R(A) := RK(B) * RK(C)
+ // stk[inst[OP_A]] = stk[inst[OP_B]] * stk[inst[OP_C]]
+ ["91901791909190"] = (OpCode.Mul, null),
+ // stk[inst[OP_A]] = stk[inst[OP_B]] * inst[OP_C]
+ ["919017919090"] = (OpCode.Mul, instruction => instruction.C += 255),
+ // stk[inst[OP_A]] = inst[OP_B] * stk[inst[OP_C]]
+ ["919017909190"] = (OpCode.Mul, instruction => instruction.B += 255),
+ // stk[inst[OP_A]] = inst[OP_B] * inst[OP_C]
+ ["9190179090"] = (OpCode.Mul, instruction =>
+ {
+ instruction.B += 255;
+ instruction.C += 255;
+ }),
+
+ // DIV A B C R(A) := RK(B) / RK(C)
+ // stk[inst[OP_A]] = stk[inst[OP_B]] / stk[inst[OP_C]]
+ ["91901891909190"] = (OpCode.Div, null),
+ //stk[inst[OP_A]] = stk[inst[OP_B]] / inst[OP_C]
+ ["919018919090"] = (OpCode.Div, instruction => instruction.C += 255),
+ // stk[inst[OP_A]] = inst[OP_B] / stk[inst[OP_C]]
+ ["919018909190"] = (OpCode.Div, instruction => instruction.B += 255),
+ // stk[inst[OP_A]] = inst[OP_B] / inst[OP_C]
+ ["9190189090"] = (OpCode.Div, instruction =>
+ {
+ instruction.B += 255;
+ instruction.C += 255;
+ }),
+
+ // MOD A B C R(A) := RK(B) % RK(C)
+ // stk[inst[OP_A]] = stk[inst[OP_B]] % stk[inst[OP_C]]
+ ["91901991909190"] = (OpCode.Mod, null),
+ // stk[inst[OP_A]] = stk[inst[OP_B]] % inst[OP_C]
+ ["919019919090"] = (OpCode.Mod, instruction => instruction.C += 255),
+ // stk[inst[OP_A]] = inst[OP_B] % stk[inst[OP_C]]
+ ["919019909190"] = (OpCode.Mod, instruction => instruction.B += 255),
+ // stk[inst[OP_A]] = inst[OP_B] % inst[OP_C]
+ ["9190199090"] = (OpCode.Mod, instruction =>
+ {
+ instruction.B += 255;
+ instruction.C += 255;
+ }),
+
+ // POW A B C R(A) := RK(B) ^ RK(C)
+ // stk[inst[OP_A]] = stk[inst[OP_B]] ^ stk[inst[OP_C]]
+ ["91902091909190"] = (OpCode.Pow, null),
+ // stk[inst[OP_A]] = stk[inst[OP_B]] ^ inst[OP_C]
+ ["919020919090"] = (OpCode.Pow, instruction => instruction.C += 255),
+ // stk[inst[OP_A]] = inst[OP_B] ^ stk[inst[OP_C]]
+ ["919020909190"] = (OpCode.Pow, instruction => instruction.B += 255),
+ // // stk[inst[OP_A]] = inst[OP_B] ^ inst[OP_C]
+ ["9190209090"] = (OpCode.Pow, instruction =>
+ {
+ instruction.B += 255;
+ instruction.C += 255;
+ }),
+
+ // UNM A B R(A) := -R(B)
+ // stk[inst[OP_A]] = -stk[inst[OP_B]]
+ ["9190319190"] = (OpCode.Unm, null),
+
+ // NOT A B R(A) := not R(B)
+ // stk[inst[OP_A]] = not stk[inst[OP_B]]
+ ["9190349190"] = (OpCode.Not, null),
+
+ // LEN A B R(A) := length of R(B)
+ // stk[inst[OP_A]] = #stk[inst[OP_B]]
+ ["9190289190"] = (OpCode.Len, null),
+
+ // CONCAT A B C R(A) := R(B).. ... ..R(C)
+ /*
+ local _216 = inst[OP_B]
+ local _217 = stk[_216]
+ for _218 = _216 + 1, inst[OP_C] do
+ _217 = _217 .. stk[_218]
+ end
+ stk[inst[OP_A]] = _217
+ */
+ ["909113159032919190"] = (OpCode.Concat, null),
+
+ // JMP sBx PC += sBx
+ // pc = inst[OP_B]
+ ["2990"] = (OpCode.Jmp, instruction => instruction.B -= instruction.PC + 1),
+
+ // EQ A B C if ((RK(B) == RK(C)) ~= A) then PC++
+ // if stk[inst[OP_A]] == stk[inst[OP_C]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["101225919091902915292990"] = (OpCode.Eq, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 0;
+ }),
+ // if stk[inst[OP_A]] == inst[OP_C] then pc = pc + 1 else pc = inst[OP_B] end
+ ["1012259190902915292990"] = (OpCode.Eq, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 0;
+ instruction.C += 255;
+ }),
+ // if inst[OP_A] == stk[inst[OP_C]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["1012259091902915292990"] = (OpCode.Eq, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 0;
+ }),
+ // if inst[OP_A] == inst[OP_C] then pc = pc + 1 else pc = inst[OP_B] end
+ ["10122590902915292990"] = (OpCode.Eq, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 0;
+ instruction.C += 255;
+ }),
+ // if stk[inst[OP_A]] ~= stk[inst[OP_C]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["101226919091902915292990"] = (OpCode.Eq, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 1;
+ }),
+ // if stk[inst[OP_A]] ~= inst[OP_C] then pc = pc + 1 else pc = inst[OP_B] end
+ ["1012269190902915292990"] = (OpCode.Eq, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 1;
+ instruction.C += 255;
+ }),
+ // if inst[OP_A] ~= stk[inst[OP_C]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["1012269091902915292990"] = (OpCode.Eq, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 1;
+ }),
+ // if inst[OP_A] ~= inst[OP_C] then pc = pc + 1 else pc = inst[OP_B] end
+ ["10122690902915292990"] = (OpCode.Eq, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 1;
+ instruction.C += 255;
+ }),
+ // pc = ((stk[inst[OP_A]] == stk[inst[OP_C]]) and inst[OP_B]) or (1 + pc)
+ ["2936352591909190901529"] = (OpCode.Eq, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 1;
+ }),
+
+ // LT A B C if ((RK(B) < RK(C)) ~= A) then PC++
+ // if stk[inst[OP_A]] < stk[inst[OP_C]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["101221919091902915292990"] = (OpCode.Lt, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 0;
+ }),
+ // if inst[OP_A] < stk[inst[OP_C]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["1012219091902915292990"] = (OpCode.Lt, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 0;
+ }),
+ // if stk[inst[OP_A]] < inst[OP_C] then pc = pc + 1 else pc = inst[OP_B] end
+ ["1012219190902915292990"] = (OpCode.Lt, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 0;
+ instruction.C += 255;
+ }),
+ // if inst[OP_A] < inst[OP_C] then pc = pc + 1 else pc = inst[OP_B] end
+ ["10122190902915292990"] = (OpCode.Lt, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 0;
+ instruction.C += 255;
+ }),
+ // if stk[inst[OP_A]] < stk[inst[OP_C]] then pc = inst[OP_B] else pc = pc + 1 end
+ ["101221919091902990291529"] = (OpCode.Lt, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 1;
+ }),
+ // if inst[OP_A] < stk[inst[OP_C]] then pc = inst[OP_B] else pc = pc + 1 end
+ ["1012219091902990291529"] = (OpCode.Lt, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 1;
+ }),
+ // if stk[inst[OP_A]] < inst[OP_C] then pc = inst[OP_B] else pc = pc + 1 end
+ ["1012219190902990291529"] = (OpCode.Lt, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 1;
+ instruction.C += 255;
+ }),
+ // if inst[OP_A] < inst[OP_C] then pc = inst[OP_B] else pc = pc + 1 end
+ ["10122190902990291529"] = (OpCode.Lt, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 1;
+ instruction.C += 255;
+ }),
+
+ // LE A B C if ((RK(B) <= RK(C)) ~= A) then PC++
+ // if stk[inst[OP_A]] <= stk[inst[OP_C]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["101223919091902915292990"] = (OpCode.Le, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 0;
+ }),
+ // if inst[OP_A] <= stk[inst[OP_C]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["1012239091902915292990"] = (OpCode.Le, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 0;
+ }),
+ // if stk[inst[OP_A]] <= inst[OP_C] then pc = pc + 1 else pc = inst[OP_B] end
+ ["1012239190902915292990"] = (OpCode.Le, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 0;
+ instruction.C += 255;
+ }),
+ // if inst[OP_A] <= inst[OP_C] then pc = pc + 1 else pc = inst[OP_B] end
+ ["10122390902915292990"] = (OpCode.Le, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 0;
+ instruction.C += 255;
+ }),
+ // if stk[inst[OP_A]] <= stk[inst[OP_C]] then pc = inst[OP_B] else pc = pc + 1 end
+ ["101223919091902990291529"] = (OpCode.Le, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 1;
+ }),
+ // if stk[inst[OP_A]] <= inst[OP_C] then pc = inst[OP_B] else pc = pc + 1 end
+ ["1012239190902990291529"] = (OpCode.Le, instruction =>
+ {
+ instruction.B = instruction.A;
+ instruction.A = 1;
+ instruction.C += 255;
+ }),
+ // if inst[OP_A] <= stk[inst[OP_C]] then pc = inst[OP_B] else pc = pc + 1 end
+ ["1012239091902990291529"] = (OpCode.Le, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 1;
+ }),
+ // if inst[OP_A] <= inst[OP_C] then pc = inst[OP_B] else pc = pc + 1 end
+ ["10122390902990291529"] = (OpCode.Le, instruction =>
+ {
+ instruction.B = instruction.A + 255;
+ instruction.A = 1;
+ instruction.C += 255;
+ }),
+
+ // TEST A C if not (R(A) <=> C) then PC++
+ // if stk[inst[OP_A]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["101291902915292990"] = (OpCode.Test, instruction =>
+ {
+ instruction.B = 0;
+ instruction.C = 0;
+ }),
+ // if not stk[inst[OP_A]] then pc = pc + 1 else pc = inst[OP_B] end
+ ["10123491902915292990"] = (OpCode.Test, instruction =>
+ {
+ instruction.B = 0;
+ instruction.C = 1;
+ }),
+
+ // TESTSET A B C if (R(B) <=> C) then R(A) := R(B) else PC++
+ // local _3 = stk[inst[OP_C]] if _3 then pc = pc + 1 else stk[inst[OP_A]] = _3 pc = inst[OP_B] end
+ ["9190101229152991902990"] = (OpCode.TestSet, instruction =>
+ {
+ instruction.B = instruction.C;
+ instruction.C = 0;
+ }),
+ // local _14 = stk[inst[OP_C]] if not _14 then pc = pc + 1 else stk[inst[OP_A]] = _14 pc = inst[OP_B] end
+ ["919010123429152991902990"] = (OpCode.TestSet, instruction =>
+ {
+ instruction.B = instruction.C;
+ instruction.C = 1;
+ }),
+
+ // CALL A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
+ // stk[inst[OP_A]]()
+ ["149190"] = (OpCode.Call, null),
+ // local _52 = inst[OP_A]; stk[_52] = stk[_52](stk[_52 + 1])
+ ["909114919115"] = (OpCode.Call, null),
+ // local _19 = inst[OP_A]; stk[_19] = stk[_19](unpack(stk, _19 + 1, inst[OP_B]))
+ ["9091149114511590"] = (OpCode.Call, instruction => instruction.B -= instruction.A - 1),
+ // local _354 = inst[OP_A]; stk[_354](stk[_354 + 1])
+ ["9014919115"] = (OpCode.Call, null),
+ // local _194 = inst[OP_A]; stk[_194](unpack(stk, _194 + 1, inst[OP_B]))
+ ["90149114511590"] = (OpCode.Call, instruction => instruction.B -= instruction.A - 1),
+ // local _80 = inst[OP_A]; stk[_80] = stk[_80](unpack(stk, _80 + 1, top))
+ ["9091149114511530"] = (OpCode.Call, null),
+ /*
+ local _324 = inst[OP_A]
+ local _325 = {
+ stk[_324](stk[_324 + 1])
+ }
+ local _326 = 0
+ for _327 = _324, inst[OP_C] do
+ _326 = _326 + 1
+ stk[_327] = _325[_326]
+ end
+ */
+ ["903314919115139015919"] = (OpCode.Call, instruction => instruction.C -= instruction.A - 2),
+ // local _85 = inst[OP_A]; stk[_85] = stk[_85]()
+ ["90911491"] = (OpCode.Call, null),
+ /*
+ local _75 = inst[OP_A]
+ local _76, _77 = _R(stk[_75]())
+ top = (_77 + _75) - 1
+ local _78 = 0
+ for _79 = _75, top do
+ _78 = _78 + 1
+ stk[_79] = _76[_78]
+ end
+ */
+ ["90141491301615133015919"] = (OpCode.Call, null),
+ /*
+ local _63 = inst[OP_A]
+ local _64, _65 = _R(stk[_63](stk[_63 + 1]))
+ top = (_65 + _63) - 1
+ local _66 = 0
+ for _67 = _63, top do
+ _66 = _66 + 1
+ stk[_67] = _64[_66]
+ end
+ */
+ ["901414919115301615133015919"] = (OpCode.Call, instruction => instruction.B -= instruction.A - 1),
+ /*
+ local _423 = inst[OP_A]
+ local _424, _425 = _R(stk[_423](unpack(stk, _423 + 1, inst[OP_B])))
+ top = (_425 + _423) - 1
+ local _426 = 0
+ for _427 = _423, top do
+ _426 = _426 + 1
+ stk[_427] = _424[_426]
+ end
+ */
+ ["9014149114511590301615133015919"] = (OpCode.Call, instruction => instruction.B -= instruction.A - 1),
+ // local _133 = inst[OP_A]; stk[_133](unpack(stk, _133 + 1, top))
+ ["90149114511530"] = (OpCode.Call, null),
+ /*
+ local _57 = inst[OP_A]
+ local _58 = {
+ stk[_57](unpack(stk, _57 + 1, inst[OP_B]))
+ }
+ local _59 = 0
+ for _60 = _57, inst[OP_C] do
+ _59 = _59 + 1
+ stk[_60] = _58[_59]
+ end
+ */
+ ["9033149114511590139015919"] = (OpCode.Call, instruction =>
+ {
+ instruction.B -= instruction.A - 1;
+ instruction.C -= instruction.A - 2;
+ }),
+ /*
+ local _1066 = inst[OP_A]
+ local _1067 = {
+ stk[_1066](unpack(stk, _1066 + 1, top))
+ }
+ local _1068 = 0
+ for _1069 = _1066, inst[OP_C] do
+ _1068 = _1068 + 1
+ stk[_1069] = _1067[_1068]
+ end
+ */
+ ["9033149114511530139015919"] = (OpCode.Call, instruction => instruction.C -= instruction.A - 2),
+ /*
+ local _511 = inst[OP_A]
+ local _512 = {
+ stk[_511]()
+ }
+ local _513 = inst[OP_C]
+ local _514 = 0
+ for _515 = _511, _513 do
+ _514 = _514 + 1
+ stk[_515] = _512[_514]
+ end
+ */
+ ["90331491901315919"] = (OpCode.Call, instruction => instruction.C -= instruction.A - 2),
+ /*
+ local _189 = inst[OP_A]
+ local _190, _191 = _R(stk[_189](unpack(stk, _189 + 1, top)))
+ top = (_191 + _189) - 1
+ local _192 = 0
+ for _193 = _189, top do
+ _192 = _192 + 1
+ stk[_193] = _190[_192]
+ end
+ */
+ ["9014149114511530301615133015919"] = (OpCode.Call, null),
+
+ // TAILCALL A B C return R(A)(R(A+1), ... ,R(A+B-1))
+ // local _64 = inst[OP_A]; do return stk[_64](unpack(stk, _64 + 1, inst[OP_B])) end
+ ["9027149114511590"] = (OpCode.TailCall, instruction => instruction.B -= instruction.A - 1),
+ // local _135 = inst[OP_A]; do return stk[_135](unpack(stk, _135 + 1, top)); end
+ ["9027149114511530"] = (OpCode.TailCall, null),
+ // do return stk[inst[OP_A]](); end
+ ["27149190"] = (OpCode.TailCall, null),
+
+ // RETURN A B return R(A), ... ,R(A+B-2)
+ // do return; end
+ ["27"] = (OpCode.Return, null),
+ // do return stk[inst[OP_A]]; end
+ ["279190"] = (OpCode.Return, null),
+ // local _1003 = inst[OP_A]; do return unpack(stk, _1003, top); end
+ ["9027145130"] = (OpCode.Return, null),
+ // local _879 = inst[OP_A]; do return unpack(stk, _879, _879 + inst[OP_B]); end
+ ["902714511590"] = (OpCode.Return, instruction => instruction.B += 2),
+ // local _601 = inst[OP_A]; do return stk[_601], stk[_601 + 1]; end
+ ["9027919115"] = (OpCode.Return, null),
+
+ /*
+ FORLOOP A sBx R(A) += R(A+2)
+ if R(A) = R(A+1) then {
+ PC += sBx; R(A+3) = R(A)
+ }
+ */
+ /*
+ local _144 = inst[OP_A]
+ local _145 = stk[_144 + 2]
+ local _146 = stk[_144] + _145
+ stk[_144] = _146
+ if _145 > 0 then
+ if _146 <= stk[_144 + 1] then
+ pc = inst[OP_B]
+ stk[_144 + 3] = _146
+ end
+ elseif _146 >= stk[_144 + 1] then
+ pc = inst[OP_B]
+ stk[_144 + 3] = _146
+ end
+ */
+ ["909115159191101122102391152990911524911529909115"] = (OpCode.ForLoop, instruction =>
+ instruction.B -= instruction.PC + 1),
+
+ // FORPREP A sBx R(A) -= R(A+2); PC += sBx
+ /*
+ local _200 = inst[OP_A]
+ local _201 = stk[_200]
+ local _202 = stk[(_200 + 2)]
+ if (_202 > 0) then
+ if (_201 > stk[(_200 + 1)]) then
+ pc = inst[OP_B]
+ else
+ stk[(_200 + 3)] = _201
+ end
+ elseif (_201 < stk[(_200 + 1)]) then
+ pc = inst[OP_B]
+ else
+ stk[(_200 + 3)] = _201
+ end
+ */
+ ["909191151011122210122291152990911521911529909115"] = (OpCode.ForPrep, instruction =>
+ instruction.B -= instruction.PC + 2),
+
+ /*
+ TFORLOOP A C R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));
+ if R(A+3) ~= nil then {
+ R(A+2) = R(A+3);
+ } else {
+ PC++;
+ }
+ */
+ /*
+ local _180 = inst[OP_A]
+ local _181 = inst[OP_C]
+ local _182 = _180 + 2
+ local _183 = {
+ stk[_180](stk[_180 + 1], stk[_182])
+ }
+ for _184 = 1, _181 do
+ stk[_182 + _184] = _183[_184]
+ end
+ local _185 = _183[1]
+ if _185 then
+ stk[_182] = _185
+ pc = inst[OP_B]
+ else
+ pc = pc + 1
+ end
+ */
+ ["909015331491911591139115991012912990291529"] = (OpCode.TForLoop, instruction => instruction.B = 0),
+
+ // SETLIST A B C R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B
+ // I know long table SETLIST is missing.
+ // local _273 = inst[OP_A]; local _275 = stk[_273]; for _277 = (_273 + 1), top do; table.insert(_275, stk[_277]); end
+ ["909113153014691"] = (OpCode.SetList, null),
+ // local _504 = inst[OP_A]; local _505 = stk[_504]; for _506 = (_504 + 1), inst[OP_B] do table.insert(_505, stk[_506]); end
+ ["909113159014691"] = (OpCode.SetList, instruction => instruction.B -= instruction.A),
+
+ // CLOSE A close all variables in the stack up to (>=) R(A)
+ /*
+ local _277 = {}
+ for _278 = 1, #lupv do
+ local _279 = lupv[_278]
+ for _280 = 0, #_279 do
+ local _281 = _279[_280]
+ local _282 = _281[1]
+ local _283 = _281[2]
+ if (_282 == stk) and (_283 >= inst[OP_A]) then
+ _277[_283] = _282[_283]
+ _281[1] = _277
+ end
+ end
+ end
+ */
+ ["3313289132899910352512490999"] = (OpCode.Close, null),
+
+ // CLOSURE A Bx R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))
+ /*
+ SNIP
+ */
+ ["990331473333927999999913902915299291012259916331991633299152891901483"] = (OpCode.Closure, null),
+ // stk[inst[OP_A]] = wrap_proto(protos[inst[OP_B]], nil, env)
+ ["91901489903"] = (OpCode.Closure, null),
+
+ // VARARG A B R(A), R(A+1), ..., R(A+B-1) = vararg
+ /*
+ local _273 = inst[OP_A]
+ top = ((_273 + varargz) - 1)
+ for _276 = _273, top do
+ local _274 = vararg[(_276 - _273)]
+ stk[_276] = _274
+ end
+ */
+ ["903016151330941691"] = (OpCode.VarArg, null),
+ /*
+ local _328 = inst[OP_A]
+ local _329 = inst[OP_B]
+ for _330 = _328, _329 do
+ stk[_330] = vararg[_330 - _328]
+ end
+ */
+ ["909013919416"] = (OpCode.VarArg, instruction => instruction.B -= instruction.A - 1)
+ };
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Rewriters/ConstantFolder.cs b/bot/src/Deobfuscation/Rewriters/ConstantFolder.cs
new file mode 100644
index 0000000..d0bdf4c
--- /dev/null
+++ b/bot/src/Deobfuscation/Rewriters/ConstantFolder.cs
@@ -0,0 +1,68 @@
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Rewriters;
+
+public class ConstantFolder : AstRewriter
+{
+ private static Expression FoldUnaryExpression(UnaryExpression node)
+ {
+ if (CanFoldTableLen(node))
+ return FoldTableLen(node);
+
+ if (CanFoldNegation(node))
+ return FoldNegation(node);
+
+ return node;
+ }
+
+ private static NumberLiteral FoldTableLen(UnaryExpression node) => new(((Table) node.Operand).Entries.Count);
+
+ private static bool CanFoldTableLen(UnaryExpression node)
+ {
+ return node is { Operator: UnaryOperator.Length, Operand: Table table }
+ && table.Entries.All(entry => entry.Key == null && entry.Value is Literal and not Nil);
+ }
+
+ private static NumberLiteral FoldNegation(UnaryExpression node) => new(-((NumberLiteral) node.Operand).Value);
+
+ private static bool CanFoldNegation(UnaryExpression node) =>
+ node is { Operator: UnaryOperator.Negate, Operand: NumberLiteral };
+
+ private static BinaryExpression NormalizeComparison(BinaryExpression node)
+ {
+ if (node is not { Left: NumberLiteral, Right: not NumberLiteral })
+ return node;
+
+ switch (node.Operator)
+ {
+ case BinaryOperator.Equals:
+ case BinaryOperator.NotEquals:
+ (node.Left, node.Right) = (node.Right, node.Left);
+ break;
+ case BinaryOperator.LessThan:
+ node.Operator = BinaryOperator.GreaterThan;
+ (node.Left, node.Right) = (node.Right, node.Left);
+ break;
+ case BinaryOperator.GreaterThan:
+ node.Operator = BinaryOperator.LessThan;
+ (node.Left, node.Right) = (node.Right, node.Left);
+ break;
+ case BinaryOperator.LessThanOrEquals:
+ node.Operator = BinaryOperator.GreaterThanOrEquals;
+ (node.Left, node.Right) = (node.Right, node.Left);
+ break;
+ case BinaryOperator.GreaterThanOrEquals:
+ node.Operator = BinaryOperator.LessThanOrEquals;
+ (node.Left, node.Right) = (node.Right, node.Left);
+ break;
+ }
+
+ return node;
+ }
+
+ public override Node Visit(BinaryExpression node) => NormalizeComparison(node);
+
+ public override Node Visit(UnaryExpression node) => FoldUnaryExpression(node);
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Rewriters/ConstantReplacer.cs b/bot/src/Deobfuscation/Rewriters/ConstantReplacer.cs
new file mode 100644
index 0000000..7ad861d
--- /dev/null
+++ b/bot/src/Deobfuscation/Rewriters/ConstantReplacer.cs
@@ -0,0 +1,22 @@
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Rewriters;
+
+public class ConstantReplacer(Dictionary constants) : AstRewriter
+{
+ public override Node Visit(MemberAccess node) =>
+ constants.TryGetValue(node.Key.Value, out var values) ? CreateReplacement(values) : node;
+
+ private static Expression CreateReplacement(string[] values)
+ {
+ if (int.TryParse(values[0], out var result))
+ return new NumberLiteral(result);
+
+ Expression name = new Name(values[0]);
+ return values
+ .Skip(1)
+ .Aggregate(name, (acc, str) => new MemberAccess(acc, new Name(str)));
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Rewriters/ControlFlowSolver.cs b/bot/src/Deobfuscation/Rewriters/ControlFlowSolver.cs
new file mode 100644
index 0000000..a77a4ae
--- /dev/null
+++ b/bot/src/Deobfuscation/Rewriters/ControlFlowSolver.cs
@@ -0,0 +1,116 @@
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Deobfuscation.Utils;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Rewriters;
+
+public class ControlFlowSolver : AstRewriter
+{
+ public override Node Visit(Repeat node) =>
+ node is { Condition: BooleanLiteral { Value: true } } ? CreateReplacement(node.Body) : node;
+
+ public override Node Visit(NumericFor node)
+ {
+ var match = node is
+ {
+ Body.Statements: [If { IfClause.Body.Statements: [.., Break or Do] }, ..] or [.., Break or Do]
+ };
+
+ return match ? CreateReplacement(node.Body) : node;
+ }
+
+ /*
+ FiveM does this shit
+ if (-3 ~= n) then
+ if (0 < n) then
+ r = f
+ goto omluRWOc
+ end
+ d = e
+ ::omluRWOc::
+ else
+ */
+ public override Node Visit(If node) =>
+ node.IfClause.Body.Statements is [If, .., Label] ? CreateReplacement(node.IfClause.Body) : node;
+
+ public override Node Visit(Block node)
+ {
+ RemoveStateMachines(node);
+ RemoveUnreachable(node);
+ return node;
+ }
+
+ private static void RemoveUnreachable(Block node)
+ {
+ var statements = node.Statements;
+ var idx = statements.FindIndex(stat => stat is Do or Return or Break);
+
+ if (idx != -1 && idx + 1 < statements.Count)
+ statements.RemoveRange(idx + 1, statements.Count - idx - 1);
+ }
+
+ private static void RemoveStateMachines(Block node)
+ {
+ var stats = node.Statements;
+ var stateMachines = stats.Where(IsStateMachine).ToList();
+
+ if (stateMachines.Count == 0)
+ return;
+
+ foreach (var stateMachine in stateMachines)
+ {
+ var idx = stats.IndexOf(stateMachine);
+ var stateName = GetStateName(stateMachine);
+ var tree = GetTree(stateMachine);
+
+ var solved = new TreeSolver(tree, stateName).Solve()
+ .OrderBy(it => it.Key)
+ .SelectMany(it => it.Value.Statements);
+
+ stats.RemoveAt(idx);
+ stats.InsertRange(idx, solved);
+ }
+ }
+
+ private static If CreateReplacement(Block node)
+ {
+ var innerIf = (If) node.Statements[0];
+ var ifBody = new Block();
+ var elseBody = new Block();
+
+ ifBody.Statements.AddRange(innerIf.IfClause.Body.Statements
+ .TakeWhile(stat => stat is not (Break or Label or Goto)));
+ elseBody.Statements.AddRange(node.Statements
+ .Skip(1)
+ .TakeWhile(stat => stat is not (Break or Label or Goto)));
+
+ var clause = new If.Clause(innerIf.IfClause.Condition, ifBody);
+
+ return new If(clause, [], elseBody);
+ }
+
+ private static bool IsStateMachine(Statement node)
+ {
+ if (node is While { Condition: BinaryExpression { Right: NumberLiteral { Value: -1 } } })
+ return true;
+
+ return node is NumericFor
+ {
+ InitialValue: NumberLiteral,
+ FinalValue: NumberLiteral,
+ Body.Statements: [If]
+ };
+ }
+
+ private static string GetStateName(Statement node)
+ {
+ return node is While w
+ ? ((Name) ((BinaryExpression) w.Condition).Left).Value
+ : ((NumericFor) node).IteratorName.Value;
+ }
+
+ private static If GetTree(Statement node) =>
+ (If) (node is While w ? w.Body.Statements[0] : ((NumericFor) node).Body.Statements[0]);
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Rewriters/HandlerRewriter.cs b/bot/src/Deobfuscation/Rewriters/HandlerRewriter.cs
new file mode 100644
index 0000000..8f30c42
--- /dev/null
+++ b/bot/src/Deobfuscation/Rewriters/HandlerRewriter.cs
@@ -0,0 +1,194 @@
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Statements;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Rewriters;
+
+// Hyi vittu kehtaako tätä edes julkaista...?
+public class HandlerRewriter : AstRewriter
+{
+ public override Node Visit(Call node)
+ {
+ var args = node.Arguments;
+
+ // stk(_201, _200) => stk(inst[OP_A], inst[OP_B])
+ if (node.Function is Name { Value: "stk" } && args is [Name name1, Name name2])
+ {
+ var newArg1 = ReplaceWithElementAccess(name1);
+ var newArg2 = ReplaceWithElementAccess(name2);
+
+ if (name1 != newArg1 || name2 != newArg2)
+ return new Call(node.Function, [newArg1, newArg2]);
+ }
+
+ return node;
+ }
+
+ public override Node Visit(ElementAccess node)
+ {
+ var newTable = ReplaceWithName(node.Table);
+ var newKey = ReplaceWithName(node.Key);
+
+ /*
+ local _186 = _184[_185] => local _186 = inst[OP_A]
+ */
+ if (node.Table != newTable || node.Key != newKey)
+ return new ElementAccess(newTable, newKey);
+
+ /*
+ stk[_185] = _186 => stk[inst[OP_A]] = _186
+ */
+ newKey = ReplaceWithElementAccess(node.Key);
+
+ if (node.Key != newKey)
+ return new ElementAccess(newTable, newKey);
+
+ return node;
+ }
+
+
+ public override Node Visit(Block node)
+ {
+ var unused = node.Statements
+ .Where(stat => stat is LocalDeclare decl && ShouldRemoveDeclaration(decl))
+ .ToHashSet();
+
+ if (unused.Count > 0)
+ {
+ node.Statements.RemoveAll(stat => unused.Contains(stat));
+
+ // make sure changes are detected
+ return new Block(node.Statements);
+ }
+
+ return node;
+ }
+
+ public override Node Visit(Assign node)
+ {
+ /*
+ _185 = OP_A => local _185 = OP_A
+ */
+ if (ShouldLocalizeAssignment(node))
+ {
+ var names = new NodeList(node.Variables.Select(name => (Name) name));
+ return new LocalDeclare(names, node.Values);
+ }
+
+ /*
+ stk[_186] = _185 => stk[_186] = stk[inst[OP_B]]
+ */
+ if (node is { Variables: [ElementAccess], Values: [Name name] })
+ {
+ var replacement = ReplaceWithElementAccess(name);
+
+ if (replacement != name)
+ return new Assign(node.Variables, [replacement]);
+ }
+
+ /*
+ local _275 = (((stk[inst[OP_A]] == stk[inst[OP_C]]) and inst[OP_B]) or (1 + pc))
+ pc = _275
+
+ => pc = (((stk[inst[OP_A]] == stk[inst[OP_C]]) and inst[OP_B]) or (1 + pc))
+ */
+ if (node is { Variables: [Name], Values: [Name name1] })
+ {
+ var newValue = ReplaceWithBinaryExpression(name1);
+
+ if (name1 != newValue)
+ return new Assign(node.Variables, [newValue]);
+ }
+
+ return node;
+ }
+
+
+ public override Node Visit(BinaryExpression node)
+ {
+ var newLeft = ReplaceWithElementAccess(node.Left);
+ var newRight = ReplaceWithElementAccess(node.Right);
+
+ /*
+ (((_273 == _274) and inst[OP_B]) or (1 + _269)) => (((stk[inst[OP_A]] == stk[inst[OP_C]]) and inst[OP_B]) or (1 + _269))
+ */
+ if (node.Left != newLeft || node.Right != newRight)
+ return new BinaryExpression(node.Operator, newLeft, newRight);
+
+ newLeft = ReplaceWithName(node.Left);
+ newRight = ReplaceWithName(node.Right);
+
+ /*
+ (1 + _269) => (1 + pc)
+ */
+ if (node.Left != newLeft || node.Right != newRight)
+ return new BinaryExpression(node.Operator, newLeft, newRight);
+
+ return node;
+ }
+
+ private bool ShouldRemoveDeclaration(LocalDeclare node)
+ {
+ if (node.Values.Count == 0)
+ return true;
+
+ foreach (var name in node.Names)
+ {
+ var info = GetVariable(name.Value);
+
+ if (info is { ReadCount: > 0 })
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool ShouldLocalizeAssignment(Assign node)
+ {
+ foreach (var variable in node.Variables)
+ {
+ if (variable is not Name name)
+ return false;
+
+ var value = name.Value;
+
+ if (!value.StartsWith('_'))
+ return false;
+
+ if (GetVariable(value)!.DeclarationLocation != node)
+ return false;
+ }
+
+ return true;
+ }
+
+ private Expression ReplaceWithName(Expression node)
+ {
+ if (node is not Name name || !name.Value.StartsWith('_'))
+ return node;
+
+ return GetVariable(name.Value) is { Value: Name } info
+ ? info.Value.Clone()
+ : node;
+ }
+
+ private Expression ReplaceWithElementAccess(Expression node)
+ {
+ if (node is not Name name || !name.Value.StartsWith('_'))
+ return node;
+
+ return GetVariable(name.Value) is { Value: ElementAccess { Table: Name, Key: Name }, ReadCount: 1 } info
+ ? info.Value.Clone()
+ : node;
+ }
+
+ private Expression ReplaceWithBinaryExpression(Expression node)
+ {
+ if (node is not Name name || !name.Value.StartsWith('_'))
+ return node;
+
+ return GetVariable(name.Value) is { Value: BinaryExpression, ReadCount: 1, AssignmentCount: 0 } info
+ ? info.Value.Clone()
+ : node;
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Utils/Extensions.cs b/bot/src/Deobfuscation/Utils/Extensions.cs
new file mode 100644
index 0000000..1819e27
--- /dev/null
+++ b/bot/src/Deobfuscation/Utils/Extensions.cs
@@ -0,0 +1,34 @@
+namespace MoonsecDeobfuscator.Deobfuscation.Utils;
+
+public static class Extensions
+{
+ public static IEnumerable> WindowedByDiscardedPairs(this IEnumerable source, Func predicate)
+ {
+ using var e = source.GetEnumerator();
+
+ if (!e.MoveNext())
+ yield break;
+
+ var window = new List { e.Current };
+ var previous = e.Current;
+
+ while (e.MoveNext())
+ {
+ var current = e.Current;
+
+ if (predicate(previous, current))
+ {
+ yield return window.Count > 1 ? [..window[..^1]] : [];
+ window.Clear();
+ previous = default!;
+ continue;
+ }
+
+ window.Add(current);
+ previous = current;
+ }
+
+ if (window.Count > 0)
+ yield return [..window];
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Utils/Matching.cs b/bot/src/Deobfuscation/Utils/Matching.cs
new file mode 100644
index 0000000..8186e86
--- /dev/null
+++ b/bot/src/Deobfuscation/Utils/Matching.cs
@@ -0,0 +1,31 @@
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Utils;
+
+public static class Matching
+{
+ public static bool IsEnumComparison(Expression node) =>
+ node is BinaryExpression { Left: Name { Value: "enum" } };
+
+ public static bool IsInstAssign(Statement node) => node is Assign
+ {
+ Variables: [Name { Value: "inst" }],
+ Values: [ElementAccess { Table: Name { Value: "insts" } }]
+ };
+
+ public static bool IsPcIncrement(Statement node) => node is Assign
+ {
+ Variables: [Name { Value: "pc" }],
+ Values:
+ [
+ BinaryExpression
+ {
+ Operator: BinaryOperator.Add,
+ Left: Name { Value: "pc" } or NumberLiteral,
+ Right: Name { Value: "pc" } or NumberLiteral
+ }
+ ]
+ };
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Utils/StringDecoding.cs b/bot/src/Deobfuscation/Utils/StringDecoding.cs
new file mode 100644
index 0000000..aca965d
--- /dev/null
+++ b/bot/src/Deobfuscation/Utils/StringDecoding.cs
@@ -0,0 +1,99 @@
+using System.Text;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Utils;
+
+public static class StringDecoding
+{
+ public static byte[] DecodeEscape(string data)
+ {
+ var parts = data.Split('\\').Skip(1).ToArray();
+ var bytes = new byte[parts.Length];
+
+ for (var i = 0; i < bytes.Length; i++)
+ bytes[i] = byte.Parse(parts[i]);
+
+ return bytes;
+ }
+
+ public static byte[] Decode(string data, int key)
+ {
+ using var decoded = new MemoryStream((data.Length - 16) / 2);
+ var chars = new Dictionary();
+
+ for (var i = 0; i < 16; i++)
+ chars[data[i]] = i;
+
+ var len = data.Length;
+ var rkey = key;
+
+ for (var i = 16; i < len; i += 2)
+ {
+ var c1 = data[i];
+ var c2 = i + 1 < len ? data[i + 1] : '\0';
+
+ var i1 = chars.ContainsKey(c1) ? chars[c1] : 0;
+ var i2 = chars.ContainsKey(c2) ? chars[c2] : 0;
+
+ decoded.WriteByte((byte) ((i1 * 16 + i2 + rkey) % 256));
+ rkey += key;
+ }
+
+ return decoded.ToArray();
+ }
+
+ public static Dictionary DecodeConstants(byte[] data)
+ {
+ var constants = new Dictionary();
+
+ using var stream = new MemoryStream(data);
+ using var reader = new BinaryReader(stream);
+
+ while (true)
+ {
+ var control = reader.ReadByte();
+
+ if (control == 5)
+ break;
+
+ if (control == 1)
+ control++;
+
+ var size = reader.ReadByte();
+ var first = Encoding.UTF8.GetString(reader.ReadBytes(size));
+
+ string[]? value = null;
+
+ switch (control)
+ {
+ case 0:
+ var strSize = reader.ReadByte();
+ var second = Encoding.UTF8.GetString(reader.ReadBytes(strSize));
+ value = [first, second];
+ break;
+ case 2:
+ case 4:
+ case 6:
+ value = [first];
+ break;
+ }
+
+ var key = Encoding.UTF8.GetString(reader.ReadBytes(8));
+ constants[key] = value!;
+ }
+
+ return constants;
+ }
+
+ public static string DecodeConstant(int key, byte[] bytes)
+ {
+ if (!(bytes.Length > 1 && bytes[0] > 0x7F))
+ return Encoding.UTF8.GetString(bytes);
+
+ var newBytes = new byte[bytes.Length - 1];
+
+ for (var i = 1; i < bytes.Length; i++)
+ newBytes[i - 1] = (byte) ((bytes[i] + key) % 256);
+
+ return Encoding.UTF8.GetString(newBytes);
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Utils/TreeSolver.cs b/bot/src/Deobfuscation/Utils/TreeSolver.cs
new file mode 100644
index 0000000..cff7763
--- /dev/null
+++ b/bot/src/Deobfuscation/Utils/TreeSolver.cs
@@ -0,0 +1,207 @@
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+using QuikGraph;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Utils;
+
+// Using QuikGraph is probably not necessary, but I built it this way because I assumed I would need QuikGraph for a control flow graph of the bytecode.
+public class TreeSolver(If tree, string stateName)
+{
+ private enum EdgeType
+ {
+ Then,
+ Else
+ }
+
+ private class Range(int min, int max)
+ {
+ public int Min = min;
+ public int Max = max;
+ }
+
+ private class Vertex
+ {
+ public BinaryOperator? Operator;
+ public int? ConstantValue;
+
+ public Range? Range;
+ public Block? Body;
+ }
+
+ private readonly BidirectionalGraph> _graph = new();
+
+ public Dictionary Solve()
+ {
+ AddBranches(tree, null, EdgeType.Then);
+ SetRanges();
+ ReduceRanges();
+ RemoveUnreachable();
+
+ var result = new Dictionary();
+
+ foreach (var vertex in _graph.Vertices.Where(IsTerminal))
+ {
+ var range = vertex.Range!;
+
+ if (range.Min != range.Max)
+ throw new Exception("Failed to reduce range!");
+
+ result[range.Min] = vertex.Body!;
+ }
+
+ return result;
+ }
+
+ private void RemoveUnreachable()
+ {
+ var unreachable = _graph.Vertices
+ .Where(v => IsTerminal(v) && v.Range is { Min: < 0, Max: < 0 })
+ .ToHashSet();
+
+ foreach (var vertex in unreachable)
+ _graph.RemoveVertex(vertex);
+ }
+
+ private void ReduceRanges()
+ {
+ var vertices = _graph.Vertices.ToList();
+ var root = vertices.First(v => _graph.InDegree(v) == 0);
+ var leafs = vertices.Where(IsTerminal);
+
+ foreach (var leaf in leafs)
+ {
+ var range = leaf.Range!;
+
+ for (var i = range.Min; i <= range.Max; i++)
+ {
+ if (HasValidPath(leaf, root, i))
+ {
+ range.Min = range.Max = i;
+ break;
+ }
+
+ if (i == range.Max)
+ range.Min = range.Max = -1;
+ }
+ }
+ }
+
+ private bool HasValidPath(Vertex current, Vertex root, int state)
+ {
+ if (current == root)
+ return true;
+
+ foreach (var edge in _graph.InEdges(current))
+ {
+ var source = edge.Source;
+
+ if (IsValidPath(source, edge, state) && HasValidPath(source, root, state))
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsValidPath(Vertex vertex, TaggedEdge edge, int state)
+ {
+ var constant = (int) vertex.ConstantValue!;
+ var valid = vertex.Operator! switch
+ {
+ BinaryOperator.Equals => state == constant,
+ BinaryOperator.NotEquals => state != constant,
+ BinaryOperator.LessThanOrEquals => state <= constant,
+ BinaryOperator.GreaterThanOrEquals => state >= constant,
+ BinaryOperator.LessThan => state < constant,
+ BinaryOperator.GreaterThan => state > constant,
+ _ => throw new Exception("Invalid operator in IsValidPath!")
+ };
+
+ return edge.Tag == EdgeType.Then == valid;
+ }
+
+ private void SetRanges()
+ {
+ foreach (var node in _graph.Vertices)
+ {
+ if (IsTerminal(node))
+ continue;
+
+ var edges = _graph.OutEdges(node).ToList();
+
+ if (edges.Count != 2)
+ throw new Exception($"Expected 2 edges but got {edges.Count}");
+
+ var thenEdge = edges.First(edge => edge.Tag == EdgeType.Then);
+ var elseEdge = edges.First(edge => edge.Tag == EdgeType.Else);
+ var ranges = ComputeRanges(node);
+
+ var thenTarget = thenEdge.Target;
+ var elseTarget = elseEdge.Target;
+
+ if (IsTerminal(thenTarget))
+ thenTarget.Range = ranges.Then;
+
+ if (IsTerminal(elseTarget))
+ elseTarget.Range = ranges.Else;
+ }
+ }
+
+ private static (Range Then, Range Else) ComputeRanges(Vertex v)
+ {
+ var c = (int) v.ConstantValue!;
+
+ return v.Operator! switch
+ {
+ BinaryOperator.Equals => (new Range(c, c), new Range(Math.Max(c - 1, 0), c + 1)),
+ BinaryOperator.NotEquals => (new Range(Math.Max(c - 1, 0), c + 1), new Range(c, c)),
+ BinaryOperator.LessThan => (new Range(Math.Max(c - 1, 0), Math.Max(c - 1, 0)), new Range(c, c)),
+ BinaryOperator.GreaterThan => (new Range(c + 1, c + 1), new Range(c, c)),
+ BinaryOperator.LessThanOrEquals => (new Range(c, c), new Range(c + 1, c + 1)),
+ BinaryOperator.GreaterThanOrEquals => (new Range(c, c), new Range(Math.Max(c - 1, 0), Math.Max(c - 1, 0))),
+ _ => throw new Exception("Invalid operator in ComputeRanges!")
+ };
+ }
+
+ private void AddBranches(If branch, Vertex? source, EdgeType edgeType)
+ {
+ var condition = (BinaryExpression) branch.IfClause.Condition;
+ var node = new Vertex
+ {
+ Operator = condition.Operator,
+ ConstantValue = (int) ((NumberLiteral) condition.Right).Value
+ };
+
+ _graph.AddVertex(node);
+
+ if (source != null)
+ _graph.AddEdge(new TaggedEdge(source, node, edgeType));
+
+ AddBranch(branch.IfClause.Body, node, EdgeType.Then);
+ AddBranch(branch.ElseBody!, node, EdgeType.Else);
+ }
+
+ private void AddBranch(Block body, Vertex source, EdgeType edgeType)
+ {
+ if (TreeContinues(body))
+ {
+ AddBranches((If) body.Statements[0], source, edgeType);
+ }
+ else
+ {
+ var leaf = new Vertex
+ {
+ Body = body,
+ };
+
+ _graph.AddVertex(leaf);
+ _graph.AddEdge(new TaggedEdge(source, leaf, edgeType));
+ }
+ }
+
+ private bool IsTerminal(Vertex vertex) => _graph.OutDegree(vertex) == 0;
+
+ private bool TreeContinues(Block node) =>
+ node.Statements is [If { IfClause.Condition: BinaryExpression { Left: Name name } }] && name.Value == stateName;
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Walkers/Analyzer.cs b/bot/src/Deobfuscation/Walkers/Analyzer.cs
new file mode 100644
index 0000000..0d016f5
--- /dev/null
+++ b/bot/src/Deobfuscation/Walkers/Analyzer.cs
@@ -0,0 +1,168 @@
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Deobfuscation.Bytecode;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Walkers;
+
+public class Analyzer(Context ctx) : AstWalker
+{
+ private int _funcCounter;
+
+ public override void Visit(LocalFunction node)
+ {
+ switch (_funcCounter++)
+ {
+ case 8:
+ GetProtoFormat(node);
+ break;
+ case 10:
+ IdentifyWrapProto(node);
+ break;
+ case 11:
+ IdentifyWrapper(node);
+ break;
+ }
+
+ base.Visit(node);
+ }
+
+ public override void Visit(Call node)
+ {
+ if (node.Arguments is [Name, StringLiteral @string])
+ ctx.BytecodeString = @string.Value.Trim('"');
+
+ base.Visit(node);
+ }
+
+ public override void Visit(BinaryExpression node)
+ {
+ if (node is { Operator: BinaryOperator.Add, Right: Call })
+ {
+ ctx.KeyExpressions.Add(node);
+ return;
+ }
+
+ base.Visit(node);
+ }
+
+ public override void Visit(LocalDeclare node)
+ {
+ if (node is { Names: [Name name], Values: [Expression expression] })
+ {
+ var info = GetVariable(name.Value);
+
+ if (info is not { AssignmentCount: 0 })
+ return;
+
+ if (expression is NumberLiteral number)
+ {
+ switch ((int) number.Value)
+ {
+ case 1:
+ Identify(name.Value, "OP_ENUM");
+ break;
+ case 2:
+ Identify(name.Value, "OP_A");
+ break;
+ case 3:
+ Identify(name.Value, "OP_B");
+ break;
+ case 4:
+ Identify(name.Value, "OP_C");
+ break;
+ }
+ }
+ else if (expression is BinaryExpression { Left: Name { Value: "unpack" } })
+ {
+ Identify(name.Value, "unpack");
+ }
+ }
+
+ base.Visit(node);
+ }
+
+ private void GetProtoFormat(LocalFunction node)
+ {
+ foreach (var stat in node.Body.Statements)
+ {
+ if (stat is Assign)
+ {
+ ctx.ProtoFormat.Add(ProtoStep.NumParams);
+ }
+ else if (stat is NumericFor numericFor)
+ {
+ switch (numericFor.Body.Statements.Count)
+ {
+ case 1:
+ ctx.ProtoFormat.Add(ProtoStep.Functions);
+ break;
+ case 2:
+ ctx.ProtoFormat.Add(ProtoStep.Instructions);
+ break;
+ case 4:
+ GetConstantFormat(numericFor);
+ ctx.ProtoFormat.Add(ProtoStep.Constants);
+ break;
+ }
+ }
+ }
+ }
+
+ private void GetConstantFormat(NumericFor node)
+ {
+ var values = node.Body.DescendantNodes()
+ .Where(it => it is BinaryExpression { Operator: BinaryOperator.Equals })
+ .Select(it => (BinaryExpression) it)
+ .Select(it => (byte) ((NumberLiteral) it.Right).Value)
+ .ToList();
+
+ ctx.ConstantFormat[values[0]] = ProtoStep.BooleanConstant;
+ ctx.ConstantFormat[values[1]] = ProtoStep.NumberConstant;
+ ctx.ConstantFormat[values[2]] = ProtoStep.StringConstant;
+ }
+
+ private void IdentifyWrapProto(LocalFunction node)
+ {
+ var names = node.Parameters.Names;
+
+ Identify(node.Name.Value, "wrap_proto");
+ Identify(names[0].Value, "proto");
+ Identify(names[1].Value, "upv");
+ Identify(names[2].Value, "env");
+ }
+
+ private void IdentifyWrapper(LocalFunction node)
+ {
+ var names = node.Body.ChildNodes()
+ .OfType()
+ .SelectMany(decl => decl.Names)
+ .ToList();
+
+ Identify(names[0].Value, "insts");
+ Identify(names[1].Value, "protos");
+ Identify(names[2].Value, "params");
+ Identify(names[4].Value, "_R");
+ Identify(names[5].Value, "pc");
+ Identify(names[6].Value, "top");
+ Identify(names[7].Value, "vararg");
+ Identify(names[8].Value, "args");
+ Identify(names[9].Value, "pcount");
+ Identify(names[10].Value, "lupv");
+ Identify(names[11].Value, "stk");
+ Identify(names[13].Value, "varargz");
+ Identify(names[14].Value, "inst");
+ Identify(names[15].Value, "enum");
+
+ ctx.Wrapper = node;
+ ctx.VmTree = (If) node.DescendantNodes()
+ .First(n => n is If { IfClause.Condition: BinaryExpression { Left: Name name } }
+ && name.Value == names[15].Value);
+ }
+
+ private void Identify(string name, string newName)
+ {
+ ctx.IdentifiedNames[name] = newName;
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Walkers/FingerprintGenerator.cs b/bot/src/Deobfuscation/Walkers/FingerprintGenerator.cs
new file mode 100644
index 0000000..08cedbf
--- /dev/null
+++ b/bot/src/Deobfuscation/Walkers/FingerprintGenerator.cs
@@ -0,0 +1,228 @@
+using System.Text;
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Statements;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Walkers;
+
+public class FingerprintGenerator : AstWalker
+{
+ private enum Operation
+ {
+ InstRef,
+ StkRef,
+ UpvRef,
+ EnvRef,
+ VarArgRef,
+ UnpackRef,
+ InsertRef,
+ SetMetatableRef,
+ WrapProtoRef,
+ Access,
+ If,
+ ElseIf,
+ Else,
+ For,
+ Call,
+ Add,
+ Sub,
+ Mul,
+ Div,
+ Mod,
+ Pow,
+ LessThan,
+ GreaterThan,
+ LessThanOrEquals,
+ GreaterThanOrEquals,
+ Equals,
+ NotEquals,
+ Return,
+ Len,
+ PcRef,
+ TopRef,
+ Negate,
+ Concat,
+ Table,
+ Not,
+ And,
+ Or
+ }
+
+ private readonly List _operations = [];
+
+ public override void Visit(Name node)
+ {
+ switch (node.Value)
+ {
+ case "stk":
+ _operations.Add(Operation.StkRef);
+ break;
+ case "env":
+ _operations.Add(Operation.EnvRef);
+ break;
+ case "upv":
+ _operations.Add(Operation.UpvRef);
+ break;
+ case "inst":
+ _operations.Add(Operation.InstRef);
+ break;
+ case "vararg":
+ _operations.Add(Operation.VarArgRef);
+ break;
+ case "unpack":
+ _operations.Add(Operation.UnpackRef);
+ break;
+ case "setmetatable":
+ _operations.Add(Operation.SetMetatableRef);
+ break;
+ case "pc":
+ _operations.Add(Operation.PcRef);
+ break;
+ case "top":
+ _operations.Add(Operation.TopRef);
+ break;
+ case "wrap_proto":
+ _operations.Add(Operation.WrapProtoRef);
+ break;
+ }
+ }
+
+ public override void Visit(MemberAccess node)
+ {
+ if (node is { Table: Name { Value: "table" }, Key.Value: "insert" })
+ _operations.Add(Operation.InsertRef);
+ else
+ _operations.Add(Operation.Access);
+
+ base.Visit(node);
+ }
+
+ public override void Visit(ElementAccess node)
+ {
+ _operations.Add(Operation.Access);
+ base.Visit(node);
+ }
+
+ public override void Visit(If node)
+ {
+ _operations.Add(Operation.If);
+
+ foreach (var _ in node.ElseIfClauses)
+ _operations.Add(Operation.ElseIf);
+
+ if (node.ElseBody != null)
+ _operations.Add(Operation.Else);
+
+ base.Visit(node);
+ }
+
+ public override void Visit(NumericFor node)
+ {
+ _operations.Add(Operation.For);
+ base.Visit(node);
+ }
+
+ public override void Visit(Call node)
+ {
+ _operations.Add(Operation.Call);
+ base.Visit(node);
+ }
+
+ public override void Visit(BinaryExpression node)
+ {
+ // Java switches are much better...
+ switch (node.Operator)
+ {
+ case BinaryOperator.Add:
+ _operations.Add(Operation.Add);
+ break;
+ case BinaryOperator.Sub:
+ _operations.Add(Operation.Sub);
+ break;
+ case BinaryOperator.Mul:
+ _operations.Add(Operation.Mul);
+ break;
+ case BinaryOperator.Div:
+ _operations.Add(Operation.Div);
+ break;
+ case BinaryOperator.Mod:
+ _operations.Add(Operation.Mod);
+ break;
+ case BinaryOperator.Pow:
+ _operations.Add(Operation.Pow);
+ break;
+ case BinaryOperator.LessThan:
+ _operations.Add(Operation.LessThan);
+ break;
+ case BinaryOperator.LessThanOrEquals:
+ _operations.Add(Operation.LessThanOrEquals);
+ break;
+ case BinaryOperator.GreaterThan:
+ _operations.Add(Operation.GreaterThan);
+ break;
+ case BinaryOperator.GreaterThanOrEquals:
+ _operations.Add(Operation.GreaterThanOrEquals);
+ break;
+ case BinaryOperator.Equals:
+ _operations.Add(Operation.Equals);
+ break;
+ case BinaryOperator.NotEquals:
+ _operations.Add(Operation.NotEquals);
+ break;
+ case BinaryOperator.Concat:
+ _operations.Add(Operation.Concat);
+ break;
+ case BinaryOperator.And:
+ _operations.Add(Operation.And);
+ break;
+ case BinaryOperator.Or:
+ _operations.Add(Operation.Or);
+ break;
+ }
+
+ base.Visit(node);
+ }
+
+ public override void Visit(UnaryExpression node)
+ {
+ switch (node.Operator)
+ {
+ case UnaryOperator.Length:
+ _operations.Add(Operation.Len);
+ break;
+ case UnaryOperator.Negate:
+ _operations.Add(Operation.Negate);
+ break;
+ case UnaryOperator.Not:
+ _operations.Add(Operation.Not);
+ break;
+ }
+
+ base.Visit(node);
+ }
+
+ public override void Visit(Return node)
+ {
+ _operations.Add(Operation.Return);
+ base.Visit(node);
+ }
+
+ public override void Visit(Table node)
+ {
+ _operations.Add(Operation.Table);
+ base.Visit(node);
+ }
+
+ public static string Generate(Block node)
+ {
+ var walker = new FingerprintGenerator();
+ walker.Visit(node);
+
+ var result = new StringBuilder();
+
+ foreach (var operation in walker._operations)
+ result.Append((int) operation);
+
+ return result.ToString();
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Walkers/Renamer.cs b/bot/src/Deobfuscation/Walkers/Renamer.cs
new file mode 100644
index 0000000..1e68472
--- /dev/null
+++ b/bot/src/Deobfuscation/Walkers/Renamer.cs
@@ -0,0 +1,192 @@
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Statements;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Walkers;
+
+public class Renamer : AstWalker
+{
+ private readonly Dictionary _names;
+ private readonly Stack _scopes = [];
+ private int _counter;
+
+ public Renamer(Dictionary names)
+ {
+ _names = names;
+ PushScope();
+ }
+
+ public Renamer() : this([])
+ {
+ }
+
+ private Scope PushScope()
+ {
+ var scope = new Scope();
+ _scopes.Push(scope);
+ return scope;
+ }
+
+ private void PopScope()
+ {
+ _scopes.Pop();
+ }
+
+ private void RenameAndDefine(Scope scope, Name name)
+ {
+ var newName = _names.TryGetValue(name.Value, out var n) ? n : $"_{_counter++}";
+ scope.Define(name.Value, newName);
+ name.Value = newName;
+ }
+
+ public override void Visit(LocalFunction node)
+ {
+ RenameAndDefine(_scopes.Peek(), node.Name);
+ var scope = PushScope();
+
+ foreach (var name in node.Parameters.Names)
+ RenameAndDefine(scope, name);
+
+ Visit(node.Body);
+ PopScope();
+ }
+
+ public override void Visit(Function node)
+ {
+ RenameAndDefine(_scopes.Peek(), node.Name);
+ var scope = PushScope();
+
+ foreach (var name in node.Parameters.Names)
+ RenameAndDefine(scope, name);
+
+ Visit(node.Body);
+ PopScope();
+ }
+
+ public override void Visit(AnonymousFunction node)
+ {
+ var scope = PushScope();
+
+ foreach (var name in node.Parameters.Names)
+ RenameAndDefine(scope, name);
+
+ Visit(node.Body);
+ PopScope();
+ }
+
+ public override void Visit(GenericFor node)
+ {
+ var scope = PushScope();
+
+ foreach (var name in node.Names)
+ RenameAndDefine(scope, name);
+
+ Visit(node.Body);
+ PopScope();
+ }
+
+ public override void Visit(NumericFor node)
+ {
+ Visit(node.InitialValue);
+ Visit(node.FinalValue);
+
+ if (node.Step != null)
+ Visit(node.Step);
+
+ RenameAndDefine(PushScope(), node.IteratorName);
+ Visit(node.Body);
+ PopScope();
+ }
+
+ public override void Visit(Repeat node)
+ {
+ PushScope();
+ Visit(node.Body);
+ Visit(node.Condition);
+ PopScope();
+ }
+
+ public override void Visit(While node)
+ {
+ Visit(node.Condition);
+ PushScope();
+ Visit(node.Body);
+ PopScope();
+ }
+
+ public override void Visit(Do node)
+ {
+ PushScope();
+ Visit(node.Body);
+ PopScope();
+ }
+
+ public override void Visit(If node)
+ {
+ Visit(node.IfClause);
+ VisitList(node.ElseIfClauses);
+
+ var elseBody = node.ElseBody;
+
+ if (elseBody != null)
+ {
+ PushScope();
+ Visit(elseBody);
+ PopScope();
+ }
+ }
+
+ public override void Visit(If.Clause node)
+ {
+ Visit(node.Condition);
+ PushScope();
+ Visit(node.Body);
+ PopScope();
+ }
+
+ public override void Visit(LocalDeclare node)
+ {
+ VisitList(node.Values);
+ var scope = _scopes.Peek();
+
+ foreach (var name in node.Names)
+ RenameAndDefine(scope, name);
+ }
+
+ public override void Visit(Assign node)
+ {
+ VisitList(node.Values);
+ VisitList(node.Variables);
+ }
+
+ public override void Visit(Name node)
+ {
+ var value = node.Value;
+
+ foreach (var scope in _scopes)
+ {
+ if (!scope.IsDefined(value))
+ continue;
+
+ node.Value = scope.GetNewName(value);
+ break;
+ }
+ }
+
+ private class Scope
+ {
+ private readonly Dictionary> _variables = [];
+
+ public void Define(string name, string newName)
+ {
+ if (!_variables.ContainsKey(name))
+ _variables[name] = [];
+
+ _variables[name].Push(newName);
+ }
+
+ public bool IsDefined(string name) => _variables.ContainsKey(name);
+
+ public string GetNewName(string name) => _variables[name].Peek();
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Deobfuscation/Walkers/StringCollector.cs b/bot/src/Deobfuscation/Walkers/StringCollector.cs
new file mode 100644
index 0000000..54b4958
--- /dev/null
+++ b/bot/src/Deobfuscation/Walkers/StringCollector.cs
@@ -0,0 +1,33 @@
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+
+namespace MoonsecDeobfuscator.Deobfuscation.Walkers;
+
+public class StringCollector : AstWalker
+{
+ private readonly List<(int?, string)> _strings = [];
+
+ public override void Visit(Call node)
+ {
+ if (node.Arguments is [NumberLiteral number, StringLiteral @string])
+ _strings.Add(((int) number.Value, @string.Value[1..^1]));
+
+ base.Visit(node);
+ }
+
+ public override void Visit(StringLiteral node)
+ {
+ var value = node.Value;
+
+ if (value.StartsWith("\"\\4\\8"))
+ _strings.Add((null, value[1..^1]));
+ }
+
+ public static List<(int?, string)> Collect(Node node)
+ {
+ var walker = new StringCollector();
+ walker.Visit(node);
+ return walker._strings;
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Program.cs b/bot/src/Program.cs
new file mode 100644
index 0000000..4a31d43
--- /dev/null
+++ b/bot/src/Program.cs
@@ -0,0 +1,234 @@
+using Discord;
+using Discord.WebSocket;
+using Discord.Interactions;
+using Microsoft.Extensions.DependencyInjection;
+using System.Reflection;
+using System.Text;
+using System.Net;
+using Microsoft.AspNetCore.Builder;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Threading;
+using MoonsecDeobfuscator.Deobfuscation;
+using MoonsecDeobfuscator.Bytecode.Models;
+
+namespace GalacticBytecodeBot
+{
+ public class Program
+ {
+ private DiscordSocketClient _client = null!;
+ private InteractionService _interactions = null!;
+ private IServiceProvider _services = null!;
+
+ public static async Task Main(string[] args)
+ {
+ // Load environment variables
+ try { DotNetEnv.Env.Load(); } catch { Console.WriteLine("No .env file found, using environment variables."); }
+
+ // Start health check server for Render
+ _ = StartHealthCheckServer();
+
+ await new Program().RunAsync();
+ }
+
+ private static async Task StartHealthCheckServer()
+ {
+ var portStr = Environment.GetEnvironmentVariable("PORT") ?? "3000";
+ var builder = WebApplication.CreateBuilder();
+
+ // FIXED: Use UseSetting instead of UseUrls for .NET 9.0
+ builder.WebHost.UseSetting("urls", $"http://0.0.0.0:{portStr}");
+
+ var app = builder.Build();
+ app.MapGet("/", () => "MoonSec Bot is running.");
+
+ Console.WriteLine($"🌐 Health check listening on port {portStr}");
+ await app.RunAsync();
+ }
+
+ public async Task RunAsync()
+ {
+ _client = new DiscordSocketClient(new DiscordSocketConfig
+ {
+ GatewayIntents = GatewayIntents.Guilds | GatewayIntents.DirectMessages,
+ AlwaysDownloadUsers = true
+ });
+
+ _interactions = new InteractionService(_client.Rest);
+
+ _services = new ServiceCollection()
+ .AddSingleton(_client)
+ .AddSingleton(_interactions)
+ .AddSingleton()
+ .BuildServiceProvider();
+
+ _client.Log += msg => { Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {msg}"); return Task.CompletedTask; };
+ _client.Ready += ReadyAsync;
+ _client.InteractionCreated += HandleInteractionAsync;
+
+ var token = Environment.GetEnvironmentVariable("DISCORD_TOKEN");
+ if (string.IsNullOrEmpty(token))
+ throw new Exception("DISCORD_TOKEN missing in environment variables");
+
+ await _client.LoginAsync(TokenType.Bot, token);
+ await _client.StartAsync();
+
+ await Task.Delay(-1);
+ }
+
+ private async Task ReadyAsync()
+ {
+ await _interactions.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
+ await _interactions.RegisterCommandsGloballyAsync(true);
+
+ await _client.SetStatusAsync(UserStatus.Online);
+ await _client.SetActivityAsync(new Game("🌙 MoonSec → Medal Pipeline"));
+ Console.WriteLine($"✅ Bot connected as {_client.CurrentUser}");
+
+ if (!File.Exists("/app/medal"))
+ Console.WriteLine("⚠️ WARNING: Medal not found at /app/medal");
+ else
+ Console.WriteLine("✅ Medal found at /app/medal");
+ }
+
+ private async Task HandleInteractionAsync(SocketInteraction interaction)
+ {
+ var context = new SocketInteractionContext(_client, interaction);
+ await _interactions.ExecuteCommandAsync(context, _services);
+ }
+ }
+
+ public class DeobfuscationModule : InteractionModuleBase
+ {
+ private readonly DeobfuscationService _service;
+
+ public DeobfuscationModule(DeobfuscationService service)
+ {
+ _service = service;
+ }
+
+ [SlashCommand("deobfuscate", "Deobfuscates a MoonSec-protected Lua file")]
+ public async Task DeobfuscateCommand(
+ [Summary("file", "Lua or text file")] IAttachment file)
+ {
+ await DeferAsync();
+
+ if (!file.Filename.EndsWith(".lua") && !file.Filename.EndsWith(".txt") &&
+ !file.Filename.EndsWith(".luau"))
+ {
+ await FollowupAsync("❌ Only `.lua`, `.luau` or `.txt` files are allowed.");
+ return;
+ }
+
+ try
+ {
+ using var http = new HttpClient();
+ var bytes = await http.GetByteArrayAsync(file.Url);
+ var sourceCode = Encoding.UTF8.GetString(bytes);
+
+ // Step 1: Deobfuscate and create bytecode
+ await FollowupAsync("🔄 **Step 1/3:** Deobfuscating with MoonSec...");
+ var bytecode = _service.GenerateBytecode(sourceCode);
+
+ var tempBytecode = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.luac");
+ await File.WriteAllBytesAsync(tempBytecode, bytecode);
+
+ // Step 2: Decompile with Medal
+ await ModifyOriginalResponseAsync(msg => msg.Content = "🔄 **Step 2/3:** Decompiling with Medal...");
+ var decompiled = await _service.DecompileWithMedal(tempBytecode);
+
+ try { File.Delete(tempBytecode); } catch { }
+
+ if (string.IsNullOrWhiteSpace(decompiled))
+ {
+ await ModifyOriginalResponseAsync(msg => msg.Content = "❌ Medal failed to decompile.");
+ return;
+ }
+
+ // Step 3: Send result - FIXED: Build after setting description
+ await ModifyOriginalResponseAsync(msg => msg.Content = "✅ **Step 3/3:** Sending result...");
+
+ var embedBuilder = new EmbedBuilder()
+ .WithTitle("✅ Deobfuscation Complete")
+ .WithColor(Color.Green)
+ .WithFooter($"Processed by {Context.User.Username}");
+
+ if (decompiled.Length > 2000)
+ {
+ // Send as file
+ await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(decompiled));
+ await FollowupWithFileAsync(stream, "decompiled.lua",
+ text: $"{Context.User.Mention} here is your decompiled code:",
+ embed: embedBuilder.Build());
+ }
+ else
+ {
+ // Send in embed
+ embedBuilder.WithDescription($"```lua\n{decompiled}\n```");
+ await FollowupAsync($"{Context.User.Mention}", embed: embedBuilder.Build());
+ }
+
+ await DeleteOriginalResponseAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"❌ Error: {ex}");
+ await FollowupAsync($"❌ Processing failed: `{ex.Message}`");
+ }
+ }
+ }
+
+ public class DeobfuscationService
+ {
+ public byte[] GenerateBytecode(string sourceCode)
+ {
+ var deob = new Deobfuscator();
+ var result = deob.Deobfuscate(sourceCode);
+
+ using var ms = new MemoryStream();
+ var serializer = new MoonsecDeobfuscator.Deobfuscation.Bytecode.Serializer(ms);
+ serializer.Serialize(result);
+ return ms.ToArray();
+ }
+
+ public async Task DecompileWithMedal(string bytecodePath)
+ {
+ var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = "/app/medal",
+ Arguments = $"\"{bytecodePath}\"",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ }
+ };
+
+ process.Start();
+ var output = await process.StandardOutput.ReadToEndAsync();
+ var error = await process.StandardError.ReadToEndAsync();
+
+ var exitTask = process.WaitForExitAsync();
+ var timeoutTask = Task.Delay(TimeSpan.FromMinutes(3), cts.Token);
+
+ if (await Task.WhenAny(exitTask, timeoutTask) == timeoutTask)
+ {
+ process.Kill();
+ throw new TimeoutException("Medal timed out after 3 minutes");
+ }
+
+ if (process.ExitCode != 0)
+ throw new Exception($"Medal failed: {error}");
+
+ return output;
+ }
+ }
+}
diff --git a/bot/src/Syntax/Ast/Block.cs b/bot/src/Syntax/Ast/Block.cs
new file mode 100644
index 0000000..50abba8
--- /dev/null
+++ b/bot/src/Syntax/Ast/Block.cs
@@ -0,0 +1,35 @@
+using System.Collections.Immutable;
+using Antlr4.Runtime;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Syntax;
+using MoonsecDeobfuscator.Syntax.Parser;
+
+namespace MoonsecDeobfuscator.Ast;
+
+public class Block(NodeList statements) : Node
+{
+ public NodeList Statements { get; set; } = statements;
+
+ public Block() : this([])
+ {
+ }
+
+ public override ImmutableList ChildNodes() => [..Statements];
+
+ public override Block Clone() => new(Statements.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+
+ public static Block FromString(string code)
+ {
+ var lexer = new LuaLexer(CharStreams.fromString(code));
+ var tokens = new CommonTokenStream(lexer);
+
+ return new CstToAstVisitor().VisitBlock(new LuaParser(tokens).block());
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/AnonymousFunction.cs b/bot/src/Syntax/Ast/Expressions/AnonymousFunction.cs
new file mode 100644
index 0000000..cad6966
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/AnonymousFunction.cs
@@ -0,0 +1,24 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public class AnonymousFunction(ParameterList parameters, Block body) : Expression
+{
+ public ParameterList Parameters { get; set; } = parameters;
+ public Block Body { get; set; } = body;
+
+ public AnonymousFunction(Block body) : this(new ParameterList(), body)
+ {
+ }
+
+ public override ImmutableList ChildNodes() => [Parameters, Body];
+
+ public override AnonymousFunction Clone() => new(Parameters.Clone(), Body.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/BinaryExpression.cs b/bot/src/Syntax/Ast/Expressions/BinaryExpression.cs
new file mode 100644
index 0000000..89e6c06
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/BinaryExpression.cs
@@ -0,0 +1,40 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public enum BinaryOperator
+{
+ Add,
+ Sub,
+ Mul,
+ Div,
+ Mod,
+ Pow,
+ Equals,
+ NotEquals,
+ LessThan,
+ GreaterThan,
+ LessThanOrEquals,
+ GreaterThanOrEquals,
+ And,
+ Or,
+ Concat
+}
+
+public class BinaryExpression(BinaryOperator @operator, Expression left, Expression right) : Expression
+{
+ public BinaryOperator Operator { get; set; } = @operator;
+ public Expression Left { get; set; } = left;
+ public Expression Right { get; set; } = right;
+
+ public override ImmutableList ChildNodes() => [Left, Right];
+
+ public override BinaryExpression Clone() => new(Operator, Left.Clone(), Right.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/Call.cs b/bot/src/Syntax/Ast/Expressions/Call.cs
new file mode 100644
index 0000000..7e594ef
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/Call.cs
@@ -0,0 +1,24 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public class Call(Expression function, NodeList arguments) : Expression
+{
+ public Expression Function { get; set; } = function;
+ public NodeList Arguments { get; set; } = arguments;
+
+ public Call(Expression function) : this(function, [])
+ {
+ }
+
+ public override ImmutableList ChildNodes() => [Function, ..Arguments];
+
+ public override Call Clone() => new(Function.Clone(), Arguments.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/ElementAccess.cs b/bot/src/Syntax/Ast/Expressions/ElementAccess.cs
new file mode 100644
index 0000000..c0f5643
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/ElementAccess.cs
@@ -0,0 +1,20 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public class ElementAccess(Expression table, Expression key) : Variable
+{
+ public Expression Table { get; set; } = table;
+ public Expression Key { get; set; } = key;
+
+ public override ImmutableList ChildNodes() => [Table, Key];
+
+ public override ElementAccess Clone() => new(Table.Clone(), Key.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/Expression.cs b/bot/src/Syntax/Ast/Expressions/Expression.cs
new file mode 100644
index 0000000..c0a7547
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/Expression.cs
@@ -0,0 +1,6 @@
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public abstract class Expression : Node
+{
+ public abstract override Expression Clone();
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/MemberAccess.cs b/bot/src/Syntax/Ast/Expressions/MemberAccess.cs
new file mode 100644
index 0000000..1397d9c
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/MemberAccess.cs
@@ -0,0 +1,20 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public class MemberAccess(Expression table, Name key) : Variable
+{
+ public Expression Table { get; set; } = table;
+ public Name Key { get; set; } = key;
+
+ public override ImmutableList ChildNodes() => [Table, Key];
+
+ public override MemberAccess Clone() => new(Table.Clone(), Key.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/MethodCall.cs b/bot/src/Syntax/Ast/Expressions/MethodCall.cs
new file mode 100644
index 0000000..8f2d3af
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/MethodCall.cs
@@ -0,0 +1,19 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public class MethodCall(Expression function, Name methodName, NodeList arguments) : Call(function, arguments)
+{
+ public Name MethodName { get; set; } = methodName;
+
+ public override ImmutableList ChildNodes() => [Function, MethodName, ..Arguments];
+
+ public override MethodCall Clone() => new(Function.Clone(), MethodName.Clone(), Arguments.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/Name.cs b/bot/src/Syntax/Ast/Expressions/Name.cs
new file mode 100644
index 0000000..d745d0a
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/Name.cs
@@ -0,0 +1,15 @@
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public class Name(string value) : Variable
+{
+ public string Value { get; set; } = value;
+
+ public override Name Clone() => new(Value);
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/Table.cs b/bot/src/Syntax/Ast/Expressions/Table.cs
new file mode 100644
index 0000000..49757b1
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/Table.cs
@@ -0,0 +1,44 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public class Table(NodeList entries) : Expression
+{
+ public NodeList Entries { get; set; } = entries;
+
+ public Table() : this([])
+ {
+ }
+
+ public override ImmutableList ChildNodes() => [..Entries];
+
+ public override Table Clone() => new(Entries.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+
+ public class Entry(Expression? key, Expression value) : Expression
+ {
+ public Expression? Key { get; set; } = key;
+ public Expression Value { get; set; } = value;
+
+ public Entry(Expression value) : this(null, value)
+ {
+ }
+
+ public override ImmutableList ChildNodes() => Key != null ? [Key!, Value] : [Value];
+
+ public override Entry Clone() => new(Key?.Clone(), Value.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/UnaryExpression.cs b/bot/src/Syntax/Ast/Expressions/UnaryExpression.cs
new file mode 100644
index 0000000..c9cffd4
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/UnaryExpression.cs
@@ -0,0 +1,27 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public enum UnaryOperator
+{
+ Not,
+ Length,
+ Negate
+}
+
+public class UnaryExpression(UnaryOperator @operator, Expression operand) : Expression
+{
+ public Expression Operand { get; set; } = operand;
+ public UnaryOperator Operator { get; set; } = @operator;
+
+ public override ImmutableList ChildNodes() => [Operand];
+
+ public override UnaryExpression Clone() => new(Operator, Operand.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/VarArg.cs b/bot/src/Syntax/Ast/Expressions/VarArg.cs
new file mode 100644
index 0000000..585b99a
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/VarArg.cs
@@ -0,0 +1,13 @@
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public class VarArg : Expression
+{
+ public override VarArg Clone() => new();
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Expressions/Variable.cs b/bot/src/Syntax/Ast/Expressions/Variable.cs
new file mode 100644
index 0000000..2227844
--- /dev/null
+++ b/bot/src/Syntax/Ast/Expressions/Variable.cs
@@ -0,0 +1,6 @@
+namespace MoonsecDeobfuscator.Ast.Expressions;
+
+public abstract class Variable : Expression
+{
+ public abstract override Variable Clone();
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Literals/BooleanLiteral.cs b/bot/src/Syntax/Ast/Literals/BooleanLiteral.cs
new file mode 100644
index 0000000..0d71e88
--- /dev/null
+++ b/bot/src/Syntax/Ast/Literals/BooleanLiteral.cs
@@ -0,0 +1,15 @@
+namespace MoonsecDeobfuscator.Ast.Literals;
+
+public class BooleanLiteral(bool value) : Literal
+{
+ public bool Value { get; set; } = value;
+
+ public override BooleanLiteral Clone() => new(Value);
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Literals/Literal.cs b/bot/src/Syntax/Ast/Literals/Literal.cs
new file mode 100644
index 0000000..969b34e
--- /dev/null
+++ b/bot/src/Syntax/Ast/Literals/Literal.cs
@@ -0,0 +1,8 @@
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Literals;
+
+public abstract class Literal : Expression
+{
+ public abstract override Literal Clone();
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Literals/Nil.cs b/bot/src/Syntax/Ast/Literals/Nil.cs
new file mode 100644
index 0000000..0954fef
--- /dev/null
+++ b/bot/src/Syntax/Ast/Literals/Nil.cs
@@ -0,0 +1,13 @@
+namespace MoonsecDeobfuscator.Ast.Literals;
+
+public class Nil : Literal
+{
+ public override Nil Clone() => new();
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Literals/NumberLiteral.cs b/bot/src/Syntax/Ast/Literals/NumberLiteral.cs
new file mode 100644
index 0000000..e8e296d
--- /dev/null
+++ b/bot/src/Syntax/Ast/Literals/NumberLiteral.cs
@@ -0,0 +1,15 @@
+namespace MoonsecDeobfuscator.Ast.Literals;
+
+public class NumberLiteral(double value) : Literal
+{
+ public double Value { get; set; } = value;
+
+ public override NumberLiteral Clone() => new(Value);
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Literals/StringLiteral.cs b/bot/src/Syntax/Ast/Literals/StringLiteral.cs
new file mode 100644
index 0000000..758e93c
--- /dev/null
+++ b/bot/src/Syntax/Ast/Literals/StringLiteral.cs
@@ -0,0 +1,15 @@
+namespace MoonsecDeobfuscator.Ast.Literals;
+
+public class StringLiteral(string value) : Literal
+{
+ public string Value { get; set; } = value;
+
+ public override StringLiteral Clone() => new(Value);
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Node.cs b/bot/src/Syntax/Ast/Node.cs
new file mode 100644
index 0000000..744172c
--- /dev/null
+++ b/bot/src/Syntax/Ast/Node.cs
@@ -0,0 +1,25 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast;
+
+public abstract class Node
+{
+ public virtual ImmutableList ChildNodes() => [];
+
+ public IEnumerable DescendantNodes()
+ {
+ foreach (var child in ChildNodes())
+ {
+ yield return child;
+
+ foreach (var descendant in child.DescendantNodes())
+ yield return descendant;
+ }
+ }
+
+ public abstract Node Clone();
+
+ public abstract void Accept(AstWalker visitor);
+
+ public abstract Node Accept(AstRewriter visitor);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/NodeList.cs b/bot/src/Syntax/Ast/NodeList.cs
new file mode 100644
index 0000000..d4f6464
--- /dev/null
+++ b/bot/src/Syntax/Ast/NodeList.cs
@@ -0,0 +1,26 @@
+namespace MoonsecDeobfuscator.Ast;
+
+public class NodeList : List where TNode : Node
+{
+ public NodeList()
+ {
+ }
+
+ public NodeList(int capacity) : base(capacity)
+ {
+ }
+
+ public NodeList(IEnumerable collection) : base(collection)
+ {
+ }
+
+ public NodeList Clone()
+ {
+ var newList = new NodeList(Count);
+
+ foreach (var element in this)
+ newList.Add((TNode) element.Clone());
+
+ return newList;
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/ParameterList.cs b/bot/src/Syntax/Ast/ParameterList.cs
new file mode 100644
index 0000000..07e8954
--- /dev/null
+++ b/bot/src/Syntax/Ast/ParameterList.cs
@@ -0,0 +1,25 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast;
+
+public class ParameterList(NodeList names, bool isVarArg = false) : Node
+{
+ public NodeList Names { get; set; } = names;
+ public bool IsVarArg { get; set; } = isVarArg;
+
+ public ParameterList() : this([])
+ {
+ }
+
+ public override ImmutableList ChildNodes() => [..Names];
+
+ public override ParameterList Clone() => new(Names.Clone(), IsVarArg);
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/PrettyPrinter.cs b/bot/src/Syntax/Ast/PrettyPrinter.cs
new file mode 100644
index 0000000..860a7a8
--- /dev/null
+++ b/bot/src/Syntax/Ast/PrettyPrinter.cs
@@ -0,0 +1,424 @@
+using System.Text;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+
+namespace MoonsecDeobfuscator.Ast;
+
+public class PrettyPrinter : AstWalker
+{
+ private readonly StringBuilder _builder = new();
+ private int _depth;
+
+ public static void Print(Node node)
+ {
+ var visitor = new PrettyPrinter();
+ node.Accept(visitor);
+ Console.WriteLine(visitor._builder);
+ }
+
+ public static string AsString(Node node)
+ {
+ var visitor = new PrettyPrinter();
+ node.Accept(visitor);
+ return visitor._builder.ToString();
+ }
+
+ private void Indent()
+ {
+ for (var i = 0; i < _depth * 4; i++)
+ _builder.Append(' ');
+ }
+
+ public override void Visit(Name node)
+ {
+ _builder.Append(node.Value);
+ }
+
+ public override void Visit(ElementAccess node)
+ {
+ Visit(node.Table);
+ _builder.Append('[');
+ Visit(node.Key);
+ _builder.Append(']');
+ }
+
+ public override void Visit(MemberAccess node)
+ {
+ Visit(node.Table);
+ _builder.Append($".{node.Key.Value}");
+ }
+
+ public override void Visit(BinaryExpression node)
+ {
+ _builder.Append('(');
+ Visit(node.Left);
+ _builder.Append(OperatorToString(node.Operator));
+ Visit(node.Right);
+ _builder.Append(')');
+ }
+
+ public override void Visit(UnaryExpression node)
+ {
+ _builder.Append(OperatorToString(node.Operator));
+ Visit(node.Operand);
+ }
+
+ public override void Visit(Call node)
+ {
+ var needsPar = node.Function is not Variable;
+
+ if (needsPar)
+ _builder.Append('(');
+
+ Visit(node.Function);
+
+ if (needsPar)
+ _builder.Append(')');
+
+ _builder.Append('(');
+ VisitList(node.Arguments);
+ _builder.Append(')');
+ }
+
+ public override void Visit(MethodCall node)
+ {
+ var needsPar = node.Function is not Variable;
+
+ if (needsPar)
+ _builder.Append('(');
+
+ Visit(node.Function);
+
+ if (needsPar)
+ _builder.Append(')');
+
+ _builder.Append($":{node.MethodName.Value}");
+ _builder.Append('(');
+ VisitList(node.Arguments);
+ _builder.Append(')');
+ }
+
+ public override void Visit(Table node)
+ {
+ var entries = node.Entries;
+
+ if (entries.Count == 0)
+ {
+ _builder.Append("{}");
+ }
+ else
+ {
+ _builder.AppendLine("{");
+ _depth++;
+
+ for (var i = 0; i < entries.Count; i++)
+ {
+ Indent();
+ Visit(entries[i]);
+
+ if (i + 1 < entries.Count)
+ _builder.Append(", ");
+
+ _builder.AppendLine();
+ }
+
+ _depth--;
+ Indent();
+ _builder.Append('}');
+ }
+ }
+
+ public override void Visit(Table.Entry node)
+ {
+ var key = node.Key;
+
+ if (key != null)
+ {
+ if (key is Name name)
+ {
+ _builder.Append($"{name.Value} = ");
+ }
+ else
+ {
+ _builder.Append('[');
+ Visit(key);
+ _builder.Append("] = ");
+ }
+ }
+
+ Visit(node.Value);
+ }
+
+ public override void Visit(AnonymousFunction node)
+ {
+ _builder.Append("function");
+ Visit(node.Parameters);
+ _depth++;
+ Visit(node.Body);
+ _depth--;
+ Indent();
+ _builder.Append("end");
+ }
+
+ public override void Visit(VarArg node)
+ {
+ _builder.Append("...");
+ }
+
+ public override void Visit(ExpressionStatement node)
+ {
+ Visit(node.Expression);
+ }
+
+ public override void Visit(While node)
+ {
+ _builder.Append("while ");
+ Visit(node.Condition);
+ _builder.AppendLine(" do");
+ _depth++;
+ Visit(node.Body);
+ _depth--;
+ Indent();
+ _builder.Append("end");
+ }
+
+ public override void Visit(LocalDeclare node)
+ {
+ _builder.Append("local ");
+ VisitList(node.Names);
+
+ if (node.Values.Count > 0)
+ {
+ _builder.Append(" = ");
+ VisitList(node.Values);
+ }
+ }
+
+ public override void Visit(Assign node)
+ {
+ VisitList(node.Variables);
+ _builder.Append(" = ");
+ VisitList(node.Values);
+ }
+
+ public override void Visit(GenericFor node)
+ {
+ _builder.Append("for ");
+ VisitList(node.Names);
+ _builder.Append(" in ");
+ VisitList(node.Expressions);
+ _builder.AppendLine(" do");
+ _depth++;
+ Visit(node.Body);
+ _depth--;
+ Indent();
+ _builder.Append("end");
+ }
+
+ public override void Visit(NumericFor node)
+ {
+ _builder.Append($"for {node.IteratorName.Value} = ");
+ Visit(node.InitialValue);
+ _builder.Append(", ");
+ Visit(node.FinalValue);
+
+ if (node.Step != null)
+ {
+ _builder.Append(", ");
+ Visit(node.Step);
+ }
+
+ _builder.AppendLine(" do");
+ _depth++;
+ Visit(node.Body);
+ _depth--;
+ Indent();
+ _builder.Append("end");
+ }
+
+ public override void Visit(If node)
+ {
+ _builder.Append("if ");
+ Visit(node.IfClause);
+
+ foreach (var elseIf in node.ElseIfClauses)
+ {
+ Indent();
+ _builder.Append("elseif ");
+ Visit(elseIf);
+ }
+
+ if (node.ElseBody != null)
+ {
+ Indent();
+ _builder.AppendLine("else");
+ _depth++;
+ Visit(node.ElseBody);
+ _depth--;
+ }
+
+ Indent();
+ _builder.Append("end");
+ }
+
+ public override void Visit(If.Clause node)
+ {
+ Visit(node.Condition);
+ _builder.AppendLine(" then");
+ _depth++;
+ Visit(node.Body);
+ _depth--;
+ }
+
+ public override void Visit(LocalFunction node)
+ {
+ _builder.Append($"local function {node.Name.Value}");
+ Visit(node.Parameters);
+ _depth++;
+ Visit(node.Body);
+ _depth--;
+ Indent();
+ _builder.Append("end");
+ }
+
+ public override void Visit(Function node)
+ {
+ _builder.Append($"function {node.Name.Value}");
+ Visit(node.Parameters);
+ _depth++;
+ Visit(node.Body);
+ _depth--;
+ Indent();
+ _builder.Append("end");
+ }
+
+ public override void Visit(Break node)
+ {
+ _builder.Append("break");
+ }
+
+ public override void Visit(Do node)
+ {
+ _builder.AppendLine("do");
+ _depth++;
+ Visit(node.Body);
+ _depth--;
+ Indent();
+ _builder.Append("end");
+ }
+
+ public override void Visit(Repeat node)
+ {
+ _builder.AppendLine("repeat");
+ _depth++;
+ Visit(node.Body);
+ _depth--;
+ Indent();
+ _builder.Append("until ");
+ Visit(node.Condition);
+ }
+
+ public override void Visit(Return node)
+ {
+ _builder.Append("return");
+
+ if (node.Values.Count > 0)
+ {
+ _builder.Append(' ');
+ VisitList(node.Values);
+ }
+ }
+
+ public override void Visit(ParameterList node)
+ {
+ _builder.Append('(');
+
+ VisitList(node.Names);
+
+ if (node.IsVarArg)
+ _builder.Append(node.Names.Count == 0 ? "..." : ", ...");
+
+ _builder.Append(')');
+ _builder.AppendLine();
+ }
+
+ public override void Visit(StringLiteral node)
+ {
+ _builder.Append(node.Value);
+ }
+
+ public override void Visit(NumberLiteral node)
+ {
+ _builder.Append(node.Value);
+ }
+
+ public override void Visit(BooleanLiteral node)
+ {
+ _builder.Append(node.Value.ToString().ToLower());
+ }
+
+ public override void Visit(Nil node)
+ {
+ _builder.Append("nil");
+ }
+
+ public override void Visit(Block node)
+ {
+ foreach (var statement in node.Statements)
+ {
+ Indent();
+ Visit(statement);
+ _builder.AppendLine();
+ }
+ }
+
+ public override void Visit(Goto node)
+ {
+ _builder.Append($"goto {node.Name.Value}");
+ }
+
+ public override void Visit(Label node)
+ {
+ _builder.Append($"::{node.Name.Value}::");
+ }
+
+ protected override void VisitList(NodeList list)
+ {
+ for (var i = 0; i < list.Count; i++)
+ {
+ Visit(list[i]);
+
+ if (i + 1 < list.Count)
+ _builder.Append(", ");
+ }
+ }
+
+ private static string OperatorToString(BinaryOperator @operator) => @operator switch
+ {
+ BinaryOperator.Add => " + ",
+ BinaryOperator.Sub => " - ",
+ BinaryOperator.Mul => " * ",
+ BinaryOperator.Div => " / ",
+ BinaryOperator.Mod => " % ",
+ BinaryOperator.Pow => " ^ ",
+ BinaryOperator.Equals => " == ",
+ BinaryOperator.NotEquals => " ~= ",
+ BinaryOperator.LessThan => " < ",
+ BinaryOperator.GreaterThan => " > ",
+ BinaryOperator.LessThanOrEquals => " <= ",
+ BinaryOperator.GreaterThanOrEquals => " >= ",
+ BinaryOperator.And => " and ",
+ BinaryOperator.Or => " or ",
+ BinaryOperator.Concat => " .. ",
+ _ => " ?? "
+ };
+
+ private static string OperatorToString(UnaryOperator @operator) => @operator switch
+ {
+ UnaryOperator.Not => "not ",
+ UnaryOperator.Length => "#",
+ UnaryOperator.Negate => "-",
+ _ => "??"
+ };
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/Assign.cs b/bot/src/Syntax/Ast/Statements/Assign.cs
new file mode 100644
index 0000000..bd0a1b3
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/Assign.cs
@@ -0,0 +1,21 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class Assign(NodeList variables, NodeList values) : Statement
+{
+ public NodeList Variables { get; set; } = variables;
+ public NodeList Values { get; set; } = values;
+
+ public override ImmutableList ChildNodes() => [..Variables, ..Values];
+
+ public override Assign Clone() => new(Variables.Clone(), Values.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/Break.cs b/bot/src/Syntax/Ast/Statements/Break.cs
new file mode 100644
index 0000000..6345f90
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/Break.cs
@@ -0,0 +1,13 @@
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class Break : Statement
+{
+ public override Break Clone() => new();
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/Do.cs b/bot/src/Syntax/Ast/Statements/Do.cs
new file mode 100644
index 0000000..1e2551b
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/Do.cs
@@ -0,0 +1,19 @@
+using System.Collections.Immutable;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class Do(Block body) : Statement
+{
+ public Block Body { get; set; } = body;
+
+ public override ImmutableList ChildNodes() => [Body];
+
+ public override Do Clone() => new(Body.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/ExpressionStatement.cs b/bot/src/Syntax/Ast/Statements/ExpressionStatement.cs
new file mode 100644
index 0000000..6c0ab8c
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/ExpressionStatement.cs
@@ -0,0 +1,20 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class ExpressionStatement(Expression expression) : Statement
+{
+ public Expression Expression { get; set; } = expression;
+
+ public override ImmutableList ChildNodes() => [Expression];
+
+ public override ExpressionStatement Clone() => new(Expression.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/Function.cs b/bot/src/Syntax/Ast/Statements/Function.cs
new file mode 100644
index 0000000..f929afb
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/Function.cs
@@ -0,0 +1,26 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class Function(Name name, ParameterList parameters, Block body) : Statement
+{
+ public Name Name { get; set; } = name;
+ public ParameterList Parameters { get; set; } = parameters;
+ public Block Body { get; set; } = body;
+
+ public Function(Name name, Block body) : this(name, new ParameterList(), body)
+ {
+ }
+
+ public override ImmutableList ChildNodes() => [Name, Parameters, Body];
+
+ public override Function Clone() => new(Name.Clone(), Parameters.Clone(), Body.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/GenericFor.cs b/bot/src/Syntax/Ast/Statements/GenericFor.cs
new file mode 100644
index 0000000..9e6a1ee
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/GenericFor.cs
@@ -0,0 +1,22 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class GenericFor(NodeList names, NodeList expressions, Block body) : Statement
+{
+ public NodeList Names { get; set; } = names;
+ public NodeList Expressions { get; set; } = expressions;
+ public Block Body { get; set; } = body;
+
+ public override ImmutableList ChildNodes() => [..Names, ..Expressions, Body];
+
+ public override GenericFor Clone() => new(Names.Clone(), Expressions.Clone(), Body.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/Goto.cs b/bot/src/Syntax/Ast/Statements/Goto.cs
new file mode 100644
index 0000000..f98acdf
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/Goto.cs
@@ -0,0 +1,20 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class Goto(Name name) : Statement
+{
+ public Name Name { get; set; } = name;
+
+ public override ImmutableList ChildNodes() => [Name];
+
+ public override Goto Clone() => new(Name.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/If.cs b/bot/src/Syntax/Ast/Statements/If.cs
new file mode 100644
index 0000000..28d9327
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/If.cs
@@ -0,0 +1,44 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class If(If.Clause ifClause, NodeList elseIfClauses, Block? elseBody = null) : Statement
+{
+ public Clause IfClause { get; set; } = ifClause;
+ public NodeList ElseIfClauses { get; set; } = elseIfClauses;
+ public Block? ElseBody { get; set; } = elseBody;
+
+ public If(Clause ifClause) : this(ifClause, [])
+ {
+ }
+
+ public override ImmutableList ChildNodes() =>
+ ElseBody != null ? [IfClause, ..ElseIfClauses, ElseBody] : [IfClause, ..ElseIfClauses];
+
+ public override If Clone() => new(IfClause.Clone(), ElseIfClauses.Clone(), ElseBody?.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+
+ public class Clause(Expression condition, Block body) : Node
+ {
+ public Expression Condition { get; set; } = condition;
+ public Block Body { get; set; } = body;
+
+ public override ImmutableList ChildNodes() => [Condition, Body];
+
+ public override Clause Clone() => new(Condition.Clone(), Body.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/Label.cs b/bot/src/Syntax/Ast/Statements/Label.cs
new file mode 100644
index 0000000..4c2dfa9
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/Label.cs
@@ -0,0 +1,20 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class Label(Name name) : Statement
+{
+ public Name Name { get; set; } = name;
+
+ public override ImmutableList ChildNodes() => [Name];
+
+ public override Label Clone() => new(Name.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/LocalDeclare.cs b/bot/src/Syntax/Ast/Statements/LocalDeclare.cs
new file mode 100644
index 0000000..43e94df
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/LocalDeclare.cs
@@ -0,0 +1,25 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class LocalDeclare(NodeList names, NodeList values) : Statement
+{
+ public NodeList Names { get; set; } = names;
+ public NodeList Values { get; set; } = values;
+
+ public LocalDeclare(NodeList names) : this(names, [])
+ {
+ }
+
+ public override ImmutableList ChildNodes() => [..Names, ..Values];
+
+ public override LocalDeclare Clone() => new(Names.Clone(), Values.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/LocalFunction.cs b/bot/src/Syntax/Ast/Statements/LocalFunction.cs
new file mode 100644
index 0000000..acffcc4
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/LocalFunction.cs
@@ -0,0 +1,26 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class LocalFunction(Name name, ParameterList parameters, Block body) : Statement
+{
+ public Name Name { get; set; } = name;
+ public ParameterList Parameters { get; set; } = parameters;
+ public Block Body { get; set; } = body;
+
+ public LocalFunction(Name name, Block body) : this(name, new ParameterList(), body)
+ {
+ }
+
+ public override ImmutableList ChildNodes() => [Name, Parameters, Body];
+
+ public override LocalFunction Clone() => new(Name.Clone(), Parameters.Clone(), Body.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/NumericFor.cs b/bot/src/Syntax/Ast/Statements/NumericFor.cs
new file mode 100644
index 0000000..67030b1
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/NumericFor.cs
@@ -0,0 +1,44 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class NumericFor : Statement
+{
+ public Name IteratorName { get; set; }
+ public Expression InitialValue { get; set; }
+ public Expression FinalValue { get; set; }
+ public Expression? Step { get; set; }
+ public Block Body { get; set; }
+
+ public NumericFor(Name iteratorName, Expression initialValue, Expression finalValue, Expression? step, Block body)
+ {
+ IteratorName = iteratorName;
+ InitialValue = initialValue;
+ FinalValue = finalValue;
+ Step = step;
+ Body = body;
+ }
+
+ public NumericFor(Name iteratorName, Expression initialValue, Expression finalValue, Block body)
+ {
+ IteratorName = iteratorName;
+ InitialValue = initialValue;
+ FinalValue = finalValue;
+ Body = body;
+ }
+
+ public override ImmutableList ChildNodes() => Step != null
+ ? [IteratorName, InitialValue, FinalValue, Step!, Body]
+ : [IteratorName, InitialValue, FinalValue, Body];
+
+ public override NumericFor Clone() =>
+ new(IteratorName.Clone(), InitialValue.Clone(), FinalValue.Clone(), Step?.Clone(), Body.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/Repeat.cs b/bot/src/Syntax/Ast/Statements/Repeat.cs
new file mode 100644
index 0000000..e0244e3
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/Repeat.cs
@@ -0,0 +1,21 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class Repeat(Block body, Expression condition) : Statement
+{
+ public Block Body { get; set; } = body;
+ public Expression Condition { get; set; } = condition;
+
+ public override ImmutableList ChildNodes() => [Body, Condition];
+
+ public override Repeat Clone() => new(Body.Clone(), Condition.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/Return.cs b/bot/src/Syntax/Ast/Statements/Return.cs
new file mode 100644
index 0000000..8ce2125
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/Return.cs
@@ -0,0 +1,24 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class Return(NodeList values) : Statement
+{
+ public NodeList Values { get; set; } = values;
+
+ public Return() : this([])
+ {
+ }
+
+ public override ImmutableList ChildNodes() => [..Values];
+
+ public override Return Clone() => new(Values.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/Statement.cs b/bot/src/Syntax/Ast/Statements/Statement.cs
new file mode 100644
index 0000000..cd18851
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/Statement.cs
@@ -0,0 +1,6 @@
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public abstract class Statement : Node
+{
+ public abstract override Statement Clone();
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Statements/While.cs b/bot/src/Syntax/Ast/Statements/While.cs
new file mode 100644
index 0000000..f3b0c5e
--- /dev/null
+++ b/bot/src/Syntax/Ast/Statements/While.cs
@@ -0,0 +1,21 @@
+using System.Collections.Immutable;
+using MoonsecDeobfuscator.Ast.Expressions;
+
+namespace MoonsecDeobfuscator.Ast.Statements;
+
+public class While(Expression condition, Block body) : Statement
+{
+ public Expression Condition { get; set; } = condition;
+ public Block Body { get; set; } = body;
+
+ public override ImmutableList ChildNodes() => [Condition, Body];
+
+ public override While Clone() => new(Condition.Clone(), Body.Clone());
+
+ public override void Accept(AstWalker visitor)
+ {
+ visitor.Visit(this);
+ }
+
+ public override Node Accept(AstRewriter visitor) => visitor.Visit(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Visitors/AstRewriter.cs b/bot/src/Syntax/Ast/Visitors/AstRewriter.cs
new file mode 100644
index 0000000..85230db
--- /dev/null
+++ b/bot/src/Syntax/Ast/Visitors/AstRewriter.cs
@@ -0,0 +1,90 @@
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Syntax.Semantic;
+
+namespace MoonsecDeobfuscator.Ast;
+
+public abstract class AstRewriter
+{
+ public SymbolTable? SymbolTable;
+
+ public void Rewrite(Node node, Order order, bool symbols = false, bool fixedPoint = false)
+ {
+ IRewriteDispatch disp = order == Order.PostOrder
+ ? new PostOrderRewriteDispatch(this)
+ : new PreOrderRewriteDispatch(this);
+
+ disp.Rewrite(node, symbols, fixedPoint);
+ }
+
+ protected VariableInfo? GetVariable(string name) => SymbolTable?.GetVariable(name);
+
+ public virtual Node Visit(Name node) => node;
+
+ public virtual Node Visit(ElementAccess node) => node;
+
+ public virtual Node Visit(MemberAccess node) => node;
+
+ public virtual Node Visit(BinaryExpression node) => node;
+
+ public virtual Node Visit(UnaryExpression node) => node;
+
+ public virtual Node Visit(Call node) => node;
+
+ public virtual Node Visit(MethodCall node) => node;
+
+ public virtual Node Visit(Table node) => node;
+
+ public virtual Node Visit(Table.Entry node) => node;
+
+ public virtual Node Visit(AnonymousFunction node) => node;
+
+ public virtual Node Visit(VarArg node) => node;
+
+ public virtual Node Visit(ExpressionStatement node) => node;
+
+ public virtual Node Visit(While node) => node;
+
+ public virtual Node Visit(LocalDeclare node) => node;
+
+ public virtual Node Visit(Assign node) => node;
+
+ public virtual Node Visit(GenericFor node) => node;
+
+ public virtual Node Visit(NumericFor node) => node;
+
+ public virtual Node Visit(If node) => node;
+
+ public virtual Node Visit(If.Clause node) => node;
+
+ public virtual Node Visit(LocalFunction node) => node;
+
+ public virtual Node Visit(Function node) => node;
+
+ public virtual Node Visit(Break node) => node;
+
+ public virtual Node Visit(Do node) => node;
+
+ public virtual Node Visit(Repeat node) => node;
+
+ public virtual Node Visit(Return node) => node;
+
+ public virtual Node Visit(ParameterList node) => node;
+
+ public virtual Node Visit(StringLiteral node) => node;
+
+ public virtual Node Visit(NumberLiteral node) => node;
+
+ public virtual Node Visit(BooleanLiteral node) => node;
+
+ public virtual Node Visit(Nil node) => node;
+
+ public virtual Node Visit(Block node) => node;
+
+ public virtual Node Visit(Goto node) => node;
+
+ public virtual Node Visit(Label node) => node;
+
+ public Node Visit(Node node) => node.Accept(this);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Visitors/AstWalker.cs b/bot/src/Syntax/Ast/Visitors/AstWalker.cs
new file mode 100644
index 0000000..5ff8c28
--- /dev/null
+++ b/bot/src/Syntax/Ast/Visitors/AstWalker.cs
@@ -0,0 +1,228 @@
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Syntax.Semantic;
+
+namespace MoonsecDeobfuscator.Ast;
+
+public abstract class AstWalker
+{
+ public SymbolTable? SymbolTable;
+
+ public void Walk(Node node, bool symbols = false)
+ {
+ if (symbols)
+ SymbolTable = SymbolTableBuilder.Build(node);
+
+ Visit(node);
+ }
+
+ protected VariableInfo? GetVariable(string name) => SymbolTable?.GetVariable(name);
+
+ protected virtual void VisitList(NodeList list) where TNode : Node
+ {
+ foreach (var node in list)
+ Visit(node);
+ }
+
+ public virtual void Visit(Name node)
+ {
+ }
+
+ public virtual void Visit(ElementAccess node)
+ {
+ Visit(node.Table);
+ Visit(node.Key);
+ }
+
+ public virtual void Visit(MemberAccess node)
+ {
+ Visit(node.Table);
+ Visit(node.Key);
+ }
+
+ public virtual void Visit(BinaryExpression node)
+ {
+ Visit(node.Left);
+ Visit(node.Right);
+ }
+
+ public virtual void Visit(UnaryExpression node)
+ {
+ Visit(node.Operand);
+ }
+
+ public virtual void Visit(Call node)
+ {
+ Visit(node.Function);
+ VisitList(node.Arguments);
+ }
+
+ public virtual void Visit(MethodCall node)
+ {
+ Visit(node.Function);
+ Visit(node.MethodName);
+ VisitList(node.Arguments);
+ }
+
+ public virtual void Visit(Table node)
+ {
+ VisitList(node.Entries);
+ }
+
+ public virtual void Visit(Table.Entry node)
+ {
+ var key = node.Key;
+
+ if (key != null)
+ Visit(key);
+
+ Visit(node.Value);
+ }
+
+ public virtual void Visit(AnonymousFunction node)
+ {
+ Visit(node.Parameters);
+ Visit(node.Body);
+ }
+
+ public virtual void Visit(VarArg node)
+ {
+ }
+
+ public virtual void Visit(ExpressionStatement node)
+ {
+ Visit(node.Expression);
+ }
+
+ public virtual void Visit(While node)
+ {
+ Visit(node.Condition);
+ Visit(node.Body);
+ }
+
+ public virtual void Visit(LocalDeclare node)
+ {
+ VisitList(node.Names);
+ VisitList(node.Values);
+ }
+
+ public virtual void Visit(Assign node)
+ {
+ VisitList(node.Variables);
+ VisitList(node.Values);
+ }
+
+ public virtual void Visit(GenericFor node)
+ {
+ VisitList(node.Names);
+ VisitList(node.Expressions);
+ Visit(node.Body);
+ }
+
+ public virtual void Visit(NumericFor node)
+ {
+ Visit(node.IteratorName);
+ Visit(node.InitialValue);
+ Visit(node.FinalValue);
+
+ var step = node.Step;
+
+ if (step != null)
+ Visit(step);
+
+ Visit(node.Body);
+ }
+
+ public virtual void Visit(If node)
+ {
+ Visit(node.IfClause);
+ VisitList(node.ElseIfClauses);
+
+ var elseBody = node.ElseBody;
+
+ if (elseBody != null)
+ Visit(elseBody);
+ }
+
+ public virtual void Visit(If.Clause node)
+ {
+ Visit(node.Condition);
+ Visit(node.Body);
+ }
+
+ public virtual void Visit(LocalFunction node)
+ {
+ Visit(node.Name);
+ Visit(node.Parameters);
+ Visit(node.Body);
+ }
+
+ public virtual void Visit(Function node)
+ {
+ Visit(node.Name);
+ Visit(node.Parameters);
+ Visit(node.Body);
+ }
+
+ public virtual void Visit(Break node)
+ {
+ }
+
+ public virtual void Visit(Do node)
+ {
+ Visit(node.Body);
+ }
+
+ public virtual void Visit(Repeat node)
+ {
+ Visit(node.Body);
+ Visit(node.Condition);
+ }
+
+ public virtual void Visit(Return node)
+ {
+ VisitList(node.Values);
+ }
+
+ public virtual void Visit(ParameterList node)
+ {
+ VisitList(node.Names);
+ }
+
+ public virtual void Visit(StringLiteral node)
+ {
+ }
+
+ public virtual void Visit(NumberLiteral node)
+ {
+ }
+
+ public virtual void Visit(BooleanLiteral node)
+ {
+ }
+
+ public virtual void Visit(Nil node)
+ {
+ }
+
+ public virtual void Visit(Block node)
+ {
+ VisitList(node.Statements);
+ }
+
+ public virtual void Visit(Goto node)
+ {
+ Visit(node.Name);
+ }
+
+ public virtual void Visit(Label node)
+ {
+ Visit(node.Name);
+ }
+
+ public void Visit(Node node)
+ {
+ node.Accept(this);
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Visitors/IRewriteDispatch.cs b/bot/src/Syntax/Ast/Visitors/IRewriteDispatch.cs
new file mode 100644
index 0000000..1d77f19
--- /dev/null
+++ b/bot/src/Syntax/Ast/Visitors/IRewriteDispatch.cs
@@ -0,0 +1,6 @@
+namespace MoonsecDeobfuscator.Ast;
+
+public interface IRewriteDispatch
+{
+ void Rewrite(Node node, bool symbols, bool fixedPoint);
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Visitors/Order.cs b/bot/src/Syntax/Ast/Visitors/Order.cs
new file mode 100644
index 0000000..9f7deec
--- /dev/null
+++ b/bot/src/Syntax/Ast/Visitors/Order.cs
@@ -0,0 +1,7 @@
+namespace MoonsecDeobfuscator.Ast;
+
+public enum Order
+{
+ PreOrder,
+ PostOrder
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Visitors/PostOrderRewriteDispatch.cs b/bot/src/Syntax/Ast/Visitors/PostOrderRewriteDispatch.cs
new file mode 100644
index 0000000..005d190
--- /dev/null
+++ b/bot/src/Syntax/Ast/Visitors/PostOrderRewriteDispatch.cs
@@ -0,0 +1,460 @@
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Syntax.Semantic;
+
+namespace MoonsecDeobfuscator.Ast;
+
+// children -> self
+public class PostOrderRewriteDispatch(AstRewriter rewriter) : AstRewriter, IRewriteDispatch
+{
+ private bool _changed;
+
+ public void Rewrite(Node node, bool symbols, bool fixedPoint)
+ {
+ do
+ {
+ if (symbols)
+ rewriter.SymbolTable = SymbolTableBuilder.Build(node);
+
+ _changed = false;
+ node.Accept(this);
+ }
+ while (_changed && fixedPoint);
+ }
+
+ private void VisitList(NodeList list) where TNode : Node
+ {
+ for (var i = 0; i < list.Count; i++)
+ {
+ var oldNode = list[i];
+ var newNode = Visit(oldNode);
+
+ if (oldNode != newNode)
+ {
+ _changed = true;
+ list[i] = (TNode) newNode;
+ }
+ }
+ }
+
+ public override Node Visit(Name node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(ElementAccess node)
+ {
+ node.Table = (Expression) Visit(node.Table);
+ node.Key = (Expression) Visit(node.Key);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(MemberAccess node)
+ {
+ node.Table = (Expression) Visit(node.Table);
+ node.Key = (Name) Visit(node.Key);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(BinaryExpression node)
+ {
+ node.Left = (Expression) Visit(node.Left);
+ node.Right = (Expression) Visit(node.Right);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(UnaryExpression node)
+ {
+ node.Operand = (Expression) Visit(node.Operand);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Call node)
+ {
+ node.Function = (Expression) Visit(node.Function);
+ VisitList(node.Arguments);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(MethodCall node)
+ {
+ node.Function = (Expression) Visit(node.Function);
+ node.MethodName = (Name) Visit(node.MethodName);
+ VisitList(node.Arguments);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Table node)
+ {
+ VisitList(node.Entries);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Table.Entry node)
+ {
+ var key = node.Key;
+
+ if (key != null)
+ node.Key = (Expression) Visit(key);
+
+ node.Value = (Expression) Visit(node.Value);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(AnonymousFunction node)
+ {
+ node.Parameters = (ParameterList) Visit(node.Parameters);
+ node.Body = (Block) Visit(node.Body);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(VarArg node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(ExpressionStatement node)
+ {
+ node.Expression = (Expression) Visit(node.Expression);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(While node)
+ {
+ node.Condition = (Expression) Visit(node.Condition);
+ node.Body = (Block) Visit(node.Body);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(LocalDeclare node)
+ {
+ VisitList(node.Names);
+ VisitList(node.Values);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Assign node)
+ {
+ VisitList(node.Variables);
+ VisitList(node.Values);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(GenericFor node)
+ {
+ VisitList(node.Names);
+ VisitList(node.Expressions);
+ node.Body = (Block) Visit(node.Body);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(NumericFor node)
+ {
+ node.IteratorName = (Name) Visit(node.IteratorName);
+ node.InitialValue = (Expression) Visit(node.InitialValue);
+ node.FinalValue = (Expression) Visit(node.FinalValue);
+
+ var step = node.Step;
+
+ if (step != null)
+ node.Step = (Expression) Visit(step);
+
+ node.Body = (Block) Visit(node.Body);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(If node)
+ {
+ node.IfClause = (If.Clause) Visit(node.IfClause);
+ VisitList(node.ElseIfClauses);
+
+ var elseBody = node.ElseBody;
+
+ if (elseBody != null)
+ node.ElseBody = (Block) Visit(elseBody);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(If.Clause node)
+ {
+ node.Condition = (Expression) Visit(node.Condition);
+ node.Body = (Block) Visit(node.Body);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(LocalFunction node)
+ {
+ node.Name = (Name) Visit(node.Name);
+ node.Parameters = (ParameterList) Visit(node.Parameters);
+ node.Body = (Block) Visit(node.Body);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Function node)
+ {
+ node.Name = (Name) Visit(node.Name);
+ node.Parameters = (ParameterList) Visit(node.Parameters);
+ node.Body = (Block) Visit(node.Body);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Break node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Do node)
+ {
+ node.Body = (Block) Visit(node.Body);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Repeat node)
+ {
+ node.Body = (Block) Visit(node.Body);
+ node.Condition = (Expression) Visit(node.Condition);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Return node)
+ {
+ VisitList(node.Values);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(ParameterList node)
+ {
+ VisitList(node.Names);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(StringLiteral node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(NumberLiteral node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(BooleanLiteral node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Nil node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Block node)
+ {
+ VisitList(node.Statements);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Goto node)
+ {
+ node.Name = (Name) Visit(node.Name);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+
+ public override Node Visit(Label node)
+ {
+ node.Name = (Name) Visit(node.Name);
+
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ _changed = true;
+
+ return newNode;
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Ast/Visitors/PreOrderRewriteDispatch.cs b/bot/src/Syntax/Ast/Visitors/PreOrderRewriteDispatch.cs
new file mode 100644
index 0000000..063e74a
--- /dev/null
+++ b/bot/src/Syntax/Ast/Visitors/PreOrderRewriteDispatch.cs
@@ -0,0 +1,559 @@
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Syntax.Semantic;
+
+namespace MoonsecDeobfuscator.Ast;
+
+// self -> children
+public class PreOrderRewriteDispatch(AstRewriter rewriter) : AstRewriter, IRewriteDispatch
+{
+ private bool _changed;
+
+ public void Rewrite(Node node, bool symbols, bool fixedPoint)
+ {
+ do
+ {
+ if (symbols)
+ rewriter.SymbolTable = SymbolTableBuilder.Build(node);
+
+ _changed = false;
+ node.Accept(this);
+ }
+ while (_changed && fixedPoint);
+ }
+
+ private void VisitList(NodeList list) where TNode : Node
+ {
+ for (var i = 0; i < list.Count; i++)
+ {
+ var oldNode = list[i];
+ var newNode = Visit(oldNode);
+
+ if (oldNode != newNode)
+ {
+ _changed = true;
+ list[i] = (TNode) newNode;
+ }
+ }
+ }
+
+ public override Node Visit(Name node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ return node;
+ }
+
+ public override Node Visit(ElementAccess node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Table = (Expression) Visit(node.Table);
+ node.Key = (Expression) Visit(node.Key);
+
+ return node;
+ }
+
+ public override Node Visit(MemberAccess node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Table = (Expression) Visit(node.Table);
+ node.Key = (Name) Visit(node.Key);
+
+ return node;
+ }
+
+ public override Node Visit(BinaryExpression node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Left = (Expression) Visit(node.Left);
+ node.Right = (Expression) Visit(node.Right);
+
+ return node;
+ }
+
+ public override Node Visit(UnaryExpression node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Operand = (Expression) Visit(node.Operand);
+
+ return node;
+ }
+
+ public override Node Visit(Call node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Function = (Expression) Visit(node.Function);
+ VisitList(node.Arguments);
+
+ return node;
+ }
+
+ public override Node Visit(MethodCall node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Function = (Expression) Visit(node.Function);
+ node.MethodName = (Name) Visit(node.MethodName);
+ VisitList(node.Arguments);
+
+ return node;
+ }
+
+ public override Node Visit(Table node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ VisitList(node.Entries);
+
+ return node;
+ }
+
+ public override Node Visit(Table.Entry node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ var key = node.Key;
+
+ if (key != null)
+ node.Key = (Expression) Visit(key);
+
+ node.Value = (Expression) Visit(node.Value);
+
+ return node;
+ }
+
+ public override Node Visit(AnonymousFunction node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Parameters = (ParameterList) Visit(node.Parameters);
+ node.Body = (Block) Visit(node.Body);
+
+ return node;
+ }
+
+ public override Node Visit(VarArg node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ return node;
+ }
+
+ public override Node Visit(ExpressionStatement node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Expression = (Expression) Visit(node.Expression);
+
+ return node;
+ }
+
+ public override Node Visit(While node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Condition = (Expression) Visit(node.Condition);
+ node.Body = (Block) Visit(node.Body);
+
+ return node;
+ }
+
+ public override Node Visit(LocalDeclare node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ VisitList(node.Names);
+ VisitList(node.Values);
+
+ return node;
+ }
+
+ public override Node Visit(Assign node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ VisitList(node.Variables);
+ VisitList(node.Values);
+
+ return node;
+ }
+
+ public override Node Visit(GenericFor node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ VisitList(node.Names);
+ VisitList(node.Expressions);
+ node.Body = (Block) Visit(node.Body);
+
+ return node;
+ }
+
+ public override Node Visit(NumericFor node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.IteratorName = (Name) Visit(node.IteratorName);
+ node.InitialValue = (Expression) Visit(node.InitialValue);
+ node.FinalValue = (Expression) Visit(node.FinalValue);
+
+ var step = node.Step;
+
+ if (step != null)
+ node.Step = (Expression) Visit(step);
+
+ node.Body = (Block) Visit(node.Body);
+
+ return node;
+ }
+
+ public override Node Visit(If node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.IfClause = (If.Clause) Visit(node.IfClause);
+ VisitList(node.ElseIfClauses);
+
+ var elseBody = node.ElseBody;
+
+ if (elseBody != null)
+ node.ElseBody = (Block) Visit(elseBody);
+
+ return node;
+ }
+
+ public override Node Visit(If.Clause node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Condition = (Expression) Visit(node.Condition);
+ node.Body = (Block) Visit(node.Body);
+
+ return node;
+ }
+
+ public override Node Visit(LocalFunction node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Name = (Name) Visit(node.Name);
+ node.Parameters = (ParameterList) Visit(node.Parameters);
+ node.Body = (Block) Visit(node.Body);
+
+ return node;
+ }
+
+ public override Node Visit(Function node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Name = (Name) Visit(node.Name);
+ node.Parameters = (ParameterList) Visit(node.Parameters);
+ node.Body = (Block) Visit(node.Body);
+
+ return node;
+ }
+
+ public override Node Visit(Break node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ return node;
+ }
+
+ public override Node Visit(Do node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Body = (Block) Visit(node.Body);
+
+ return node;
+ }
+
+ public override Node Visit(Repeat node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Body = (Block) Visit(node.Body);
+ node.Condition = (Expression) Visit(node.Condition);
+
+ return node;
+ }
+
+ public override Node Visit(Return node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ VisitList(node.Values);
+
+ return node;
+ }
+
+ public override Node Visit(ParameterList node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ VisitList(node.Names);
+
+ return node;
+ }
+
+ public override Node Visit(StringLiteral node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ return node;
+ }
+
+ public override Node Visit(NumberLiteral node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ return node;
+ }
+
+ public override Node Visit(BooleanLiteral node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ return node;
+ }
+
+ public override Node Visit(Nil node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ return node;
+ }
+
+ public override Node Visit(Block node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ VisitList(node.Statements);
+
+ return node;
+ }
+
+ public override Node Visit(Goto node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Name = (Name) Visit(node.Name);
+
+ return node;
+ }
+
+ public override Node Visit(Label node)
+ {
+ var newNode = rewriter.Visit(node);
+
+ if (newNode != node)
+ {
+ _changed = true;
+ return newNode;
+ }
+
+ node.Name = (Name) Visit(node.Name);
+
+ return node;
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/CstToAstVisitor.cs b/bot/src/Syntax/CstToAstVisitor.cs
new file mode 100644
index 0000000..196f15a
--- /dev/null
+++ b/bot/src/Syntax/CstToAstVisitor.cs
@@ -0,0 +1,338 @@
+using System.Globalization;
+using MoonsecDeobfuscator.Ast;
+using MoonsecDeobfuscator.Ast.Expressions;
+using MoonsecDeobfuscator.Ast.Literals;
+using MoonsecDeobfuscator.Ast.Statements;
+using MoonsecDeobfuscator.Syntax.Parser;
+
+namespace MoonsecDeobfuscator.Syntax;
+
+public class CstToAstVisitor : LuaBaseVisitor
+{
+ public override Block VisitBlock(LuaParser.BlockContext ctx)
+ {
+ var statContext = ctx.stat().Where(stat => stat is not LuaParser.StatSemiContext).ToList();
+ var stats = new NodeList(statContext.Count);
+
+ foreach (var stat in statContext)
+ {
+ var node = Visit(stat);
+
+ if (node is Expression expression)
+ node = new ExpressionStatement(expression);
+
+ stats.Add((Statement) node);
+ }
+
+ if (ctx.retstat() != null)
+ stats.Add(VisitRetstat(ctx.retstat()));
+
+ return new Block(stats);
+ }
+
+ public override Assign VisitStatAssign(LuaParser.StatAssignContext ctx) =>
+ new(GetVarList(ctx.varlist()), GetExpList(ctx.explist()));
+
+ public override Call VisitStatCall(LuaParser.StatCallContext ctx) => VisitFunctioncall(ctx.functioncall());
+
+ public override Break VisitStatBreak(LuaParser.StatBreakContext ctx) => new();
+
+ public override Do VisitStatDo(LuaParser.StatDoContext ctx) => new(VisitBlock(ctx.block()));
+
+ public override While VisitStatWhile(LuaParser.StatWhileContext ctx) =>
+ new((Expression) Visit(ctx.exp()), VisitBlock(ctx.block()));
+
+ public override Repeat VisitStatRepeat(LuaParser.StatRepeatContext ctx) =>
+ new(VisitBlock(ctx.block()), (Expression) Visit(ctx.exp()));
+
+ public override If VisitStatIf(LuaParser.StatIfContext ctx)
+ {
+ var ifCtx = ctx.ifstmt();
+ var elseIfCtx = ctx.elseifstmt();
+ var elseCtx = ctx.elsestmt();
+
+ var ifClause = new If.Clause((Expression) Visit(ifCtx.exp()), VisitBlock(ifCtx.block()));
+ var elseIfClause = elseIfCtx.exp()
+ .Zip(elseIfCtx.block())
+ .Select(it => new If.Clause((Expression) Visit(it.First), VisitBlock(it.Second)));
+ var elseBody = elseCtx.block() != null
+ ? VisitBlock(elseCtx.block())
+ : null;
+
+ return new If(ifClause, new NodeList(elseIfClause), elseBody);
+ }
+
+ public override NumericFor VisitStatNumericFor(LuaParser.StatNumericForContext ctx)
+ {
+ var expCtx = ctx.exp();
+
+ return new NumericFor(
+ new Name(ctx.NAME().GetText()),
+ (Expression) Visit(expCtx[0]),
+ (Expression) Visit(expCtx[1]),
+ expCtx.Length > 2 ? (Expression) Visit(expCtx[2]) : null,
+ VisitBlock(ctx.block())
+ );
+ }
+
+ public override GenericFor VisitStatGenericFor(LuaParser.StatGenericForContext ctx) =>
+ new(GetNameList(ctx.namelist()), GetExpList(ctx.explist()), VisitBlock(ctx.block()));
+
+ public override Function VisitStatFunction(LuaParser.StatFunctionContext ctx)
+ {
+ var funcBodyCtx = ctx.funcbody();
+
+ return new Function(
+ new Name(ctx.funcname().GetText()),
+ GetParList(funcBodyCtx.parlist()),
+ VisitBlock(funcBodyCtx.block())
+ );
+ }
+
+ public override LocalFunction VisitStatLocalFunction(LuaParser.StatLocalFunctionContext ctx)
+ {
+ var funcBodyCtx = ctx.funcbody();
+
+ return new LocalFunction(
+ new Name(ctx.NAME().GetText()),
+ GetParList(funcBodyCtx.parlist()),
+ VisitBlock(funcBodyCtx.block())
+ );
+ }
+
+ public override LocalDeclare VisitStatLocalDeclare(LuaParser.StatLocalDeclareContext ctx) =>
+ new(GetNameList(ctx.namelist()), GetExpList(ctx.explist()));
+
+ public override Goto VisitStatGoto(LuaParser.StatGotoContext ctx) => new(new Name(ctx.NAME().GetText()));
+
+ public override Label VisitStatLabel(LuaParser.StatLabelContext ctx) => new(new Name(ctx.label().NAME().GetText()));
+
+ public override Return VisitRetstat(LuaParser.RetstatContext ctx) => new(GetExpList(ctx.explist()));
+
+ public override Nil VisitExpNil(LuaParser.ExpNilContext ctx) => new();
+
+ public override BooleanLiteral VisitExpFalse(LuaParser.ExpFalseContext ctx) => new(false);
+
+ public override BooleanLiteral VisitExpTrue(LuaParser.ExpTrueContext ctx) => new(true);
+
+ public override NumberLiteral VisitExpNumber(LuaParser.ExpNumberContext ctx)
+ {
+ var numberCtx = ctx.number();
+
+ if (numberCtx.INT() != null || numberCtx.FLOAT() != null)
+ return new NumberLiteral(double.Parse(numberCtx.GetText()));
+
+ return new NumberLiteral(int.Parse(numberCtx.GetText().Replace("0x", ""), NumberStyles.HexNumber));
+ }
+
+ public override StringLiteral VisitExpString(LuaParser.ExpStringContext ctx) => new(ctx.@string().GetText());
+
+ public override VarArg VisitExpVarArg(LuaParser.ExpVarArgContext ctx) => new();
+
+ public override AnonymousFunction VisitExpFunction(LuaParser.ExpFunctionContext ctx)
+ {
+ var funcBodyCtx = ctx.functiondef().funcbody();
+ return new AnonymousFunction(GetParList(funcBodyCtx.parlist()), VisitBlock(funcBodyCtx.block()));
+ }
+
+ public override Expression VisitPrefixexp(LuaParser.PrefixexpContext ctx)
+ {
+ var nameAndArgsCtx = ctx.nameAndArgs();
+ var varOrExp = VisitVarOrExp(ctx.varOrExp());
+
+ return nameAndArgsCtx.Length == 0 ? varOrExp : GetCallChain(varOrExp, nameAndArgsCtx);
+ }
+
+ public override Table VisitExpTable(LuaParser.ExpTableContext ctx) => GetTable(ctx.tableconstructor());
+
+ public override BinaryExpression VisitExpPow(LuaParser.ExpPowContext ctx) =>
+ new(BinaryOperator.Pow, (Expression) Visit(ctx.exp(0)), (Expression) Visit(ctx.exp(1)));
+
+ public override UnaryExpression VisitExpUnary(LuaParser.ExpUnaryContext ctx)
+ {
+ var @operator = ctx.operatorUnary().GetText() switch
+ {
+ "not" => UnaryOperator.Not,
+ "#" => UnaryOperator.Length,
+ "-" => UnaryOperator.Negate,
+ _ => throw new Exception($"Invalid operator: {ctx.GetText()}")
+ };
+
+ return new UnaryExpression(@operator, (Expression) Visit(ctx.exp()));
+ }
+
+ public override BinaryExpression VisitExpMulDivMod(LuaParser.ExpMulDivModContext ctx)
+ {
+ var @operator = ctx.operatorMulDivMod().GetText() switch
+ {
+ "*" => BinaryOperator.Mul,
+ "/" => BinaryOperator.Div,
+ "%" => BinaryOperator.Mod,
+ _ => throw new Exception($"Invalid operator: {ctx.GetText()}")
+ };
+
+ return new BinaryExpression(@operator, (Expression) Visit(ctx.exp(0)), (Expression) Visit(ctx.exp(1)));
+ }
+
+ public override BinaryExpression VisitExpAddSub(LuaParser.ExpAddSubContext ctx)
+ {
+ var @operator = ctx.operatorAddSub().GetText() == "+" ? BinaryOperator.Add : BinaryOperator.Sub;
+ return new BinaryExpression(@operator, (Expression) Visit(ctx.exp(0)), (Expression) Visit(ctx.exp(1)));
+ }
+
+ public override BinaryExpression VisitExpConcat(LuaParser.ExpConcatContext ctx) =>
+ new(BinaryOperator.Concat, (Expression) Visit(ctx.exp(0)), (Expression) Visit(ctx.exp(1)));
+
+ public override BinaryExpression VisitExpCompare(LuaParser.ExpCompareContext ctx)
+ {
+ var @operator = ctx.operatorComparison().GetText() switch
+ {
+ "==" => BinaryOperator.Equals,
+ "~=" => BinaryOperator.NotEquals,
+ "<=" => BinaryOperator.LessThanOrEquals,
+ ">=" => BinaryOperator.GreaterThanOrEquals,
+ "<" => BinaryOperator.LessThan,
+ ">" => BinaryOperator.GreaterThan,
+ _ => throw new Exception($"Invalid operator: {ctx.GetText()}")
+ };
+
+ return new BinaryExpression(@operator, (Expression) Visit(ctx.exp(0)), (Expression) Visit(ctx.exp(1)));
+ }
+
+ public override BinaryExpression VisitExpAnd(LuaParser.ExpAndContext ctx) =>
+ new(BinaryOperator.And, (Expression) Visit(ctx.exp(0)), (Expression) Visit(ctx.exp(1)));
+
+ public override BinaryExpression VisitExpOr(LuaParser.ExpOrContext ctx) =>
+ new(BinaryOperator.Or, (Expression) Visit(ctx.exp(0)), (Expression) Visit(ctx.exp(1)));
+
+ public override Call VisitFunctioncall(LuaParser.FunctioncallContext ctx) =>
+ GetCallChain(VisitVarOrExp(ctx.varOrExp()), ctx.nameAndArgs());
+
+ public override Expression VisitVarOrExp(LuaParser.VarOrExpContext ctx) =>
+ ctx.exp() != null ? (Expression) Visit(ctx.exp()) : VisitVar(ctx.var());
+
+ public override Variable VisitVar(LuaParser.VarContext ctx)
+ {
+ var varSuffixCtx = ctx.varSuffix();
+ var name = ctx.NAME() != null ? new Name(ctx.NAME().GetText()) : (Expression) Visit(ctx.exp());
+
+ if (varSuffixCtx.Length == 0)
+ return (Variable) name;
+
+ return (Variable) varSuffixCtx.Aggregate(name, (expression, suffix) => suffix.NAME() != null
+ ? new MemberAccess(expression, new Name(suffix.NAME().GetText()))
+ : new ElementAccess(expression, (Expression) Visit(suffix.exp()))
+ );
+ }
+
+ private NodeList GetExpList(LuaParser.ExplistContext? ctx)
+ {
+ if (ctx == null)
+ return [];
+
+ var exprs = ctx.exp();
+ var list = new NodeList(exprs.Length);
+
+ foreach (var expression in exprs)
+ list.Add((Expression) Visit(expression));
+
+ return list;
+ }
+
+ private static NodeList GetNameList(LuaParser.NamelistContext context)
+ {
+ var names = context.NAME();
+ var list = new NodeList(names.Length);
+
+ foreach (var name in names)
+ list.Add(new Name(name.GetText()));
+
+ return list;
+ }
+
+ private NodeList GetVarList(LuaParser.VarlistContext context)
+ {
+ var vars = context.var();
+ var list = new NodeList(vars.Length);
+
+ foreach (var variable in vars)
+ list.Add(VisitVar(variable));
+
+ return list;
+ }
+
+ private NodeList GetArgs(LuaParser.ArgsContext ctx)
+ {
+ if (ctx.explist() != null)
+ return GetExpList(ctx.explist());
+
+ var args = new NodeList();
+
+ if (ctx.tableconstructor() != null)
+ args.Add(GetTable(ctx.tableconstructor()));
+ else if (ctx.@string() != null)
+ args.Add(new StringLiteral(ctx.@string().GetText()));
+
+ return args;
+ }
+
+ private Call GetCallChain(Expression varOrExp, LuaParser.NameAndArgsContext[] nameAndArgsCtx)
+ {
+ return (Call) nameAndArgsCtx.Aggregate(varOrExp, (expression, nameAndArgs) =>
+ {
+ var args = GetArgs(nameAndArgs.args());
+
+ return nameAndArgs.NAME() != null
+ ? new MethodCall(expression, new Name(nameAndArgs.NAME().GetText()), args)
+ : new Call(expression, args);
+ });
+ }
+
+ private Table.Entry GetField(LuaParser.FieldContext ctx)
+ {
+ var expCtx = ctx.exp();
+
+ if (expCtx.Length == 2)
+ {
+ var key = (Expression) Visit(expCtx[0]);
+ var value = (Expression) Visit(expCtx[1]);
+
+ return new Table.Entry(key, value);
+ }
+
+ if (ctx.NAME() != null)
+ {
+ var key = new Name(ctx.NAME().GetText());
+ var value = (Expression) Visit(expCtx[0]);
+
+ return new Table.Entry(key, value);
+ }
+
+ return new Table.Entry((Expression) Visit(expCtx[0]));
+ }
+
+ private Table GetTable(LuaParser.TableconstructorContext ctx)
+ {
+ var entries = new NodeList();
+ var fieldListCtx = ctx.fieldlist();
+
+ if (fieldListCtx != null)
+ {
+ foreach (var fieldCtx in fieldListCtx.field())
+ entries.Add(GetField(fieldCtx));
+ }
+
+ return new Table(entries);
+ }
+
+ private static ParameterList GetParList(LuaParser.ParlistContext? ctx)
+ {
+ if (ctx == null)
+ return new ParameterList();
+
+ var names = ctx.namelist() != null ? GetNameList(ctx.namelist()) : [];
+ var isVarArg = ctx.children.Any(child => child.GetText() == "...");
+
+ return new ParameterList(names, isVarArg);
+ }
+}
\ No newline at end of file
diff --git a/bot/src/Syntax/Grammar/Lua.g4 b/bot/src/Syntax/Grammar/Lua.g4
new file mode 100644
index 0000000..f866676
--- /dev/null
+++ b/bot/src/Syntax/Grammar/Lua.g4
@@ -0,0 +1,359 @@
+/*
+BSD License
+
+Copyright (c) 2013, Kazunori Sakamoto
+Copyright (c) 2016, Alexander Alexeev
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. Neither the NAME of Rainer Schuster nor the NAMEs of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+This grammar file derived from:
+
+ Lua 5.3 Reference Manual
+ http://www.lua.org/manual/5.3/manual.html
+
+ Lua 5.2 Reference Manual
+ http://www.lua.org/manual/5.2/manual.html
+
+ Lua 5.1 grammar written by Nicolai Mainiero
+ http://www.antlr3.org/grammar/1178608849736/Lua.g
+
+Tested by Kazunori Sakamoto with Test suite for Lua 5.2 (http://www.lua.org/tests/5.2/)
+
+Tested by Alexander Alexeev with Test suite for Lua 5.3 http://www.lua.org/tests/lua-5.3.2-tests.tar.gz
+*/
+
+grammar Lua;
+
+chunk
+ : block EOF
+ ;
+
+block
+ : stat* retstat?
+ ;
+
+stat
+ : ';' # statSemi
+ | varlist '=' explist # statAssign
+ | functioncall # statCall
+ | label # statLabel
+ | 'break' # statBreak
+ | 'goto' NAME # statGoto
+ | 'do' block 'end' # statDo
+ | 'while' exp 'do' block 'end' # statWhile
+ | 'repeat' block 'until' exp # statRepeat
+ | ifstmt elseifstmt elsestmt 'end' # statIf
+ | 'for' NAME '=' exp ',' exp (',' exp)? 'do' block 'end' # statNumericFor
+ | 'for' namelist 'in' explist 'do' block 'end' # statGenericFor
+ | 'function' funcname funcbody # statFunction
+ | 'local' 'function' NAME funcbody # statLocalFunction
+ | 'local' namelist ('=' explist)? # statLocalDeclare
+ ;
+
+ifstmt
+ :
+ 'if' exp 'then' block
+ ;
+
+elseifstmt
+ :
+ ('elseif' exp 'then' block)*
+ ;
+
+elsestmt
+ :
+ ('else' block)?
+ ;
+
+retstat
+ : 'return' explist? ';'?
+ ;
+
+label
+ : '::' NAME '::'
+ ;
+
+funcname
+ : NAME ('.' NAME)* (':' NAME)?
+ ;
+
+varlist
+ : var (',' var)*
+ ;
+
+namelist
+ : NAME (',' NAME)*
+ ;
+
+explist
+ : exp (',' exp)*
+ ;
+
+exp
+ : 'nil' # expNil
+ | 'false' # expFalse
+ | 'true' # expTrue
+ | number # expNumber
+ | string # expString
+ | '...' # expVarArg
+ | functiondef # expFunction
+ | prefixexp # expPrefix
+ | tableconstructor # expTable
+ | exp operatorPower exp # expPow
+ | operatorUnary exp # expUnary
+ | exp operatorMulDivMod exp # expMulDivMod
+ | exp operatorAddSub exp # expAddSub
+ | exp operatorStrcat exp # expConcat
+ | exp operatorComparison exp # expCompare
+ | exp operatorAnd exp # expAnd
+ | exp operatorOr exp # expOr
+ | exp operatorBitwise exp # expBitwise
+ ;
+
+prefixexp
+ : varOrExp nameAndArgs*
+ ;
+
+functioncall
+ : varOrExp nameAndArgs+
+ ;
+
+varOrExp
+ : var | '(' exp ')'
+ ;
+
+var
+ : (NAME | '(' exp ')' varSuffix) varSuffix*
+ ;
+
+/*
+varSuffix
+ : nameAndArgs* ('[' exp ']' | '.' NAME)
+ ;
+*/
+
+varSuffix
+ : '[' exp ']'
+ | '.' NAME
+ ;
+
+nameAndArgs
+ : (':' NAME)? args
+ ;
+
+/*
+var
+ : NAME | prefixexp '[' exp ']' | prefixexp '.' NAME
+ ;
+
+prefixexp
+ : var | functioncall | '(' exp ')'
+ ;
+
+functioncall
+ : prefixexp args | prefixexp ':' NAME args
+ ;
+*/
+
+args
+ : '(' explist? ')' | tableconstructor | string
+ ;
+
+functiondef
+ : 'function' funcbody
+ ;
+
+funcbody
+ : '(' parlist? ')' block 'end'
+ ;
+
+parlist
+ : namelist (',' '...')? | '...'
+ ;
+
+tableconstructor
+ : '{' fieldlist? '}'
+ ;
+
+fieldlist
+ : field (fieldsep field)* fieldsep?
+ ;
+
+field
+ : '[' exp ']' '=' exp | NAME '=' exp | exp
+ ;
+
+fieldsep
+ : ',' | ';'
+ ;
+
+operatorOr
+ : 'or';
+
+operatorAnd
+ : 'and';
+
+operatorComparison
+ : '<' | '>' | '<=' | '>=' | '~=' | '==';
+
+operatorStrcat
+ : '..';
+
+operatorAddSub
+ : '+' | '-';
+
+operatorMulDivMod
+ : '*' | '/' | '%' | '//';
+
+operatorBitwise
+ : '&' | '|' | '~' | '<<' | '>>';
+
+operatorUnary
+ : 'not' | '#' | '-' | '~';
+
+operatorPower
+ : '^';
+
+number
+ : INT | HEX | FLOAT | HEX_FLOAT
+ ;
+
+string
+ : NORMALSTRING | CHARSTRING | LONGSTRING
+ ;
+
+// LEXER
+
+NAME
+ : [a-zA-Z_][a-zA-Z_0-9]*
+ ;
+
+NORMALSTRING
+ : '"' ( EscapeSequence | ~('\\'|'"') )* '"'
+ ;
+
+CHARSTRING
+ : '\'' ( EscapeSequence | ~('\''|'\\') )* '\''
+ ;
+
+LONGSTRING
+ : '[' NESTED_STR ']'
+ ;
+
+fragment
+NESTED_STR
+ : '=' NESTED_STR '='
+ | '[' .*? ']'
+ ;
+
+INT
+ : Digit+
+ ;
+
+HEX
+ : '0' [xX] HexDigit+
+ ;
+
+FLOAT
+ : Digit+ '.' Digit* ExponentPart?
+ | '.' Digit+ ExponentPart?
+ | Digit+ ExponentPart
+ ;
+
+HEX_FLOAT
+ : '0' [xX] HexDigit+ '.' HexDigit* HexExponentPart?
+ | '0' [xX] '.' HexDigit+ HexExponentPart?
+ | '0' [xX] HexDigit+ HexExponentPart
+ ;
+
+fragment
+ExponentPart
+ : [eE] [+-]? Digit+
+ ;
+
+fragment
+HexExponentPart
+ : [pP] [+-]? Digit+
+ ;
+
+fragment
+EscapeSequence
+ : '\\' [abfnrtvz"'\\]
+ | '\\' '\r'? '\n'
+ | DecimalEscape
+ | HexEscape
+ | UtfEscape
+ ;
+
+fragment
+DecimalEscape
+ : '\\' Digit
+ | '\\' Digit Digit
+ | '\\' [0-2] Digit Digit
+ ;
+
+fragment
+HexEscape
+ : '\\' 'x' HexDigit HexDigit
+ ;
+
+fragment
+UtfEscape
+ : '\\' 'u{' HexDigit+ '}'
+ ;
+
+fragment
+Digit
+ : [0-9]
+ ;
+
+fragment
+HexDigit
+ : [0-9a-fA-F]
+ ;
+
+COMMENT
+ : '--[' NESTED_STR ']' -> channel(HIDDEN)
+ ;
+
+LINE_COMMENT
+ : '--'
+ ( // --
+ | '[' '='* // --[==
+ | '[' '='* ~('='|'['|'\r'|'\n') ~('\r'|'\n')* // --[==AA
+ | ~('['|'\r'|'\n') ~('\r'|'\n')* // --AAA
+ ) ('\r\n'|'\r'|'\n'|EOF)
+ -> channel(HIDDEN)
+ ;
+
+WS
+ : [ \t\u000C\r\n]+ -> skip
+ ;
+
+SHEBANG
+ : '#' '!' ~('\n'|'\r')* -> channel(HIDDEN)
+ ;
\ No newline at end of file
diff --git a/bot/src/Syntax/Parser/LuaBaseVisitor.cs b/bot/src/Syntax/Parser/LuaBaseVisitor.cs
new file mode 100644
index 0000000..f505b67
--- /dev/null
+++ b/bot/src/Syntax/Parser/LuaBaseVisitor.cs
@@ -0,0 +1,748 @@
+// Unreachable code detected
+#pragma warning disable 0162
+// The variable '...' is assigned but its value is never used
+#pragma warning disable 0219
+// Missing XML comment for publicly visible type or member '...'
+#pragma warning disable 1591
+// Ambiguous reference in cref attribute
+#pragma warning disable 419
+
+using Antlr4.Runtime.Misc;
+using Antlr4.Runtime.Tree;
+
+namespace MoonsecDeobfuscator.Syntax.Parser;
+
+///
+/// This class provides an empty implementation of ,
+/// which can be extended to create a visitor which only needs to handle a subset
+/// of the available methods.
+///
+/// The return type of the visit operation.
+[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")]
+[System.Diagnostics.DebuggerNonUserCode]
+[System.CLSCompliant(false)]
+public partial class LuaBaseVisitor : AbstractParseTreeVisitor, ILuaVisitor {
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitChunk([NotNull] LuaParser.ChunkContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitBlock([NotNull] LuaParser.BlockContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statSemi
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatSemi([NotNull] LuaParser.StatSemiContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the statAssign
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatAssign([NotNull] LuaParser.StatAssignContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statCall
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatCall([NotNull] LuaParser.StatCallContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statLabel
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatLabel([NotNull] LuaParser.StatLabelContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the statBreak
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatBreak([NotNull] LuaParser.StatBreakContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statGoto
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatGoto([NotNull] LuaParser.StatGotoContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the statDo
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatDo([NotNull] LuaParser.StatDoContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statWhile
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatWhile([NotNull] LuaParser.StatWhileContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statRepeat
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatRepeat([NotNull] LuaParser.StatRepeatContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statIf
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatIf([NotNull] LuaParser.StatIfContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statNumericFor
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatNumericFor([NotNull] LuaParser.StatNumericForContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statGenericFor
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatGenericFor([NotNull] LuaParser.StatGenericForContext ctx) { return VisitChildren(ctx); }
+ ///
+ /// Visit a parse tree produced by the statFunction
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatFunction([NotNull] LuaParser.StatFunctionContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the statLocalFunction
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatLocalFunction([NotNull] LuaParser.StatLocalFunctionContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the statLocalDeclare
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitStatLocalDeclare([NotNull] LuaParser.StatLocalDeclareContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitIfstmt([NotNull] LuaParser.IfstmtContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitElseifstmt([NotNull] LuaParser.ElseifstmtContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitElsestmt([NotNull] LuaParser.ElsestmtContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitRetstat([NotNull] LuaParser.RetstatContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitLabel([NotNull] LuaParser.LabelContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitFuncname([NotNull] LuaParser.FuncnameContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitVarlist([NotNull] LuaParser.VarlistContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitNamelist([NotNull] LuaParser.NamelistContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExplist([NotNull] LuaParser.ExplistContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expNumber
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpNumber([NotNull] LuaParser.ExpNumberContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expTrue
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpTrue([NotNull] LuaParser.ExpTrueContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expOr
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpOr([NotNull] LuaParser.ExpOrContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expBitwise
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpBitwise([NotNull] LuaParser.ExpBitwiseContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expMulDivMod
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpMulDivMod([NotNull] LuaParser.ExpMulDivModContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expFalse
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpFalse([NotNull] LuaParser.ExpFalseContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expString
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpString([NotNull] LuaParser.ExpStringContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expPrefix
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpPrefix([NotNull] LuaParser.ExpPrefixContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expUnary
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpUnary([NotNull] LuaParser.ExpUnaryContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expAnd
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpAnd([NotNull] LuaParser.ExpAndContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expPow
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpPow([NotNull] LuaParser.ExpPowContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expFunction
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpFunction([NotNull] LuaParser.ExpFunctionContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expCompare
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpCompare([NotNull] LuaParser.ExpCompareContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expConcat
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpConcat([NotNull] LuaParser.ExpConcatContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expNil
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpNil([NotNull] LuaParser.ExpNilContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expAddSub
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpAddSub([NotNull] LuaParser.ExpAddSubContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expVarArg
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpVarArg([NotNull] LuaParser.ExpVarArgContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by the expTable
+ /// labeled alternative in .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitExpTable([NotNull] LuaParser.ExpTableContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitPrefixexp([NotNull] LuaParser.PrefixexpContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitFunctioncall([NotNull] LuaParser.FunctioncallContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitVarOrExp([NotNull] LuaParser.VarOrExpContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitVar([NotNull] LuaParser.VarContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitVarSuffix([NotNull] LuaParser.VarSuffixContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitNameAndArgs([NotNull] LuaParser.NameAndArgsContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitArgs([NotNull] LuaParser.ArgsContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitFunctiondef([NotNull] LuaParser.FunctiondefContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitFuncbody([NotNull] LuaParser.FuncbodyContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitParlist([NotNull] LuaParser.ParlistContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitTableconstructor([NotNull] LuaParser.TableconstructorContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitFieldlist([NotNull] LuaParser.FieldlistContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitField([NotNull] LuaParser.FieldContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitFieldsep([NotNull] LuaParser.FieldsepContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitOperatorOr([NotNull] LuaParser.OperatorOrContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitOperatorAnd([NotNull] LuaParser.OperatorAndContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitOperatorComparison([NotNull] LuaParser.OperatorComparisonContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitOperatorStrcat([NotNull] LuaParser.OperatorStrcatContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitOperatorAddSub([NotNull] LuaParser.OperatorAddSubContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitOperatorMulDivMod([NotNull] LuaParser.OperatorMulDivModContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitOperatorBitwise([NotNull] LuaParser.OperatorBitwiseContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitOperatorUnary([NotNull] LuaParser.OperatorUnaryContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitOperatorPower([NotNull] LuaParser.OperatorPowerContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitNumber([NotNull] LuaParser.NumberContext context) { return VisitChildren(context); }
+ ///
+ /// Visit a parse tree produced by .
+ ///
+ /// The default implementation returns the result of calling
+ /// on .
+ ///
+ ///
+ /// The parse tree.
+ /// The visitor result.
+ public virtual Result VisitString([NotNull] LuaParser.StringContext context) { return VisitChildren(context); }
+}
diff --git a/bot/src/Syntax/Parser/LuaLexer.cs b/bot/src/Syntax/Parser/LuaLexer.cs
new file mode 100644
index 0000000..09706b9
--- /dev/null
+++ b/bot/src/Syntax/Parser/LuaLexer.cs
@@ -0,0 +1,323 @@
+// Unreachable code detected
+#pragma warning disable 0162
+// The variable '...' is assigned but its value is never used
+#pragma warning disable 0219
+// Missing XML comment for publicly visible type or member '...'
+#pragma warning disable 1591
+// Ambiguous reference in cref attribute
+#pragma warning disable 419
+
+using Antlr4.Runtime;
+using Antlr4.Runtime.Atn;
+using Antlr4.Runtime.Misc;
+using DFA = Antlr4.Runtime.Dfa.DFA;
+
+namespace MoonsecDeobfuscator.Syntax.Parser;
+
+[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")]
+[System.CLSCompliant(false)]
+public partial class LuaLexer : Lexer {
+ protected static DFA[] decisionToDFA;
+ protected static PredictionContextCache sharedContextCache = new PredictionContextCache();
+ public const int
+ T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9,
+ T__9=10, T__10=11, T__11=12, T__12=13, T__13=14, T__14=15, T__15=16, T__16=17,
+ T__17=18, T__18=19, T__19=20, T__20=21, T__21=22, T__22=23, T__23=24,
+ T__24=25, T__25=26, T__26=27, T__27=28, T__28=29, T__29=30, T__30=31,
+ T__31=32, T__32=33, T__33=34, T__34=35, T__35=36, T__36=37, T__37=38,
+ T__38=39, T__39=40, T__40=41, T__41=42, T__42=43, T__43=44, T__44=45,
+ T__45=46, T__46=47, T__47=48, T__48=49, T__49=50, T__50=51, T__51=52,
+ T__52=53, T__53=54, T__54=55, NAME=56, NORMALSTRING=57, CHARSTRING=58,
+ LONGSTRING=59, INT=60, HEX=61, FLOAT=62, HEX_FLOAT=63, COMMENT=64, LINE_COMMENT=65,
+ WS=66, SHEBANG=67;
+ public static string[] channelNames = {
+ "DEFAULT_TOKEN_CHANNEL", "HIDDEN"
+ };
+
+ public static string[] modeNames = {
+ "DEFAULT_MODE"
+ };
+
+ public static readonly string[] ruleNames = {
+ "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8",
+ "T__9", "T__10", "T__11", "T__12", "T__13", "T__14", "T__15", "T__16",
+ "T__17", "T__18", "T__19", "T__20", "T__21", "T__22", "T__23", "T__24",
+ "T__25", "T__26", "T__27", "T__28", "T__29", "T__30", "T__31", "T__32",
+ "T__33", "T__34", "T__35", "T__36", "T__37", "T__38", "T__39", "T__40",
+ "T__41", "T__42", "T__43", "T__44", "T__45", "T__46", "T__47", "T__48",
+ "T__49", "T__50", "T__51", "T__52", "T__53", "T__54", "NAME", "NORMALSTRING",
+ "CHARSTRING", "LONGSTRING", "NESTED_STR", "INT", "HEX", "FLOAT", "HEX_FLOAT",
+ "ExponentPart", "HexExponentPart", "EscapeSequence", "DecimalEscape",
+ "HexEscape", "UtfEscape", "Digit", "HexDigit", "COMMENT", "LINE_COMMENT",
+ "WS", "SHEBANG"
+ };
+
+
+ public LuaLexer(ICharStream input)
+ : this(input, Console.Out, Console.Error) { }
+
+ public LuaLexer(ICharStream input, TextWriter output, TextWriter errorOutput)
+ : base(input, output, errorOutput)
+ {
+ Interpreter = new LexerATNSimulator(this, _ATN, decisionToDFA, sharedContextCache);
+ }
+
+ private static readonly string[] _LiteralNames = {
+ null, "';'", "'='", "'break'", "'goto'", "'do'", "'end'", "'while'", "'repeat'",
+ "'until'", "'for'", "','", "'in'", "'function'", "'local'", "'if'", "'then'",
+ "'elseif'", "'else'", "'return'", "'::'", "'.'", "':'", "'nil'", "'false'",
+ "'true'", "'...'", "'('", "')'", "'['", "']'", "'{'", "'}'", "'or'", "'and'",
+ "'<'", "'>'", "'<='", "'>='", "'~='", "'=='", "'..'", "'+'", "'-'", "'*'",
+ "'/'", "'%'", "'//'", "'&'", "'|'", "'~'", "'<<'", "'>>'", "'not'", "'#'",
+ "'^'"
+ };
+ private static readonly string[] _SymbolicNames = {
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, "NAME", "NORMALSTRING",
+ "CHARSTRING", "LONGSTRING", "INT", "HEX", "FLOAT", "HEX_FLOAT", "COMMENT",
+ "LINE_COMMENT", "WS", "SHEBANG"
+ };
+ public static readonly IVocabulary DefaultVocabulary = new Vocabulary(_LiteralNames, _SymbolicNames);
+
+ [NotNull]
+ public override IVocabulary Vocabulary
+ {
+ get
+ {
+ return DefaultVocabulary;
+ }
+ }
+
+ public override string GrammarFileName { get { return "Lua.g4"; } }
+
+ public override string[] RuleNames { get { return ruleNames; } }
+
+ public override string[] ChannelNames { get { return channelNames; } }
+
+ public override string[] ModeNames { get { return modeNames; } }
+
+ public override int[] SerializedAtn { get { return _serializedATN; } }
+
+ static LuaLexer() {
+ decisionToDFA = new DFA[_ATN.NumberOfDecisions];
+ for (int i = 0; i < _ATN.NumberOfDecisions; i++) {
+ decisionToDFA[i] = new DFA(_ATN.GetDecisionState(i), i);
+ }
+ }
+ private static int[] _serializedATN = {
+ 4,0,67,601,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,
+ 6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,
+ 7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,
+ 7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,
+ 7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,
+ 7,35,2,36,7,36,2,37,7,37,2,38,7,38,2,39,7,39,2,40,7,40,2,41,7,41,2,42,
+ 7,42,2,43,7,43,2,44,7,44,2,45,7,45,2,46,7,46,2,47,7,47,2,48,7,48,2,49,
+ 7,49,2,50,7,50,2,51,7,51,2,52,7,52,2,53,7,53,2,54,7,54,2,55,7,55,2,56,
+ 7,56,2,57,7,57,2,58,7,58,2,59,7,59,2,60,7,60,2,61,7,61,2,62,7,62,2,63,
+ 7,63,2,64,7,64,2,65,7,65,2,66,7,66,2,67,7,67,2,68,7,68,2,69,7,69,2,70,
+ 7,70,2,71,7,71,2,72,7,72,2,73,7,73,2,74,7,74,2,75,7,75,1,0,1,0,1,1,1,1,
+ 1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,5,1,5,1,5,1,
+ 5,1,6,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,
+ 1,8,1,8,1,9,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,
+ 12,1,12,1,12,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,
+ 15,1,15,1,15,1,15,1,15,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,
+ 17,1,17,1,17,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,19,1,19,1,19,1,20,1,
+ 20,1,21,1,21,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,23,1,23,1,24,1,
+ 24,1,24,1,24,1,24,1,25,1,25,1,25,1,25,1,26,1,26,1,27,1,27,1,28,1,28,1,
+ 29,1,29,1,30,1,30,1,31,1,31,1,32,1,32,1,32,1,33,1,33,1,33,1,33,1,34,1,
+ 34,1,35,1,35,1,36,1,36,1,36,1,37,1,37,1,37,1,38,1,38,1,38,1,39,1,39,1,
+ 39,1,40,1,40,1,40,1,41,1,41,1,42,1,42,1,43,1,43,1,44,1,44,1,45,1,45,1,
+ 46,1,46,1,46,1,47,1,47,1,48,1,48,1,49,1,49,1,50,1,50,1,50,1,51,1,51,1,
+ 51,1,52,1,52,1,52,1,52,1,53,1,53,1,54,1,54,1,55,1,55,5,55,345,8,55,10,
+ 55,12,55,348,9,55,1,56,1,56,1,56,5,56,353,8,56,10,56,12,56,356,9,56,1,
+ 56,1,56,1,57,1,57,1,57,5,57,363,8,57,10,57,12,57,366,9,57,1,57,1,57,1,
+ 58,1,58,1,58,1,58,1,59,1,59,1,59,1,59,1,59,1,59,5,59,380,8,59,10,59,12,
+ 59,383,9,59,1,59,3,59,386,8,59,1,60,4,60,389,8,60,11,60,12,60,390,1,61,
+ 1,61,1,61,4,61,396,8,61,11,61,12,61,397,1,62,4,62,401,8,62,11,62,12,62,
+ 402,1,62,1,62,5,62,407,8,62,10,62,12,62,410,9,62,1,62,3,62,413,8,62,1,
+ 62,1,62,4,62,417,8,62,11,62,12,62,418,1,62,3,62,422,8,62,1,62,4,62,425,
+ 8,62,11,62,12,62,426,1,62,1,62,3,62,431,8,62,1,63,1,63,1,63,4,63,436,8,
+ 63,11,63,12,63,437,1,63,1,63,5,63,442,8,63,10,63,12,63,445,9,63,1,63,3,
+ 63,448,8,63,1,63,1,63,1,63,1,63,4,63,454,8,63,11,63,12,63,455,1,63,3,63,
+ 459,8,63,1,63,1,63,1,63,4,63,464,8,63,11,63,12,63,465,1,63,1,63,3,63,470,
+ 8,63,1,64,1,64,3,64,474,8,64,1,64,4,64,477,8,64,11,64,12,64,478,1,65,1,
+ 65,3,65,483,8,65,1,65,4,65,486,8,65,11,65,12,65,487,1,66,1,66,1,66,1,66,
+ 3,66,494,8,66,1,66,1,66,1,66,1,66,3,66,500,8,66,1,67,1,67,1,67,1,67,1,
+ 67,1,67,1,67,1,67,1,67,1,67,1,67,3,67,513,8,67,1,68,1,68,1,68,1,68,1,68,
+ 1,69,1,69,1,69,1,69,1,69,4,69,525,8,69,11,69,12,69,526,1,69,1,69,1,70,
+ 1,70,1,71,1,71,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,72,1,73,1,73,
+ 1,73,1,73,1,73,1,73,5,73,550,8,73,10,73,12,73,553,9,73,1,73,1,73,5,73,
+ 557,8,73,10,73,12,73,560,9,73,1,73,1,73,5,73,564,8,73,10,73,12,73,567,
+ 9,73,1,73,1,73,5,73,571,8,73,10,73,12,73,574,9,73,3,73,576,8,73,1,73,1,
+ 73,1,73,3,73,581,8,73,1,73,1,73,1,74,4,74,586,8,74,11,74,12,74,587,1,74,
+ 1,74,1,75,1,75,1,75,5,75,595,8,75,10,75,12,75,598,9,75,1,75,1,75,1,381,
+ 0,76,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,
+ 14,29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,51,
+ 26,53,27,55,28,57,29,59,30,61,31,63,32,65,33,67,34,69,35,71,36,73,37,75,
+ 38,77,39,79,40,81,41,83,42,85,43,87,44,89,45,91,46,93,47,95,48,97,49,99,
+ 50,101,51,103,52,105,53,107,54,109,55,111,56,113,57,115,58,117,59,119,
+ 0,121,60,123,61,125,62,127,63,129,0,131,0,133,0,135,0,137,0,139,0,141,
+ 0,143,0,145,64,147,65,149,66,151,67,1,0,17,3,0,65,90,95,95,97,122,4,0,
+ 48,57,65,90,95,95,97,122,2,0,34,34,92,92,2,0,39,39,92,92,2,0,88,88,120,
+ 120,2,0,69,69,101,101,2,0,43,43,45,45,2,0,80,80,112,112,10,0,34,34,39,
+ 39,92,92,97,98,102,102,110,110,114,114,116,116,118,118,122,122,1,0,48,
+ 50,1,0,48,57,3,0,48,57,65,70,97,102,4,0,10,10,13,13,61,61,91,91,2,0,10,
+ 10,13,13,3,0,10,10,13,13,91,91,2,1,10,10,13,13,3,0,9,10,12,13,32,32,638,
+ 0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,
+ 0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,
+ 1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,
+ 0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,
+ 1,0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,
+ 0,0,57,1,0,0,0,0,59,1,0,0,0,0,61,1,0,0,0,0,63,1,0,0,0,0,65,1,0,0,0,0,67,
+ 1,0,0,0,0,69,1,0,0,0,0,71,1,0,0,0,0,73,1,0,0,0,0,75,1,0,0,0,0,77,1,0,0,
+ 0,0,79,1,0,0,0,0,81,1,0,0,0,0,83,1,0,0,0,0,85,1,0,0,0,0,87,1,0,0,0,0,89,
+ 1,0,0,0,0,91,1,0,0,0,0,93,1,0,0,0,0,95,1,0,0,0,0,97,1,0,0,0,0,99,1,0,0,
+ 0,0,101,1,0,0,0,0,103,1,0,0,0,0,105,1,0,0,0,0,107,1,0,0,0,0,109,1,0,0,
+ 0,0,111,1,0,0,0,0,113,1,0,0,0,0,115,1,0,0,0,0,117,1,0,0,0,0,121,1,0,0,
+ 0,0,123,1,0,0,0,0,125,1,0,0,0,0,127,1,0,0,0,0,145,1,0,0,0,0,147,1,0,0,
+ 0,0,149,1,0,0,0,0,151,1,0,0,0,1,153,1,0,0,0,3,155,1,0,0,0,5,157,1,0,0,
+ 0,7,163,1,0,0,0,9,168,1,0,0,0,11,171,1,0,0,0,13,175,1,0,0,0,15,181,1,0,
+ 0,0,17,188,1,0,0,0,19,194,1,0,0,0,21,198,1,0,0,0,23,200,1,0,0,0,25,203,
+ 1,0,0,0,27,212,1,0,0,0,29,218,1,0,0,0,31,221,1,0,0,0,33,226,1,0,0,0,35,
+ 233,1,0,0,0,37,238,1,0,0,0,39,245,1,0,0,0,41,248,1,0,0,0,43,250,1,0,0,
+ 0,45,252,1,0,0,0,47,256,1,0,0,0,49,262,1,0,0,0,51,267,1,0,0,0,53,271,1,
+ 0,0,0,55,273,1,0,0,0,57,275,1,0,0,0,59,277,1,0,0,0,61,279,1,0,0,0,63,281,
+ 1,0,0,0,65,283,1,0,0,0,67,286,1,0,0,0,69,290,1,0,0,0,71,292,1,0,0,0,73,
+ 294,1,0,0,0,75,297,1,0,0,0,77,300,1,0,0,0,79,303,1,0,0,0,81,306,1,0,0,
+ 0,83,309,1,0,0,0,85,311,1,0,0,0,87,313,1,0,0,0,89,315,1,0,0,0,91,317,1,
+ 0,0,0,93,319,1,0,0,0,95,322,1,0,0,0,97,324,1,0,0,0,99,326,1,0,0,0,101,
+ 328,1,0,0,0,103,331,1,0,0,0,105,334,1,0,0,0,107,338,1,0,0,0,109,340,1,
+ 0,0,0,111,342,1,0,0,0,113,349,1,0,0,0,115,359,1,0,0,0,117,369,1,0,0,0,
+ 119,385,1,0,0,0,121,388,1,0,0,0,123,392,1,0,0,0,125,430,1,0,0,0,127,469,
+ 1,0,0,0,129,471,1,0,0,0,131,480,1,0,0,0,133,499,1,0,0,0,135,512,1,0,0,
+ 0,137,514,1,0,0,0,139,519,1,0,0,0,141,530,1,0,0,0,143,532,1,0,0,0,145,
+ 534,1,0,0,0,147,543,1,0,0,0,149,585,1,0,0,0,151,591,1,0,0,0,153,154,5,
+ 59,0,0,154,2,1,0,0,0,155,156,5,61,0,0,156,4,1,0,0,0,157,158,5,98,0,0,158,
+ 159,5,114,0,0,159,160,5,101,0,0,160,161,5,97,0,0,161,162,5,107,0,0,162,
+ 6,1,0,0,0,163,164,5,103,0,0,164,165,5,111,0,0,165,166,5,116,0,0,166,167,
+ 5,111,0,0,167,8,1,0,0,0,168,169,5,100,0,0,169,170,5,111,0,0,170,10,1,0,
+ 0,0,171,172,5,101,0,0,172,173,5,110,0,0,173,174,5,100,0,0,174,12,1,0,0,
+ 0,175,176,5,119,0,0,176,177,5,104,0,0,177,178,5,105,0,0,178,179,5,108,
+ 0,0,179,180,5,101,0,0,180,14,1,0,0,0,181,182,5,114,0,0,182,183,5,101,0,
+ 0,183,184,5,112,0,0,184,185,5,101,0,0,185,186,5,97,0,0,186,187,5,116,0,
+ 0,187,16,1,0,0,0,188,189,5,117,0,0,189,190,5,110,0,0,190,191,5,116,0,0,
+ 191,192,5,105,0,0,192,193,5,108,0,0,193,18,1,0,0,0,194,195,5,102,0,0,195,
+ 196,5,111,0,0,196,197,5,114,0,0,197,20,1,0,0,0,198,199,5,44,0,0,199,22,
+ 1,0,0,0,200,201,5,105,0,0,201,202,5,110,0,0,202,24,1,0,0,0,203,204,5,102,
+ 0,0,204,205,5,117,0,0,205,206,5,110,0,0,206,207,5,99,0,0,207,208,5,116,
+ 0,0,208,209,5,105,0,0,209,210,5,111,0,0,210,211,5,110,0,0,211,26,1,0,0,
+ 0,212,213,5,108,0,0,213,214,5,111,0,0,214,215,5,99,0,0,215,216,5,97,0,
+ 0,216,217,5,108,0,0,217,28,1,0,0,0,218,219,5,105,0,0,219,220,5,102,0,0,
+ 220,30,1,0,0,0,221,222,5,116,0,0,222,223,5,104,0,0,223,224,5,101,0,0,224,
+ 225,5,110,0,0,225,32,1,0,0,0,226,227,5,101,0,0,227,228,5,108,0,0,228,229,
+ 5,115,0,0,229,230,5,101,0,0,230,231,5,105,0,0,231,232,5,102,0,0,232,34,
+ 1,0,0,0,233,234,5,101,0,0,234,235,5,108,0,0,235,236,5,115,0,0,236,237,
+ 5,101,0,0,237,36,1,0,0,0,238,239,5,114,0,0,239,240,5,101,0,0,240,241,5,
+ 116,0,0,241,242,5,117,0,0,242,243,5,114,0,0,243,244,5,110,0,0,244,38,1,
+ 0,0,0,245,246,5,58,0,0,246,247,5,58,0,0,247,40,1,0,0,0,248,249,5,46,0,
+ 0,249,42,1,0,0,0,250,251,5,58,0,0,251,44,1,0,0,0,252,253,5,110,0,0,253,
+ 254,5,105,0,0,254,255,5,108,0,0,255,46,1,0,0,0,256,257,5,102,0,0,257,258,
+ 5,97,0,0,258,259,5,108,0,0,259,260,5,115,0,0,260,261,5,101,0,0,261,48,
+ 1,0,0,0,262,263,5,116,0,0,263,264,5,114,0,0,264,265,5,117,0,0,265,266,
+ 5,101,0,0,266,50,1,0,0,0,267,268,5,46,0,0,268,269,5,46,0,0,269,270,5,46,
+ 0,0,270,52,1,0,0,0,271,272,5,40,0,0,272,54,1,0,0,0,273,274,5,41,0,0,274,
+ 56,1,0,0,0,275,276,5,91,0,0,276,58,1,0,0,0,277,278,5,93,0,0,278,60,1,0,
+ 0,0,279,280,5,123,0,0,280,62,1,0,0,0,281,282,5,125,0,0,282,64,1,0,0,0,
+ 283,284,5,111,0,0,284,285,5,114,0,0,285,66,1,0,0,0,286,287,5,97,0,0,287,
+ 288,5,110,0,0,288,289,5,100,0,0,289,68,1,0,0,0,290,291,5,60,0,0,291,70,
+ 1,0,0,0,292,293,5,62,0,0,293,72,1,0,0,0,294,295,5,60,0,0,295,296,5,61,
+ 0,0,296,74,1,0,0,0,297,298,5,62,0,0,298,299,5,61,0,0,299,76,1,0,0,0,300,
+ 301,5,126,0,0,301,302,5,61,0,0,302,78,1,0,0,0,303,304,5,61,0,0,304,305,
+ 5,61,0,0,305,80,1,0,0,0,306,307,5,46,0,0,307,308,5,46,0,0,308,82,1,0,0,
+ 0,309,310,5,43,0,0,310,84,1,0,0,0,311,312,5,45,0,0,312,86,1,0,0,0,313,
+ 314,5,42,0,0,314,88,1,0,0,0,315,316,5,47,0,0,316,90,1,0,0,0,317,318,5,
+ 37,0,0,318,92,1,0,0,0,319,320,5,47,0,0,320,321,5,47,0,0,321,94,1,0,0,0,
+ 322,323,5,38,0,0,323,96,1,0,0,0,324,325,5,124,0,0,325,98,1,0,0,0,326,327,
+ 5,126,0,0,327,100,1,0,0,0,328,329,5,60,0,0,329,330,5,60,0,0,330,102,1,
+ 0,0,0,331,332,5,62,0,0,332,333,5,62,0,0,333,104,1,0,0,0,334,335,5,110,
+ 0,0,335,336,5,111,0,0,336,337,5,116,0,0,337,106,1,0,0,0,338,339,5,35,0,
+ 0,339,108,1,0,0,0,340,341,5,94,0,0,341,110,1,0,0,0,342,346,7,0,0,0,343,
+ 345,7,1,0,0,344,343,1,0,0,0,345,348,1,0,0,0,346,344,1,0,0,0,346,347,1,
+ 0,0,0,347,112,1,0,0,0,348,346,1,0,0,0,349,354,5,34,0,0,350,353,3,133,66,
+ 0,351,353,8,2,0,0,352,350,1,0,0,0,352,351,1,0,0,0,353,356,1,0,0,0,354,
+ 352,1,0,0,0,354,355,1,0,0,0,355,357,1,0,0,0,356,354,1,0,0,0,357,358,5,
+ 34,0,0,358,114,1,0,0,0,359,364,5,39,0,0,360,363,3,133,66,0,361,363,8,3,
+ 0,0,362,360,1,0,0,0,362,361,1,0,0,0,363,366,1,0,0,0,364,362,1,0,0,0,364,
+ 365,1,0,0,0,365,367,1,0,0,0,366,364,1,0,0,0,367,368,5,39,0,0,368,116,1,
+ 0,0,0,369,370,5,91,0,0,370,371,3,119,59,0,371,372,5,93,0,0,372,118,1,0,
+ 0,0,373,374,5,61,0,0,374,375,3,119,59,0,375,376,5,61,0,0,376,386,1,0,0,
+ 0,377,381,5,91,0,0,378,380,9,0,0,0,379,378,1,0,0,0,380,383,1,0,0,0,381,
+ 382,1,0,0,0,381,379,1,0,0,0,382,384,1,0,0,0,383,381,1,0,0,0,384,386,5,
+ 93,0,0,385,373,1,0,0,0,385,377,1,0,0,0,386,120,1,0,0,0,387,389,3,141,70,
+ 0,388,387,1,0,0,0,389,390,1,0,0,0,390,388,1,0,0,0,390,391,1,0,0,0,391,
+ 122,1,0,0,0,392,393,5,48,0,0,393,395,7,4,0,0,394,396,3,143,71,0,395,394,
+ 1,0,0,0,396,397,1,0,0,0,397,395,1,0,0,0,397,398,1,0,0,0,398,124,1,0,0,
+ 0,399,401,3,141,70,0,400,399,1,0,0,0,401,402,1,0,0,0,402,400,1,0,0,0,402,
+ 403,1,0,0,0,403,404,1,0,0,0,404,408,5,46,0,0,405,407,3,141,70,0,406,405,
+ 1,0,0,0,407,410,1,0,0,0,408,406,1,0,0,0,408,409,1,0,0,0,409,412,1,0,0,
+ 0,410,408,1,0,0,0,411,413,3,129,64,0,412,411,1,0,0,0,412,413,1,0,0,0,413,
+ 431,1,0,0,0,414,416,5,46,0,0,415,417,3,141,70,0,416,415,1,0,0,0,417,418,
+ 1,0,0,0,418,416,1,0,0,0,418,419,1,0,0,0,419,421,1,0,0,0,420,422,3,129,
+ 64,0,421,420,1,0,0,0,421,422,1,0,0,0,422,431,1,0,0,0,423,425,3,141,70,
+ 0,424,423,1,0,0,0,425,426,1,0,0,0,426,424,1,0,0,0,426,427,1,0,0,0,427,
+ 428,1,0,0,0,428,429,3,129,64,0,429,431,1,0,0,0,430,400,1,0,0,0,430,414,
+ 1,0,0,0,430,424,1,0,0,0,431,126,1,0,0,0,432,433,5,48,0,0,433,435,7,4,0,
+ 0,434,436,3,143,71,0,435,434,1,0,0,0,436,437,1,0,0,0,437,435,1,0,0,0,437,
+ 438,1,0,0,0,438,439,1,0,0,0,439,443,5,46,0,0,440,442,3,143,71,0,441,440,
+ 1,0,0,0,442,445,1,0,0,0,443,441,1,0,0,0,443,444,1,0,0,0,444,447,1,0,0,
+ 0,445,443,1,0,0,0,446,448,3,131,65,0,447,446,1,0,0,0,447,448,1,0,0,0,448,
+ 470,1,0,0,0,449,450,5,48,0,0,450,451,7,4,0,0,451,453,5,46,0,0,452,454,
+ 3,143,71,0,453,452,1,0,0,0,454,455,1,0,0,0,455,453,1,0,0,0,455,456,1,0,
+ 0,0,456,458,1,0,0,0,457,459,3,131,65,0,458,457,1,0,0,0,458,459,1,0,0,0,
+ 459,470,1,0,0,0,460,461,5,48,0,0,461,463,7,4,0,0,462,464,3,143,71,0,463,
+ 462,1,0,0,0,464,465,1,0,0,0,465,463,1,0,0,0,465,466,1,0,0,0,466,467,1,
+ 0,0,0,467,468,3,131,65,0,468,470,1,0,0,0,469,432,1,0,0,0,469,449,1,0,0,
+ 0,469,460,1,0,0,0,470,128,1,0,0,0,471,473,7,5,0,0,472,474,7,6,0,0,473,
+ 472,1,0,0,0,473,474,1,0,0,0,474,476,1,0,0,0,475,477,3,141,70,0,476,475,
+ 1,0,0,0,477,478,1,0,0,0,478,476,1,0,0,0,478,479,1,0,0,0,479,130,1,0,0,
+ 0,480,482,7,7,0,0,481,483,7,6,0,0,482,481,1,0,0,0,482,483,1,0,0,0,483,
+ 485,1,0,0,0,484,486,3,141,70,0,485,484,1,0,0,0,486,487,1,0,0,0,487,485,
+ 1,0,0,0,487,488,1,0,0,0,488,132,1,0,0,0,489,490,5,92,0,0,490,500,7,8,0,
+ 0,491,493,5,92,0,0,492,494,5,13,0,0,493,492,1,0,0,0,493,494,1,0,0,0,494,
+ 495,1,0,0,0,495,500,5,10,0,0,496,500,3,135,67,0,497,500,3,137,68,0,498,
+ 500,3,139,69,0,499,489,1,0,0,0,499,491,1,0,0,0,499,496,1,0,0,0,499,497,
+ 1,0,0,0,499,498,1,0,0,0,500,134,1,0,0,0,501,502,5,92,0,0,502,513,3,141,
+ 70,0,503,504,5,92,0,0,504,505,3,141,70,0,505,506,3,141,70,0,506,513,1,
+ 0,0,0,507,508,5,92,0,0,508,509,7,9,0,0,509,510,3,141,70,0,510,511,3,141,
+ 70,0,511,513,1,0,0,0,512,501,1,0,0,0,512,503,1,0,0,0,512,507,1,0,0,0,513,
+ 136,1,0,0,0,514,515,5,92,0,0,515,516,5,120,0,0,516,517,3,143,71,0,517,
+ 518,3,143,71,0,518,138,1,0,0,0,519,520,5,92,0,0,520,521,5,117,0,0,521,
+ 522,5,123,0,0,522,524,1,0,0,0,523,525,3,143,71,0,524,523,1,0,0,0,525,526,
+ 1,0,0,0,526,524,1,0,0,0,526,527,1,0,0,0,527,528,1,0,0,0,528,529,5,125,
+ 0,0,529,140,1,0,0,0,530,531,7,10,0,0,531,142,1,0,0,0,532,533,7,11,0,0,
+ 533,144,1,0,0,0,534,535,5,45,0,0,535,536,5,45,0,0,536,537,5,91,0,0,537,
+ 538,1,0,0,0,538,539,3,119,59,0,539,540,5,93,0,0,540,541,1,0,0,0,541,542,
+ 6,72,0,0,542,146,1,0,0,0,543,544,5,45,0,0,544,545,5,45,0,0,545,575,1,0,
+ 0,0,546,576,1,0,0,0,547,551,5,91,0,0,548,550,5,61,0,0,549,548,1,0,0,0,
+ 550,553,1,0,0,0,551,549,1,0,0,0,551,552,1,0,0,0,552,576,1,0,0,0,553,551,
+ 1,0,0,0,554,558,5,91,0,0,555,557,5,61,0,0,556,555,1,0,0,0,557,560,1,0,
+ 0,0,558,556,1,0,0,0,558,559,1,0,0,0,559,561,1,0,0,0,560,558,1,0,0,0,561,
+ 565,8,12,0,0,562,564,8,13,0,0,563,562,1,0,0,0,564,567,1,0,0,0,565,563,
+ 1,0,0,0,565,566,1,0,0,0,566,576,1,0,0,0,567,565,1,0,0,0,568,572,8,14,0,
+ 0,569,571,8,13,0,0,570,569,1,0,0,0,571,574,1,0,0,0,572,570,1,0,0,0,572,
+ 573,1,0,0,0,573,576,1,0,0,0,574,572,1,0,0,0,575,546,1,0,0,0,575,547,1,
+ 0,0,0,575,554,1,0,0,0,575,568,1,0,0,0,576,580,1,0,0,0,577,578,5,13,0,0,
+ 578,581,5,10,0,0,579,581,7,15,0,0,580,577,1,0,0,0,580,579,1,0,0,0,581,
+ 582,1,0,0,0,582,583,6,73,0,0,583,148,1,0,0,0,584,586,7,16,0,0,585,584,
+ 1,0,0,0,586,587,1,0,0,0,587,585,1,0,0,0,587,588,1,0,0,0,588,589,1,0,0,
+ 0,589,590,6,74,1,0,590,150,1,0,0,0,591,592,5,35,0,0,592,596,5,33,0,0,593,
+ 595,8,13,0,0,594,593,1,0,0,0,595,598,1,0,0,0,596,594,1,0,0,0,596,597,1,
+ 0,0,0,597,599,1,0,0,0,598,596,1,0,0,0,599,600,6,75,0,0,600,152,1,0,0,0,
+ 40,0,346,352,354,362,364,381,385,390,397,402,408,412,418,421,426,430,437,
+ 443,447,455,458,465,469,473,478,482,487,493,499,512,526,551,558,565,572,
+ 575,580,587,596,2,0,1,0,6,0,0
+ };
+
+ public static readonly ATN _ATN =
+ new ATNDeserializer().Deserialize(_serializedATN);
+
+
+}
diff --git a/bot/src/Syntax/Parser/LuaParser.cs b/bot/src/Syntax/Parser/LuaParser.cs
new file mode 100644
index 0000000..a735a17
--- /dev/null
+++ b/bot/src/Syntax/Parser/LuaParser.cs
@@ -0,0 +1,3207 @@
+// Unreachable code detected
+#pragma warning disable 0162
+// The variable '...' is assigned but its value is never used
+#pragma warning disable 0219
+// Missing XML comment for publicly visible type or member '...'
+#pragma warning disable 1591
+// Ambiguous reference in cref attribute
+#pragma warning disable 419
+
+using Antlr4.Runtime;
+using Antlr4.Runtime.Atn;
+using Antlr4.Runtime.Misc;
+using Antlr4.Runtime.Tree;
+using DFA = Antlr4.Runtime.Dfa.DFA;
+
+namespace MoonsecDeobfuscator.Syntax.Parser;
+
+[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")]
+[System.CLSCompliant(false)]
+public partial class LuaParser : Antlr4.Runtime.Parser {
+ protected static DFA[] decisionToDFA;
+ protected static PredictionContextCache sharedContextCache = new PredictionContextCache();
+ public const int
+ T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9,
+ T__9=10, T__10=11, T__11=12, T__12=13, T__13=14, T__14=15, T__15=16, T__16=17,
+ T__17=18, T__18=19, T__19=20, T__20=21, T__21=22, T__22=23, T__23=24,
+ T__24=25, T__25=26, T__26=27, T__27=28, T__28=29, T__29=30, T__30=31,
+ T__31=32, T__32=33, T__33=34, T__34=35, T__35=36, T__36=37, T__37=38,
+ T__38=39, T__39=40, T__40=41, T__41=42, T__42=43, T__43=44, T__44=45,
+ T__45=46, T__46=47, T__47=48, T__48=49, T__49=50, T__50=51, T__51=52,
+ T__52=53, T__53=54, T__54=55, NAME=56, NORMALSTRING=57, CHARSTRING=58,
+ LONGSTRING=59, INT=60, HEX=61, FLOAT=62, HEX_FLOAT=63, COMMENT=64, LINE_COMMENT=65,
+ WS=66, SHEBANG=67;
+ public const int
+ RULE_chunk = 0, RULE_block = 1, RULE_stat = 2, RULE_ifstmt = 3, RULE_elseifstmt = 4,
+ RULE_elsestmt = 5, RULE_retstat = 6, RULE_label = 7, RULE_funcname = 8,
+ RULE_varlist = 9, RULE_namelist = 10, RULE_explist = 11, RULE_exp = 12,
+ RULE_prefixexp = 13, RULE_functioncall = 14, RULE_varOrExp = 15, RULE_var = 16,
+ RULE_varSuffix = 17, RULE_nameAndArgs = 18, RULE_args = 19, RULE_functiondef = 20,
+ RULE_funcbody = 21, RULE_parlist = 22, RULE_tableconstructor = 23, RULE_fieldlist = 24,
+ RULE_field = 25, RULE_fieldsep = 26, RULE_operatorOr = 27, RULE_operatorAnd = 28,
+ RULE_operatorComparison = 29, RULE_operatorStrcat = 30, RULE_operatorAddSub = 31,
+ RULE_operatorMulDivMod = 32, RULE_operatorBitwise = 33, RULE_operatorUnary = 34,
+ RULE_operatorPower = 35, RULE_number = 36, RULE_string = 37;
+ public static readonly string[] ruleNames = {
+ "chunk", "block", "stat", "ifstmt", "elseifstmt", "elsestmt", "retstat",
+ "label", "funcname", "varlist", "namelist", "explist", "exp", "prefixexp",
+ "functioncall", "varOrExp", "var", "varSuffix", "nameAndArgs", "args",
+ "functiondef", "funcbody", "parlist", "tableconstructor", "fieldlist",
+ "field", "fieldsep", "operatorOr", "operatorAnd", "operatorComparison",
+ "operatorStrcat", "operatorAddSub", "operatorMulDivMod", "operatorBitwise",
+ "operatorUnary", "operatorPower", "number", "string"
+ };
+
+ private static readonly string[] _LiteralNames = {
+ null, "';'", "'='", "'break'", "'goto'", "'do'", "'end'", "'while'", "'repeat'",
+ "'until'", "'for'", "','", "'in'", "'function'", "'local'", "'if'", "'then'",
+ "'elseif'", "'else'", "'return'", "'::'", "'.'", "':'", "'nil'", "'false'",
+ "'true'", "'...'", "'('", "')'", "'['", "']'", "'{'", "'}'", "'or'", "'and'",
+ "'<'", "'>'", "'<='", "'>='", "'~='", "'=='", "'..'", "'+'", "'-'", "'*'",
+ "'/'", "'%'", "'//'", "'&'", "'|'", "'~'", "'<<'", "'>>'", "'not'", "'#'",
+ "'^'"
+ };
+ private static readonly string[] _SymbolicNames = {
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, "NAME", "NORMALSTRING",
+ "CHARSTRING", "LONGSTRING", "INT", "HEX", "FLOAT", "HEX_FLOAT", "COMMENT",
+ "LINE_COMMENT", "WS", "SHEBANG"
+ };
+ public static readonly IVocabulary DefaultVocabulary = new Vocabulary(_LiteralNames, _SymbolicNames);
+
+ [NotNull]
+ public override IVocabulary Vocabulary
+ {
+ get
+ {
+ return DefaultVocabulary;
+ }
+ }
+
+ public override string GrammarFileName { get { return "Lua.g4"; } }
+
+ public override string[] RuleNames { get { return ruleNames; } }
+
+ public override int[] SerializedAtn { get { return _serializedATN; } }
+
+ static LuaParser() {
+ decisionToDFA = new DFA[_ATN.NumberOfDecisions];
+ for (int i = 0; i < _ATN.NumberOfDecisions; i++) {
+ decisionToDFA[i] = new DFA(_ATN.GetDecisionState(i), i);
+ }
+ }
+
+ public LuaParser(ITokenStream input) : this(input, Console.Out, Console.Error) { }
+
+ public LuaParser(ITokenStream input, TextWriter output, TextWriter errorOutput)
+ : base(input, output, errorOutput)
+ {
+ Interpreter = new ParserATNSimulator(this, _ATN, decisionToDFA, sharedContextCache);
+ }
+
+ public partial class ChunkContext : ParserRuleContext {
+ [System.Diagnostics.DebuggerNonUserCode] public BlockContext block() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode Eof() { return GetToken(LuaParser.Eof, 0); }
+ public ChunkContext(ParserRuleContext parent, int invokingState)
+ : base(parent, invokingState)
+ {
+ }
+ public override int RuleIndex { get { return RULE_chunk; } }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitChunk(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+
+ [RuleVersion(0)]
+ public ChunkContext chunk() {
+ ChunkContext _localctx = new ChunkContext(Context, State);
+ EnterRule(_localctx, 0, RULE_chunk);
+ try {
+ EnterOuterAlt(_localctx, 1);
+ {
+ State = 76;
+ block();
+ State = 77;
+ Match(Eof);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ ErrorHandler.ReportError(this, re);
+ ErrorHandler.Recover(this, re);
+ }
+ finally {
+ ExitRule();
+ }
+ return _localctx;
+ }
+
+ public partial class BlockContext : ParserRuleContext {
+ [System.Diagnostics.DebuggerNonUserCode] public StatContext[] stat() {
+ return GetRuleContexts();
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public StatContext stat(int i) {
+ return GetRuleContext(i);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public RetstatContext retstat() {
+ return GetRuleContext(0);
+ }
+ public BlockContext(ParserRuleContext parent, int invokingState)
+ : base(parent, invokingState)
+ {
+ }
+ public override int RuleIndex { get { return RULE_block; } }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitBlock(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+
+ [RuleVersion(0)]
+ public BlockContext block() {
+ BlockContext _localctx = new BlockContext(Context, State);
+ EnterRule(_localctx, 2, RULE_block);
+ int _la;
+ try {
+ EnterOuterAlt(_localctx, 1);
+ {
+ State = 82;
+ ErrorHandler.Sync(this);
+ _la = TokenStream.LA(1);
+ while ((((_la) & ~0x3f) == 0 && ((1L << _la) & 72057594173253050L) != 0)) {
+ {
+ {
+ State = 79;
+ stat();
+ }
+ }
+ State = 84;
+ ErrorHandler.Sync(this);
+ _la = TokenStream.LA(1);
+ }
+ State = 86;
+ ErrorHandler.Sync(this);
+ _la = TokenStream.LA(1);
+ if (_la==T__18) {
+ {
+ State = 85;
+ retstat();
+ }
+ }
+
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ ErrorHandler.ReportError(this, re);
+ ErrorHandler.Recover(this, re);
+ }
+ finally {
+ ExitRule();
+ }
+ return _localctx;
+ }
+
+ public partial class StatContext : ParserRuleContext {
+ public StatContext(ParserRuleContext parent, int invokingState)
+ : base(parent, invokingState)
+ {
+ }
+ public override int RuleIndex { get { return RULE_stat; } }
+
+ public StatContext() { }
+ public virtual void CopyFrom(StatContext context) {
+ base.CopyFrom(context);
+ }
+ }
+ public partial class StatGenericForContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public NamelistContext namelist() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public ExplistContext explist() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public BlockContext block() {
+ return GetRuleContext(0);
+ }
+ public StatGenericForContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatGenericFor(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatFunctionContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public FuncnameContext funcname() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public FuncbodyContext funcbody() {
+ return GetRuleContext(0);
+ }
+ public StatFunctionContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatFunction(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatBreakContext : StatContext {
+ public StatBreakContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatBreak(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatSemiContext : StatContext {
+ public StatSemiContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatSemi(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatLocalFunctionContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NAME() { return GetToken(LuaParser.NAME, 0); }
+ [System.Diagnostics.DebuggerNonUserCode] public FuncbodyContext funcbody() {
+ return GetRuleContext(0);
+ }
+ public StatLocalFunctionContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatLocalFunction(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatLocalDeclareContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public NamelistContext namelist() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public ExplistContext explist() {
+ return GetRuleContext(0);
+ }
+ public StatLocalDeclareContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatLocalDeclare(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatCallContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public FunctioncallContext functioncall() {
+ return GetRuleContext(0);
+ }
+ public StatCallContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatCall(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatLabelContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public LabelContext label() {
+ return GetRuleContext(0);
+ }
+ public StatLabelContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatLabel(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatDoContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public BlockContext block() {
+ return GetRuleContext(0);
+ }
+ public StatDoContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatDo(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatWhileContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public ExpContext exp() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public BlockContext block() {
+ return GetRuleContext(0);
+ }
+ public StatWhileContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatWhile(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatGotoContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NAME() { return GetToken(LuaParser.NAME, 0); }
+ public StatGotoContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatGoto(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatAssignContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public VarlistContext varlist() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public ExplistContext explist() {
+ return GetRuleContext(0);
+ }
+ public StatAssignContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatAssign(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatRepeatContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public BlockContext block() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public ExpContext exp() {
+ return GetRuleContext(0);
+ }
+ public StatRepeatContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatRepeat(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatIfContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public IfstmtContext ifstmt() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public ElseifstmtContext elseifstmt() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public ElsestmtContext elsestmt() {
+ return GetRuleContext(0);
+ }
+ public StatIfContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatIf(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+ public partial class StatNumericForContext : StatContext {
+ [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NAME() { return GetToken(LuaParser.NAME, 0); }
+ [System.Diagnostics.DebuggerNonUserCode] public ExpContext[] exp() {
+ return GetRuleContexts();
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public ExpContext exp(int i) {
+ return GetRuleContext(i);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public BlockContext block() {
+ return GetRuleContext(0);
+ }
+ public StatNumericForContext(StatContext context) { CopyFrom(context); }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitStatNumericFor(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+
+ [RuleVersion(0)]
+ public StatContext stat() {
+ StatContext _localctx = new StatContext(Context, State);
+ EnterRule(_localctx, 4, RULE_stat);
+ int _la;
+ try {
+ State = 154;
+ ErrorHandler.Sync(this);
+ switch ( Interpreter.AdaptivePredict(TokenStream,4,Context) ) {
+ case 1:
+ _localctx = new StatSemiContext(_localctx);
+ EnterOuterAlt(_localctx, 1);
+ {
+ State = 88;
+ Match(T__0);
+ }
+ break;
+ case 2:
+ _localctx = new StatAssignContext(_localctx);
+ EnterOuterAlt(_localctx, 2);
+ {
+ State = 89;
+ varlist();
+ State = 90;
+ Match(T__1);
+ State = 91;
+ explist();
+ }
+ break;
+ case 3:
+ _localctx = new StatCallContext(_localctx);
+ EnterOuterAlt(_localctx, 3);
+ {
+ State = 93;
+ functioncall();
+ }
+ break;
+ case 4:
+ _localctx = new StatLabelContext(_localctx);
+ EnterOuterAlt(_localctx, 4);
+ {
+ State = 94;
+ label();
+ }
+ break;
+ case 5:
+ _localctx = new StatBreakContext(_localctx);
+ EnterOuterAlt(_localctx, 5);
+ {
+ State = 95;
+ Match(T__2);
+ }
+ break;
+ case 6:
+ _localctx = new StatGotoContext(_localctx);
+ EnterOuterAlt(_localctx, 6);
+ {
+ State = 96;
+ Match(T__3);
+ State = 97;
+ Match(NAME);
+ }
+ break;
+ case 7:
+ _localctx = new StatDoContext(_localctx);
+ EnterOuterAlt(_localctx, 7);
+ {
+ State = 98;
+ Match(T__4);
+ State = 99;
+ block();
+ State = 100;
+ Match(T__5);
+ }
+ break;
+ case 8:
+ _localctx = new StatWhileContext(_localctx);
+ EnterOuterAlt(_localctx, 8);
+ {
+ State = 102;
+ Match(T__6);
+ State = 103;
+ exp(0);
+ State = 104;
+ Match(T__4);
+ State = 105;
+ block();
+ State = 106;
+ Match(T__5);
+ }
+ break;
+ case 9:
+ _localctx = new StatRepeatContext(_localctx);
+ EnterOuterAlt(_localctx, 9);
+ {
+ State = 108;
+ Match(T__7);
+ State = 109;
+ block();
+ State = 110;
+ Match(T__8);
+ State = 111;
+ exp(0);
+ }
+ break;
+ case 10:
+ _localctx = new StatIfContext(_localctx);
+ EnterOuterAlt(_localctx, 10);
+ {
+ State = 113;
+ ifstmt();
+ State = 114;
+ elseifstmt();
+ State = 115;
+ elsestmt();
+ State = 116;
+ Match(T__5);
+ }
+ break;
+ case 11:
+ _localctx = new StatNumericForContext(_localctx);
+ EnterOuterAlt(_localctx, 11);
+ {
+ State = 118;
+ Match(T__9);
+ State = 119;
+ Match(NAME);
+ State = 120;
+ Match(T__1);
+ State = 121;
+ exp(0);
+ State = 122;
+ Match(T__10);
+ State = 123;
+ exp(0);
+ State = 126;
+ ErrorHandler.Sync(this);
+ _la = TokenStream.LA(1);
+ if (_la==T__10) {
+ {
+ State = 124;
+ Match(T__10);
+ State = 125;
+ exp(0);
+ }
+ }
+
+ State = 128;
+ Match(T__4);
+ State = 129;
+ block();
+ State = 130;
+ Match(T__5);
+ }
+ break;
+ case 12:
+ _localctx = new StatGenericForContext(_localctx);
+ EnterOuterAlt(_localctx, 12);
+ {
+ State = 132;
+ Match(T__9);
+ State = 133;
+ namelist();
+ State = 134;
+ Match(T__11);
+ State = 135;
+ explist();
+ State = 136;
+ Match(T__4);
+ State = 137;
+ block();
+ State = 138;
+ Match(T__5);
+ }
+ break;
+ case 13:
+ _localctx = new StatFunctionContext(_localctx);
+ EnterOuterAlt(_localctx, 13);
+ {
+ State = 140;
+ Match(T__12);
+ State = 141;
+ funcname();
+ State = 142;
+ funcbody();
+ }
+ break;
+ case 14:
+ _localctx = new StatLocalFunctionContext(_localctx);
+ EnterOuterAlt(_localctx, 14);
+ {
+ State = 144;
+ Match(T__13);
+ State = 145;
+ Match(T__12);
+ State = 146;
+ Match(NAME);
+ State = 147;
+ funcbody();
+ }
+ break;
+ case 15:
+ _localctx = new StatLocalDeclareContext(_localctx);
+ EnterOuterAlt(_localctx, 15);
+ {
+ State = 148;
+ Match(T__13);
+ State = 149;
+ namelist();
+ State = 152;
+ ErrorHandler.Sync(this);
+ _la = TokenStream.LA(1);
+ if (_la==T__1) {
+ {
+ State = 150;
+ Match(T__1);
+ State = 151;
+ explist();
+ }
+ }
+
+ }
+ break;
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ ErrorHandler.ReportError(this, re);
+ ErrorHandler.Recover(this, re);
+ }
+ finally {
+ ExitRule();
+ }
+ return _localctx;
+ }
+
+ public partial class IfstmtContext : ParserRuleContext {
+ [System.Diagnostics.DebuggerNonUserCode] public ExpContext exp() {
+ return GetRuleContext(0);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public BlockContext block() {
+ return GetRuleContext(0);
+ }
+ public IfstmtContext(ParserRuleContext parent, int invokingState)
+ : base(parent, invokingState)
+ {
+ }
+ public override int RuleIndex { get { return RULE_ifstmt; } }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept(IParseTreeVisitor visitor) {
+ ILuaVisitor typedVisitor = visitor as ILuaVisitor;
+ if (typedVisitor != null) return typedVisitor.VisitIfstmt(this);
+ else return visitor.VisitChildren(this);
+ }
+ }
+
+ [RuleVersion(0)]
+ public IfstmtContext ifstmt() {
+ IfstmtContext _localctx = new IfstmtContext(Context, State);
+ EnterRule(_localctx, 6, RULE_ifstmt);
+ try {
+ EnterOuterAlt(_localctx, 1);
+ {
+ State = 156;
+ Match(T__14);
+ State = 157;
+ exp(0);
+ State = 158;
+ Match(T__15);
+ State = 159;
+ block();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ ErrorHandler.ReportError(this, re);
+ ErrorHandler.Recover(this, re);
+ }
+ finally {
+ ExitRule();
+ }
+ return _localctx;
+ }
+
+ public partial class ElseifstmtContext : ParserRuleContext {
+ [System.Diagnostics.DebuggerNonUserCode] public ExpContext[] exp() {
+ return GetRuleContexts();
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public ExpContext exp(int i) {
+ return GetRuleContext(i);
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public BlockContext[] block() {
+ return GetRuleContexts();
+ }
+ [System.Diagnostics.DebuggerNonUserCode] public BlockContext block(int i) {
+ return GetRuleContext(i);
+ }
+ public ElseifstmtContext(ParserRuleContext parent, int invokingState)
+ : base(parent, invokingState)
+ {
+ }
+ public override int RuleIndex { get { return RULE_elseifstmt; } }
+ [System.Diagnostics.DebuggerNonUserCode]
+ public override TResult Accept