- 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](https://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
```bash
# 1. Clone the repository
git clone https://github.com/WeritoP/LInjector-FORKED-
cd LInjector-FORKED-
# 2. Open in Visual Studio 2022
start LInjector.sln
# 3. Set build configuration in VS:
# Configuration: Release
# Platform: x86
# 4. Build via keyboard shortcut
# CTRL + SHIFT + B
# OR via CLI with MSBuild
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#
```csharp
// In your Form class — get script content from Monaco
private string GetEditorContent()
{
// Execute JavaScript to retrieve editor value
object result = webBrowser.Document.InvokeScript(
"eval",
new object[] { "monaco.editor.getModels()[0].getValue()" }
);
return result?.ToString() ?? string.Empty;
}
// Set content in Monaco editor
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)
```html
<!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
}
);
});
// Helper functions callable from C#
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.
```csharp
// TabSystem.cs — Example pattern for managing script tabs
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;
}
// Add a new script tab
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;
}
// Remove tab by index
public void RemoveTab(int index)
{
if (index < 0 || index >= _tabs.Count) return;
_tabControl.TabPages.Remove(_tabs[index].TabPage);
_tabs.RemoveAt(index);
}
// Save current tab content before switching
public void SaveCurrentTabContent(string content)
{
if (_activeIndex >= 0 && _activeIndex < _tabs.Count)
_tabs[_activeIndex].Content = content;
}
// Get content for a tab
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:
```csharp
// Classes/Injector.cs — Pattern for DLL injection
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;
/// <summary>
/// Inject a DLL into a target process by name.
/// Returns true on success.
/// </summary>
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
```csharp
// In MainForm.cs — Inject button click handler
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
```csharp
// Execute the current script via the injected DLL's named pipe or HTTP endpoint
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)
{
// Example: communicate with injected DLL via named pipe
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
```csharp
// Save script to file
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);
}
// Load script from file
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:
```csharp
// Reading/writing settings (App.config / user settings)
// In Settings.settings, add keys: Theme, FontSize, AutoInject
// Read
string theme = Properties.Settings.Default.Theme ?? "vs-dark";
int fontSize = Properties.Settings.Default.FontSize > 0
? Properties.Settings.Default.FontSize : 14;
// Write and save
Properties.Settings.Default.Theme = "vs-dark";
Properties.Settings.Default.FontSize = 16;
Properties.Settings.Default.Save();
// Apply theme to Monaco from C#
private void ApplyEditorTheme(string theme)
{
webBrowser.Document.InvokeScript(
"eval",
new object[] { $"monaco.editor.setTheme('{theme}')" }
);
}
```
---
## Common Customization Patterns
### Change Target Process
```csharp
// Replace "RobloxPlayerBeta" with your target process name
private const string TARGET_PROCESS = "RobloxPlayerBeta";
// or for UWP:
// private const string TARGET_PROCESS = "Windows10Universal";
```
### Add Custom Luau Autocomplete to Monaco
```javascript
// In Monaco/index.html — register Luau-specific completions
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',
View on GitHub