| name | linjector-luau-script-ide |
| description | Open-source Luau/Lua script IDE and executor built as a Windows Forms C# application with Monaco editor integration |
| triggers | ["help me build LInjector","how do I set up LInjector","LInjector Monaco editor integration","customize LInjector script executor","LInjector tab system","build Lua script IDE with C#","LInjector DLL injection","modify LInjector source code"] |
LInjector Luau Script IDE
Skill by ara.so ā Daily 2026 Skills collection.
LInjector is an open-source Luau/Lua script IDE and executor built as a Windows Forms application in C# (.NET Framework 4.8, 32-bit). It features the Monaco editor (the same editor powering VS Code) with Luau syntax highlighting, an optimized multi-tab system, and DLL injection capabilities targeting the Roblox UWP client.
Important Note (2026): The UWP Version is patched due to Hyperion/Byfron anti-cheat. You can still build, modify, and extend LInjector by providing your own working DLL or injection method.
Project Structure
LInjector/
āāā LInjector.sln # Visual Studio solution
āāā LInjector/
ā āāā LInjector.csproj # Project file (.NET 4.8, x86)
ā āāā Forms/ # Windows Forms UI
ā ā āāā MainForm.cs # Main application window
ā ā āāā *.cs # Other form files
ā āāā Classes/ # Core logic classes
ā ā āāā Injector.cs # DLL injection logic
ā ā āāā TabSystem.cs # Optimized tab management
ā ā āāā *.cs
ā āāā Monaco/ # Monaco editor web assets
ā ā āāā index.html # Editor host page
ā ā āāā *.js # Monaco JS files
ā āāā Resources/ # Embedded resources
Build Requirements
- Visual Studio 2022
- .NET Framework 4.8 SDK
- Target Platform: x86 (32-bit)
- Configuration: Release
How to Build
git clone https://github.com/WeritoP/LInjector-FORKED-
cd LInjector-FORKED-
start LInjector.sln
msbuild LInjector.sln /p:Configuration=Release /p:Platform=x86
Important: Always compile before editing Forms. If you open a Form before compiling, Visual Studio's designer will fail. If this happens, restart Visual Studio.
Monaco Editor Integration
LInjector embeds Monaco in a WebBrowser or WebView2 control. The editor is hosted in a local HTML file.
Accessing the Monaco Editor from C#
private string GetEditorContent()
{
object result = webBrowser.Document.InvokeScript(
"eval",
new object[] { "monaco.editor.getModels()[0].getValue()" }
);
return result?.ToString() ?? string.Empty;
}
private void SetEditorContent(string script)
{
string escaped = script
.Replace("\\", "\\\\")
.Replace("'", "\\'")
.Replace("\n", "\\n")
.Replace("\r", "\\r");
webBrowser.Document.InvokeScript(
"eval",
new object[] { $"monaco.editor.getModels()[0].setValue('{escaped}')" }
);
}
Monaco Editor Host HTML (Monaco/index.html pattern)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
html, body, #container {
width: 100%; height: 100%;
margin: 0; padding: 0;
overflow: hidden;
background: #1e1e1e;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="monaco/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': 'monaco/min/vs' } });
require(['vs/editor/editor.main'], function () {
window.editor = monaco.editor.create(
document.getElementById('container'), {
value: '-- LInjector Script\n',
language: 'lua',
theme: 'vs-dark',
fontSize: 14,
automaticLayout: true,
minimap: { enabled: false },
scrollBeyondLastLine: false
}
);
});
function getScript() {
return window.editor.getValue();
}
function setScript(val) {
window.editor.setValue(val);
}
function clearScript() {
window.editor.setValue('');
}
</script>
</body>
</html>
Tab System
LInjector's tab system is optimized to cap RAM usage at ~1 GB even with 40+ tabs open.
public class ScriptTab
{
public string Name { get; set; }
public string Content { get; set; }
public TabPage TabPage { get; set; }
public ScriptTab(string name, string content = "")
{
Name = name;
Content = content;
}
}
public class TabManager
{
private readonly List<ScriptTab> _tabs = new List<ScriptTab>();
private readonly TabControl _tabControl;
private int _activeIndex = 0;
public TabManager(TabControl tabControl)
{
_tabControl = tabControl;
}
public void AddTab(string name = null)
{
int tabNumber = _tabs.Count + 1;
string tabName = name ?? $"Script {tabNumber}";
var tab = new ScriptTab(tabName);
var tabPage = new TabPage(tabName);
tab.TabPage = tabPage;
_tabs.Add(tab);
_tabControl.TabPages.Add(tabPage);
_tabControl.SelectedTab = tabPage;
}
public void RemoveTab(int index)
{
if (index < 0 || index >= _tabs.Count) return;
_tabControl.TabPages.Remove(_tabs[index].TabPage);
_tabs.RemoveAt(index);
}
public void SaveCurrentTabContent(string content)
{
if (_activeIndex >= 0 && _activeIndex < _tabs.Count)
_tabs[_activeIndex].Content = content;
}
public string GetTabContent(int index)
{
if (index < 0 || index >= _tabs.Count) return string.Empty;
return _tabs[index].Content;
}
}
DLL Injection (Custom Integration)
Since the original injection target is patched, here is the pattern used to plug in your own DLL:
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
public class Injector
{
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(int access, bool inherit, int processId);
[DllImport("kernel32.dll")]
private static extern IntPtr VirtualAllocEx(
IntPtr hProcess, IntPtr lpAddress,
uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
private static extern bool WriteProcessMemory(
IntPtr hProcess, IntPtr lpBaseAddress,
byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
private static extern IntPtr CreateRemoteThread(
IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize,
IntPtr lpStartAddress, IntPtr lpParameter,
uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
private const int PROCESS_ALL_ACCESS = 0x1F0FFF;
private const uint MEM_COMMIT = 0x1000;
private const uint MEM_RESERVE = 0x2000;
private const uint PAGE_READWRITE = 0x04;
public static bool InjectDll(string processName, string dllPath)
{
if (!File.Exists(dllPath))
throw new FileNotFoundException($"DLL not found: {dllPath}");
Process[] procs = Process.GetProcessesByName(processName);
if (procs.Length == 0)
throw new Exception($"Process '{processName}' not found.");
Process target = procs[0];
IntPtr hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, target.Id);
if (hProcess == IntPtr.Zero)
throw new Exception("Failed to open process.");
byte[] dllBytes = System.Text.Encoding.ASCII.GetBytes(dllPath + "\0");
IntPtr allocMem = VirtualAllocEx(
hProcess, IntPtr.Zero, (uint)dllBytes.Length,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (allocMem == IntPtr.Zero)
throw new Exception("Memory allocation failed.");
WriteProcessMemory(hProcess, allocMem, dllBytes,
(uint)dllBytes.Length, out _);
IntPtr loadLibAddr = GetProcAddress(
GetModuleHandle("kernel32.dll"), "LoadLibraryA");
IntPtr thread = CreateRemoteThread(
hProcess, IntPtr.Zero, 0,
loadLibAddr, allocMem, 0, IntPtr.Zero);
return thread != IntPtr.Zero;
}
}
Using the Injector from UI
private async void btnInject_Click(object sender, EventArgs e)
{
string dllPath = Path.Combine(
Application.StartupPath, "Modules", "your_dll.dll");
try
{
bool success = await Task.Run(() =>
Injector.InjectDll("RobloxPlayerBeta", dllPath));
lblStatus.Text = success
? "ā Injected successfully"
: "ā Injection failed";
}
catch (Exception ex)
{
MessageBox.Show($"Error: {ex.Message}", "LInjector",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Script Execution Pattern
private async void btnExecute_Click(object sender, EventArgs e)
{
string script = GetEditorContent();
if (string.IsNullOrWhiteSpace(script)) return;
try
{
await ExecuteScriptAsync(script);
lblStatus.Text = "Script executed.";
}
catch (Exception ex)
{
lblStatus.Text = $"Error: {ex.Message}";
}
}
private async Task ExecuteScriptAsync(string script)
{
using var pipe = new System.IO.Pipes.NamedPipeClientStream(
".", "LInjectorPipe",
System.IO.Pipes.PipeDirection.Out);
await pipe.ConnectAsync(timeoutMilliseconds: 3000);
byte[] data = System.Text.Encoding.UTF8.GetBytes(script);
await pipe.WriteAsync(data, 0, data.Length);
}
Script File Management
private void SaveScript(string content, string filePath = null)
{
if (filePath == null)
{
using var sfd = new SaveFileDialog
{
Filter = "Lua Files (*.lua)|*.lua|Text Files (*.txt)|*.txt|All Files (*.*)|*.*",
DefaultExt = "lua",
FileName = "script"
};
if (sfd.ShowDialog() != DialogResult.OK) return;
filePath = sfd.FileName;
}
File.WriteAllText(filePath, content, System.Text.Encoding.UTF8);
}
private string LoadScript()
{
using var ofd = new OpenFileDialog
{
Filter = "Lua Files (*.lua)|*.lua|Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
};
if (ofd.ShowDialog() != DialogResult.OK) return null;
return File.ReadAllText(ofd.FileName, System.Text.Encoding.UTF8);
}
Configuration Pattern
LInjector uses app settings for persistent configuration:
string theme = Properties.Settings.Default.Theme ?? "vs-dark";
int fontSize = Properties.Settings.Default.FontSize > 0
? Properties.Settings.Default.FontSize : 14;
Properties.Settings.Default.Theme = "vs-dark";
Properties.Settings.Default.FontSize = 16;
Properties.Settings.Default.Save();
private void ApplyEditorTheme(string theme)
{
webBrowser.Document.InvokeScript(
"eval",
new object[] { $"monaco.editor.setTheme('{theme}')" }
);
}
Common Customization Patterns
Change Target Process
private const string TARGET_PROCESS = "RobloxPlayerBeta";
Add Custom Luau Autocomplete to Monaco
monaco.languages.registerCompletionItemProvider('lua', {
provideCompletionItems: function(model, position) {
return {
suggestions: [
{
label: 'game',
kind: monaco.languages.CompletionItemKind.Variable,
insertText: 'game',
documentation: 'The root DataModel instance'
},
{
label: 'workspace',
kind: monaco.languages.CompletionItemKind.Variable,
insertText: 'workspace',
documentation: 'Alias for game.Workspace'
},
{
label: 'loadstring',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'loadstring(${1:source})()',
insertTextRules:
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Load and execute a string as Lua code'
}
]
};
}
});
Troubleshooting
| Problem | Cause | Fix |
|---|
| Forms designer blank/broken | Compiled before opening form | Compile first (CTRL+SHIFT+B), then restart VS |
msbuild not found | MSBuild not in PATH | Use VS Developer Command Prompt |
| Injection returns false | Process not found or access denied | Run as Administrator; verify process name |
| Monaco editor blank | WebBrowser control needs IE11 emulation | Add registry key for IE emulation (see below) |
| DLL not found error | DLL missing from Modules/ folder | Place your DLL in the output Modules/ directory |
Fix Monaco Blank in WebBrowser Control
Add this to your app startup (requires running as admin once, or via installer):
private static void SetWebBrowserEmulation()
{
string appName = Path.GetFileName(Application.ExecutablePath);
using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
@"SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION",
writable: true);
key?.SetValue(appName, 11001, Microsoft.Win32.RegistryValueKind.DWord);
}
Verify Target Process is Running
public static bool IsProcessRunning(string name)
=> Process.GetProcessesByName(name).Length > 0;
if (!IsProcessRunning("RobloxPlayerBeta"))
{
MessageBox.Show("Please launch Roblox before injecting.",
"LInjector", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
Key Facts
- Language: C# / .NET Framework 4.8
- Platform: Windows, x86 (32-bit) only
- Editor: Monaco (VS Code engine) with Lua/Luau syntax
- Injection speed: ~300ms (when working)
- Tab RAM cap: ~1 GB for 40+ tabs
- License: MIT
- Status (2026): UWP target patched by Hyperion; source remains functional for custom DLL targets