├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build-all.zig ├── build.zig ├── gyro.lock ├── gyro.zzz └── src └── main.zig /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup Zig 15 | uses: goto-bus-stop/setup-zig@v1 16 | with: 17 | version: 0.9.1 18 | 19 | - uses: mattnite/setup-gyro@v1 20 | 21 | - run: gyro fetch 22 | 23 | - run: zig build --build-file "build-all.zig" 24 | 25 | # - run: rm zig-out/bin/installer-cli 26 | 27 | - name: Release 28 | uses: softprops/action-gh-release@v0.1.13 29 | with: 30 | name: ${{ github.sha }} 31 | tag_name: ${{ github.ref }} 32 | # Note-worthy description of changes in release 33 | body: ${{ join(github.event.commits.*.message, '\n') }} 34 | # Newline-delimited list of path globs for asset files to upload 35 | files: "zig-out/bin/*" 36 | # Fails if any of the `files` globs match nothing. Defaults to false 37 | fail_on_unmatched_files: true 38 | # Repository to make releases against, in / format 39 | repository: "kernel-mod/installer-cli" 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-* 2 | .gyro 3 | deps.zig 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kyza 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Download 2 | 3 | Get the build for your system from [releases](https://github.com/kernel-mod/installer-cli/releases/latest). 4 | 5 | ## Community 6 | 7 | Join on [Discord](https://discord.gg/8mPTjTZ4SZ) or [Matrix](https://matrix.to/#/!iWdiwStUmqwDcNfYbG:bigdumb.gq?via=bigdumb.gq&via=catvibers.me&via=matrix.org). 8 | 9 | ## Build From Source 10 | 11 | Real instructions coming soon. You need [Zig 0.9.0](https://ziglang.org/download/) or higher and [Gyro](https://github.com/mattnite/gyro). 12 | 13 | ## Usage 14 | 15 | ```bash 16 | installer-cli -i path/to/electron/app -k path/to/kernel 17 | ``` 18 | 19 | The `-i` flag specifies the path to the Electron app to inject into. 20 | 21 | It should be the path to the directory above the `resources` folder. 22 | 23 | For example on Windows for Discord: `C:/Users/Kyza/AppData/Local/Discord/app-XXXX/` 24 | 25 | The `-k` flag specifies the path to the folder Kernel is in. 26 | -------------------------------------------------------------------------------- /build-all.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pkgs = @import("deps.zig").pkgs; 3 | const builtin = @import("builtin"); 4 | 5 | const Mode = std.builtin.Mode; 6 | 7 | const CrossTarget = std.zig.CrossTarget; 8 | const Target = std.Target; 9 | 10 | const BuildTarget = struct { 11 | name: []const u8, 12 | cross_target: CrossTarget, 13 | mode: Mode, 14 | }; 15 | 16 | pub fn build(b: *std.build.Builder) void { 17 | // Standard release options allow the person running `zig build` to select 18 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 19 | 20 | const targets = [_]BuildTarget{ 21 | .{ 22 | .name = "kernel-installer-i386-windows", 23 | .cross_target = .{ 24 | .cpu_arch = Target.Cpu.Arch.i386, 25 | .os_tag = Target.Os.Tag.windows, 26 | }, 27 | .mode = Mode.ReleaseFast 28 | }, 29 | .{ 30 | .name = "kernel-installer-x86_64-windows", 31 | .cross_target = .{ 32 | .cpu_arch = Target.Cpu.Arch.x86_64, 33 | .os_tag = Target.Os.Tag.windows, 34 | }, 35 | .mode = Mode.ReleaseFast 36 | }, 37 | .{ 38 | .name = "kernel-installer-i386-linux", 39 | .cross_target = .{ 40 | .cpu_arch = Target.Cpu.Arch.i386, 41 | .os_tag = Target.Os.Tag.linux, 42 | }, 43 | .mode = Mode.ReleaseFast 44 | }, 45 | .{ 46 | .name = "kernel-installer-x86_64-linux", 47 | .cross_target = .{ 48 | .cpu_arch = Target.Cpu.Arch.x86_64, 49 | .os_tag = Target.Os.Tag.linux, 50 | }, 51 | .mode = Mode.ReleaseFast 52 | }, 53 | .{ 54 | .name = "kernel-installer-x86_64-macos", 55 | .cross_target = .{ 56 | .cpu_arch = Target.Cpu.Arch.x86_64, 57 | .os_tag = Target.Os.Tag.macos, 58 | }, 59 | .mode = Mode.ReleaseFast 60 | }, 61 | }; 62 | 63 | for (targets) |target| { 64 | const exe = b.addExecutable(target.name, "src/main.zig"); 65 | exe.strip = true; 66 | exe.single_threaded = true; 67 | exe.setTarget(target.cross_target); 68 | exe.setBuildMode(target.mode); 69 | exe.linkLibC(); 70 | pkgs.addAllTo(exe); 71 | exe.install(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const pkgs = @import("deps.zig").pkgs; 3 | 4 | pub fn build(b: *std.build.Builder) void { 5 | // Standard target options allows the person running `zig build` to choose 6 | // what target to build for. Here we do not override the defaults, which 7 | // means any target is allowed, and the default is native. Other options 8 | // for restricting supported target set are available. 9 | const target = b.standardTargetOptions(.{}); 10 | 11 | // Standard release options allow the person running `zig build` to select 12 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 13 | const mode = b.standardReleaseOptions(); 14 | 15 | const exe = b.addExecutable("installer-cli", "src/main.zig"); 16 | exe.strip = true; 17 | exe.single_threaded = true; 18 | exe.setTarget(target); 19 | exe.setBuildMode(mode); 20 | exe.linkLibC(); 21 | pkgs.addAllTo(exe); 22 | exe.install(); 23 | 24 | const run_cmd = exe.run(); 25 | run_cmd.step.dependOn(b.getInstallStep()); 26 | if (b.args) |args| { 27 | run_cmd.addArgs(args); 28 | } 29 | 30 | const run_step = b.step("run", "Run the app"); 31 | run_step.dependOn(&run_cmd.step); 32 | } 33 | -------------------------------------------------------------------------------- /gyro.lock: -------------------------------------------------------------------------------- 1 | github Hejsil zig-clap master clap.zig 4be3fcdb616647215dc162c9e2f64b7d6e3ad09d 2 | github Hejsil zig-clap zig-master clap.zig 27899f951e94a67bf68d6dfebbf4ab9cf182d896 3 | github Kyza zig-clap zig-master clap.zig 668012cce24355606f712b182ded99730c496d03 4 | -------------------------------------------------------------------------------- /gyro.zzz: -------------------------------------------------------------------------------- 1 | deps: 2 | clap: 3 | src: 4 | github: 5 | user: Kyza 6 | repo: zig-clap 7 | ref: zig-master 8 | root: clap.zig 9 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const clap = @import("clap"); 2 | const std = @import("std"); 3 | 4 | const debug = std.debug; 5 | const io = std.io; 6 | const mem = std.mem; 7 | const time = std.time; 8 | const fs = std.fs; 9 | 10 | const allocator = std.heap.c_allocator; 11 | 12 | var timer: ?time.Timer = null; 13 | 14 | pub fn main() !void { 15 | timer = try time.Timer.start(); 16 | 17 | const params = comptime [_]clap.Param(clap.Help) { 18 | clap.parseParam("-h, --help Display this help and exit.") catch unreachable, 19 | clap.parseParam("-i, --inject The path to the Electron app.") catch unreachable, 20 | clap.parseParam("-u, --uninject The path to the Electron app.") catch unreachable, 21 | clap.parseParam("-k, --kernel The path to your Kernel distro. If left out it uses the current directory.") catch unreachable, 22 | }; 23 | 24 | var diag = clap.Diagnostic{}; 25 | var args = clap.parse(clap.Help, ¶ms, .{ .diagnostic = &diag }) catch |err| { 26 | diag.report(io.getStdErr().writer(), err) catch {}; 27 | return err; 28 | }; 29 | defer args.deinit(); 30 | 31 | if (args.option("--inject")) |inject| { 32 | if (args.option("--kernel")) |kernel| { 33 | debug.print("Injecting...\n", .{}); 34 | 35 | const kernel_path = try fs.path.resolve(allocator, &[_][]const u8{ kernel }); 36 | 37 | const app_path = try fs.path.resolve(allocator, &[_][]const u8{ inject }); 38 | const resources_path = try fs.path.join(allocator, &[_][]const u8{ app_path, "resources" }); 39 | 40 | const app_folder_path = try fs.path.join(allocator, &[_][]const u8{ resources_path, "app" }); 41 | const app_asar_path = try fs.path.join(allocator, &[_][]const u8{ resources_path, "app.asar" }); 42 | 43 | const app_folder_index_path = try fs.path.join(allocator, &[_][]const u8{ resources_path, "app", "index.js" }); 44 | const app_folder_package_path = try fs.path.join(allocator, &[_][]const u8{ resources_path, "app", "package.json" }); 45 | 46 | const app_original_folder_path = try fs.path.join(allocator, &[_][]const u8{ resources_path, "app-original" }); 47 | const app_original_asar_path = try fs.path.join(allocator, &[_][]const u8{ resources_path, "app-original.asar" }); 48 | 49 | // Test if the folder is (probably) a valid Electron app. 50 | _ = fs.openDirAbsolute(resources_path, .{}) catch invalidAppDir(); 51 | 52 | debug.print("Detecting injection...\n", .{}); 53 | var injected = true; 54 | _ = fs.openDirAbsolute(app_original_folder_path, .{}) catch { 55 | injected = false; 56 | }; 57 | if (injected) alreadyInjected(); 58 | injected = true; 59 | _ = fs.openFileAbsolute(app_original_asar_path, .{}) catch { 60 | injected = false; 61 | }; 62 | if (injected) alreadyInjected(); 63 | 64 | debug.print("No injection visible.\n", .{}); 65 | debug.print("Detecting ASAR.\n", .{}); 66 | var usesAsar = false; 67 | _ = fs.openDirAbsolute(app_asar_path, .{}) catch { 68 | usesAsar = true; 69 | }; 70 | debug.print("Uses ASAR: {s}\n", .{ usesAsar }); 71 | debug.print("Renaming...\n", .{}); 72 | if (usesAsar) { 73 | fs.renameAbsolute(app_asar_path, app_original_asar_path) catch appRunning(); 74 | } else { 75 | fs.renameAbsolute(app_folder_path, app_original_folder_path) catch appRunning(); 76 | } 77 | 78 | debug.print("Adding files.\n", .{}); 79 | try fs.makeDirAbsolute(app_folder_path); 80 | 81 | const index = try fs.createFileAbsolute(app_folder_index_path, .{}); 82 | const package = try fs.createFileAbsolute(app_folder_package_path, .{}); 83 | defer index.close(); 84 | defer package.close(); 85 | 86 | try index.writeAll( 87 | \\const pkg = require("./package.json"); 88 | \\const Module = require("module"); 89 | \\const path = require("path"); 90 | \\ 91 | \\try { 92 | \\ const kernel = require(path.join(pkg.location, "kernel.asar")); 93 | \\ if (kernel?.default) kernel.default({ startOriginal: true }); 94 | \\} catch(e) { 95 | \\ console.error("Kernel failed to load: ", e.message); 96 | \\ Module._load(path.join(__dirname, "..", "app-original.asar"), null, true); 97 | \\} 98 | ); 99 | 100 | const package_start = "{\"main\":\"index.js\",\"location\":\""; 101 | const package_end = "\"}"; 102 | 103 | const package_json = try mem.join(allocator, "", &[_][]const u8{ package_start, kernel_path, package_end }); 104 | defer allocator.free(package_json); 105 | try package.writeAll(try replaceSlashes(package_json)); 106 | if (timer) |t| { 107 | const end_time = t.read(); 108 | debug.print("Done in: {d}\n", .{ std.fmt.fmtDuration(end_time) }); 109 | } 110 | 111 | return std.os.exit(0); 112 | } 113 | } 114 | 115 | try clap.help( 116 | io.getStdErr().writer(), 117 | comptime ¶ms 118 | ); 119 | 120 | std.os.exit(0); 121 | } 122 | 123 | pub fn invalidAppDir() void { 124 | debug.print("Invalid Electron app directory.\n", .{}); 125 | std.os.exit(0); 126 | } 127 | 128 | pub fn alreadyInjected() void { 129 | debug.print("Something is already injected there.\n", .{}); 130 | std.os.exit(0); 131 | } 132 | 133 | pub fn appRunning() void { 134 | debug.print("The app is running, close it before injecting.\n", .{}); 135 | std.os.exit(0); 136 | } 137 | 138 | pub fn replaceSlashes(string: []const u8) ![]u8 { 139 | const result = try allocator.alloc(u8, string.len); 140 | var i: i128 = 0; 141 | for (string) |char| { 142 | if (char == '\\') { 143 | result[@intCast(usize, i)] = '/'; 144 | } else { 145 | result[@intCast(usize, i)] = char; 146 | } 147 | i += 1; 148 | } 149 | return result; 150 | } 151 | --------------------------------------------------------------------------------