├── .devcontainer ├── Dockerfile ├── devcontainer.json └── postCreateCommand.sh ├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── dotnet.yml │ ├── format.yml │ └── package.yml ├── .gitignore ├── .idea └── .idea.nativeaot-patcher │ └── .idea │ ├── .gitignore │ ├── AndroidProjectSystem.xml │ ├── indexLayout.xml │ ├── projectSettingsUpdater.xml │ ├── vcs.xml │ └── workspace.xml ├── Directory.Build.props ├── Directory.Packages.props ├── Dockerfile ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── api │ ├── .gitignore │ └── index.md ├── articles │ ├── build │ │ ├── asm-build.md │ │ ├── ilc-build.md │ │ ├── kernel-compilation-steps.md │ │ └── patcher-build.md │ ├── plugs.md │ ├── testing.md │ └── toc.yml ├── docfx.json ├── index.md └── toc.yml ├── examples └── KernelExample │ ├── .gitignore │ ├── Kernel.csproj │ ├── Makefile │ └── src │ ├── Asm │ └── io.asm │ ├── Base64.cs │ ├── Internal.cs │ ├── KernelEntry.cs │ ├── Limine │ ├── LimineFramebuffer.cs │ └── LimineID.cs │ ├── PCScreen.cs │ ├── Stdlib.cs │ ├── Stellib.cs │ ├── limine.conf │ └── linker.ld ├── global.json ├── nativeaot-patcher.sln ├── src ├── Cosmos.API │ ├── Attributes │ │ ├── PlugAttribute.cs │ │ └── PlugMemberAttribute.cs │ ├── Cosmos.API.csproj │ ├── Enum │ │ └── TargetPlatform.cs │ └── LabelMaker.cs ├── Cosmos.Asm.Build │ ├── Cosmos.Asm.Build.csproj │ ├── Tasks │ │ ├── LdTask.cs │ │ └── YasmBuildTask.cs │ └── build │ │ ├── Asm.Build.Unix.targets │ │ ├── Asm.Build.Windows.targets │ │ ├── Cosmos.Asm.Build.props │ │ └── Cosmos.Asm.Build.targets ├── Cosmos.Common.Build │ ├── Cosmos.Common.Build.csproj │ └── build │ │ ├── Common.Build.Unix.targets │ │ ├── Common.Build.Windows.targets │ │ ├── Cosmos.Common.Build.props │ │ └── Cosmos.Common.Build.targets ├── Cosmos.Ilc.Build │ ├── Cosmos.Ilc.Build.csproj │ └── build │ │ ├── Cosmos.Ilc.Build.props │ │ └── Cosmos.Ilc.Build.targets ├── Cosmos.Patcher.Analyzer.CodeFixes │ ├── Cosmos.Patcher.Analyzer.CodeFixes.csproj │ ├── Models │ │ └── ProjectInfo.cs │ └── PatcherCodeFixProvider.cs ├── Cosmos.Patcher.Analyzer.Package │ ├── Cosmos.Patcher.Analyzer.Package.csproj │ └── tools │ │ ├── install.ps1 │ │ └── uninstall.ps1 ├── Cosmos.Patcher.Analyzer │ ├── Cosmos.Patcher.Analyzer.csproj │ ├── DiagnosticMessages.cs │ ├── Extensions │ │ ├── AttributeExtensions.cs │ │ ├── EnumerableExtensions.cs │ │ ├── SymbolExtensions.cs │ │ └── SyntaxNodeExtensions.cs │ ├── Models │ │ └── PlugInfo.cs │ └── PatcherAnalyzer.cs ├── Cosmos.Patcher.Build │ ├── Cosmos.Patcher.Build.csproj │ ├── Tasks │ │ └── PatcherTask.cs │ └── build │ │ ├── Cosmos.Patcher.Build.props │ │ └── Cosmos.Patcher.Build.targets └── Cosmos.Patcher │ ├── Cosmos.Patcher.csproj │ ├── Extensions │ └── MethodBodyEx.cs │ ├── MonoCecilExtension.cs │ ├── PatchCommand.cs │ ├── PlugPatcher.cs │ ├── PlugScanner.cs │ ├── PlugUtils.cs │ └── Program.cs └── tests ├── Cosmos.Asm.Build.Test ├── Cosmos.Asm.Build.Test.csproj ├── GlobalUsings.cs ├── UnitTest1.cs └── asm │ └── test.asm ├── Cosmos.NativeLibrary ├── Cosmos.NativeLibrary.vcxproj ├── Cosmos.NativeLibrary.vcxproj.filters └── nativelibrary.c ├── Cosmos.NativeWrapper ├── Cosmos.NativeWrapper.csproj ├── NativeWrapper.cs ├── NativeWrapperObject.cs ├── NativeWrapperObjectPlug.cs └── NativeWrapperPlug.cs ├── Cosmos.Patcher.Analyzer.Tests ├── AnalyzerTests.cs └── Cosmos.Patcher.Analyzer.Tests.csproj ├── Cosmos.Patcher.Tests ├── Cosmos.Patcher.Tests.csproj ├── PlugPatcherTest_ObjectPlugs.cs └── PlugPatcherTest_StaticPlugs.cs └── Cosmos.Scanner.Tests ├── Cosmos.Scanner.Tests.csproj ├── PlugScannerTests_LoadPlugMethods.cs └── PlugScannerTests_LoadPlugs.cs /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the base image for .NET development 2 | FROM mcr.microsoft.com/devcontainers/dotnet:1-9.0-bookworm 3 | 4 | # Install clang, xorriso, lld, and yasm 5 | RUN apt-get update && apt-get install -y clang xorriso lld llvm yasm && apt-get clean 6 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/dotnet 3 | { 4 | "name": "Cosmos Develeopment Container", 5 | // Use a Dockerfile instead of a prebuilt image 6 | "build": { 7 | "dockerfile": "Dockerfile" 8 | }, 9 | "features": { 10 | "ghcr.io/devcontainers/features/dotnet:2": {}, 11 | "ghcr.io/devcontainers/features/powershell:1": {} 12 | }, 13 | "hostRequirements": { 14 | "cpus": 4, 15 | "memory": "8gb" 16 | }, 17 | 18 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 19 | // "forwardPorts": [5000, 5001], 20 | // "portsAttributes": { 21 | // "5001": { 22 | // "protocol": "https" 23 | // } 24 | // } 25 | 26 | // Use 'postCreateCommand' to run commands after the container is created. 27 | "postCreateCommand": "./.devcontainer/postCreateCommand.sh", 28 | "customizations": { 29 | "vscode": { 30 | "extensions": [ 31 | "ms-dotnettools.csdevkit", 32 | "GitHub.copilot-chat", 33 | "GitHub.copilot", 34 | "ms-vscode.cpptools", 35 | "ms-vscode.cpptools-extension-pack" 36 | ] 37 | }, 38 | "settings": { 39 | // Loading projects on demand is better for larger codebases 40 | "omnisharp.enableMsBuildLoadProjectsOnDemand": true, 41 | "omnisharp.enableRoslynAnalyzers": true, 42 | "omnisharp.enableEditorConfigSupport": true, 43 | "omnisharp.enableAsyncCompletion": true 44 | } 45 | } 46 | 47 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 48 | // "remoteUser": "root" 49 | } 50 | -------------------------------------------------------------------------------- /.devcontainer/postCreateCommand.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Pack all projects 6 | dotnet build ./src/Cosmos.API/Cosmos.API.csproj --configuration Release 7 | dotnet build ./src/Cosmos.Patcher.Build/Cosmos.Patcher.Build.csproj --configuration Release 8 | dotnet build ./src/Cosmos.Patcher/Cosmos.Patcher.csproj --configuration Release 9 | dotnet build ./src/Cosmos.Common.Build/Cosmos.Common.Build.csproj --configuration Release 10 | dotnet build ./src/Cosmos.Ilc.Build/Cosmos.Ilc.Build.csproj --configuration Release 11 | dotnet build ./src/Cosmos.Asm.Build/Cosmos.Asm.Build.csproj --configuration Release 12 | dotnet build ./src/Cosmos.Patcher.Analyzer.Package/Cosmos.Patcher.Analyzer.Package.csproj --configuration Release 13 | 14 | # Add output folder as a local NuGet source 15 | dotnet nuget add source "$PWD/artifacts/package/release" --name local-packages 16 | 17 | # Clear all NuGet locals cache 18 | dotnet nuget locals all --clear 19 | 20 | dotnet restore 21 | 22 | dotnet tool install -g ilc 23 | dotnet tool install -g Cosmos.Patcher -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Default settings: 7 | # A newline ending every file 8 | # Use 4 spaces as indentation 9 | [*] 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | trim_trailing_whitespace = true 14 | 15 | # Generated code 16 | [*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}] 17 | generated_code = true 18 | 19 | # C# files 20 | [*.cs] 21 | # New line preferences 22 | csharp_new_line_before_open_brace = all 23 | csharp_new_line_before_else = true 24 | csharp_new_line_before_catch = true 25 | csharp_new_line_before_finally = true 26 | csharp_new_line_before_members_in_object_initializers = true 27 | csharp_new_line_before_members_in_anonymous_types = true 28 | csharp_new_line_between_query_expression_clauses = true 29 | 30 | # Indentation preferences 31 | csharp_indent_block_contents = true 32 | csharp_indent_braces = false 33 | csharp_indent_case_contents = true 34 | csharp_indent_case_contents_when_block = false 35 | csharp_indent_switch_labels = true 36 | csharp_indent_labels = one_less_than_current 37 | 38 | # Modifier preferences 39 | csharp_preferred_modifier_order = public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async:suggestion 40 | 41 | # avoid this. unless absolutely necessary 42 | dotnet_style_qualification_for_field = false:suggestion 43 | dotnet_style_qualification_for_property = false:suggestion 44 | dotnet_style_qualification_for_method = false:suggestion 45 | dotnet_style_qualification_for_event = false:suggestion 46 | 47 | # Types: use keywords instead of BCL types, and permit var only when the type is clear 48 | csharp_style_var_for_built_in_types = false:suggestion 49 | csharp_style_var_when_type_is_apparent = false:none 50 | csharp_style_var_elsewhere = false:suggestion 51 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 52 | dotnet_style_predefined_type_for_member_access = true:suggestion 53 | 54 | # name all constant fields using PascalCase 55 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 56 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 57 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 58 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 59 | dotnet_naming_symbols.constant_fields.required_modifiers = const 60 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 61 | 62 | # static fields should have s_ prefix 63 | dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion 64 | dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields 65 | dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style 66 | dotnet_naming_symbols.static_fields.applicable_kinds = field 67 | dotnet_naming_symbols.static_fields.required_modifiers = static 68 | dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected 69 | dotnet_naming_style.static_prefix_style.required_prefix = s_ 70 | dotnet_naming_style.static_prefix_style.capitalization = camel_case 71 | 72 | # internal and private fields should be _camelCase 73 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 74 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 75 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 76 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 77 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal 78 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 79 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 80 | 81 | # Code style defaults 82 | csharp_using_directive_placement = outside_namespace:suggestion 83 | dotnet_sort_system_directives_first = true 84 | csharp_prefer_braces = true:silent 85 | csharp_preserve_single_line_blocks = true:none 86 | csharp_preserve_single_line_statements = false:none 87 | csharp_prefer_static_local_function = true:suggestion 88 | csharp_prefer_simple_using_statement = false:none 89 | csharp_style_prefer_switch_expression = true:suggestion 90 | dotnet_style_readonly_field = true:suggestion 91 | 92 | # Expression-level preferences 93 | dotnet_style_object_initializer = true:suggestion 94 | dotnet_style_collection_initializer = true:suggestion 95 | dotnet_style_prefer_collection_expression = when_types_exactly_match 96 | dotnet_style_explicit_tuple_names = true:suggestion 97 | dotnet_style_coalesce_expression = true:suggestion 98 | dotnet_style_null_propagation = true:suggestion 99 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 100 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 101 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 102 | dotnet_style_prefer_auto_properties = true:suggestion 103 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 104 | dotnet_style_prefer_conditional_expression_over_return = true:silent 105 | csharp_prefer_simple_default_expression = true:suggestion 106 | 107 | # Expression-bodied members 108 | csharp_style_expression_bodied_methods = true:silent 109 | csharp_style_expression_bodied_constructors = true:silent 110 | csharp_style_expression_bodied_operators = true:silent 111 | csharp_style_expression_bodied_properties = true:silent 112 | csharp_style_expression_bodied_indexers = true:silent 113 | csharp_style_expression_bodied_accessors = true:silent 114 | csharp_style_expression_bodied_lambdas = true:silent 115 | csharp_style_expression_bodied_local_functions = true:silent 116 | 117 | # Pattern matching 118 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 119 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 120 | csharp_style_inlined_variable_declaration = true:suggestion 121 | 122 | # Null checking preferences 123 | csharp_style_throw_expression = true:suggestion 124 | csharp_style_conditional_delegate_call = true:suggestion 125 | 126 | # Other features 127 | csharp_style_prefer_index_operator = false:none 128 | csharp_style_prefer_range_operator = false:none 129 | csharp_style_pattern_local_over_anonymous_function = false:none 130 | 131 | # Space preferences 132 | csharp_space_after_cast = false 133 | csharp_space_after_colon_in_inheritance_clause = true 134 | csharp_space_after_comma = true 135 | csharp_space_after_dot = false 136 | csharp_space_after_keywords_in_control_flow_statements = true 137 | csharp_space_after_semicolon_in_for_statement = true 138 | csharp_space_around_binary_operators = before_and_after 139 | csharp_space_around_declaration_statements = do_not_ignore 140 | csharp_space_before_colon_in_inheritance_clause = true 141 | csharp_space_before_comma = false 142 | csharp_space_before_dot = false 143 | csharp_space_before_open_square_brackets = false 144 | csharp_space_before_semicolon_in_for_statement = false 145 | csharp_space_between_empty_square_brackets = false 146 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 147 | csharp_space_between_method_call_name_and_opening_parenthesis = false 148 | csharp_space_between_method_call_parameter_list_parentheses = false 149 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 150 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 151 | csharp_space_between_method_declaration_parameter_list_parentheses = false 152 | csharp_space_between_parentheses = false 153 | csharp_space_between_square_brackets = false 154 | 155 | # License header 156 | file_header_template = This code is licensed under MIT license (see LICENSE for details) 157 | 158 | # C++ Files 159 | [*.{cpp,h,in}] 160 | curly_bracket_next_line = true 161 | indent_brace_style = Allman 162 | 163 | # Xml project files 164 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] 165 | indent_size = 2 166 | 167 | [*.{csproj,vbproj,proj,nativeproj,locproj}] 168 | charset = utf-8 169 | 170 | # Xml build files 171 | [*.builds] 172 | indent_size = 2 173 | 174 | # Xml files 175 | [*.{xml,stylecop,resx,ruleset}] 176 | indent_size = 2 177 | 178 | # Xml config files 179 | [*.{props,targets,config,nuspec}] 180 | indent_size = 2 181 | 182 | # YAML config files 183 | [*.{yml,yaml}] 184 | indent_size = 2 185 | 186 | # Shell scripts 187 | [*.sh] 188 | end_of_line = lf 189 | 190 | [*.{cmd,bat}] 191 | end_of_line = crlf 192 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # reformat project 2 | 08a5740697f28a26a152e6972838d6b793ab25a7 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for more information: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | # https://containers.dev/guide/dependabot 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "devcontainers" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET Tests 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "**" ] 8 | 9 | jobs: 10 | packages: 11 | uses: ./.github/workflows/package.yml 12 | patcher-tests: 13 | name: Run Cosmos.Patcher.Tests 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ ubuntu-latest ] 18 | dotnet-version: [ 9.0.x ] 19 | steps: 20 | - name: 📥 Checkout Code 21 | uses: actions/checkout@v4 22 | 23 | - name: 🛠️ Setup .NET 24 | uses: actions/setup-dotnet@v4 25 | with: 26 | dotnet-version: ${{ matrix.dotnet-version }} 27 | 28 | - name: 🔄 Restore Dependencies 29 | run: dotnet restore ./tests/Cosmos.Patcher.Tests/Cosmos.Patcher.Tests.csproj 30 | 31 | - name: 🔨 Build Cosmos.Patcher 32 | run: dotnet build ./src/Cosmos.Patcher/Cosmos.Patcher.csproj 33 | 34 | - name: 🔨 Build Cosmos.Patcher.Build 35 | run: dotnet build ./src/Cosmos.Patcher.Build/Cosmos.Patcher.Build.csproj 36 | 37 | - name: 🔨 Build Cosmos.Patcher.Tests 38 | run: dotnet build ./tests/Cosmos.Patcher.Tests/Cosmos.Patcher.Tests.csproj --configuration Debug --no-restore 39 | 40 | - name: 🚀 Run Tests 41 | run: dotnet test ./tests/Cosmos.Patcher.Tests/Cosmos.Patcher.Tests.csproj --no-build --configuration Debug --logger "trx;LogFileName=Cosmos.Patcher.Tests.trx" 42 | 43 | - name: 📤 Upload Test Results 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: Cosmos.Patcher.Tests-Results 47 | path: ./tests/Cosmos.Patcher.Tests/TestResults/Cosmos.Patcher.Tests.trx 48 | 49 | scanner-tests: 50 | name: Run Cosmos.Scanner.Tests 51 | runs-on: ${{ matrix.os }} 52 | strategy: 53 | matrix: 54 | os: [ ubuntu-latest ] 55 | dotnet-version: [ 9.0.x ] 56 | steps: 57 | - name: 📥 Checkout Code 58 | uses: actions/checkout@v4 59 | 60 | - name: 🛠️ Setup .NET 61 | uses: actions/setup-dotnet@v4 62 | with: 63 | dotnet-version: ${{ matrix.dotnet-version }} 64 | 65 | - name: 🔄 Restore Dependencies 66 | run: dotnet restore ./tests/Cosmos.Scanner.Tests/Cosmos.Scanner.Tests.csproj 67 | 68 | - name: 🔨 Build Cosmos.Patcher 69 | run: dotnet build ./src/Cosmos.Patcher/Cosmos.Patcher.csproj 70 | 71 | - name: 🔨 Build Cosmos.Patcher.Build 72 | run: dotnet build ./src/Cosmos.Patcher.Build/Cosmos.Patcher.Build.csproj 73 | 74 | - name: 🔨 Build Cosmos.Scanner.Tests 75 | run: dotnet build ./tests/Cosmos.Scanner.Tests/Cosmos.Scanner.Tests.csproj --configuration Debug --no-restore 76 | 77 | - name: 🚀 Run Tests 78 | run: dotnet test ./tests/Cosmos.Scanner.Tests/Cosmos.Scanner.Tests.csproj --no-build --configuration Debug --logger "trx;LogFileName=Cosmos.Scanner.Tests.trx" 79 | 80 | - name: 📤 Upload Test Results 81 | uses: actions/upload-artifact@v4 82 | with: 83 | name: Cosmos.Scanner.Tests-Results 84 | path: ./tests/Cosmos.Scanner.Tests/TestResults/Cosmos.Scanner.Tests.trx 85 | 86 | analyzer-tests: 87 | name: Run Cosmos.Patcher.Analyzer.Tests 88 | runs-on: ${{ matrix.os }} 89 | strategy: 90 | matrix: 91 | os: [ ubuntu-latest ] 92 | dotnet-version: [ 9.0.x ] 93 | steps: 94 | - name: 📥 Checkout Code 95 | uses: actions/checkout@v4 96 | 97 | - name: 🛠️ Setup .NET 98 | uses: actions/setup-dotnet@v4 99 | with: 100 | dotnet-version: ${{ matrix.dotnet-version }} 101 | 102 | - name: 🔄 Restore Dependencies 103 | run: dotnet restore ./tests/Cosmos.Patcher.Analyzer.Tests/Cosmos.Patcher.Analyzer.Tests.csproj 104 | 105 | - name: 🔨 Build Cosmos.Patcher.Analyzer.Tests 106 | run: dotnet build ./tests/Cosmos.Patcher.Analyzer.Tests/Cosmos.Patcher.Analyzer.Tests.csproj --configuration Debug --no-restore 107 | 108 | - name: 🚀 Run Tests 109 | run: dotnet test ./tests/Cosmos.Patcher.Analyzer.Tests/Cosmos.Patcher.Analyzer.Tests.csproj --no-build --configuration Debug --logger "trx;LogFileName=Cosmos.Patcher.Analyzer.Tests.trx" 110 | 111 | - name: 📤 Upload Test Results 112 | uses: actions/upload-artifact@v4 113 | with: 114 | name: Cosmos.Patcher.Analyzer.Tests-Results 115 | path: ./tests/Cosmos.Patcher.Analyzer.Tests/TestResults/Cosmos.Patcher.Analyzer.Tests.trx 116 | 117 | asm-tests: 118 | name: Run Cosmos.Asm.Build.Test 119 | runs-on: ${{ matrix.os }} 120 | strategy: 121 | matrix: 122 | os: [ ubuntu-latest ] 123 | dotnet-version: [ 9.0.x ] 124 | steps: 125 | - name: 📥 Checkout Code 126 | uses: actions/checkout@v4 127 | 128 | - name: 🛠️ Setup .NET 129 | uses: actions/setup-dotnet@v4 130 | with: 131 | dotnet-version: ${{ matrix.dotnet-version }} 132 | 133 | - name: 🔗 Install Linking Tools 134 | run: sudo apt-get update && sudo apt-get install -y yasm 135 | 136 | - name: 🔄 Restore Dependencies 137 | run: dotnet restore ./tests/Cosmos.Asm.Build.Test/Cosmos.Asm.Build.Test.csproj 138 | 139 | - name: 🔨 Build Cosmos.Patcher 140 | run: dotnet build ./src/Cosmos.Patcher/Cosmos.Patcher.csproj 141 | 142 | - name: 🔨 Build Cosmos.Patcher.Build 143 | run: dotnet build ./src/Cosmos.Patcher.Build/Cosmos.Patcher.Build.csproj 144 | 145 | - name: 🔨 Build Cosmos.Asm.Build.Test 146 | run: dotnet build ./tests/Cosmos.Asm.Build.Test/Cosmos.Asm.Build.Test.csproj --configuration Debug --no-restore 147 | 148 | - name: 🚀 Run Tests 149 | run: dotnet test ./tests/Cosmos.Asm.Build.Test/Cosmos.Asm.Build.Test.csproj --no-build --configuration Debug --logger "trx;LogFileName=Cosmos.Asm.Build.Test.trx" 150 | 151 | - name: 📤 Upload Test Results 152 | uses: actions/upload-artifact@v4 153 | with: 154 | name: Cosmos.Asm.Build.Test-Results 155 | path: ./tests/Cosmos.Asm.Build.Test/TestResults/Cosmos.Asm.Build.Test.trx 156 | 157 | unix-iso-tests: 158 | name: Run ISO Build Tests - Unix 159 | needs: [packages] 160 | runs-on: ${{ matrix.os }} 161 | strategy: 162 | matrix: 163 | os: [ ubuntu-latest ] 164 | dotnet-version: [ 9.0.x ] 165 | steps: 166 | - name: 📥 Checkout Code 167 | uses: actions/checkout@v4 168 | 169 | - name: 🛠️ Setup .NET 170 | uses: actions/setup-dotnet@v4 171 | with: 172 | dotnet-version: ${{ matrix.dotnet-version }} 173 | 174 | - name: 🔄 Download Packages 175 | uses: actions/download-artifact@v4 176 | with: 177 | name: Cosmos.Patcher.Packages 178 | path: packages 179 | 180 | - name: 📂 List Directory Contents 181 | run: ls -R $GITHUB_WORKSPACE/packages 182 | 183 | - name: 🛠️ Setup Nuget Local 184 | run: dotnet nuget add source $GITHUB_WORKSPACE/packages 185 | 186 | - name: 🔗 Install Linking Tools 187 | run: sudo apt-get update && sudo apt-get install -y xorriso lld yasm 188 | 189 | - name: 🔧 Install ilc 190 | run: dotnet tool install -g ilc 191 | 192 | - name: 🔧 Install Patcher 193 | run: dotnet tool install -g Cosmos.Patcher 194 | 195 | - name: 🔄 Restore Dependencies 196 | run: dotnet restore 197 | 198 | - name: 🚀 Run Tests 199 | run: dotnet publish -c Debug -r linux-x64 --verbosity detailed ./examples/KernelExample/Kernel.csproj -o ./output 200 | 201 | - name: 🕵️‍♂️ Verify Output 202 | run: | 203 | [ -f ./output/Kernel.iso ] && echo "ISO exists" || (echo "ISO missing" && exit 1) 204 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: .NET Format 2 | 3 | on: 4 | pull_request: 5 | branches: [ "main" ] 6 | 7 | jobs: 8 | format: 9 | name: Check Code Formatting 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | dotnet-version: [ 9.0.x ] 14 | 15 | steps: 16 | - name: Checkout Code 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup .NET 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: ${{ matrix.dotnet-version }} 23 | 24 | - name: Restore Dependencies 25 | run: dotnet restore 26 | 27 | - name: Check Formatting 28 | run: dotnet format --verify-no-changes --severity error || { echo "::error file=Code Formatting::Some files need formatting. Run 'dotnet format' locally."; exit 1; } 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: Package 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_call: 8 | jobs: 9 | pack-patcher: 10 | name: Pack Cosmos Patcher 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ windows-latest ] 15 | dotnet-version: [ 9.0.x ] 16 | 17 | steps: 18 | - name: Checkout Code 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup .NET 22 | uses: actions/setup-dotnet@v4 23 | with: 24 | dotnet-version: ${{ matrix.dotnet-version }} 25 | 26 | - name: Pack Cosmos.Api 27 | run: dotnet build ./src/Cosmos.API/Cosmos.API.csproj --configuration Release --artifacts-path ./out 28 | 29 | - name: Pack Cosmos.Patcher.Build 30 | run: dotnet build ./src/Cosmos.Patcher.Build/Cosmos.Patcher.Build.csproj --configuration Release --artifacts-path ./out 31 | 32 | - name: Pack Cosmos.Patcher 33 | run: dotnet build ./src/Cosmos.Patcher/Cosmos.Patcher.csproj --configuration Release --artifacts-path ./out 34 | 35 | - name: Pack Cosmos.Common.Build 36 | run: dotnet build ./src/Cosmos.Common.Build/Cosmos.Common.Build.csproj --configuration Release --artifacts-path ./out 37 | 38 | - name: Pack Cosmos.Ilc.Build 39 | run: dotnet build ./src/Cosmos.Ilc.Build/Cosmos.Ilc.Build.csproj --configuration Release --artifacts-path ./out 40 | 41 | - name: Pack Cosmos.Asm.Build 42 | run: dotnet build ./src/Cosmos.Asm.Build/Cosmos.Asm.Build.csproj --configuration Release --artifacts-path ./out 43 | 44 | - name: Pack Cosmos.Patcher.Analyzer.Package 45 | run: dotnet build ./src/Cosmos.Patcher.Analyzer.Package/Cosmos.Patcher.Analyzer.Package.csproj --configuration Release --artifacts-path ./out 46 | 47 | - name: Upload Packages 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: Cosmos.Patcher.Packages 51 | path: ./out/package/release 52 | 53 | 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.o 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.tlog 95 | *.vspscc 96 | *.vssscc 97 | .builds 98 | *.pidb 99 | *.svclog 100 | *.scc 101 | *.d 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 300 | *.vbp 301 | 302 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 303 | *.dsw 304 | *.dsp 305 | 306 | # Visual Studio 6 technical files 307 | *.ncb 308 | *.aps 309 | 310 | # Visual Studio LightSwitch build output 311 | **/*.HTMLClient/GeneratedArtifacts 312 | **/*.DesktopClient/GeneratedArtifacts 313 | **/*.DesktopClient/ModelManifest.xml 314 | **/*.Server/GeneratedArtifacts 315 | **/*.Server/ModelManifest.xml 316 | _Pvt_Extensions 317 | 318 | # Paket dependency manager 319 | .paket/paket.exe 320 | paket-files/ 321 | 322 | # FAKE - F# Make 323 | .fake/ 324 | 325 | # CodeRush personal settings 326 | .cr/personal 327 | 328 | # Python Tools for Visual Studio (PTVS) 329 | __pycache__/ 330 | *.pyc 331 | 332 | # Cake - Uncomment if you are using it 333 | # tools/** 334 | # !tools/packages.config 335 | 336 | # Tabs Studio 337 | *.tss 338 | 339 | # Telerik's JustMock configuration file 340 | *.jmconfig 341 | 342 | # BizTalk build output 343 | *.btp.cs 344 | *.btm.cs 345 | *.odx.cs 346 | *.xsd.cs 347 | 348 | # OpenCover UI analysis results 349 | OpenCover/ 350 | 351 | # Azure Stream Analytics local run output 352 | ASALocalRun/ 353 | 354 | # MSBuild Binary and Structured Log 355 | *.binlog 356 | 357 | # NVidia Nsight GPU debugger configuration file 358 | *.nvuser 359 | 360 | # MFractors (Xamarin productivity tool) working folder 361 | .mfractor/ 362 | 363 | # Local History for Visual Studio 364 | .localhistory/ 365 | 366 | # Visual Studio History (VSHistory) files 367 | .vshistory/ 368 | 369 | # BeatPulse healthcheck temp database 370 | healthchecksdb 371 | 372 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 373 | MigrationBackup/ 374 | 375 | # Ionide (cross platform F# VS Code tools) working folder 376 | .ionide/ 377 | 378 | # Fody - auto-generated XML schema 379 | FodyWeavers.xsd 380 | 381 | # VS Code files for those working on multiple tools 382 | .vscode/* 383 | !.vscode/settings.json 384 | !.vscode/tasks.json 385 | !.vscode/launch.json 386 | !.vscode/extensions.json 387 | *.code-workspace 388 | 389 | # Local History for Visual Studio Code 390 | .history/ 391 | 392 | # Windows Installer files from build outputs 393 | *.cab 394 | *.msi 395 | *.msix 396 | *.msm 397 | *.msp 398 | 399 | # JetBrains Rider 400 | *.sln.iml 401 | 402 | out/ 403 | 404 | *.iso 405 | *.efi 406 | main 407 | 408 | /kernel-deps 409 | /ovmf 410 | *.iso 411 | *.hdd 412 | output 413 | -------------------------------------------------------------------------------- /.idea/.idea.nativeaot-patcher/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | workspace.xml 2 | -------------------------------------------------------------------------------- /.idea/.idea.nativeaot-patcher/.idea/AndroidProjectSystem.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/.idea.nativeaot-patcher/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.nativeaot-patcher/.idea/projectSettingsUpdater.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.nativeaot-patcher/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | enable 5 | latest 6 | enable 7 | false 8 | false 9 | true 10 | 3.0.0 11 | true 12 | $(NoWarn);1591;CS1998;NU1903; 13 | true 14 | true 15 | true 16 | 17 | 18 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | all 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build 2 | 3 | WORKDIR /app 4 | 5 | COPY . ./ 6 | 7 | RUN dotnet restore ./Cosmos.Patcher.Tests/Cosmos.Patcher.Tests.csproj 8 | 9 | RUN dotnet build ./Cosmos.Patcher.Tests/Cosmos.Patcher.Tests.csproj --configuration Debug --no-restore 10 | 11 | CMD ["dotnet", "test", "./Cosmos.Patcher.Tests/Cosmos.Patcher.Tests.csproj", "--no-build", "--configuration", "Debug", "--logger", "trx;LogFileName=Cosmos.Patcher.Tests.trx"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kaleb McGhie aka zarlo 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 | # nativeaot-patcher 2 | [![.NET Tests](https://github.com/valentinbreiz/nativeaot-patcher/actions/workflows/dotnet.yml/badge.svg?branch=main&event=push)](https://github.com/valentinbreiz/nativeaot-patcher/actions/workflows/dotnet.yml) 3 | [![Package](https://github.com/valentinbreiz/nativeaot-patcher/actions/workflows/package.yml/badge.svg)](https://github.com/valentinbreiz/nativeaot-patcher/actions/workflows/package.yml) 4 | 5 | Zarlo's NativeAOT patcher. Main goal is to port Cosmos plug system, assembly loading for NativeAOT. See https://github.com/CosmosOS/Cosmos/issues/3088 for details. 6 | 7 | ## Documentation 8 | - [Wiki](https://github.com/valentinbreiz/nativeaot-patcher/wiki/) 9 | 10 | ## Priority 11 | - [Priority Board](https://github.com/users/valentinbreiz/projects/2/views/2) 12 | 13 | ## Credit: 14 | - [@zarlo](https://github.com/zarlo) 15 | - [@kumja1](https://github.com/kumja1) 16 | - [@Guillermo-Santos](https://github.com/Guillermo-Santos) 17 | - [@valentinbreiz](https://github.com/valentinbreiz) 18 | - [@ascpixi](https://github.com/ascpixi) 19 | - [@ilobilo](https://github.com/ilobilo) 20 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | _site 10 | -------------------------------------------------------------------------------- /docs/api/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # temp file # 3 | ############### 4 | *.yml 5 | .manifest 6 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valentinbreiz/nativeaot-patcher/f2997cb2146da322481bc0437a54a3634af92924/docs/api/index.md -------------------------------------------------------------------------------- /docs/articles/build/asm-build.md: -------------------------------------------------------------------------------- 1 | The `Cosmos.Asm.Build` component is part of the Cosmos OS SDK. Its purpose is to compile assembly files into object files, which can then be consumed by other `Cosmos.*.Build` components or custom user targets. 2 | 3 | --- 4 | 5 | ## Input Parameters 6 | | Name | Description | Default Value | 7 | |------------|---------------------------------------------------------------------|---------------------------------------------------| 8 | | `YasmPath` | Full file path to Yasm, the tool used to compile the object files. | Unix: `/usr/bin/yasm`, Windows: To be decided. | 9 | | `AsmSearch`| One or more directory paths that contain assembly files. | None | 10 | | `AsmFiles` | One or more paths to specific assembly files. | None | 11 | 12 | --- 13 | 14 | ## Output 15 | All compiled object files are placed in the intermediate output directory of the project, specifically at `$(IntermediateOutput)\cosmos\asm`. 16 | 17 | --- 18 | 19 | ## Tasks 20 | | Name | Description | Depends On | 21 | |-------------------------|---------------------------------------------------------------------------------------------------|------------------------| 22 | | `FindSourceFilesForYasm`| Filters all `AsmSearch` and `AsmFiles` paths specified by the user. | `Build` | 23 | | `BuildYasm` | Compiles all specified assembly files and places them in the `Cosmos.Asm.Build` output directory. | `FindSourceFilesForYasm` | 24 | | `CleanYasm` | Removes all files generated by `Cosmos.Asm.Build`. | `Clean` | 25 | 26 | --- 27 | 28 | ## Understanding the `AsmSearch` Parameter 29 | The `AsmSearch` parameter defines a collection of directories to scan for `.asm` files. TThe scan is restricted to the root of each specified directory, with no recursive scanning of subdirectories. 30 | 31 | ### Operations 32 | The following operations are performed on the `AsmSearch` parameter: 33 | 34 | 1. **Remove Non-Existent Directories** 35 | Any directory or assembly file that does not exist is removed from the list. A warning with the code `TODO: Pick an error code` is generated for each missing entry. 36 | 37 | 2. **Scan for Architecture-Specific Subfolders** 38 | If a subdirectory matches the current target architecture, it replaces its parent directory as an entry for scanning. 39 | 40 | 3. **Search for Assembly Files** 41 | The remaining entries in the `AsmSearch` parameter are scanned for `.asm` files. These files are then added to the `AsmFiles` parameter as input for the `Yasm` task. 42 | -------------------------------------------------------------------------------- /docs/articles/build/ilc-build.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | **Cosmos.ilc.Build** is the build system for CosmosOS-projects. It is responsible for compiling the patched dll outputted by **Cosmos.Patcher** into native code which is then linked with native libraries on the target os. 4 | 5 | --- 6 | 7 | ## Workflow 8 | 9 | ### Step 0: Run Patcher 10 | The **Cosmos.ilc.Build**: 11 | - Calls **Cosmos.Patcher** to patch the project assembly. 12 | 13 | ### Step 1: Compile with ILC 14 | The **Cosmos.ilc.Build**: 15 | - Compiles the patched dll to a `.obj` file. 16 | 17 | ### Step 2: Link with native libraries 18 | The **Cosmos.ilc.Build**: 19 | - Links the generated `.obj` file with native libraries 20 | - Outputs a native binary file ready for further processing 21 | --- 22 | -------------------------------------------------------------------------------- /docs/articles/build/kernel-compilation-steps.md: -------------------------------------------------------------------------------- 1 | This document outlines the build process for the C# kernel, which utilizes .NET NativeAOT compilation combined with a custom patching mechanism ([Plugs](/articles/Plugs.html)) to produce a bootable ELF kernel file and a final bootable ISO image. 2 | 3 | ## Overview 4 | 5 | The compilation process transforms C# source code into a native executable kernel (`Kernel.elf`) and packages it with the Limine bootloader into a bootable ISO image (`.iso`). It involves several stages orchestrated by MSBuild, leveraging custom build components like `Cosmos.Asm.Build`, `Cosmos.Patcher.Analyzer`, `Cosmos.Ilc.Build`, and `Cosmos.Common.Build`. These components provide MSBuild tasks and targets to manage assembly, C# compilation, static analysis, IL patching, NativeAOT compilation, linking, and ISO image creation. 6 | 7 | ![image](https://github.com/user-attachments/assets/d0cb98a5-9c61-48e4-8722-0a7dd151f86f) 8 | > Visual by [Guillermo-Santos](https://github.com/Guillermo-Santos) 9 | 10 | ## Prerequisites 11 | 12 | Ensure the following tools and SDKs are installed: 13 | 14 | * **.NET SDK**: Version 9.0.104 or later (as specified in `global.json`), including the NativeAOT compilation tools. 15 | * **YASM Assembler**: Required by `Cosmos.Asm.Build` for compiling `.asm` files. 16 | * **LLD Linker (`ld.lld`)**: Part of the LLVM toolchain, required by `Cosmos.Common.Build` for linking object files into the final ELF executable. 17 | * **xorriso**: Required by `Cosmos.Common.Build` for creating the final bootable ISO image. 18 | 19 | ## Compilation Stages 20 | 21 | The build process, orchestrated by MSBuild using tasks and targets from the various `Cosmos.*.Build` components follows these main steps: 22 | 23 | 1. **Assembly Compilation (`.asm` -> `.obj`) - via [Cosmos.Asm.Build](/articles/Cosmos.Asm.Build.html)**: 24 | * Handwritten assembly files (`.asm`) containing low-level x86 and ARM routines (e.g., implementations for functions marked `[RuntimeImport]` like `_native_io_*`) are identified. 25 | * The MSBuild task provided by `Cosmos.Asm.Build`, `YasmBuildTask` is executed. 26 | * `yasm` is invoked (with `-felf64`) for each `.asm` file. 27 | * Resulting object files (*.obj`) are generated. 28 | 29 | 2. **C# Compilation & Static Analysis (`.cs` -> IL `.dll`) - via .NET SDK + `Cosmos.Patcher.Analyzer`**: 30 | * MSBuild invokes the Roslyn C# compiler. 31 | * Kernel source files (`KernelEntry.cs`, `Internal.cs`, `Stdlib.cs`, etc.) are compiled into a standard .NET IL assembly (`Kernel.dll`). 32 | * During compilation, the **`PatcherAnalyzer`** (from `Cosmos.Patcher.Analyzer`) runs, checking code against plug rules (`DiagnosticMessages.cs`) and reporting diagnostics. 33 | * The `PatcherCodeFixProvider` offers IDE assistance. 34 | 35 | 3. **IL Patching (Mono.Cecil) - via [Cosmos.Patcher.Build](/articles/Liquip.Patcher.html)**: 36 | * MSBuild targets execute the `PatcherTask` (from `Cosmos.Patcher.Build.Tasks`). 37 | * `PatcherTask` runs the `Cosmos.Patcher` tool. 38 | * The tool uses **Mono.Cecil** to load the target IL assembly and plug assemblies. 39 | * It **modifies the IL** of target methods based on `[Plug]` attributes, replacing their bodies with the plug code. 40 | * The modified (patched) IL assembly (`Kernel_patched.dll`) is saved. 41 | 42 | 4. **NativeAOT Compilation (ILC: Patched IL -> Native `.obj`) - via [Cosmos.Ilc.Build](/articles/Liquip.ilc.Build.html)**: 43 | * MSBuild targets (likely from **`Cosmos.Ilc.Build`**) invoke the .NET ILCompiler (ILC). 44 | * ILC processes the **patched IL assembly** (`Kernel_patched.dll`). 45 | * It performs tree-shaking and compiles the reachable IL into native object files (`.obj` or `.o`) for the target architecture. 46 | 47 | 5. **Linking (`.obj` -> `.elf`) - via `Cosmos.Common.Build`**: 48 | * MSBuild targets provided by **`Cosmos.Common.Build`** invoke the LLVM linker (`ld.lld`). 49 | * It takes all native object files from ILC (C#) and YASM (`.asm`). 50 | * It links them using a **linker script** (essential for memory layout, entry point `kmain`, etc.). 51 | * It resolves external symbols (like `_native_io_*`). 52 | * The final kernel executable is produced: `Kernel.elf`. 53 | 54 | 6. **ISO Image Creation (`.iso`) - via `Cosmos.Common.Build`**: 55 | * MSBuild targets provided by **`Cosmos.Common.Build`** execute this step. 56 | * `xorriso` assembles the necessary components into a bootable ISO 9660 image: 57 | * Limine bootloader files. 58 | * The bootloader configuration file (`limine.conf`). 59 | * The compiled kernel (`Kernel.elf` from step 5). 60 | * The final output is a bootable `.iso` file (e.g., `Kernel.iso`). 61 | 62 | 7. **Deployment / Execution**: 63 | * The generated `.iso` file can be used with a virtual machine (like QEMU, VMware, VirtualBox) or written to a USB drive to boot on physical hardware. 64 | 65 | ## Build Automation 66 | 67 | These steps are automated via MSBuild targets and custom tasks defined within the project's `.csproj` file and shared build components (`Cosmos.Asm.Build`, `Cosmos.Patcher.Analyzer`, `Cosmos.Ilc.Build`, `Cosmos.Common.Build`). This allows the entire kernel and bootable ISO to be built using standard `dotnet build` or `dotnet publish` commands with appropriate configurations. The goal is to make this process as nuget packages which can be integrated without needing to import .targets files from the `.csproj`. 68 | -------------------------------------------------------------------------------- /docs/articles/build/patcher-build.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | **Cosmos.ilc.Build** is the build system for CosmosOS-projects. It is responsible for compiling the patched dll outputted by **Cosmos.Patcher** into native code which is then linked with native libraries on the target os. 4 | 5 | --- 6 | 7 | ## Workflow 8 | 9 | ### Step 0: Run Patcher 10 | The **Cosmos.ilc.Build**: 11 | - Calls **Cosmos.Patcher** to patch the project assembly. 12 | 13 | ### Step 1: Compile with ILC 14 | The **Cosmos.ilc.Build**: 15 | - Compiles the patched dll to a `.obj` file. 16 | 17 | ### Step 2: Link with native libraries 18 | The **Cosmos.ilc.Build**: 19 | - Links the generated `.obj` file with native libraries 20 | - Outputs a native binary file ready for further processing 21 | --- 22 | -------------------------------------------------------------------------------- /docs/articles/plugs.md: -------------------------------------------------------------------------------- 1 | A **plug** is a mechanism that replaces existing methods, variables, or types with custom implementations to enable platform-specific functionality ([what is a plug?](https://cosmosos.github.io/articles/Kernel/Plugs.html)). 2 | 3 | ## Cosmos gen3 plug template 4 | 5 | _Feel free to propose changes_ 6 | 7 | ```csharp 8 | using System.IO; 9 | 10 | namespace Cosmos.Plugs; 11 | 12 | [Plug(typeof(System.IO.FileStream))] 13 | public class FileStream 14 | { 15 | /* Plug static fields from System.IO.FileStream */ 16 | [PlugMember] 17 | public static ulong StaticField 18 | { 19 | get; set; 20 | } 21 | 22 | /* Plug instance fields from System.IO.FileStream */ 23 | [PlugMember] 24 | public static ulong classInstanceField 25 | { 26 | get; set; 27 | } 28 | 29 | /* Add private static fields to plugged class */ 30 | [Expose] 31 | private static ulong _privateStaticField; 32 | 33 | /* Add private fields to plugged class */ 34 | [Expose] 35 | private static ulong _privateInstanceField; 36 | 37 | /* Add private static methods to plugged class */ 38 | [Expose] 39 | private static void PrivateStaticMethod() 40 | { 41 | } 42 | 43 | /* Add private objects methods to plugged class */ 44 | [Expose] 45 | private static void PrivateInstanceMethod(FileStream aThis) 46 | { 47 | aThis.WriteByte('a'); 48 | } 49 | 50 | /* Plug non static method with aThis to access fields */ 51 | [PlugMember] 52 | public static long Seek(FileStream aThis, long offset, SeekOrigin origin) 53 | { 54 | PrivateMethod(); 55 | _privateInstanceField = 0; 56 | aThis.WriteByte('a'); 57 | classStaticField = 0; 58 | aThis.classInstanceField = 0; //or classInstanceField = 0; 59 | // ... 60 | } 61 | 62 | /* Plug static method */ 63 | [PlugMember] 64 | public static long StaticMethod() 65 | { 66 | 67 | } 68 | 69 | /* Access private fields from plugged class */ 70 | [PlugMember] 71 | public static long AccessFieldsMethod(FileStream aThis, [FieldAccess(Name = "InstanceField2")] ulong InstanceField2) 72 | { 73 | InstanceField2 = 0; 74 | } 75 | 76 | /* Assembly plug for System.IO.FileStream.WriteByte */ 77 | [PlugMember, RuntimeImport("asm_writebyte")] 78 | public static void WriteByte(FileStream aThis, byte b); 79 | 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /docs/articles/testing.md: -------------------------------------------------------------------------------- 1 | ## Available Unit Tests 2 | - **Liquip.Patcher.Analyzer.Test**: 3 | Validates that code does not contain plug architecture errors. 4 | - Test_TypeNotFoundDiagnostic 5 | - Test_PlugNotStaticDiagnostic 6 | - Test_MethodNeedsPlugDiagnostic 7 | - Test_AnalyzeAccessedMember 8 | - Test_MethodNotImplemented 9 | - **Liquip.Scanner.Tests**: 10 | Validates that all required plugs are detected correctly. 11 | - LoadPlugMethods_ShouldReturnPublicStaticMethods 12 | - LoadPlugMethods_ShouldReturnEmpty_WhenNoMethodsExist 13 | - LoadPlugMethods_ShouldContainAddMethod_WhenPlugged 14 | - LoadPlugs_ShouldFindPluggedClasses 15 | - LoadPlugs_ShouldIgnoreClassesWithoutPlugAttribute 16 | - LoadPlugs_ShouldHandleOptionalPlugs 17 | - **Liquip.Patcher.Tests**: 18 | Ensures that plugs are applied successfully to target methods and types. 19 | - PatchObjectWithAThis_ShouldPlugInstanceCorrectly 20 | - PatchConstructor_ShouldPlugCtorCorrectly 21 | - PatchType_ShouldReplaceAllMethodsCorrectly 22 | - PatchType_ShouldPlugAssembly 23 | - AddMethod_BehaviorBeforeAndAfterPlug 24 | - **Liquip.ilc.Tests**: 25 | Tests patched assemblies for runtime behavior on NativeAOT. 26 | 27 | ## Use 28 | Two options are available to run the test suite. 29 | 30 | - The first one is to run the tests inside Visual Studio. 31 | 32 | - And the second is to run Liquip.Patcher.Tests using Docker. You have to go at the root of the repository and run: 33 | 34 | `docker build -t patcher-tests .` 35 | 36 | `docker run --rm patcher-tests` 37 | -------------------------------------------------------------------------------- /docs/articles/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Plugs 2 | href: plugs.md 3 | - name: Cosmos.Asm.Build 4 | href: asm-build.md 5 | - name: Cosmos.ilc.Build 6 | href: ilc-build.md 7 | - name: Cosmos.Patcher.Build 8 | href: patcher-build.md 9 | - name: Kernel Compilation Steps 10 | href: kernel-compilation-steps.md 11 | - name: Testing 12 | href: testing.md 13 | -------------------------------------------------------------------------------- /docs/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json", 3 | "metadata": [ 4 | { 5 | "src": [ 6 | { 7 | "src": "./../Liquip/src", 8 | "files": [ 9 | "**/*.csproj" 10 | ], 11 | "exclude": [ 12 | "**/bin/**", 13 | "**/obj/**" 14 | ] 15 | } 16 | ], 17 | "dest": "api" 18 | } 19 | ], 20 | "build": { 21 | "content": [ 22 | { 23 | "files": [ 24 | "**/*.{md,yml}" 25 | ], 26 | "exclude": [ 27 | "_site/**" 28 | ] 29 | } 30 | ], 31 | "resource": [ 32 | { 33 | "files": [ 34 | "images/**" 35 | ] 36 | } 37 | ], 38 | "output": "_site", 39 | "template": [ 40 | "default", 41 | "modern" 42 | ], 43 | "globalMetadata": { 44 | "_appName": "Cosmos Gen 3", 45 | "_appTitle": "Cosmos Gen 3", 46 | "_enableSearch": true, 47 | "pdf": false 48 | }, 49 | "postProcessors": [], 50 | "markdownEngineName": "markdig", 51 | "noLangKeyword": false, 52 | "keepFileLink": false, 53 | "cleanupCacheHistory": false, 54 | "disableGitFeatures": false 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | _layout: landing 3 | --- 4 | 5 | Welcome to the nativeaot-patcher wiki! 6 | 7 | ## Documentation 8 | - [Kernel Compilation Steps](/articles/build/kernel-compilation-steps.md) 9 | - [Plugs](/articles/plugs.md) 10 | - [Testing](/articles/testing.md) 11 | - [Cosmos.Asm.Build](/articles/build/asm-build.md) 12 | - [Cosmos.Patcher.Build](/articles/build/patcher-build.md) 13 | - [Cosmos.ilc.Build](/articles/build/ilc-build.md) 14 | 15 | ## Resources 16 | - [Cosmos Gen3: The NativeAOT Era and the End of IL2CPU?](https://valentin.bzh/posts/3) 17 | - [NativeAOT Developer Workflow](https://github.com/dotnet/runtime/blob/main/docs/workflow/building/coreclr/nativeaot.md) 18 | - [NativeAOT Limitations](https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/limitations.md) 19 | 20 | 21 | xref link [xrefmap.yml](/xrefmap.yml) 22 | -------------------------------------------------------------------------------- /docs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Docs 2 | href: articles/ 3 | - name: API 4 | href: api/ -------------------------------------------------------------------------------- /examples/KernelExample/.gitignore: -------------------------------------------------------------------------------- 1 | /limine 2 | /output 3 | -------------------------------------------------------------------------------- /examples/KernelExample/Kernel.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | true 7 | 8 | RunPatcher;$(IlcDependsOn) 9 | CompileWithIlc;BuildYasm;$(LinkTargetDependsOn) 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | runtime; native; contentfiles; analyzers; buildtransitive 23 | all 24 | 25 | 26 | runtime; native; contentfiles; analyzers; buildtransitive 27 | all 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/KernelExample/Makefile: -------------------------------------------------------------------------------- 1 | DOTNET = dotnet 2 | ARGS = publish -c Debug -r linux-x64 3 | CSPROJ = Kernel.csproj 4 | 5 | QEMU = qemu-system-x86_64 6 | QEMU_OUTPUT = ./output/Kernel.iso 7 | QEMU_ARGS = -cdrom $(QEMU_OUTPUT) 8 | 9 | all: build run 10 | 11 | # Build the kernel 12 | build: 13 | $(DOTNET) $(ARGS) $(CSPROJ) 14 | 15 | # Run the kernel (requires build) 16 | run: build 17 | $(QEMU) $(QEMU_ARGS) 18 | 19 | # Clean the build 20 | clean: 21 | $(DOTNET) clean $(CSPROJ) 22 | rm -rf ./bin/ 23 | rm -rf ./obj/ 24 | rm -rf ./output/ 25 | 26 | .PHONY: build run clean -------------------------------------------------------------------------------- /examples/KernelExample/src/Asm/io.asm: -------------------------------------------------------------------------------- 1 | global _native_io_write_byte 2 | global _native_io_write_word 3 | global _native_io_write_dword 4 | global _native_io_write_qword 5 | 6 | global _native_io_read_byte 7 | global _native_io_read_word 8 | global _native_io_read_dword 9 | global _native_io_read_qword 10 | 11 | section .text 12 | 13 | ; void out_byte(ushort port, byte value) 14 | _native_io_write_byte: 15 | mov dx, di 16 | mov al, sil 17 | out dx, al 18 | ret 19 | 20 | _native_io_write_word: 21 | mov dx, di 22 | mov ax, si 23 | out dx, ax 24 | ret 25 | 26 | ; void out_dword(ushort port, uint value) 27 | _native_io_write_dword: 28 | mov dx, di 29 | mov eax, esi 30 | out dx, eax 31 | ret 32 | 33 | ; byte in_byte(ushort port) 34 | _native_io_read_byte: 35 | mov dx, di 36 | in al, dx 37 | ret 38 | 39 | ; ushort in_word(ushort port) 40 | _native_io_read_word: 41 | mov dx, di 42 | in ax, dx 43 | ret 44 | 45 | ; uint in_dword(ushort port) 46 | _native_io_read_dword: 47 | mov dx, di 48 | in eax, dx 49 | ret 50 | -------------------------------------------------------------------------------- /examples/KernelExample/src/Base64.cs: -------------------------------------------------------------------------------- 1 | namespace EarlyBird.Conversion 2 | { 3 | public static unsafe class Base64 4 | { 5 | public static byte* Decode(char* Encoded, uint Length) 6 | { 7 | // Determine padding based on '=' characters 8 | int Padding = 0; 9 | if (Encoded[Length - 1] == '=') 10 | { 11 | Padding++; 12 | if (Encoded[Length - 2] == '=') 13 | { 14 | Padding++; 15 | } 16 | } 17 | 18 | // Calculate the length of the decoded data 19 | int DecodedLength = (int)((Length * 3) / 4 - Padding); 20 | byte* Decoded = (byte*)MemoryOp.Alloc((uint)DecodedLength); 21 | 22 | // Base64 decoding table for A-Z, a-z, 0-9, +, / 23 | byte* Base64Table = (byte*)MemoryOp.Alloc(128); 24 | for (int i = 0; i < 26; i++) Base64Table['A' + i] = (byte)(i); // A-Z -> 0-25 25 | for (int i = 0; i < 26; i++) Base64Table['a' + i] = (byte)(26 + i); // a-z -> 26-51 26 | for (int i = 0; i < 10; i++) Base64Table['0' + i] = (byte)(52 + i); // 0-9 -> 52-61 27 | Base64Table['+'] = 62; // '+' -> 62 28 | Base64Table['/'] = 63; // '/' -> 63 29 | 30 | int DecodedIndex = 0; 31 | for (int i = 0; i < Length; i += 4) 32 | { 33 | byte b0 = Base64Table[Encoded[i]]; 34 | byte b1 = Base64Table[Encoded[i + 1]]; 35 | byte b2 = Base64Table[Encoded[i + 2]]; 36 | byte b3 = Base64Table[Encoded[i + 3]]; 37 | 38 | Decoded[DecodedIndex++] = (byte)((b0 << 2) | (b1 >> 4)); // First byte 39 | if (i + 2 < Length - Padding) // Make sure we're not out of bounds 40 | { 41 | Decoded[DecodedIndex++] = (byte)((b1 << 4) | (b2 >> 2)); // Second byte 42 | } 43 | if (i + 3 < Length - Padding) // Make sure we're not out of bounds 44 | { 45 | Decoded[DecodedIndex++] = (byte)((b2 << 6) | b3); // Third byte 46 | } 47 | } 48 | 49 | return Decoded; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/KernelExample/src/Internal.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace EarlyBird.Internal 5 | { 6 | 7 | public static class Native 8 | { 9 | public static class IO 10 | { 11 | [MethodImpl(MethodImplOptions.InternalCall)] 12 | [RuntimeImport("*", "_native_io_write_byte")] 13 | public static extern void Write8(ushort Port, byte Value); 14 | 15 | [MethodImpl(MethodImplOptions.InternalCall)] 16 | [RuntimeImport("*", "_native_io_write_word")] 17 | public static extern void Write16(ushort Port, ushort Value); 18 | 19 | [MethodImpl(MethodImplOptions.InternalCall)] 20 | [RuntimeImport("*", "_native_io_write_dword")] 21 | public static extern void Write32(ushort Port, uint Value); 22 | 23 | [MethodImpl(MethodImplOptions.InternalCall)] 24 | [RuntimeImport("*", "_native_io_read_byte")] 25 | public static extern byte Read8(ushort Port); 26 | 27 | [MethodImpl(MethodImplOptions.InternalCall)] 28 | [RuntimeImport("*", "_native_io_read_word")] 29 | public static extern ushort Read16(ushort Port); 30 | 31 | [MethodImpl(MethodImplOptions.InternalCall)] 32 | [RuntimeImport("*", "_native_io_read_dword")] 33 | public static extern uint Read32(ushort Port); 34 | } 35 | } 36 | 37 | public static class Serial 38 | { 39 | private static readonly ushort COM1 = 0x3F8; 40 | 41 | private static void WaitForTransmitBufferEmpty() 42 | { 43 | while ((Native.IO.Read8((ushort)(COM1 + 5)) & 0x20) == 0) ; 44 | } 45 | 46 | public static void ComWrite(byte value) 47 | { 48 | // Wait for the transmit buffer to be empty 49 | WaitForTransmitBufferEmpty(); 50 | // Write the byte to the COM port 51 | Native.IO.Write8(COM1, value); 52 | } 53 | 54 | 55 | 56 | public static void ComInit() 57 | { 58 | Native.IO.Write8((ushort)(COM1 + 1), 0x00); 59 | Native.IO.Write8((ushort)(COM1 + 3), 0x80); 60 | Native.IO.Write8(COM1, 0x01); 61 | Native.IO.Write8((ushort)(COM1 + 1), 0x00); 62 | Native.IO.Write8((ushort)(COM1 + 3), 0x03); 63 | Native.IO.Write8((ushort)(COM1 + 2), 0xC7); 64 | } 65 | 66 | public static unsafe void WriteString(string str) 67 | { 68 | fixed (char* ptr = str) 69 | { 70 | for (int i = 0; i < str.Length; i++) 71 | { 72 | ComWrite((byte)ptr[i]); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/KernelExample/src/KernelEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime; 2 | using System.Runtime.CompilerServices; 3 | using Cosmos.Boot.Limine; 4 | using EarlyBird; 5 | using EarlyBird.Internal; 6 | 7 | using static EarlyBird.Graphics; 8 | 9 | unsafe class Program 10 | { 11 | static readonly LimineFramebufferRequest Framebuffer = new(); 12 | static readonly LimineHHDMRequest HHDM = new(); 13 | 14 | [RuntimeExport("kmain")] 15 | static void Main() 16 | { 17 | MemoryOp.InitializeHeap(HHDM.Offset, 0x1000000); 18 | var fb = Framebuffer.Response->Framebuffers[0]; 19 | Canvas.Address = (uint*)fb->Address; 20 | Canvas.Pitch = (uint)fb->Pitch; 21 | Canvas.Width = (uint)fb->Width; 22 | Canvas.Height = (uint)fb->Height; 23 | 24 | Canvas.ClearScreen(Color.Black); 25 | 26 | Canvas.DrawString("CosmosOS booted.", 0, 0, Color.White); 27 | 28 | Serial.ComInit(); 29 | 30 | Canvas.DrawString("UART started.", 0, 28, Color.White); 31 | 32 | Serial.WriteString("Hello from UART\n"); 33 | 34 | while (true) ; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/KernelExample/src/Limine/LimineFramebuffer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Cosmos.Boot.Limine; 4 | 5 | // Adapted from Azerou. 6 | [StructLayout(LayoutKind.Sequential)] 7 | public readonly struct LimineHHDMRequest() 8 | { 9 | public readonly LimineID ID = new(0x48dcf1cb8ad2b852, 0x63984e959a98244b); 10 | public readonly ulong Revision = 0; 11 | public readonly ulong Offset; 12 | } 13 | 14 | [StructLayout(LayoutKind.Sequential)] 15 | public readonly unsafe struct LimineFramebufferRequest() 16 | { 17 | public readonly LimineID ID = new(0x9d5827dcd881dd75, 0xa3148604f6fab11b); 18 | public readonly ulong Revision = 0; 19 | public readonly LimineFramebufferResponse* Response; 20 | } 21 | 22 | [StructLayout(LayoutKind.Sequential)] 23 | public readonly unsafe struct LimineFramebufferResponse 24 | { 25 | public readonly ulong Revision; 26 | public readonly ulong FramebufferCount; 27 | public readonly LimineFramebuffer** Framebuffers; 28 | } 29 | 30 | public enum LimineFbMemoryModel : byte 31 | { 32 | Rgb = 1 33 | } 34 | 35 | [StructLayout(LayoutKind.Sequential)] 36 | public readonly unsafe struct LimineFramebuffer 37 | { 38 | public readonly void* Address; 39 | public readonly ulong Width; 40 | public readonly ulong Height; 41 | public readonly ulong Pitch; 42 | public readonly ulong BitsPerPixel; 43 | public readonly LimineFbMemoryModel MemoryModel; 44 | public readonly byte RedMaskSize; 45 | public readonly byte RedMaskShift; 46 | public readonly byte GreenMaskSize; 47 | public readonly byte GreenMaskShift; 48 | public readonly byte BlueMaskSize; 49 | public readonly byte BlueMaskShift; 50 | private readonly byte p1, p2, p3, p4, p5, p6, p7; 51 | public readonly ulong EdidSize; 52 | public readonly void* Edid; 53 | } 54 | -------------------------------------------------------------------------------- /examples/KernelExample/src/Limine/LimineID.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | // Adapted from Azerou. 4 | 5 | namespace Cosmos.Boot.Limine; 6 | 7 | [StructLayout(LayoutKind.Sequential)] 8 | public readonly struct LimineID 9 | { 10 | public readonly ulong One, Two, Three, Four; 11 | 12 | public LimineID(ulong a3, ulong a4) 13 | { 14 | One = 0xc7b1dd30df4c8b88; // LIMINE_COMMON_MAGIC 15 | Two = 0x0a82e883a194f07b; 16 | Three = a3; 17 | Four = a4; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/KernelExample/src/Stdlib.cs: -------------------------------------------------------------------------------- 1 | #region A couple very basic things 2 | 3 | using System; 4 | using System.Runtime; 5 | 6 | namespace System 7 | { 8 | public struct Void { } 9 | 10 | // The layout of primitive types is special cased because it would be recursive. 11 | // These really don't need any fields to work. 12 | public struct Boolean { } 13 | public struct Char { } 14 | public struct SByte { } 15 | public struct Byte { } 16 | public struct Int16 { } 17 | public struct UInt16 { } 18 | public struct Int32 { } 19 | public struct UInt32 { } 20 | public struct Int64 { } 21 | public struct UInt64 { } 22 | public struct IntPtr { } 23 | public struct UIntPtr { } 24 | public struct Single { } 25 | public struct Double { } 26 | 27 | public class Object 28 | { 29 | #pragma warning disable 169 30 | // The layout of object is a contract with the compiler. 31 | private IntPtr m_pMethodTable; 32 | #pragma warning restore 169 33 | } 34 | 35 | public class Exception 36 | { 37 | public Exception() { } 38 | protected Exception(string message) { } 39 | } 40 | 41 | public abstract class Type { } 42 | public abstract class ValueType { } 43 | public abstract class Enum : ValueType { } 44 | 45 | public struct Nullable where T : struct { } 46 | 47 | public sealed class String 48 | { 49 | public readonly int Length; 50 | } 51 | 52 | public abstract class Array 53 | { 54 | public readonly int Length; 55 | } 56 | public abstract class Delegate { } 57 | public abstract class MulticastDelegate : Delegate { } 58 | 59 | public struct RuntimeTypeHandle { } 60 | public struct RuntimeMethodHandle { } 61 | public struct RuntimeFieldHandle { } 62 | 63 | public class Attribute { } 64 | 65 | public sealed class AttributeUsageAttribute : Attribute 66 | { 67 | public AttributeUsageAttribute(AttributeTargets validOn) { } 68 | public bool AllowMultiple { get; set; } 69 | public bool Inherited { get; set; } 70 | } 71 | 72 | public enum AttributeTargets { } 73 | 74 | public class AppContext 75 | { 76 | public static void SetData(string s, object o) { } 77 | } 78 | 79 | namespace Runtime.CompilerServices 80 | { 81 | public class RuntimeHelpers 82 | { 83 | public static unsafe int OffsetToStringData => sizeof(IntPtr) + sizeof(int); 84 | } 85 | 86 | public static class RuntimeFeature 87 | { 88 | public const string UnmanagedSignatureCallingConvention = nameof(UnmanagedSignatureCallingConvention); 89 | } 90 | 91 | public enum MethodImplOptions 92 | { 93 | Unmanaged = 0x0004, 94 | NoInlining = 0x0008, 95 | NoOptimization = 0x0040, 96 | AggressiveInlining = 0x0100, 97 | AggressiveOptimization = 0x200, 98 | InternalCall = 0x1000, 99 | } 100 | 101 | //Implementing the MethodImpl attribute for RuntimeExport to work 102 | public sealed class MethodImplAttribute : Attribute 103 | { 104 | public MethodImplAttribute(MethodImplOptions opt) { } 105 | } 106 | } 107 | } 108 | 109 | namespace System.Runtime.InteropServices 110 | { 111 | public class UnmanagedType { } 112 | 113 | public class MarshalDirectiveException : Exception 114 | { 115 | public MarshalDirectiveException() 116 | { } 117 | public MarshalDirectiveException(string message) : base(message) { } 118 | } 119 | 120 | sealed class StructLayoutAttribute : Attribute 121 | { 122 | public StructLayoutAttribute(LayoutKind layoutKind) 123 | { 124 | } 125 | } 126 | 127 | public sealed class DllImportAttribute : Attribute 128 | { 129 | public string EntryPoint; 130 | public CharSet CharSet; 131 | public bool SetLastError; 132 | public bool ExactSpelling; 133 | public CallingConvention CallingConvention; 134 | public bool BestFitMapping; 135 | public bool PreserveSig; 136 | public bool ThrowOnUnmappableChar; 137 | 138 | public string Value { get; } 139 | 140 | public DllImportAttribute(string dllName) 141 | { 142 | Value = dllName; 143 | } 144 | } 145 | 146 | internal enum LayoutKind 147 | { 148 | Sequential = 0, // 0x00000008, 149 | Explicit = 2, // 0x00000010, 150 | Auto = 3, // 0x00000000, 151 | } 152 | 153 | public enum CharSet 154 | { 155 | None = 1, // User didn't specify how to marshal strings. 156 | Ansi = 2, // Strings should be marshalled as ANSI 1 byte chars. 157 | Unicode = 3, // Strings should be marshalled as Unicode 2 byte chars. 158 | Auto = 4, // Marshal Strings in the right way for the target system. 159 | } 160 | 161 | public enum CallingConvention 162 | { 163 | Winapi = 1, 164 | Cdecl = 2, 165 | StdCall = 3, 166 | ThisCall = 4, 167 | FastCall = 5, 168 | } 169 | } 170 | #endregion 171 | 172 | #region Things needed by ILC 173 | namespace System 174 | { 175 | namespace Runtime 176 | { 177 | internal enum InternalGCCollectionMode 178 | { 179 | Default, 180 | Forced, 181 | Optimized 182 | } 183 | 184 | internal sealed class RuntimeExportAttribute : Attribute 185 | { 186 | public RuntimeExportAttribute(string entry) { } 187 | } 188 | 189 | internal sealed class RuntimeImportAttribute : Attribute 190 | { 191 | public string DllName { get; } 192 | public string EntryPoint { get; } 193 | 194 | public RuntimeImportAttribute(string entry) 195 | { 196 | EntryPoint = entry; 197 | } 198 | 199 | public RuntimeImportAttribute(string dllName, string entry) 200 | { 201 | EntryPoint = entry; 202 | DllName = dllName; 203 | } 204 | } 205 | } 206 | 207 | class Array : Array { } 208 | } 209 | 210 | namespace Internal.Runtime.CompilerHelpers 211 | { 212 | // A class that the compiler looks for that has helpers to initialize the 213 | // process. The compiler can gracefully handle the helpers not being present, 214 | // but the class itself being absent is unhandled. Let's add an empty class. 215 | class StartupCodeHelpers 216 | { 217 | // A couple symbols the generated code will need we park them in this class 218 | // for no particular reason. These aid in transitioning to/from managed code. 219 | // Since we don't have a GC, the transition is a no-op. 220 | [RuntimeExport("RhpReversePInvoke")] 221 | static void RhpReversePInvoke(IntPtr frame) { } 222 | [RuntimeExport("RhpReversePInvokeReturn")] 223 | static void RhpReversePInvokeReturn(IntPtr frame) { } 224 | [RuntimeExport("RhpPInvoke")] 225 | static void RhpPInvoke(IntPtr frame) { } 226 | [RuntimeExport("RhpPInvokeReturn")] 227 | static void RhpPInvokeReturn(IntPtr frame) { } 228 | 229 | [RuntimeExport("RhpFallbackFailFast")] 230 | static void RhpFallbackFailFast() { while (true) ; } 231 | 232 | [RuntimeExport("InitializeModules")] 233 | static unsafe void InitializeModules(IntPtr osModule, IntPtr* pModuleHeaders, int count, IntPtr* pClasslibFunctions, int nClasslibFunctions) { } 234 | 235 | } 236 | 237 | public static class ThrowHelpers 238 | { 239 | public static void ThrowNotImplementedException() 240 | { 241 | while (true) ; 242 | } 243 | 244 | public static void ThrowNullReferenceException() 245 | { 246 | while (true) ; 247 | } 248 | 249 | public static void ThrowIndexOutOfRangeException() 250 | { 251 | while (true) ; 252 | } 253 | 254 | public static void ThrowInvalidProgramException() 255 | { 256 | while (true) ; 257 | } 258 | 259 | public static void ThrowTypeLoadException() 260 | { 261 | while (true) ; 262 | } 263 | 264 | public static void ThrowTypeLoadExceptionWithArgument() 265 | { 266 | while (true) ; 267 | } 268 | 269 | public static void ThrowInvalidProgramExceptionWithArgument() 270 | { 271 | while (true) ; 272 | } 273 | 274 | public static void ThrowOverflowException() 275 | { 276 | while (true) ; 277 | } 278 | } 279 | } 280 | 281 | namespace Internal.Runtime 282 | { 283 | internal abstract class ThreadStatics 284 | { 285 | public static unsafe object GetThreadStaticBaseForType(TypeManagerSlot* pModuleData, int typeTlsIndex) 286 | { 287 | return null; 288 | } 289 | } 290 | 291 | internal struct TypeManagerSlot { } 292 | } 293 | #endregion 294 | -------------------------------------------------------------------------------- /examples/KernelExample/src/Stellib.cs: -------------------------------------------------------------------------------- 1 | using EarlyBird.PSF; 2 | 3 | namespace EarlyBird 4 | { 5 | public class Color 6 | { 7 | public static uint Red = 0xFF0000; 8 | public static uint Green = 0x00FF00; 9 | public static uint Blue = 0x0000FF; 10 | public static uint White = 0xFFFFFF; 11 | public static uint Black = 0x000000; 12 | public static uint Yellow = 0xFFFF00; 13 | public static uint Cyan = 0x00FFFF; 14 | public static uint Magenta = 0xFF00FF; 15 | public static uint Orange = 0xFFA500; 16 | public static uint Purple = 0x800080; 17 | public static uint Pink = 0xFFC0CB; 18 | public static uint Brown = 0xA52A2A; 19 | public static uint Gray = 0x808080; 20 | public static uint LightGray = 0xD3D3D3; 21 | public static uint DarkGray = 0xA9A9A9; 22 | public static uint LightRed = 0xFF7F7F; 23 | public static uint LightGreen = 0x7FFF7F; 24 | public static uint LightBlue = 0x7F7FFF; 25 | public static uint LightYellow = 0xFFFF7F; 26 | public static uint LightCyan = 0x7FFFFF; 27 | public static uint LightMagenta = 0xFF7FFF; 28 | public static uint LightOrange = 0xFFBF00; 29 | public static uint LightPurple = 0xBF00FF; 30 | public static uint LightPink = 0xFFB6C1; 31 | public static uint LightBrown = 0xD2B48C; 32 | public static uint Transparent = 0x00000000; // Transparent color 33 | } 34 | 35 | public static class Math 36 | { 37 | public static int Abs(int value) 38 | { 39 | return value < 0 ? -value : value; 40 | } 41 | 42 | public static int Min(int a, int b) 43 | { 44 | return a < b ? a : b; 45 | } 46 | 47 | public static int Max(int a, int b) 48 | { 49 | return a > b ? a : b; 50 | } 51 | } 52 | public static unsafe class MemoryOp 53 | { 54 | 55 | private static ulong HeapBase; 56 | private static ulong HeapEnd; 57 | private static ulong FreeListHead; 58 | 59 | public static void InitializeHeap(ulong heapBase, ulong heapSize) 60 | { 61 | HeapBase = heapBase; 62 | HeapEnd = heapBase + heapSize; 63 | FreeListHead = heapBase; 64 | 65 | // Initialize the free list with a single large block 66 | *(ulong*)FreeListHead = heapSize; // Block size 67 | *((ulong*)FreeListHead + 1) = 0; // Next block pointer 68 | } 69 | 70 | public static void* Alloc(uint size) 71 | { 72 | size = (uint)((size + 7) & ~7); // Align size to 8 bytes 73 | ulong prev = 0; 74 | ulong current = FreeListHead; 75 | 76 | while (current != 0) 77 | { 78 | ulong blockSize = *(ulong*)current; 79 | ulong next = *((ulong*)current + 1); 80 | 81 | if (blockSize >= size + 16) // Enough space for allocation and metadata 82 | { 83 | ulong remaining = blockSize - size - 16; 84 | if (remaining >= 16) // Split block 85 | { 86 | *(ulong*)(current + 16 + size) = remaining; 87 | *((ulong*)(current + 16 + size) + 1) = next; 88 | *((ulong*)current + 1) = current + 16 + size; 89 | } 90 | else // Use entire block 91 | { 92 | size = (uint)(blockSize) - 16; 93 | *((ulong*)current + 1) = next; 94 | } 95 | 96 | if (prev == 0) 97 | { 98 | FreeListHead = *((ulong*)current + 1); 99 | } 100 | else 101 | { 102 | *((ulong*)prev + 1) = *((ulong*)current + 1); 103 | } 104 | 105 | *(ulong*)current = size; // Store allocated size 106 | return (void*)(current + 16); 107 | } 108 | 109 | prev = current; 110 | current = next; 111 | } 112 | 113 | return null; // Out of memory 114 | } 115 | 116 | public static void Free(void* ptr) 117 | { 118 | ulong block = (ulong)ptr - 16; 119 | ulong blockSize = *(ulong*)block; 120 | 121 | *(ulong*)block = blockSize + 16; 122 | *((ulong*)block + 1) = FreeListHead; 123 | FreeListHead = block; 124 | } 125 | 126 | public static void MemSet(byte* dest, byte value, int count) 127 | { 128 | for (int i = 0; i < count; i++) 129 | { 130 | dest[i] = value; 131 | } 132 | } 133 | public static void MemSet(uint* dest, uint value, int count) 134 | { 135 | for (int i = 0; i < count; i++) 136 | { 137 | dest[i] = value; 138 | } 139 | } 140 | 141 | public static void MemCopy(uint* dest, uint* src, int count) 142 | { 143 | for (int i = 0; i < count; i++) 144 | { 145 | dest[i] = src[i]; 146 | } 147 | } 148 | 149 | public static bool MemCmp(uint* dest, uint* src, int count) 150 | { 151 | for (int i = 0; i < count; i++) 152 | { 153 | if (dest[i] != src[i]) 154 | { 155 | return false; 156 | } 157 | } 158 | return true; 159 | } 160 | 161 | public static void MemMove(uint* dest, uint* src, int count) 162 | { 163 | if (dest < src) 164 | { 165 | for (int i = 0; i < count; i++) 166 | { 167 | dest[i] = src[i]; 168 | } 169 | } 170 | else 171 | { 172 | for (int i = count - 1; i >= 0; i--) 173 | { 174 | dest[i] = src[i]; 175 | } 176 | } 177 | } 178 | } 179 | 180 | public static class Graphics 181 | { 182 | public unsafe class Canvas 183 | { 184 | public static uint* Address; 185 | public static uint Width; 186 | public static uint Height; 187 | public static uint Pitch; 188 | 189 | public static void DrawPixel(uint color, int x, int y) 190 | { 191 | if (x >= 0 && x < Width && y >= 0 && y < Height) 192 | { 193 | Address[y * (int)(Pitch / 4) + x] = color; 194 | } 195 | } 196 | 197 | public static void DrawLine(uint color, int x1, int y1, int x2, int y2) 198 | { 199 | int dx = x2 - x1; 200 | int dy = y2 - y1; 201 | int absDx = Math.Abs(dx); 202 | int absDy = Math.Abs(dy); 203 | int sx = (dx > 0) ? 1 : -1; 204 | int sy = (dy > 0) ? 1 : -1; 205 | int err = absDx - absDy; 206 | 207 | while (true) 208 | { 209 | DrawPixel(color, x1, y1); 210 | if (x1 == x2 && y1 == y2) break; 211 | int err2 = err * 2; 212 | if (err2 > -absDy) 213 | { 214 | err -= absDy; 215 | x1 += sx; 216 | } 217 | if (err2 < absDx) 218 | { 219 | err += absDx; 220 | y1 += sy; 221 | } 222 | } 223 | } 224 | 225 | public static void DrawRectangle(uint color, int x, int y, int width, int height) 226 | { 227 | for (int i = 0; i < width; i++) 228 | { 229 | for (int j = 0; j < height; j++) 230 | { 231 | DrawPixel(color, x + i, y + j); 232 | } 233 | } 234 | } 235 | 236 | public static void DrawCircle(uint color, int x, int y, int radius) 237 | { 238 | for (int i = -radius; i <= radius; i++) 239 | { 240 | for (int j = -radius; j <= radius; j++) 241 | { 242 | if (i * i + j * j <= radius * radius) 243 | { 244 | int px = x + j; 245 | int py = y + i; 246 | if (px >= 0 && px < Width && py >= 0 && py < Height) 247 | { 248 | DrawPixel(color, px, py); 249 | } 250 | } 251 | } 252 | } 253 | } 254 | 255 | public static void ClearScreen(uint color) 256 | { 257 | MemoryOp.MemSet(Address, color, (int)((Pitch / 4) * Height)); 258 | } 259 | 260 | public static void DrawChar(char c, int x, int y, uint color) 261 | { 262 | PCScreenFont.PutChar(c, x, y, color, Color.Transparent); 263 | } 264 | 265 | public static void DrawString(string text, int x, int y, uint color) 266 | { 267 | PCScreenFont.PutString(text, x, y, color, Color.Transparent); 268 | } 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /examples/KernelExample/src/limine.conf: -------------------------------------------------------------------------------- 1 | # Timeout in seconds that Limine will use before automatically booting. 2 | timeout: 0 3 | 4 | # The entry name that will be displayed in the boot menu. 5 | /Limine Template 6 | # We use the Limine boot protocol. 7 | protocol: limine 8 | 9 | # Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located. 10 | path: boot():/boot/Kernel.elf -------------------------------------------------------------------------------- /examples/KernelExample/src/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | 3 | ENTRY(kmain) 4 | 5 | PHDRS 6 | { 7 | text PT_LOAD; 8 | rodata PT_LOAD; 9 | data PT_LOAD; 10 | } 11 | 12 | SECTIONS 13 | { 14 | . = 0xffffffff80000000; 15 | 16 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 17 | 18 | .text : { 19 | *(.text .text.*) 20 | } :text 21 | 22 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 23 | 24 | .rodata : { 25 | *(.rodata .rodata.*) 26 | } :rodata 27 | 28 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 29 | 30 | .data : { 31 | *(.data .data.*) 32 | } :data 33 | 34 | .bss : { 35 | *(.bss .bss.*) 36 | *(COMMON) 37 | } :data 38 | 39 | /DISCARD/ : { 40 | *(.eh_frame*) 41 | *(.note .note.*) 42 | } 43 | } -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.104", 4 | "rollForward": "latestFeature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Cosmos.API/Attributes/PlugAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Cosmos.API.Attributes; 2 | 3 | [AttributeUsage(AttributeTargets.Class, Inherited = false)] 4 | public sealed class PlugAttribute(string targetName, bool isOptional = false, bool replaceBase = false) : Attribute 5 | { 6 | // public TargetPlatform TargetPlatform; 7 | 8 | /// 9 | /// does not have a base type 10 | /// 11 | public PlugAttribute() : this(string.Empty) 12 | { 13 | } 14 | 15 | /// 16 | /// set base type by type 17 | /// 18 | /// 19 | /// 20 | public PlugAttribute(Type target) : this(target.FullName) 21 | { 22 | } 23 | 24 | public PlugAttribute(bool replaceBase) : this(string.Empty, replaceBase: replaceBase) 25 | { 26 | } 27 | 28 | public PlugAttribute(bool isOptional = false, bool replaceBase = false) : this(string.Empty, isOptional, 29 | replaceBase) 30 | { 31 | } 32 | 33 | /// 34 | /// the type as a string 35 | /// 36 | public string? TargetName { get; set; } = targetName; 37 | 38 | /// 39 | /// if the type cant be found skip 40 | /// 41 | public bool IsOptional { get; set; } = isOptional; 42 | 43 | public bool ReplaceBase { get; set; } = replaceBase; 44 | } 45 | -------------------------------------------------------------------------------- /src/Cosmos.API/Attributes/PlugMemberAttribute.cs: -------------------------------------------------------------------------------- 1 | // This code is licensed under MIT license (see LICENSE for details) 2 | 3 | namespace Cosmos.API.Attributes; 4 | 5 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property, Inherited = false)] 6 | public class PlugMemberAttribute(string targetName) : Attribute 7 | { 8 | public string TargetName { get; set; } = targetName; 9 | 10 | public PlugMemberAttribute() : this(string.Empty) { } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/Cosmos.API/Cosmos.API.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | true 6 | true 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Cosmos.API/Enum/TargetPlatform.cs: -------------------------------------------------------------------------------- 1 | namespace Cosmos.API.Enum; 2 | 3 | public enum TargetPlatform 4 | { 5 | /// 6 | /// amd64 7 | /// 8 | // ReSharper disable once InconsistentNaming 9 | x86_64, 10 | 11 | /// 12 | /// arm 13 | /// 14 | Arm64, 15 | 16 | /// 17 | /// any 18 | /// 19 | Any 20 | } 21 | -------------------------------------------------------------------------------- /src/Cosmos.API/LabelMaker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Cosmos.API; 7 | 8 | public static class LabelMaker 9 | { 10 | /// 11 | /// Cache for label names. 12 | /// 13 | private static readonly Dictionary LabelNamesCache = []; 14 | 15 | private static readonly Dictionary AssemblyIds = []; 16 | 17 | // All label naming code should be changed to use this class. 18 | 19 | // Label bases can be up to 200 chars. If larger they will be shortened with an included hash. 20 | // This leaves up to 56 chars for suffix information. 21 | 22 | // Suffixes are a series of tags and have their own prefixes to preserve backwards compat. 23 | // .GUID_xxxxxx 24 | // .IL_0000 25 | // .ASM_00 - future, currently is IL_0000 or IL_0000.00 26 | // Would be nice to combine IL and ASM into IL_0000_00, but the way we work with the assembler currently 27 | // we cant because the ASM labels are issued as local labels. 28 | // 29 | // - Methods use a variety of alphanumeric suffixes for support code. 30 | // - .00 - asm markers at beginning of method 31 | // - .0000.00 IL.ASM marker 32 | 33 | public static int LabelCount { get; private set; } 34 | 35 | // Max length of labels at 256. We use lower here so that we still have room for suffixes for IL positions, etc. 36 | private const int MaxLengthWithoutSuffix = 200; 37 | 38 | public static string Get(MethodBase aMethod) 39 | { 40 | if (LabelNamesCache.TryGetValue(aMethod, out string? result)) 41 | { 42 | return result; 43 | } 44 | 45 | result = Final(GetFullName(aMethod)); 46 | LabelNamesCache.Add(aMethod, result); 47 | return result; 48 | } 49 | 50 | private const string IllegalIdentifierChars = "&.,+$<>{}-`\'/\\ ()[]*!="; 51 | 52 | // no array bracket, they need to replace, for unique names for used types in methods 53 | private static readonly Regex IllegalCharsReplace = new(@"[&.,+$<>{}\-\`\\'/\\ \(\)\*!=]", RegexOptions.Compiled); 54 | 55 | private static string FilterStringForIncorrectChars(string aName) 56 | { 57 | string? xTempResult = aName; 58 | foreach (char c in IllegalIdentifierChars) 59 | { 60 | xTempResult = xTempResult.Replace(c, '_'); 61 | } 62 | 63 | return xTempResult; 64 | } 65 | 66 | private static string Final(string xName) 67 | { 68 | xName = xName.Replace("[]", "array"); 69 | xName = xName.Replace("<>", "compilergenerated"); 70 | xName = xName.Replace("[,]", "array"); 71 | xName = xName.Replace("*", "pointer"); 72 | xName = xName.Replace("|", "sLine"); 73 | 74 | xName = IllegalCharsReplace.Replace(xName, string.Empty); 75 | 76 | if (xName.Length > MaxLengthWithoutSuffix) 77 | { 78 | using (MD5? xHash = MD5.Create()) 79 | { 80 | byte[]? xValue = xHash.ComputeHash(Encoding.GetEncoding(0).GetBytes(xName)); 81 | StringBuilder? xSb = new(xName); 82 | // Keep length max same as before. 83 | xSb.Length = MaxLengthWithoutSuffix - xValue.Length * 2; 84 | foreach (byte xByte in xValue) 85 | { 86 | xSb.Append(xByte.ToString("X2")); 87 | } 88 | 89 | xName = xSb.ToString(); 90 | } 91 | } 92 | 93 | LabelCount++; 94 | return xName; 95 | } 96 | 97 | /// 98 | /// Get internal name for the type 99 | /// 100 | /// 101 | /// If true, the assembly id is included 102 | /// 103 | public static string GetFullName(Type? aType, bool aAssemblyIncluded = true) 104 | { 105 | if (aType is null) 106 | { 107 | throw new ArgumentException("type is null", nameof(aType)); 108 | } 109 | 110 | if (aType.IsGenericParameter) 111 | { 112 | return aType.FullName; 113 | } 114 | 115 | StringBuilder? stringBuilder = new(256); 116 | 117 | if (aAssemblyIncluded) 118 | { 119 | // Start the string with the id of the assembly 120 | Assembly? assembly = aType.Assembly; 121 | if (!AssemblyIds.ContainsKey(assembly)) 122 | { 123 | AssemblyIds.Add(assembly, AssemblyIds.Count); 124 | } 125 | 126 | stringBuilder.Append("A" + AssemblyIds[assembly]); 127 | } 128 | 129 | if (aType.IsArray) 130 | { 131 | stringBuilder.Append(GetFullName(aType.GetElementType(), aAssemblyIncluded)); 132 | stringBuilder.Append("["); 133 | int xRank = aType.GetArrayRank(); 134 | while (xRank > 1) 135 | { 136 | stringBuilder.Append(","); 137 | xRank--; 138 | } 139 | 140 | stringBuilder.Append("]"); 141 | return stringBuilder.ToString(); 142 | } 143 | 144 | if (aType is { IsByRef: true, HasElementType: true }) 145 | { 146 | return "&" + GetFullName(aType.GetElementType(), aAssemblyIncluded); 147 | } 148 | 149 | if (aType is { IsGenericType: true, IsGenericTypeDefinition: false }) 150 | { 151 | stringBuilder.Append(GetFullName(aType.GetGenericTypeDefinition(), aAssemblyIncluded)); 152 | 153 | stringBuilder.Append("<"); 154 | Type[]? xArgs = aType.GetGenericArguments(); 155 | for (int i = 0; i < xArgs.Length - 1; i++) 156 | { 157 | stringBuilder.Append(GetFullName(xArgs[i], aAssemblyIncluded)); 158 | stringBuilder.Append(", "); 159 | } 160 | 161 | stringBuilder.Append(GetFullName(xArgs.Last(), aAssemblyIncluded)); 162 | stringBuilder.Append(">"); 163 | } 164 | else 165 | { 166 | stringBuilder.Append(aType.FullName); 167 | } 168 | 169 | return stringBuilder.ToString(); 170 | } 171 | 172 | /// 173 | /// Get the full name for the method 174 | /// 175 | /// 176 | /// If true, id of assembly is included 177 | /// 178 | /// 179 | public static string GetFullName(MethodBase aMethod, bool aAssemblyIncluded = true) 180 | { 181 | if (aMethod == null) 182 | { 183 | throw new ArgumentNullException(nameof(aMethod)); 184 | } 185 | 186 | StringBuilder? xBuilder = new(256); 187 | string[]? xParts = aMethod.ToString().Split(' '); 188 | MethodInfo? xMethodInfo = aMethod as MethodInfo; 189 | if (xMethodInfo != null) 190 | { 191 | xBuilder.Append(GetFullName(xMethodInfo.ReturnType, aAssemblyIncluded)); 192 | } 193 | else 194 | { 195 | ConstructorInfo? xCtor = aMethod as ConstructorInfo; 196 | if (xCtor != null) 197 | { 198 | xBuilder.Append(typeof(void).FullName); 199 | } 200 | else 201 | { 202 | xBuilder.Append(xParts[0]); 203 | } 204 | } 205 | 206 | xBuilder.Append(" "); 207 | if (aMethod.DeclaringType != null) 208 | { 209 | xBuilder.Append(GetFullName(aMethod.DeclaringType, aAssemblyIncluded)); 210 | } 211 | else 212 | { 213 | xBuilder.Append("dynamic_method"); 214 | } 215 | 216 | xBuilder.Append("."); 217 | if (aMethod.IsGenericMethod && !aMethod.IsGenericMethodDefinition) 218 | { 219 | xBuilder.Append(xMethodInfo.GetGenericMethodDefinition().Name); 220 | 221 | Type[]? xGenArgs = aMethod.GetGenericArguments(); 222 | if (xGenArgs.Length > 0) 223 | { 224 | xBuilder.Append("<"); 225 | for (int i = 0; i < xGenArgs.Length - 1; i++) 226 | { 227 | xBuilder.Append(GetFullName(xGenArgs[i], aAssemblyIncluded)); 228 | xBuilder.Append(", "); 229 | } 230 | 231 | xBuilder.Append(GetFullName(xGenArgs.Last(), aAssemblyIncluded)); 232 | xBuilder.Append(">"); 233 | } 234 | } 235 | else 236 | { 237 | xBuilder.Append(aMethod.Name); 238 | } 239 | 240 | xBuilder.Append("("); 241 | ParameterInfo[]? xParams = aMethod.GetParameters(); 242 | for (int i = 0; i < xParams.Length; i++) 243 | { 244 | if (i == 0 && xParams[i].Name == "aThis") 245 | { 246 | continue; 247 | } 248 | 249 | xBuilder.Append(GetFullName(xParams[i].ParameterType, aAssemblyIncluded)); 250 | if (i < xParams.Length - 1) 251 | { 252 | xBuilder.Append(", "); 253 | } 254 | } 255 | 256 | xBuilder.Append(")"); 257 | return xBuilder.ToString(); 258 | } 259 | 260 | public static string GetFullName(FieldInfo aField) => GetFullName(aField.FieldType, false) + " " + 261 | GetFullName(aField.DeclaringType, false) + "." + aField.Name; 262 | 263 | /// 264 | /// Gets a label for the given static field 265 | /// 266 | /// 267 | /// 268 | /// 269 | /// throws if its not static 270 | public static string GetStaticFieldName(Type aType, string aField) => GetStaticFieldName(aType.GetField(aField)); 271 | 272 | /// 273 | /// Gets a label for the given static field 274 | /// 275 | /// 276 | /// 277 | /// throws if its not static 278 | public static string GetStaticFieldName(FieldInfo aField) 279 | { 280 | if (!aField.IsStatic) 281 | { 282 | throw new NotSupportedException($"{aField.Name}: is not static"); 283 | } 284 | 285 | return FilterStringForIncorrectChars( 286 | "static_field__" + GetFullName(aField.DeclaringType) + "." + aField.Name); 287 | } 288 | 289 | public static string GetRandomLabel() => $"random_label__{Guid.NewGuid()}"; 290 | } 291 | -------------------------------------------------------------------------------- /src/Cosmos.Asm.Build/Cosmos.Asm.Build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | True 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Cosmos.Asm.Build/Tasks/LdTask.cs: -------------------------------------------------------------------------------- 1 | // This code is licensed under MIT license (see LICENSE for details) 2 | 3 | using System.Text; 4 | using Microsoft.Build.Framework; 5 | using Microsoft.Build.Utilities; 6 | 7 | namespace Cosmos.Asm.Build.Tasks; 8 | 9 | public sealed class LdTask : ToolTask 10 | { 11 | [Required] public string? LdPath { get; set; } 12 | [Required] public string? ObjectPath { get; set; } 13 | [Required] public string? OutputFile { get; set; } 14 | 15 | protected override string GenerateFullPathToTool() => 16 | LdPath; 17 | 18 | protected override string GenerateCommandLineCommands() 19 | { 20 | StringBuilder sb = new(); 21 | 22 | sb.Append($" -o {OutputFile} "); 23 | 24 | IEnumerable paths = Directory.EnumerateFiles(ObjectPath, "*.obj", SearchOption.TopDirectoryOnly); 25 | 26 | sb.Append(string.Join(" ", paths)); 27 | 28 | return sb.ToString(); 29 | } 30 | 31 | public override bool Execute() 32 | { 33 | Log.LogMessage(MessageImportance.High, "Running Cosmos.Asm-ld..."); 34 | Log.LogMessage(MessageImportance.High, $"Tool Path: {LdPath}"); 35 | Log.LogMessage(MessageImportance.High, $"Object Path: {ObjectPath}"); 36 | 37 | return base.Execute(); 38 | } 39 | 40 | protected override string ToolName => "Cosmos.Asm-ld"; 41 | } 42 | -------------------------------------------------------------------------------- /src/Cosmos.Asm.Build/Tasks/YasmBuildTask.cs: -------------------------------------------------------------------------------- 1 | // This code is licensed under MIT license (see LICENSE for details) 2 | 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using Microsoft.Build.Framework; 6 | using Microsoft.Build.Utilities; 7 | 8 | namespace Cosmos.Asm.Build.Tasks; 9 | 10 | public sealed class YasmBuildTask : ToolTask 11 | { 12 | [Required] public string? YasmPath { get; set; } 13 | [Required] public string[]? SourceFiles { get; set; } 14 | [Required] public string? OutputPath { get; set; } 15 | 16 | protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.Normal; 17 | 18 | protected override string GenerateFullPathToTool() => 19 | YasmPath!; 20 | 21 | private string? FilePath { get; set; } 22 | private string? FileName { get; set; } 23 | 24 | protected override string GenerateCommandLineCommands() 25 | { 26 | Log.LogMessage(MessageImportance.Low, $"[Debug] Generating command-line args for {FilePath} -> {FileName}"); 27 | StringBuilder sb = new(); 28 | 29 | sb.Append($" -felf64 "); 30 | sb.Append($" -o {Path.Combine(OutputPath, FileName)} "); 31 | sb.Append($" {FilePath} "); 32 | 33 | return sb.ToString(); 34 | } 35 | 36 | public override bool Execute() 37 | { 38 | LogStandardErrorAsError = true; 39 | Log.LogMessage(MessageImportance.High, "Running Cosmos.Asm-Yasm..."); 40 | Log.LogMessage(MessageImportance.High, $"Tool Path: {YasmPath}"); 41 | 42 | string paths = string.Join(",", SourceFiles); 43 | Log.LogMessage(MessageImportance.High, $"Source Files: {paths}"); 44 | Log.LogMessage(MessageImportance.Low, "[Debug] Beginning file matching"); 45 | 46 | if (!Directory.Exists(OutputPath)) 47 | { 48 | Log.LogMessage(MessageImportance.Low, $"[Debug] Creating output directory: {OutputPath}"); 49 | Directory.CreateDirectory(OutputPath); 50 | } 51 | 52 | using SHA1? hasher = SHA1.Create(); 53 | 54 | foreach (string file in SourceFiles!) 55 | { 56 | FilePath = file; 57 | using FileStream stream = File.OpenRead(FilePath); 58 | byte[] fileHash = hasher.ComputeHash(stream); 59 | FileName = $"{Path.GetFileNameWithoutExtension(file)}-{BitConverter.ToString(fileHash).Replace("-", "").ToLower()}.obj"; 60 | Log.LogMessage(MessageImportance.High, $"[Debug] About to run base.Execute() for {FileName}"); 61 | 62 | if (!base.Execute()) 63 | { 64 | Log.LogError($"[Debug] YasmBuildTask failed for {FilePath}"); 65 | return false; 66 | } 67 | } 68 | 69 | Log.LogMessage(MessageImportance.High, "✅ YasmBuildTask completed successfully."); 70 | return true; 71 | } 72 | 73 | protected override string ToolName => "Cosmos.Asm-Yasm"; 74 | } 75 | -------------------------------------------------------------------------------- /src/Cosmos.Asm.Build/build/Asm.Build.Unix.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Cosmos.Asm.Build/build/Asm.Build.Windows.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Cosmos.Asm.Build/build/Cosmos.Asm.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(IntermediateOutputPath)cosmos-asm\ 6 | 7 | 8 | 9 | /usr/bin/yasm 10 | 11 | 12 | 13 | $(MSBuildThisFileDirectory)\..\lib\netstandard2.0\Cosmos.Asm.Build.dll 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Cosmos.Asm.Build/build/Cosmos.Asm.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(RuntimeIdentifier.Split('-')[1]) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | @(AsmSearchPath->'%(FullPath)/*.asm') 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | /usr/bin/yasm 49 | 50 | $(IntermediateOutputPath)/cosmos/asm/ 51 | 52 | 53 | 54 | 55 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | $(IntermediateOutputPath)/cosmos/asm/ 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/Cosmos.Common.Build/Cosmos.Common.Build.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | false 5 | True 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Cosmos.Common.Build/build/Common.Build.Unix.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | xorriso 4 | $(IsoRoot) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Cosmos.Common.Build/build/Common.Build.Windows.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | $([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)/xorriso-exe-for-windows/xorriso.exe')) 17 | cygdrive/c/$(MSBuildProjectDirectoryNoRoot)/output/iso_root 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Cosmos.Common.Build/build/Cosmos.Common.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | GetLinker;$(LinkTargetDependsOn) 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Cosmos.Common.Build/build/Cosmos.Common.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(OutputPath)/cosmos 4 | $(IntermediateOutputPath)/cosmos 5 | $(CosmosIntermediateOutputPath)/iso_root 6 | $(CosmosOutputPath)/$(AssemblyName).iso 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | $([MSBuild]::NormalizePath('$(OutputPath)/$(AssemblyName).elf')) 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Cosmos.Ilc.Build/Cosmos.Ilc.Build.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | $(AssemblyName) 5 | 1.0.0 6 | Cosmos 7 | Build tool to run ilc. 8 | false 9 | True 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Cosmos.Ilc.Build/build/Cosmos.Ilc.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @(ResolvedILCompilerPack->'%(PackageDirectory)') 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Cosmos.Ilc.Build/build/Cosmos.Ilc.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | latest 4 | Static 5 | Library 6 | true 7 | true 8 | v4.0.30319 9 | false 10 | false 11 | disable 12 | ResolveIlcPath 13 | $(DefaultIlcDependsOn);$(IlcDependsOn) 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | $(BundledNETCoreAppPackageVersion) 26 | runtime.$(RuntimeIdentifier).Microsoft.DotNet.ILCompiler 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | @(ResolvedILCompilerPack->'%(PackageDirectory)') 40 | $([MSBuild]::NormalizePath($(IlcHostPackagePath)/tools/)) 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | $([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)')) 49 | $([System.IO.Path]::GetFullPath('$(OutputPath)')) 50 | 51 | $([System.IO.Path]::GetFullPath('$(OutputPath)/cosmos/native/')) 52 | $(FullIntermediateOutputPath)/cosmos/native/ 53 | $(NativeOutputPath)$(AssemblyName) 54 | 55 | 56 | 57 | 58 | 59 | $([System.IO.Path]::GetFullPath('$(IlcIntermediateOutputPath)$(AssemblyName).ilc.rsp')) 60 | $([MSBuild]::NormalizePath('$(IlcIntermediateOutputPath)$(AssemblyName).o')) 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer.CodeFixes/Cosmos.Patcher.Analyzer.CodeFixes.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | false 5 | Cosmos.Patcher.Analyzer.CodeFixes 6 | 7 | 8 | 9 | 10 | 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | all 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer.CodeFixes/Models/ProjectInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace Cosmos.Patcher.Analyzer.CodeFixes.Models; 4 | 5 | public readonly record struct ProjectInfo(IEnumerable PlugReferences) 6 | { 7 | public static ProjectInfo From(XDocument csproj) => new( 8 | PlugReferences: csproj.Descendants("ItemGroup") 9 | .Where(x => x.Name == "PlugsReference") 10 | .Select(x => x.Attribute("Include")!.Value) 11 | ); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer.CodeFixes/PatcherCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Xml.Linq; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeActions; 6 | using Microsoft.CodeAnalysis.CodeFixes; 7 | using Microsoft.CodeAnalysis.CSharp; 8 | using Microsoft.CodeAnalysis.CSharp.Syntax; 9 | using Microsoft.CodeAnalysis.Editing; 10 | using ProjectInfo = Cosmos.Patcher.Analyzer.CodeFixes.Models.ProjectInfo; 11 | 12 | namespace Cosmos.Patcher.Analyzer.CodeFixes; 13 | 14 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PatcherCodeFixProvider))] 15 | [Shared] 16 | public class PatcherCodeFixProvider : CodeFixProvider 17 | { 18 | public sealed override ImmutableArray FixableDiagnosticIds => 19 | DiagnosticMessages.SupportedDiagnostics.Select(d => d.Id).ToImmutableArray(); 20 | 21 | public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 22 | 23 | private string? _currentPath; 24 | private ProjectInfo _currentProject; 25 | 26 | private ProjectInfo? LoadCurrentProject(string projectPath) 27 | { 28 | if (_currentPath != projectPath) 29 | { 30 | _currentPath = projectPath; 31 | _currentProject = ProjectInfo.From(XDocument.Load(_currentPath)); 32 | } 33 | 34 | return _currentProject; 35 | } 36 | 37 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 38 | { 39 | SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 40 | if (root == null) 41 | { 42 | return; 43 | } 44 | 45 | foreach (Diagnostic diagnostic in context.Diagnostics) 46 | { 47 | if (!diagnostic.Id.StartsWith(PatcherAnalyzer.DiagnosticId)) 48 | { 49 | continue; 50 | } 51 | 52 | SyntaxNode declaration = root.FindNode(diagnostic.Location.SourceSpan); 53 | switch (diagnostic.Id) 54 | { 55 | case var id when id == DiagnosticMessages.StaticConstructorTooManyParams.Id: 56 | RegisterCodeFix(context, CodeActions.RemoveExtraParametersTitle, 57 | _ => CodeActions.RemoveExtraParameters(context.Document, declaration), diagnostic); 58 | break; 59 | 60 | case var id when id == DiagnosticMessages.MethodNeedsPlug.Id: 61 | RegisterCodeFix(context, CodeActions.PlugMethodTitle, 62 | c => CodeActions.PlugMethod(context.Document, declaration, c, diagnostic, 63 | LoadCurrentProject(context.Document.Project.FilePath).Value), diagnostic); 64 | break; 65 | 66 | case var id when id == DiagnosticMessages.PlugNameDoesNotMatch.Id: 67 | RegisterCodeFix(context, CodeActions.RenamePlugTitle, 68 | c => CodeActions.RenamePlug(context.Document, declaration, c, diagnostic), diagnostic); 69 | break; 70 | 71 | // case var id when id == DiagnosticMessages.MethodNotImplemented.Id: 72 | // RegisterCodeFix(context, CodeActions.RemoveMethodTitle, c => CodeActions.RemoveMethod(context.Document, declaration, c, diagnostic), diagnostic); 73 | // break; 74 | } 75 | } 76 | } 77 | 78 | private static void RegisterCodeFix(CodeFixContext context, string title, 79 | Func> createChangedSolution, Diagnostic diagnostic) => 80 | context.RegisterCodeFix( 81 | CodeAction.Create( 82 | title, 83 | createChangedSolution, 84 | title), 85 | diagnostic); 86 | } 87 | 88 | internal static class CodeActions 89 | { 90 | public const string MakeStaticModifierTitle = "Make class static"; 91 | public const string PlugMethodTitle = "Plug method"; 92 | public const string RenamePlugTitle = "Rename plug"; 93 | public const string RemoveExtraParametersTitle = "Remove extra parameters"; 94 | // public const string RemoveMethodTitle = "Remove method"; 95 | 96 | public static async Task RemoveExtraParameters(Document document, SyntaxNode declaration) 97 | { 98 | if (declaration is not MethodDeclarationSyntax methodDeclaration) 99 | { 100 | return document.Project.Solution; 101 | } 102 | 103 | DocumentEditor editor = await DocumentEditor.CreateAsync(document).ConfigureAwait(false); 104 | editor.ReplaceNode(methodDeclaration, methodDeclaration.WithParameterList( 105 | methodDeclaration.ParameterList.WithParameters( 106 | SyntaxFactory.SeparatedList( 107 | methodDeclaration.ParameterList.Parameters.Where(p => p.Identifier.Text != "aThis"))))); 108 | return editor.GetChangedDocument().Project.Solution; 109 | } 110 | 111 | public static async Task RenamePlug(Document document, SyntaxNode declaration, CancellationToken c, 112 | Diagnostic diagnostic) 113 | { 114 | if (declaration is not ClassDeclarationSyntax classDeclaration) 115 | { 116 | return document.Project.Solution; 117 | } 118 | 119 | if (!diagnostic.Properties.TryGetValue("ExpectedName", out string? expectedName)) 120 | { 121 | return document.Project.Solution; 122 | } 123 | 124 | DocumentEditor editor = await DocumentEditor.CreateAsync(document, c).ConfigureAwait(false); 125 | 126 | editor.SetName(classDeclaration, expectedName); 127 | return editor.GetChangedDocument().Project.Solution; 128 | } 129 | 130 | public static async Task PlugMethod(Document document, SyntaxNode declaration, CancellationToken c, 131 | Diagnostic diagnostic, ProjectInfo currentProject) 132 | { 133 | SyntaxNode? root = await document.GetSyntaxRootAsync(c).ConfigureAwait(false); 134 | if (root == null || !diagnostic.Properties.TryGetValue("MethodName", out string? methodName) || 135 | !diagnostic.Properties.TryGetValue("ClassName", out string? className)) 136 | return document.Project.Solution; 137 | 138 | DocumentEditor editor = await DocumentEditor.CreateAsync(document, c).ConfigureAwait(false); 139 | SyntaxGenerator syntaxGenerator = editor.Generator; 140 | 141 | SyntaxNode plugMethod = syntaxGenerator.AddAttributes(syntaxGenerator.MethodDeclaration(methodName, 142 | [ 143 | syntaxGenerator.ParameterDeclaration("aThis", syntaxGenerator.TypeExpression(SpecialType.System_Object)) 144 | ], null, null, Accessibility.Public, DeclarationModifiers.Static), syntaxGenerator.Attribute("PlugMember")); 145 | 146 | if (declaration is ClassDeclarationSyntax classDeclaration) 147 | return await AddMethodToPlug((classDeclaration, document), plugMethod); 148 | 149 | if (diagnostic.Properties.TryGetValue("PlugClass", out string? plugClassName)) 150 | { 151 | (ClassDeclarationSyntax PlugClass, Document Document)? plugInfo = 152 | await GetPlugClass(plugClassName, document, currentProject); 153 | if (plugInfo == null) 154 | { 155 | return document.Project.Solution; 156 | } 157 | 158 | return await AddMethodToPlug(plugInfo.Value, plugMethod); 159 | } 160 | 161 | BaseNamespaceDeclarationSyntax? namespaceDeclaration = root 162 | .ChildNodes() 163 | .OfType() 164 | .FirstOrDefault(); 165 | 166 | if (namespaceDeclaration == null) 167 | return document.Project.Solution; 168 | 169 | SyntaxNode newPlug = syntaxGenerator.ClassDeclaration($"{className}Impl", null, Accessibility.Public, 170 | DeclarationModifiers.Static, null, null, [plugMethod]); 171 | 172 | editor.AddMember(namespaceDeclaration, newPlug); 173 | return editor.GetChangedDocument().Project.Solution; 174 | } 175 | 176 | private static async Task<(ClassDeclarationSyntax PlugClass, Document Document)?> GetPlugClass( 177 | string? plugClassName, Document document, ProjectInfo currentProject) 178 | { 179 | if (plugClassName == null) 180 | { 181 | return null; 182 | } 183 | 184 | SyntaxNode? root = await document.GetSyntaxRootAsync().ConfigureAwait(false); 185 | if (root == null) 186 | { 187 | return null; 188 | } 189 | 190 | ClassDeclarationSyntax? plugClass = root.DescendantNodes() 191 | .OfType() 192 | .FirstOrDefault(c => c.Identifier.Text == plugClassName); 193 | 194 | if (plugClass != null) 195 | { 196 | return (plugClass, document); 197 | } 198 | 199 | foreach (string reference in currentProject.PlugReferences) 200 | { 201 | Project? project = document.Project.Solution.Projects.FirstOrDefault(p => p.OutputFilePath == reference); 202 | if (project == null) 203 | { 204 | continue; 205 | } 206 | 207 | Compilation? compilation = await project.GetCompilationAsync().ConfigureAwait(false); 208 | INamedTypeSymbol? symbol = compilation?.GetTypeByMetadataName(plugClassName); 209 | 210 | SyntaxReference? syntaxReference = symbol?.DeclaringSyntaxReferences.FirstOrDefault(); 211 | if (syntaxReference == null) 212 | { 213 | continue; 214 | } 215 | 216 | SyntaxNode syntaxNode = await syntaxReference.GetSyntaxAsync().ConfigureAwait(false); 217 | return ((ClassDeclarationSyntax)syntaxNode, project.GetDocument(syntaxReference.SyntaxTree)!); 218 | } 219 | 220 | return null; 221 | } 222 | 223 | private static async Task AddMethodToPlug((ClassDeclarationSyntax PlugClass, Document Document) plugInfo, 224 | SyntaxNode plugMethod) 225 | { 226 | DocumentEditor editor = await DocumentEditor.CreateAsync(plugInfo.Document).ConfigureAwait(false); 227 | editor.AddMember(plugInfo.PlugClass, plugMethod); 228 | return editor.GetChangedDocument().Project.Solution; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer.Package/Cosmos.Patcher.Analyzer.Package.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | true 5 | false 6 | true 7 | 8 | 9 | Cosmos.Patcher.Analyzer 10 | 1.0.0.0 11 | https://github.com/valentinbreiz/nativeaot-patcher/blob/main/LICENSE 12 | https://github.com/valentinbreiz/nativeaot-patcher.git 13 | 14 | https://github.com/valentinbreiz/nativeaot-patcher.git 15 | false 16 | Cosmos.Patcher.Analyzer 17 | Release 18 | Copyright 19 | Cosmos.Patcher.Analyzer, analyzers 20 | true 21 | true 22 | $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer/Cosmos.Patcher.Analyzer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | Cosmos.Patcher.Analyzer 5 | Library 6 | true 7 | 8 | $(MSBuildProjectFile) 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer/DiagnosticMessages.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace Cosmos.Patcher.Analyzer; 5 | 6 | public sealed class DiagnosticMessages 7 | { 8 | public static readonly DiagnosticDescriptor TypeNotFound = new( 9 | "NAOT0001", 10 | "Type Not Found", 11 | "The specified type '{0}' could not be located", 12 | "Naming", 13 | DiagnosticSeverity.Error, 14 | true, 15 | "Ensure that the type name is correct and that the type is accessible." 16 | ); 17 | 18 | public static readonly DiagnosticDescriptor MethodNeedsPlug = new( 19 | "NAOT0002", 20 | "Method Needs Plug", 21 | "Method '{0}' in class '{1}' requires a plug", 22 | "Usage", 23 | DiagnosticSeverity.Error, 24 | true, 25 | "Ensure that the method has a corresponding plug. See http://www.gocosmos.org/docs/plugs/missing/ for more information." 26 | ); 27 | 28 | public static readonly DiagnosticDescriptor PlugNameDoesNotMatch = new( 29 | "NAOT0004", 30 | "Plug Name Does Not Match", 31 | "Plug '{0}' should be renamed to '{1}'", 32 | "Naming", 33 | DiagnosticSeverity.Info, 34 | true, 35 | "Ensure that the plug name matches the plugged class name." 36 | ); 37 | 38 | public static readonly DiagnosticDescriptor MethodNotImplemented = new( 39 | "NAOT0005", 40 | "Method Not Implemented", 41 | "Method '{0}' does not exist in '{1}'", 42 | "Usage", 43 | DiagnosticSeverity.Info, 44 | true, 45 | "Ensure that the method name is correct and that the method exists." 46 | ); 47 | 48 | 49 | public static readonly DiagnosticDescriptor StaticConstructorTooManyParams = new( 50 | "NAOT0006", 51 | "Static Constructor Has Too Many Parameters", 52 | "The static constructor '{0}' contains too many parameters. A static constructor must not have more than one parameter.", 53 | "Usage", 54 | DiagnosticSeverity.Error, 55 | true, 56 | "A static constructor should have at most one parameter." 57 | ); 58 | 59 | 60 | public static ImmutableArray SupportedDiagnostics => ImmutableArray.Create(TypeNotFound, 61 | MethodNeedsPlug, PlugNameDoesNotMatch, MethodNotImplemented, StaticConstructorTooManyParams); 62 | } 63 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer/Extensions/AttributeExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp.Syntax; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | 5 | namespace Cosmos.Patcher.Analyzer.Extensions; 6 | 7 | public static class AttributeExtensions 8 | { 9 | public static T? GetAttributeValue(this AttributeSyntax attribute, object indexOrString, 10 | SyntaxNodeAnalysisContext context) 11 | { 12 | ExpressionSyntax? expression = GetArgumentExpression(attribute, indexOrString); 13 | return expression != null ? GetValueFromExpression(expression, context) : default; 14 | } 15 | 16 | public static bool GetAttributeValue(this AttributeSyntax attribute, object indexOrString, 17 | SyntaxNodeAnalysisContext context, out T? value) 18 | { 19 | value = GetAttributeValue(attribute, indexOrString, context); 20 | return value != null && (value is not string str || !string.IsNullOrEmpty(str)); 21 | } 22 | 23 | public static T? GetAttributeValue(this AttributeData attribute, object indexOrString) 24 | { 25 | TypedConstant argument = GetConstructorArgument(attribute, indexOrString); 26 | return argument.Kind == TypedConstantKind.Error ? default : ConvertArgumentValue(argument); 27 | } 28 | 29 | private static T? GetValueFromExpression(ExpressionSyntax expression, SyntaxNodeAnalysisContext context) => 30 | expression switch 31 | { 32 | MemberAccessExpressionSyntax { Name: { } name } when typeof(T).IsEnum 33 | => ParseEnum(name.ToString()), 34 | LiteralExpressionSyntax literal 35 | => (T?)literal.Token.Value, 36 | TypeOfExpressionSyntax typeOf 37 | => (T?)(object?)GetTypeFromTypeOf(typeOf, context), 38 | _ => default 39 | }; 40 | 41 | private static string? GetTypeFromTypeOf(TypeOfExpressionSyntax typeOf, SyntaxNodeAnalysisContext context) 42 | { 43 | ITypeSymbol? symbol = context.SemanticModel.GetSymbolInfo(typeOf.Type).Symbol as ITypeSymbol; 44 | return symbol is not null 45 | ? symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)) 46 | : "Unknown"; 47 | } 48 | 49 | private static T? ParseEnum(string name) 50 | { 51 | T value; 52 | return (value = (T)Enum.Parse(typeof(T), name)) != null ? value : default; 53 | } 54 | 55 | private static TypedConstant GetConstructorArgument(AttributeData attribute, object indexOrString) => 56 | indexOrString switch 57 | { 58 | int index when index >= 0 && index < attribute.ConstructorArguments.Length 59 | => attribute.ConstructorArguments[index], 60 | string name 61 | => attribute.NamedArguments.FirstOrDefault(kvp => 62 | kvp.Key.Equals(name, StringComparison.OrdinalIgnoreCase)).Value, 63 | _ => default 64 | }; 65 | 66 | private static ExpressionSyntax? GetArgumentExpression(AttributeSyntax attribute, object indexOrString) => 67 | indexOrString switch 68 | { 69 | int index when attribute.ArgumentList?.Arguments.Count > index 70 | => attribute.ArgumentList.Arguments[index].Expression, 71 | string name => attribute.ArgumentList?.Arguments 72 | .FirstOrDefault(a => (a.NameEquals?.Name ?? a.NameColon?.Name)?.ToString() == name)? 73 | .Expression, 74 | _ => null 75 | }; 76 | 77 | private static T? ConvertArgumentValue(TypedConstant argument) 78 | { 79 | if (argument.Value is T value) 80 | { 81 | return value; 82 | } 83 | 84 | return typeof(T).IsEnum && argument.Value != null ? ConvertEnum(argument.Value) 85 | : typeof(T).Name == "Type" && argument.Value is ITypeSymbol type ? (T?)type 86 | : default; 87 | } 88 | 89 | private static T? ConvertEnum(object value) 90 | => (T?)Enum.ToObject(typeof(T), value); 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Cosmos.Patcher.Analyzer.Extensions; 2 | 3 | public static class EnumerableExtensions 4 | { 5 | 6 | public static bool Any(this IEnumerable enumerable, Func predicate, out T value) 7 | { 8 | using IEnumerator enumerator = enumerable.GetEnumerator(); 9 | while (enumerator.MoveNext()) 10 | { 11 | if (!predicate(enumerator.Current)) continue; 12 | value = enumerator.Current; 13 | return true; 14 | } 15 | 16 | value = default; 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer/Extensions/SymbolExtensions.cs: -------------------------------------------------------------------------------- 1 | // This code is licensed under MIT license (see LICENSE for details) 2 | 3 | using Microsoft.CodeAnalysis; 4 | 5 | namespace Cosmos.Patcher.Analyzer.Extensions; 6 | 7 | public static class SymbolExtensions 8 | { 9 | public static bool HasAttribute(this ISymbol symbol, params string[] attributeNames) => symbol.GetAttributes().Any(a => attributeNames.Contains(a?.AttributeClass?.Name)); 10 | } 11 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer/Extensions/SyntaxNodeExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp.Syntax; 3 | using Microsoft.CodeAnalysis.Text; 4 | 5 | namespace Cosmos.Patcher.Analyzer.Extensions; 6 | 7 | public static class SyntaxNodeExtensions 8 | { 9 | public static bool TryGetMemberByName(this ClassDeclarationSyntax declaration, string name, out T? member) 10 | where T : MemberDeclarationSyntax 11 | { 12 | foreach (MemberDeclarationSyntax memberDeclarationSyntax in declaration.Members) 13 | { 14 | if (memberDeclarationSyntax is not T memberDeclaration) 15 | continue; 16 | 17 | if (memberDeclaration.GetName() != name) 18 | continue; 19 | 20 | member = memberDeclaration; 21 | return true; 22 | } 23 | member = null; 24 | return false; 25 | } 26 | 27 | 28 | public static string? GetName(this MemberDeclarationSyntax member) => member switch 29 | { 30 | MethodDeclarationSyntax method => method.Identifier.ValueText, 31 | PropertyDeclarationSyntax property => property.Identifier.ValueText, 32 | FieldDeclarationSyntax field => field.Declaration.Variables 33 | .FirstOrDefault()?.Identifier.ValueText, 34 | _ => null 35 | }; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Analyzer/Models/PlugInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Cosmos.Patcher.Analyzer.Models; 4 | 5 | public record PlugInfo(bool IsExternal, INamedTypeSymbol PlugSymbol); 6 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Build/Cosmos.Patcher.Build.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | Library 5 | True 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Build/Tasks/PatcherTask.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Framework; 2 | using Microsoft.Build.Utilities; 3 | 4 | namespace Cosmos.Patcher.Build.Tasks; 5 | 6 | public sealed class PatcherTask : ToolTask 7 | { 8 | 9 | [Required] public string? TargetAssembly { get; set; } 10 | 11 | [Required] public required ITaskItem[] PlugsReferences { get; set; } 12 | 13 | [Required] public required string OutputPath { get; set; } 14 | 15 | protected override string GenerateFullPathToTool() => ToolName; 16 | 17 | protected override string GenerateCommandLineCommands() 18 | { 19 | CommandLineBuilder builder = new(); 20 | 21 | // Add main command 22 | builder.AppendSwitch("patch"); 23 | 24 | // Add --target arg 25 | builder.AppendSwitch("--target"); 26 | builder.AppendFileNameIfNotNull(TargetAssembly); 27 | 28 | 29 | // Add plugs 30 | builder.AppendSwitch("--plugs"); 31 | foreach (ITaskItem plug in PlugsReferences) 32 | { 33 | builder.AppendFileNameIfNotNull(plug.ItemSpec); 34 | } 35 | 36 | // Add --output arg 37 | builder.AppendSwitch("--output"); 38 | builder.AppendFileNameIfNotNull(OutputPath); 39 | 40 | return builder.ToString(); 41 | } 42 | 43 | public override bool Execute() 44 | { 45 | Log.LogMessage(MessageImportance.High, "Running Liquip.Patcher..."); 46 | Log.LogMessage(MessageImportance.High, $"Platform: {Environment.OSVersion.Platform}"); 47 | Log.LogMessage(MessageImportance.High, $"Target Assembly: {TargetAssembly}"); 48 | Log.LogMessage(MessageImportance.High, $"Output Path: {OutputPath}"); 49 | Log.LogMessage(MessageImportance.High, 50 | $"Plugs References: {string.Join(", ", PlugsReferences.Select(p => p.ItemSpec))}"); 51 | Log.LogMessage(MessageImportance.High, $"Command: {GenerateCommandLineCommands()}"); 52 | 53 | return base.Execute(); 54 | } 55 | 56 | protected override string ToolName => "Cosmos.Patcher"; 57 | } 58 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Build/build/Cosmos.Patcher.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(MSBuildThisFileDirectory)\..\lib\netstandard2.0\Cosmos.Patcher.Build.dll 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher.Build/build/Cosmos.Patcher.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | true 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | $(IntermediateOutputPath)/cosmos/ref 41 | $(PatcherOutputPath)/$(AssemblyName)_patched.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher/Cosmos.Patcher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | Exe 5 | 6 | true 7 | true 8 | True 9 | cosmos.patcher 10 | 1.0.0 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher/Extensions/MethodBodyEx.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | using MonoMod.Utils; 5 | 6 | namespace Cosmos.Patcher.Extensions; 7 | 8 | public static class MethodBodyEx 9 | { 10 | [return: NotNullIfNotNull("bo")] 11 | public static MethodDefinition? ReplaceMethodWithJump(this MethodDefinition? bo, MethodDefinition m) 12 | { 13 | Helpers.ThrowIfArgumentNull(m); 14 | 15 | if (bo == null) 16 | { 17 | return null; 18 | } 19 | 20 | MethodBody? bc = new(m); 21 | 22 | if (!bo.HasParameters) 23 | { 24 | Instruction? jump = Instruction.Create(OpCodes.Call, m); 25 | bc.Instructions.Add(jump); 26 | bc.Instructions.Add(Instruction.Create(OpCodes.Ret)); 27 | } 28 | 29 | 30 | bo.Body = bc; 31 | 32 | return bo; 33 | } 34 | 35 | /// 36 | /// replace method body 37 | /// 38 | /// 39 | /// 40 | /// 41 | /// 42 | [return: NotNullIfNotNull("newBody")] 43 | public static MethodBody? ReplaceMethodBody(this MethodBody? newBody, MethodDefinition m) 44 | { 45 | Helpers.ThrowIfArgumentNull(m); 46 | 47 | if (newBody == null) 48 | { 49 | return null; 50 | } 51 | 52 | MethodBody? bc = new(m); 53 | bc.MaxStackSize = newBody.MaxStackSize; 54 | bc.InitLocals = newBody.InitLocals; 55 | bc.LocalVarToken = newBody.LocalVarToken; 56 | 57 | bc.Instructions.AddRange(newBody.Instructions.Select(o => 58 | { 59 | Instruction? c = Instruction.Create(OpCodes.Nop); 60 | c.OpCode = o.OpCode; 61 | c.Operand = o.Operand; 62 | c.Offset = o.Offset; 63 | return c; 64 | })); 65 | 66 | foreach (Instruction? instruction in bc.Instructions) 67 | { 68 | if (instruction.Operand is Instruction target) 69 | { 70 | instruction.Operand = bc.Instructions[newBody.Instructions.IndexOf(target)]; 71 | } 72 | else if (instruction.Operand is Instruction[] targets) 73 | { 74 | instruction.Operand = targets 75 | .Select(i => bc.Instructions[newBody.Instructions.IndexOf(i)]) 76 | .ToArray(); 77 | } 78 | } 79 | 80 | bc.ExceptionHandlers.AddRange(newBody.ExceptionHandlers.Select(o => 81 | { 82 | ExceptionHandler? c = new(o.HandlerType); 83 | c.TryStart = o.TryStart == null ? null : bc.Instructions[newBody.Instructions.IndexOf(o.TryStart)]; 84 | c.TryEnd = o.TryEnd == null ? null : bc.Instructions[newBody.Instructions.IndexOf(o.TryEnd)]; 85 | c.FilterStart = o.FilterStart == null ? null : bc.Instructions[newBody.Instructions.IndexOf(o.FilterStart)]; 86 | c.HandlerStart = o.HandlerStart == null 87 | ? null 88 | : bc.Instructions[newBody.Instructions.IndexOf(o.HandlerStart)]; 89 | c.HandlerEnd = o.HandlerEnd == null ? null : bc.Instructions[newBody.Instructions.IndexOf(o.HandlerEnd)]; 90 | c.CatchType = o.CatchType; 91 | return c; 92 | })); 93 | 94 | bc.Variables.AddRange(newBody.Variables.Select(o => 95 | { 96 | VariableDefinition? c = new(o.VariableType); 97 | return c; 98 | })); 99 | 100 | Instruction ResolveInstrOff(int off) 101 | { 102 | // Can't check cloned instruction offsets directly, as those can change for some reason 103 | for (int i = 0; i < newBody.Instructions.Count; i++) 104 | { 105 | if (newBody.Instructions[i].Offset == off) 106 | { 107 | return bc.Instructions[i]; 108 | } 109 | } 110 | 111 | throw new ArgumentException($"Invalid instruction offset {off}"); 112 | } 113 | 114 | m.CustomDebugInformations.AddRange(newBody.Method.CustomDebugInformations.Select(o => 115 | { 116 | if (o is AsyncMethodBodyDebugInformation ao) 117 | { 118 | AsyncMethodBodyDebugInformation? c = new(); 119 | if (ao.CatchHandler.Offset >= 0) 120 | { 121 | c.CatchHandler = ao.CatchHandler.IsEndOfMethod 122 | ? new InstructionOffset() 123 | : new InstructionOffset(ResolveInstrOff(ao.CatchHandler.Offset)); 124 | } 125 | 126 | c.Yields.AddRange(ao.Yields.Select(off => 127 | off.IsEndOfMethod ? new InstructionOffset() : new InstructionOffset(ResolveInstrOff(off.Offset)))); 128 | c.Resumes.AddRange(ao.Resumes.Select(off => 129 | off.IsEndOfMethod ? new InstructionOffset() : new InstructionOffset(ResolveInstrOff(off.Offset)))); 130 | c.ResumeMethods.AddRange(ao.ResumeMethods); 131 | return c; 132 | } 133 | 134 | if (o is StateMachineScopeDebugInformation so) 135 | { 136 | StateMachineScopeDebugInformation? c = new(); 137 | c.Scopes.AddRange(so.Scopes.Select(s => new StateMachineScope(ResolveInstrOff(s.Start.Offset), 138 | s.End.IsEndOfMethod ? null : ResolveInstrOff(s.End.Offset)))); 139 | return c; 140 | } 141 | 142 | return o; 143 | })); 144 | 145 | m.DebugInformation.SequencePoints.AddRange(newBody.Method.DebugInformation.SequencePoints.Select(o => 146 | { 147 | SequencePoint? c = new(ResolveInstrOff(o.Offset), o.Document); 148 | c.StartLine = o.StartLine; 149 | c.StartColumn = o.StartColumn; 150 | c.EndLine = o.EndLine; 151 | c.EndColumn = o.EndColumn; 152 | return c; 153 | })); 154 | 155 | return bc; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher/PatchCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Mono.Cecil; 3 | using Spectre.Console.Cli; 4 | 5 | namespace Cosmos.Patcher; 6 | 7 | public sealed class PatchCommand : Command 8 | { 9 | public class Settings : CommandSettings 10 | { 11 | [CommandOption("--target ")] 12 | [Description("Path to the target assembly.")] 13 | public string TargetAssembly { get; set; } = null!; 14 | 15 | [CommandOption("--plugs ")] 16 | [Description("Paths to plug assemblies.")] 17 | public string[] PlugsReferences { get; set; } = []; 18 | 19 | [CommandOption("--output ")] 20 | [Description("Output path for the patched dll")] 21 | public string? OutputPath { get; set; } 22 | } 23 | 24 | public override int Execute(CommandContext context, Settings settings) 25 | { 26 | Console.WriteLine("Running PatchCommand..."); 27 | 28 | if (!File.Exists(settings.TargetAssembly)) 29 | { 30 | Console.WriteLine($"Error: Target assembly '{settings.TargetAssembly}' not found."); 31 | return -1; 32 | } 33 | 34 | string[]? plugPaths = settings.PlugsReferences.Where(File.Exists).ToArray(); 35 | if (plugPaths.Length == 0) 36 | { 37 | Console.WriteLine("Error: No valid plug assemblies provided."); 38 | return -1; 39 | } 40 | 41 | try 42 | { 43 | AssemblyDefinition? targetAssembly = AssemblyDefinition.ReadAssembly(settings.TargetAssembly); 44 | Console.WriteLine($"Loaded target assembly: {settings.TargetAssembly}"); 45 | 46 | AssemblyDefinition[]? plugAssemblies = [.. plugPaths 47 | .Select(AssemblyDefinition.ReadAssembly) 48 | ]; 49 | 50 | Console.WriteLine("Loaded plug assemblies:"); 51 | foreach (string? plug in plugPaths) 52 | { 53 | Console.WriteLine($" - {plug}"); 54 | } 55 | 56 | PlugPatcher? plugPatcher = new(new PlugScanner()); 57 | plugPatcher.PatchAssembly(targetAssembly, plugAssemblies); 58 | 59 | settings.OutputPath ??= Path.GetDirectoryName(settings.TargetAssembly)!; 60 | 61 | string finalPath = Path.Combine(settings.OutputPath, Path.GetFileNameWithoutExtension(settings.TargetAssembly) + "_patched.dll"); 62 | targetAssembly.Write(finalPath); 63 | Console.WriteLine($"Patched assembly saved to: {settings.OutputPath}"); 64 | 65 | Console.WriteLine("Patching completed successfully."); 66 | return 0; 67 | } 68 | catch (Exception ex) 69 | { 70 | Console.WriteLine($"Error during patching: {ex.Message}"); 71 | return -1; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher/PlugScanner.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.API.Attributes; 2 | using Mono.Cecil; 3 | using MonoMod.Utils; 4 | 5 | namespace Cosmos.Patcher; 6 | 7 | public sealed class PlugScanner 8 | { 9 | public List LoadPlugs(params AssemblyDefinition[] assemblies) 10 | { 11 | List output = 12 | [ 13 | ..assemblies 14 | .SelectMany(assembly => assembly.Modules) 15 | .SelectMany(module => module.Types) 16 | .Where(i=> i.HasCustomAttribute(typeof(PlugAttribute).FullName)) 17 | 18 | ]; 19 | 20 | foreach (TypeDefinition? type in output) 21 | { 22 | Console.WriteLine($"Plug found: {type.Name}"); 23 | } 24 | 25 | return output; 26 | } 27 | 28 | public List LoadPlugMethods(TypeDefinition plugType) => 29 | [.. plugType.Methods.Where(i => i.IsPublic && i.IsStatic)]; 30 | } 31 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher/PlugUtils.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | namespace Cosmos.Patcher; 4 | 5 | public static class PlugUtils 6 | { 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// 12 | public static void Save(this AssemblyDefinition assembly, string rootPath) => 13 | assembly.Write(Path.Combine(rootPath, $"{assembly.Name.Name}.dll")); 14 | 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | public static void Save(this AssemblyDefinition assembly, string rootPath, string fileName) => 22 | assembly.Write(Path.Combine(rootPath, $"{fileName}")); 23 | 24 | public static List LoadAssemblies(params string[] paths) => 25 | [.. paths.Select(AssemblyDefinition.ReadAssembly)]; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/Cosmos.Patcher/Program.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.Patcher; 2 | using Spectre.Console.Cli; 3 | 4 | CommandApp? app = new(); 5 | app.Configure(config => 6 | { 7 | config.UseAssemblyInformationalVersion(); 8 | config.AddCommand("patch"); 9 | }); 10 | 11 | app.Run(args); 12 | -------------------------------------------------------------------------------- /tests/Cosmos.Asm.Build.Test/Cosmos.Asm.Build.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | ..\..\..\..\..\..\..\..\home\zarlo\.nuget\packages\microsoft.build.framework\17.11.4\lib\net8.0\Microsoft.Build.Framework.dll 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | PreserveNewest 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/Cosmos.Asm.Build.Test/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | -------------------------------------------------------------------------------- /tests/Cosmos.Asm.Build.Test/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.Asm.Build.Tasks; 2 | using Microsoft.Build.Framework; 3 | using Moq; 4 | using Xunit.Sdk; 5 | 6 | namespace Cosmos.Asm.Build.Test; 7 | 8 | public class UnitTest1 9 | { 10 | private Mock buildEngine; 11 | private List errors; 12 | 13 | public UnitTest1() 14 | { 15 | buildEngine = new Mock(); 16 | errors = []; 17 | buildEngine.Setup(x => x.LogErrorEvent(It.IsAny())) 18 | .Callback(e => errors.Add(e)); 19 | } 20 | 21 | [Theory] 22 | [InlineData("/usr/bin/yasm", PlatformID.Unix)] 23 | public void Test1(string path, PlatformID platform) 24 | { 25 | if (Environment.OSVersion.Platform != platform) 26 | throw SkipException.ForSkip("skiping this test"); 27 | 28 | YasmBuildTask yasm = new() 29 | { 30 | YasmPath = path, 31 | SourceFiles = ["./asm/test.asm"], 32 | OutputPath = "./output", 33 | BuildEngine = buildEngine.Object 34 | }; 35 | 36 | 37 | bool success = yasm.Execute(); 38 | 39 | Assert.True(success); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Cosmos.Asm.Build.Test/asm/test.asm: -------------------------------------------------------------------------------- 1 | test_lbl: 2 | add rax, 0x50 -------------------------------------------------------------------------------- /tests/Cosmos.NativeLibrary/Cosmos.NativeLibrary.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 17.0 23 | Win32Proj 24 | {8bb57b06-1852-42a6-8c53-c04adf8b50a1} 25 | CosmosNativeLibrary 26 | 10.0 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | DynamicLibrary 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | $(SolutionDir)Cosmos.NativeLibrary\x64\Debug 75 | 76 | 77 | 78 | Level3 79 | true 80 | WIN32;_DEBUG;LIQUIPNATIVELIBRARY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 81 | true 82 | Use 83 | pch.h 84 | 85 | 86 | Windows 87 | true 88 | false 89 | 90 | 91 | 92 | 93 | Level3 94 | true 95 | true 96 | true 97 | WIN32;NDEBUG;LIQUIPNATIVELIBRARY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 98 | true 99 | Use 100 | pch.h 101 | 102 | 103 | Windows 104 | true 105 | true 106 | true 107 | false 108 | 109 | 110 | 111 | 112 | Level3 113 | true 114 | _DEBUG;LIQUIPNATIVELIBRARY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 115 | true 116 | NotUsing 117 | pch.h 118 | 119 | 120 | Windows 121 | true 122 | false 123 | 124 | 125 | 126 | 127 | Level3 128 | true 129 | true 130 | true 131 | NDEBUG;LIQUIPNATIVELIBRARY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 132 | true 133 | Use 134 | pch.h 135 | 136 | 137 | Windows 138 | true 139 | true 140 | true 141 | false 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /tests/Cosmos.NativeLibrary/Cosmos.NativeLibrary.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/Cosmos.NativeLibrary/nativelibrary.c: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #define EXPORT __declspec(dllexport) 3 | #else 4 | #define EXPORT 5 | #endif 6 | 7 | EXPORT int Add(int a, int b) 8 | { 9 | return a + b; 10 | } 11 | -------------------------------------------------------------------------------- /tests/Cosmos.NativeWrapper/Cosmos.NativeWrapper.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | true 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/Cosmos.NativeWrapper/NativeWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Runtime.InteropServices; 3 | using Cosmos.API.Attributes; 4 | 5 | namespace Cosmos.NativeWrapper; 6 | 7 | public class TestClass 8 | { 9 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 10 | public static extern int OutputDebugString(string lpOutputString); 11 | 12 | [DllImport("Cosmos.NativeLibrary.dll", EntryPoint = "Add", CallingConvention = CallingConvention.Cdecl)] 13 | public static extern int Add(int a, int b); 14 | 15 | 16 | [UnmanagedCallersOnly(EntryPoint = "Native_Add", CallConvs = [typeof(CallConvCdecl)])] 17 | public static int NativeAdd(int a, int b) => 18 | // _ = OutputDebugString("NativeAdd method called"); 19 | Add(a, b); 20 | 21 | public static int ManagedAdd(int a, int b) => 22 | // OutputDebugString("ManagedAdd method called"); 23 | a + b; 24 | } 25 | 26 | public class MockTarget 27 | { 28 | } 29 | 30 | public class NonPlug 31 | { 32 | } 33 | 34 | [Plug(typeof(MockTarget))] 35 | public class MockPlug 36 | { 37 | } 38 | 39 | [Plug(typeof(MockTarget))] 40 | public class EmptyPlug 41 | { 42 | // No methods defined 43 | } 44 | 45 | [Plug(typeof(MockTarget))] 46 | public class MockPlugWithMethods 47 | { 48 | [PlugMember] 49 | public static void StaticMethod() 50 | { 51 | } 52 | 53 | [PlugMember] 54 | public void InstanceMethod() 55 | { 56 | } 57 | } 58 | 59 | [Plug("OptionalTarget", IsOptional = true)] 60 | public class OptionalPlug 61 | { 62 | } 63 | -------------------------------------------------------------------------------- /tests/Cosmos.NativeWrapper/NativeWrapperObject.cs: -------------------------------------------------------------------------------- 1 | namespace Cosmos.NativeWrapper; 2 | 3 | public class NativeWrapperObject 4 | { 5 | public NativeWrapperObject() => Console.WriteLine("Base ctor"); 6 | 7 | public void Speak() => Console.WriteLine(InstanceField); 8 | 9 | public int InstanceMethod(int value) => value + 1; 10 | 11 | public string InstanceField = "Hello World"; 12 | 13 | public string InstanceProperty { get; set; } = "Goodbye World"; 14 | 15 | private string _instanceBackingField = "Backing Field"; 16 | 17 | public string InstanceBackingFieldProperty 18 | { 19 | get => _instanceBackingField; 20 | set => _instanceBackingField = value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Cosmos.NativeWrapper/NativeWrapperObjectPlug.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.API.Attributes; 2 | 3 | namespace Cosmos.NativeWrapper; 4 | 5 | [Plug(typeof(NativeWrapperObject))] 6 | public class NativeWrapperObjectImpl 7 | { 8 | [PlugMember] 9 | public static void Ctor(object aThis) => Console.WriteLine("Plugged ctor"); 10 | 11 | [PlugMember] 12 | public static void Speakg(object aThis) => Console.WriteLine("bz bz plugged hello"); 13 | 14 | [PlugMember] 15 | public static int InstanceMethod(object aThis, int value) => value * 2; 16 | 17 | [PlugMember] public string InstanceField = "Plugged Hello World"; 18 | 19 | [PlugMember] public string InstanceProperty { get; set; } = "Plugged Goodbye World"; 20 | 21 | private string _instanceBackingField = "Plugged Backing Field"; 22 | 23 | [PlugMember] 24 | public string InstanceBackingFieldProperty 25 | { 26 | get => _instanceBackingField; 27 | set => _instanceBackingField = value; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Cosmos.NativeWrapper/NativeWrapperPlug.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.API.Attributes; 2 | 3 | namespace Cosmos.NativeWrapper; 4 | 5 | [Plug(typeof(TestClass))] 6 | public class TestClassPlug 7 | { 8 | [PlugMember] 9 | public static int Add(int a, int b) => a * b; 10 | 11 | [PlugMember] 12 | public static void OutputDebugString(object aThis) 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Cosmos.Patcher.Analyzer.Tests/AnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Cosmos.API.Attributes; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | using Xunit; 7 | 8 | namespace Cosmos.Patcher.Analyzer.Tests; 9 | 10 | public class AnalyzerTests 11 | { 12 | private static readonly MetadataReference CorlibReference = 13 | MetadataReference.CreateFromFile(typeof(object).Assembly.Location); 14 | 15 | private static readonly MetadataReference PlugAttributeReference = 16 | MetadataReference.CreateFromFile(typeof(PlugAttribute).Assembly.Location); 17 | 18 | private static SyntaxTree ParseCode(string code) => CSharpSyntaxTree.ParseText(code); 19 | 20 | 21 | [Fact] 22 | public async Task Test_TypeNotFoundDiagnostic() 23 | { 24 | const string code = """ 25 | using System; 26 | using Cosmos.API.Attributes; 27 | 28 | namespace ConsoleApplication1 29 | { 30 | [Plug("System.NonExistent", IsOptional = false)] 31 | public static class Test 32 | { 33 | [DllImport("example.dll")] 34 | public static extern void ExternalMethod(); 35 | } 36 | } 37 | """; 38 | ImmutableArray diagnostics = await GetDiagnosticsAsync(code); 39 | Assert.Contains(diagnostics, 40 | d => d.Id == DiagnosticMessages.TypeNotFound.Id && d.GetMessage().Contains("System.NonExistent")); 41 | } 42 | 43 | private static async Task> GetDiagnosticsAsync(string code) 44 | { 45 | SyntaxTree syntaxTree = ParseCode(code); 46 | 47 | CSharpCompilation compilation = CSharpCompilation.Create("TestCompilation") 48 | .AddReferences(CorlibReference, PlugAttributeReference) 49 | .AddSyntaxTrees(syntaxTree); 50 | 51 | PatcherAnalyzer analyzer = new(); 52 | CompilationWithAnalyzers compilationWithAnalyzers = 53 | compilation.WithAnalyzers(ImmutableArray.Create(analyzer)); 54 | return await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(); 55 | } 56 | 57 | 58 | [Fact] 59 | public async Task Test_AnalyzeAccessedMember() 60 | { 61 | const string code = @" 62 | using System.Runtime.CompilerServices; 63 | using System.Runtime.InteropServices; 64 | using Cosmos.API.Attributes; 65 | 66 | namespace ConsoleApplication1 67 | { 68 | public static class TestNativeType 69 | { 70 | [DllImport(""example.dll"")] 71 | public static extern void ExternalMethod(); 72 | 73 | [MethodImpl(MethodImplOptions.InternalCall)] 74 | public static extern void NativeMethod(); 75 | } 76 | 77 | public class Test 78 | { 79 | public void TestMethod() 80 | { 81 | TestNativeType.ExternalMethod(); 82 | TestNativeType.NativeMethod(); 83 | } 84 | } 85 | }"; 86 | 87 | ImmutableArray diagnostics = await GetDiagnosticsAsync(code); 88 | 89 | Assert.Contains(diagnostics, 90 | d => d.Id == DiagnosticMessages.MethodNeedsPlug.Id && d.GetMessage().Contains("ExternalMethod")); 91 | Assert.Contains(diagnostics, 92 | d => d.Id == DiagnosticMessages.MethodNeedsPlug.Id && d.GetMessage().Contains("NativeMethod")); 93 | } 94 | 95 | [Fact] 96 | public async Task Test_MethodNotImplemented() 97 | { 98 | const string code = """ 99 | using System.Runtime.CompilerServices; 100 | using System.Runtime.InteropServices; 101 | using Cosmos.API.Attributes; 102 | 103 | namespace ConsoleApplication1 104 | { 105 | public static class TestNativeType 106 | { 107 | [DllImport("example.dll")] 108 | public static extern void ExternalMethod(); 109 | 110 | [MethodImpl(MethodImplOptions.InternalCall)] 111 | public static extern void NativeMethod(); 112 | } 113 | 114 | [Plug("ConsoleApplication1.TestNativeType", IsOptional = false)] 115 | public static class Test 116 | { 117 | public static void NotImplemented() {} 118 | } 119 | } 120 | """; 121 | 122 | ImmutableArray diagnostics = await GetDiagnosticsAsync(code); 123 | 124 | Assert.Contains(diagnostics, 125 | d => d.Id == DiagnosticMessages.MethodNotImplemented.Id && d.GetMessage().Contains("NotImplemented")); 126 | } 127 | 128 | [Fact] 129 | public async Task Test_StaticConstructorTooManyParameters() 130 | { 131 | const string code = """ 132 | 133 | using System.Runtime.CompilerServices; 134 | using System.Runtime.InteropServices; 135 | using Cosmos.API.Attributes; 136 | 137 | namespace ConsoleApplication1 138 | { 139 | public static class TestNativeType 140 | { 141 | [DllImport("example.dll")] 142 | public static extern void ExternalMethod(); 143 | 144 | [MethodImpl(MethodImplOptions.InternalCall)] 145 | public static extern void NativeMethod(); 146 | 147 | static TestNativeType() => Console.WriteLine("123"); 148 | } 149 | 150 | [Plug("ConsoleApplication1.TestNativeType", IsOptional = false)] 151 | public static class Test 152 | { 153 | public static void CCtor(object aThis, object param) => Console.WriteLine(param); 154 | } 155 | } 156 | """; 157 | 158 | ImmutableArray diagnostics = await GetDiagnosticsAsync(code); 159 | 160 | Assert.Contains(diagnostics, 161 | d => d.Id == DiagnosticMessages.StaticConstructorTooManyParams.Id && d.GetMessage().Contains("CCtor")); 162 | } 163 | 164 | [Fact] 165 | public async Task Test_StaticConstructorNotImplemented() 166 | { 167 | const string code = """ 168 | 169 | using System.Runtime.CompilerServices; 170 | using System.Runtime.InteropServices; 171 | using Cosmos.API.Attributes; 172 | 173 | namespace ConsoleApplication1 174 | { 175 | public static class TestNativeType 176 | { 177 | [DllImport("example.dll")] 178 | public static extern void ExternalMethod(); 179 | 180 | [MethodImpl(MethodImplOptions.InternalCall)] 181 | public static extern void NativeMethod(); 182 | } 183 | 184 | [Plug("ConsoleApplication1.TestNativeType", IsOptional = false)] 185 | public static class Test 186 | { 187 | public static void CCtor(object param, object the) => Console.WriteLine(param); 188 | } 189 | } 190 | """; 191 | 192 | ImmutableArray diagnostics = await GetDiagnosticsAsync(code); 193 | 194 | Assert.Contains(diagnostics, 195 | d => d.Id == DiagnosticMessages.MethodNotImplemented.Id && d.GetMessage().Contains(".cctor")); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /tests/Cosmos.Patcher.Analyzer.Tests/Cosmos.Patcher.Analyzer.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/Cosmos.Patcher.Tests/Cosmos.Patcher.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/Cosmos.Patcher.Tests/PlugPatcherTest_ObjectPlugs.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Cosmos.NativeWrapper; 3 | using Mono.Cecil; 4 | 5 | namespace Cosmos.Patcher.Tests; 6 | 7 | public class PlugPatcherTest_ObjectPlugs 8 | { 9 | private AssemblyDefinition CreateMockAssembly() 10 | { 11 | string? assemblyPath = typeof(T).Assembly.Location; 12 | return AssemblyDefinition.ReadAssembly(assemblyPath); 13 | } 14 | 15 | [Fact] 16 | public void PatchObjectWithAThis_ShouldPlugInstanceCorrectly() 17 | { 18 | // Arrange 19 | PlugScanner? plugScanner = new(); 20 | PlugPatcher? patcher = new(plugScanner); 21 | 22 | AssemblyDefinition? targetAssembly = CreateMockAssembly(); 23 | AssemblyDefinition? plugAssembly = CreateMockAssembly(); 24 | 25 | TypeDefinition? targetType = 26 | targetAssembly.MainModule.Types.FirstOrDefault(t => t.Name == nameof(NativeWrapperObject)); 27 | Assert.NotNull(targetType); 28 | 29 | // Act: Apply the plug 30 | patcher.PatchAssembly(targetAssembly, plugAssembly); 31 | 32 | targetAssembly.Save("./", "targetObjectAssembly.dll"); 33 | 34 | object? result = ExecuteObject(targetAssembly, "NativeWrapperObject", "InstanceMethod", 10); 35 | Assert.Equal(20, result); 36 | } 37 | 38 | private object ExecuteObject(AssemblyDefinition assemblyDefinition, string typeName, string methodName, 39 | params object[] parameters) 40 | { 41 | using MemoryStream? memoryStream = new(); 42 | assemblyDefinition.Write(memoryStream); 43 | memoryStream.Seek(0, SeekOrigin.Begin); 44 | 45 | Assembly? loadedAssembly = Assembly.Load(memoryStream.ToArray()); 46 | Type? type = loadedAssembly.GetType("Cosmos.NativeWrapper.NativeWrapperObject"); 47 | Assert.NotNull(type); 48 | 49 | object? instance = Activator.CreateInstance(type); 50 | MethodInfo? method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); 51 | Assert.NotNull(method); 52 | 53 | return method.Invoke(instance, parameters); 54 | } 55 | 56 | private object ExecutePropertyGet(AssemblyDefinition assemblyDefinition, string typeName, string propertyName) 57 | { 58 | using MemoryStream? memoryStream = new(); 59 | assemblyDefinition.Write(memoryStream); 60 | memoryStream.Seek(0, SeekOrigin.Begin); 61 | 62 | Assembly? loadedAssembly = Assembly.Load(memoryStream.ToArray()); 63 | Type? type = loadedAssembly.GetType(typeName); 64 | Assert.NotNull(type); 65 | 66 | object? instance = Activator.CreateInstance(type); 67 | MethodInfo? method = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance).GetMethod; 68 | 69 | Assert.NotNull(method); 70 | 71 | return method.Invoke(instance, null); 72 | } 73 | 74 | [Fact] 75 | public void PatchConstructor_ShouldPlugCtorCorrectly() 76 | { 77 | // Arrange 78 | PlugScanner? plugScanner = new(); 79 | PlugPatcher? patcher = new(plugScanner); 80 | 81 | AssemblyDefinition? targetAssembly = CreateMockAssembly(); 82 | AssemblyDefinition? plugAssembly = CreateMockAssembly(); 83 | 84 | TypeDefinition? targetType = 85 | targetAssembly.MainModule.Types.FirstOrDefault(t => t.Name == nameof(NativeWrapperObject)); 86 | Assert.NotNull(targetType); 87 | 88 | // Act: Apply the plug 89 | patcher.PatchAssembly(targetAssembly, plugAssembly); 90 | 91 | targetAssembly.Save("./", "targetCtorAssembly.dll"); 92 | 93 | using StringWriter? stringWriter = new(); 94 | Console.SetOut(stringWriter); 95 | 96 | object? instance = ExecuteConstructor(targetAssembly, "NativeWrapperObject"); 97 | 98 | // Assert: Check the standard output for the constructor plug 99 | string? output = stringWriter.ToString(); 100 | Assert.Contains("Plugged ctor", output); 101 | } 102 | 103 | [Fact] 104 | public void PatchProperty_ShouldPlugProperty() 105 | { 106 | TextWriter originalOutput = Console.Out; // Store the original output 107 | try 108 | { 109 | using var stringWriter = new StringWriter(); 110 | Console.SetOut(stringWriter); 111 | 112 | PlugScanner plugScanner = new(); 113 | PlugPatcher patcher = new(plugScanner); 114 | 115 | AssemblyDefinition targetAssembly = CreateMockAssembly(); 116 | AssemblyDefinition plugAssembly = CreateMockAssembly(); 117 | 118 | TypeDefinition targetType = 119 | targetAssembly.MainModule.Types.FirstOrDefault(t => t.Name == nameof(NativeWrapperObject)); 120 | Assert.NotNull(targetType); 121 | 122 | patcher.PatchAssembly(targetAssembly, plugAssembly); 123 | targetAssembly.Save("./", "targetPropertyAssembly.dll"); 124 | 125 | // Test Get 126 | object getResult = ExecutePropertyGet(targetAssembly, typeof(NativeWrapperObject).FullName, "InstanceProperty"); 127 | Assert.Equal("Plugged Goodbye World", getResult); 128 | 129 | // Test Get 130 | object getResult2 = ExecutePropertyGet(targetAssembly, typeof(NativeWrapperObject).FullName, "InstanceBackingFieldProperty"); 131 | Assert.Equal("Plugged Backing Field", getResult2); 132 | } 133 | finally 134 | { 135 | // Restore the original output 136 | Console.SetOut(originalOutput); 137 | } 138 | } 139 | 140 | private object ExecuteConstructor(AssemblyDefinition assemblyDefinition, string typeName) 141 | { 142 | using MemoryStream? memoryStream = new(); 143 | assemblyDefinition.Write(memoryStream); 144 | memoryStream.Seek(0, SeekOrigin.Begin); 145 | 146 | Assembly? loadedAssembly = Assembly.Load(memoryStream.ToArray()); 147 | Type? type = loadedAssembly.GetType("Cosmos.NativeWrapper." + typeName); 148 | Assert.NotNull(type); 149 | 150 | return Activator.CreateInstance(type); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/Cosmos.Patcher.Tests/PlugPatcherTest_StaticPlugs.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Cosmos.API.Attributes; 3 | using Cosmos.NativeWrapper; 4 | using Mono.Cecil; 5 | 6 | namespace Cosmos.Patcher.Tests; 7 | 8 | public class PlugPatcherTest_StaticPlugs 9 | { 10 | private AssemblyDefinition CreateMockAssembly() 11 | { 12 | string? assemblyPath = typeof(T).Assembly.Location; 13 | return AssemblyDefinition.ReadAssembly(assemblyPath); 14 | } 15 | 16 | [Fact] 17 | public void PatchType_ShouldReplaceAllMethodsCorrectly() 18 | { 19 | // Arrange 20 | PlugScanner plugScanner = new(); 21 | PlugPatcher patcher = new(plugScanner); 22 | 23 | AssemblyDefinition targetAssembly = CreateMockAssembly(); 24 | TypeDefinition targetType = targetAssembly.MainModule.Types.First(t => t.Name == nameof(TestClass)); 25 | 26 | AssemblyDefinition plugAssembly = CreateMockAssembly(); 27 | TypeDefinition plugType = plugAssembly.MainModule.Types.First(t => t.Name == nameof(TestClassPlug)); 28 | 29 | // Act 30 | patcher.PatchType(targetType, plugAssembly); 31 | 32 | // Assert 33 | foreach (MethodDefinition plugMethod in plugType.Methods) 34 | { 35 | //TODO: Only evaluate methods with PlugMemberAttribute 36 | if (!plugMethod.IsPublic || !plugMethod.IsStatic || !plugMethod.IsConstructor) 37 | { 38 | continue; 39 | } 40 | 41 | MethodDefinition targetMethod = targetType.Methods.FirstOrDefault(m => m.Name == plugMethod.Name); 42 | Assert.NotNull(targetMethod); // Ensure the method exists in the target type 43 | Assert.NotNull(targetMethod.Body); // Ensure the method has a body 44 | Assert.Equal(plugMethod.Body.Instructions.Count, targetMethod.Body.Instructions.Count); // Ensure the method body matches 45 | } 46 | } 47 | 48 | [Fact] 49 | public void PatchType_ShouldPlugAssembly() 50 | { 51 | // Arrange 52 | PlugScanner plugScanner = new(); 53 | PlugPatcher patcher = new(plugScanner); 54 | 55 | AssemblyDefinition targetAssembly = CreateMockAssembly(); 56 | AssemblyDefinition plugAssembly = CreateMockAssembly(); 57 | 58 | // Act 59 | patcher.PatchAssembly(targetAssembly, plugAssembly); 60 | 61 | // Assert 62 | TypeDefinition targetType = targetAssembly.MainModule.Types.FirstOrDefault(t => t.Name == nameof(TestClass)); 63 | TypeDefinition plugType = plugAssembly.MainModule.Types.FirstOrDefault(t => t.Name == nameof(TestClassPlug)); 64 | 65 | Assert.NotNull(targetType); // Ensure the target type exists 66 | Assert.NotNull(plugType); // Ensure the plug type exists 67 | 68 | foreach (MethodDefinition plugMethod in plugType.Methods) 69 | { 70 | //TODO: Only evaluate methods with PlugMemberAttribute 71 | if (!plugMethod.IsPublic || !plugMethod.IsStatic || !plugMethod.IsConstructor) 72 | { 73 | continue; 74 | } 75 | 76 | MethodDefinition targetMethod = targetType.Methods.FirstOrDefault(m => m.Name == plugMethod.Name); 77 | Assert.NotNull(targetMethod); // Ensure the method exists in the target type 78 | Assert.NotNull(targetMethod.Body); // Ensure the method has a body 79 | Assert.Equal(plugMethod.Body.Instructions.Count, targetMethod.Body.Instructions.Count); // Ensure the method body matches 80 | } 81 | } 82 | 83 | [Fact] 84 | public void AddMethod_BehaviorBeforeAndAfterPlug() 85 | { 86 | // Arrange 87 | PlugScanner? plugScanner = new(); 88 | PlugPatcher? patcher = new(plugScanner); 89 | 90 | AssemblyDefinition? targetAssembly = CreateMockAssembly(); 91 | AssemblyDefinition? plugAssembly = CreateMockAssembly(); 92 | 93 | // Act 94 | patcher.PatchAssembly(targetAssembly, plugAssembly); 95 | 96 | targetAssembly.Save("./", "targetAssembly.dll"); 97 | 98 | object? result = ExecuteObject(targetAssembly, "TestClass", "Add", 3, 4); 99 | Assert.Equal(12, result); 100 | } 101 | 102 | private object ExecuteObject(AssemblyDefinition assemblyDefinition, string typeName, string methodName, 103 | params object[] parameters) 104 | { 105 | using MemoryStream? memoryStream = new(); 106 | assemblyDefinition.Write(memoryStream); 107 | memoryStream.Seek(0, SeekOrigin.Begin); 108 | 109 | Assembly? loadedAssembly = Assembly.Load(memoryStream.ToArray()); 110 | Type? type = loadedAssembly.GetType("Cosmos.NativeWrapper.TestClass"); 111 | Assert.NotNull(type); 112 | MethodInfo? method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); 113 | 114 | Assert.NotNull(method); 115 | 116 | return (int)method.Invoke(null, parameters); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/Cosmos.Scanner.Tests/Cosmos.Scanner.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/Cosmos.Scanner.Tests/PlugScannerTests_LoadPlugMethods.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.NativeWrapper; 2 | using Mono.Cecil; 3 | 4 | namespace Cosmos.Patcher.Tests; 5 | 6 | public class PlugScannerTests_LoadPlugMethods 7 | { 8 | private AssemblyDefinition CreateMockAssembly() 9 | { 10 | string assemblyPath = typeof(T).Assembly.Location; 11 | return AssemblyDefinition.ReadAssembly(assemblyPath); 12 | } 13 | 14 | [Fact] 15 | public void LoadPlugMethods_ShouldReturnPublicStaticMethods() 16 | { 17 | // Arrange 18 | AssemblyDefinition assembly = CreateMockAssembly(); 19 | PlugScanner scanner = new(); 20 | TypeDefinition? plugType = assembly.MainModule.Types.First(t => t.Name == nameof(MockPlugWithMethods)); 21 | 22 | // Act 23 | List methods = scanner.LoadPlugMethods(plugType); 24 | 25 | // Assert 26 | Assert.NotEmpty(methods); 27 | Assert.Contains(methods, method => method.Name == nameof(MockPlugWithMethods.StaticMethod)); 28 | Assert.DoesNotContain(methods, method => method.Name == nameof(MockPlugWithMethods.InstanceMethod)); 29 | } 30 | 31 | [Fact] 32 | public void LoadPlugMethods_ShouldReturnEmpty_WhenNoMethodsExist() 33 | { 34 | // Arrange 35 | AssemblyDefinition assembly = CreateMockAssembly(); 36 | PlugScanner scanner = new(); 37 | TypeDefinition? plugType = assembly.MainModule.Types.First(t => t.Name == nameof(EmptyPlug)); 38 | 39 | // Act 40 | List methods = scanner.LoadPlugMethods(plugType); 41 | 42 | // Assert 43 | Assert.Empty(methods); 44 | } 45 | 46 | [Fact] 47 | public void LoadPlugMethods_ShouldContainAddMethod_WhenPlugged() 48 | { 49 | // Arrange 50 | AssemblyDefinition assembly = CreateMockAssembly(); 51 | PlugScanner scanner = new(); 52 | TypeDefinition? plugType = assembly.MainModule.Types.First(t => t.Name == nameof(TestClassPlug)); 53 | 54 | // Act 55 | List methods = scanner.LoadPlugMethods(plugType); 56 | 57 | // Assert 58 | Assert.Contains(methods, method => method.Name == "Add"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Cosmos.Scanner.Tests/PlugScannerTests_LoadPlugs.cs: -------------------------------------------------------------------------------- 1 | using Cosmos.API.Attributes; 2 | using Cosmos.NativeWrapper; 3 | using Mono.Cecil; 4 | using Mono.Collections.Generic; 5 | 6 | namespace Cosmos.Patcher.Tests; 7 | 8 | public class PlugScannerTests_LoadPlugs 9 | { 10 | private AssemblyDefinition CreateMockAssembly() 11 | { 12 | string? assemblyPath = typeof(T).Assembly.Location; 13 | return AssemblyDefinition.ReadAssembly(assemblyPath); 14 | } 15 | 16 | [Fact] 17 | public void LoadPlugs_ShouldFindPluggedClasses() 18 | { 19 | // Arrange 20 | AssemblyDefinition? assembly = CreateMockAssembly(); 21 | PlugScanner? scanner = new(); 22 | 23 | // Act 24 | List? plugs = scanner.LoadPlugs(assembly); 25 | 26 | // Assert 27 | Assert.Contains(plugs, plug => plug.Name == nameof(MockPlug)); 28 | TypeDefinition? plug = plugs.FirstOrDefault(p => p.Name == nameof(MockPlug)); 29 | Assert.NotNull(plug); 30 | 31 | Collection? customAttributes = plug.CustomAttributes; 32 | Assert.Contains(customAttributes, attr => attr.AttributeType.FullName == typeof(PlugAttribute).FullName); 33 | CustomAttribute? plugAttribute = 34 | customAttributes.First(attr => attr.AttributeType.FullName == typeof(PlugAttribute).FullName); 35 | 36 | string? expected = typeof(MockTarget).FullName; 37 | string? actual = plugAttribute.ConstructorArguments[0].Value?.ToString(); 38 | Assert.Equal(expected, actual); 39 | } 40 | 41 | [Fact] 42 | public void LoadPlugs_ShouldIgnoreClassesWithoutPlugAttribute() 43 | { 44 | // Arrange 45 | AssemblyDefinition? assembly = CreateMockAssembly(); 46 | PlugScanner? scanner = new(); 47 | 48 | // Act 49 | List? plugs = scanner.LoadPlugs(assembly); 50 | 51 | // Assert 52 | Assert.DoesNotContain(plugs, plug => plug.Name == nameof(NonPlug)); 53 | } 54 | 55 | [Fact] 56 | public void LoadPlugs_ShouldHandleOptionalPlugs() 57 | { 58 | // Arrange 59 | AssemblyDefinition? assembly = CreateMockAssembly(); 60 | PlugScanner? scanner = new(); 61 | 62 | // Act 63 | List? plugs = scanner.LoadPlugs(assembly); 64 | TypeDefinition? optionalPlug = plugs.FirstOrDefault(p => p.Name == nameof(OptionalPlug)); 65 | 66 | // Assert 67 | Assert.NotNull(optionalPlug); 68 | 69 | Collection? customAttributes = optionalPlug.CustomAttributes; 70 | Assert.Contains(customAttributes, attr => attr.AttributeType.FullName == typeof(PlugAttribute).FullName); 71 | CustomAttribute? plugAttribute = 72 | customAttributes.First(attr => attr.AttributeType.FullName == typeof(PlugAttribute).FullName); 73 | Assert.Equal("OptionalTarget", plugAttribute.ConstructorArguments[0].Value); 74 | Assert.True((bool)plugAttribute.Properties.FirstOrDefault(p => p.Name == "IsOptional").Argument.Value); 75 | } 76 | } 77 | --------------------------------------------------------------------------------