diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..9dada3a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,34 @@
+name: CI
+
+# Fast, cross-platform continuous integration that runs on every push and pull
+# request (including feature branches). It complements the Windows-only
+# "Build Data Center CoreCLR" workflow by running the unit tests, which do not
+# require Unity or IL2CPP assemblies.
+
+on:
+ push:
+ pull_request:
+
+concurrency:
+ group: ci-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ test:
+ name: Unit tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ - name: Restore
+ run: dotnet restore tests/UnityExplorer.Tests/UnityExplorer.Tests.csproj
+
+ - name: Test
+ run: dotnet test tests/UnityExplorer.Tests/UnityExplorer.Tests.csproj --configuration Release --no-restore --verbosity normal
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 034f8f5..43cb532 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,14 @@ All notable changes to this Data Center-focused UnityExplorer fork are documente
### English
+- Repository housekeeping: fixed the corrupted (mojibake) `src/.editorconfig` and rewrote it as clean UTF-8.
+- Aligned the Unity editor package version (`UnityEditorPackage/package.json`) with the assembly version `4.13.5`.
+- Documented the version scheme: `4.13.5` is the upstream UnityExplorer base; the fork release series (`v1.x`) lives in this changelog.
+- Pinned the .NET SDK via `global.json` for reproducible builds.
+- Unified `Microsoft.Unity.Analyzers` to `1.19.0` across both projects.
+- Added a `CONTRIBUTING.md` with branch, build and style guidance.
+- Added a pull-request CI workflow that compiles the Data Center CoreCLR target and runs unit tests.
+- Added a first unit-test project (`tests/UnityExplorer.Tests`) covering platform-independent utility logic.
- Added a support package script for collecting Data Center logs, file hashes and detected runtime versions.
- Improved `install-datacenter.ps1` with dry-run, wait-for-exit, MelonLoader preflight and duplicate-file warnings.
- Added a manual GitHub Actions release workflow for Data Center CoreCLR builds.
@@ -18,6 +26,14 @@ All notable changes to this Data Center-focused UnityExplorer fork are documente
### Deutsch
+- Repository-Aufräumarbeiten: beschädigte (Mojibake-)`src/.editorconfig` behoben und als sauberes UTF-8 neu geschrieben.
+- Version des Unity-Editor-Pakets (`UnityEditorPackage/package.json`) an die Assembly-Version `4.13.5` angeglichen.
+- Versionsschema dokumentiert: `4.13.5` ist die UnityExplorer-Upstream-Basis; die Fork-Release-Serie (`v1.x`) steht in diesem Changelog.
+- .NET-SDK über `global.json` für reproduzierbare Builds gepinnt.
+- `Microsoft.Unity.Analyzers` in beiden Projekten auf `1.19.0` vereinheitlicht.
+- `CONTRIBUTING.md` mit Branch-, Build- und Style-Hinweisen ergänzt.
+- Pull-Request-CI-Workflow ergänzt, der das Data-Center-CoreCLR-Ziel kompiliert und Unit-Tests ausführt.
+- Erstes Unit-Test-Projekt (`tests/UnityExplorer.Tests`) für plattformunabhängige Utility-Logik hinzugefügt.
- Support-Paket-Skript zum Sammeln von Data-Center-Logs, Datei-Hashes und erkannten Runtime-Versionen hinzugefügt.
- `install-datacenter.ps1` mit Dry-Run, Warten auf Spielende, MelonLoader-Vorprüfung und Warnungen vor doppelten Dateien verbessert.
- Manuellen GitHub-Actions-Release-Workflow für Data-Center-CoreCLR-Builds hinzugefügt.
@@ -28,6 +44,20 @@ All notable changes to this Data Center-focused UnityExplorer fork are documente
- Lokale zweisprachige Installations- und Troubleshooting-Dokumente für Nutzer ohne Wiki ergänzt.
- Doppelte GitHub-Issue-Vorlagen bereinigt und den Kontaktlink zur Installationsanleitung korrigiert.
+### Dependency updates (pending in-game verification) / Dependency-Updates (In-Game-Verifikation ausstehend)
+
+> These bumps were prepared as isolated commits and have **not** been compiled or
+> tested against Data Center yet. Verify each in-game (see `CONTRIBUTING.md`)
+> before releasing; revert individually if a game regression appears.
+>
+> Diese Bumps wurden als isolierte Commits vorbereitet und sind noch **nicht**
+> kompiliert oder gegen Data Center getestet. Vor einem Release jeweils im Spiel
+> verifizieren (siehe `CONTRIBUTING.md`); bei einer Regression einzeln zurückrollen.
+
+- **Il2CppInterop `1.0.0` → `1.5.1`** (`src` and `UniverseLib`, Interop/CoreCLR configs) to match the Il2CppInterop runtime bundled with MelonLoader 0.7.3 (`1.5.1-ci.845`).
+- **HarmonyX `2.5.2` → `2.14.0`** (`src` and `UniverseLib`, compile-only). Runtime patching still uses MelonLoader's force-resolved `0Harmony.dll`; only the compile-time API surface changes.
+- **Tomlet `3.1.3` → `3.2.2`** (`src`, bundled with the mod). Stays within major `3.x` so the config code (`TomlParser.ParseFile`, `TomlDocument.Put(key, value, bool)`) keeps working; `4.x`/`6.x` are deferred as they change that API.
+
## v1.0.1 - CoreCLR build cleanup
### English
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..6370ef8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,165 @@
+# Contributing / Mitwirken
+
+Thanks for your interest in this Data Center-focused UnityExplorer fork!
+This document is bilingual: **English first, Deutsch darunter.**
+
+---
+
+## English
+
+### Scope of this fork
+
+This repository is a compatibility fork of UnityExplorer, specialized for the
+Steam game **Data Center** (Unity 6000, IL2CPP, MelonLoader CoreCLR / net6).
+Changes should keep this target working. Features that only make sense for other
+games or loaders are welcome only if they do not destabilize the Data Center path.
+
+### Branching
+
+- The default branch is `main`.
+- Do **not** commit directly to `main`. Create a feature branch, e.g.
+ `feature/short-description` or `fix/short-description`, and open a pull request.
+- Keep one logical change per pull request where possible.
+
+### Building
+
+Prerequisites: Windows, PowerShell, and the .NET SDK. The SDK version is pinned
+in [`global.json`](global.json); install a matching 8.0.x SDK.
+
+- **Data Center target only** (fastest, what most users need):
+
+ ```powershell
+ .\build-datacenter.ps1
+ ```
+
+ This builds the `ML_Cpp_CoreCLR` configuration and produces
+ `Release\UnityExplorer.MelonLoader.IL2CPP.CoreCLR.zip` plus
+ `Release\CHECKSUMS_SHA256.txt`.
+
+- **All configurations** (full upstream build matrix):
+
+ ```powershell
+ .\build.ps1
+ ```
+
+- **Install into a local Data Center install** for testing:
+
+ ```powershell
+ .\install-datacenter.ps1 -BuildIfMissing
+ ```
+
+### Tests
+
+Platform-independent logic is covered by the test project in
+[`tests/`](tests). Run:
+
+```powershell
+dotnet test tests/UnityExplorer.Tests/UnityExplorer.Tests.csproj
+```
+
+UI/runtime behavior that depends on Unity or IL2CPP cannot be unit-tested and
+must be verified in-game (see below).
+
+### Code style
+
+- Style is enforced by [`src/.editorconfig`](src/.editorconfig) and the
+ `Microsoft.Unity.Analyzers` package. Please keep new code warning-clean.
+- Match the surrounding code: naming, brace style, and the existing
+ `#if CPP / MONO / INTEROP / UNHOLLOWER` conditional-compilation patterns.
+
+### Verifying a change in-game
+
+Dependency and runtime changes **must** be smoke-tested against Data Center
+before merging:
+
+1. Build with `build-datacenter.ps1` and install the resulting mod.
+2. Launch the game and open the UnityExplorer UI.
+3. Confirm the Object Explorer lists objects, the C# console evaluates an
+ expression, the Inspector opens, and a hook can be created.
+
+### Pull request expectations
+
+- Describe what changed and why, and note whether you tested in-game.
+- Update [`CHANGELOG.md`](CHANGELOG.md) under `## Unreleased` (bilingual entries).
+- Bump versions only when releasing (see the version scheme note in `CHANGELOG.md`).
+
+---
+
+## Deutsch
+
+### Ziel dieses Forks
+
+Dieses Repository ist ein Kompatibilitäts-Fork von UnityExplorer, spezialisiert
+auf das Steam-Spiel **Data Center** (Unity 6000, IL2CPP, MelonLoader CoreCLR /
+net6). Änderungen sollen dieses Ziel lauffähig halten. Funktionen, die nur für
+andere Spiele oder Loader sinnvoll sind, sind nur willkommen, wenn sie den
+Data-Center-Pfad nicht destabilisieren.
+
+### Branches
+
+- Der Standard-Branch ist `main`.
+- **Nicht** direkt auf `main` committen. Lege einen Feature-Branch an, z. B.
+ `feature/kurzbeschreibung` oder `fix/kurzbeschreibung`, und öffne einen Pull Request.
+- Möglichst eine logische Änderung pro Pull Request.
+
+### Bauen
+
+Voraussetzungen: Windows, PowerShell und das .NET SDK. Die SDK-Version ist in
+[`global.json`](global.json) gepinnt; installiere ein passendes 8.0.x-SDK.
+
+- **Nur Data-Center-Ziel** (am schnellsten, für die meisten Nutzer ausreichend):
+
+ ```powershell
+ .\build-datacenter.ps1
+ ```
+
+ Baut die Konfiguration `ML_Cpp_CoreCLR` und erzeugt
+ `Release\UnityExplorer.MelonLoader.IL2CPP.CoreCLR.zip` plus
+ `Release\CHECKSUMS_SHA256.txt`.
+
+- **Alle Konfigurationen** (vollständige Upstream-Build-Matrix):
+
+ ```powershell
+ .\build.ps1
+ ```
+
+- **In eine lokale Data-Center-Installation installieren** (zum Testen):
+
+ ```powershell
+ .\install-datacenter.ps1 -BuildIfMissing
+ ```
+
+### Tests
+
+Plattformunabhängige Logik wird durch das Testprojekt in [`tests/`](tests)
+abgedeckt. Ausführen:
+
+```powershell
+dotnet test tests/UnityExplorer.Tests/UnityExplorer.Tests.csproj
+```
+
+UI-/Laufzeitverhalten, das von Unity oder IL2CPP abhängt, lässt sich nicht per
+Unit-Test prüfen und muss im Spiel verifiziert werden (siehe unten).
+
+### Code-Stil
+
+- Der Stil wird durch [`src/.editorconfig`](src/.editorconfig) und das Paket
+ `Microsoft.Unity.Analyzers` erzwungen. Bitte neuen Code warnungsfrei halten.
+- Am umgebenden Code orientieren: Benennung, Klammer-Stil und die bestehenden
+ `#if CPP / MONO / INTEROP / UNHOLLOWER`-Muster der bedingten Kompilierung.
+
+### Änderung im Spiel verifizieren
+
+Dependency- und Laufzeitänderungen **müssen** vor dem Merge gegen Data Center
+smoke-getestet werden:
+
+1. Mit `build-datacenter.ps1` bauen und das resultierende Mod installieren.
+2. Spiel starten und die UnityExplorer-UI öffnen.
+3. Prüfen, dass der Object Explorer Objekte listet, die C#-Konsole einen
+ Ausdruck auswertet, der Inspector öffnet und ein Hook erstellt werden kann.
+
+### Erwartungen an Pull Requests
+
+- Beschreibe, was sich geändert hat und warum, und ob du im Spiel getestet hast.
+- Aktualisiere [`CHANGELOG.md`](CHANGELOG.md) unter `## Unreleased` (zweisprachige Einträge).
+- Versionen nur beim Release erhöhen (siehe Versionsschema-Hinweis in `CHANGELOG.md`).
diff --git a/UnityEditorPackage/package.json b/UnityEditorPackage/package.json
index 2047403..498eb86 100644
--- a/UnityEditorPackage/package.json
+++ b/UnityEditorPackage/package.json
@@ -1,6 +1,6 @@
{
"name": "com.sinai-dev.unityexplorer",
- "version": "4.12.7",
+ "version": "4.13.5",
"displayName": "UnityExplorer",
"description": "An in-game UI for exploring, debugging and modifying Unity games.",
"unity": "2017.1",
diff --git a/UniverseLib/src/UniverseLib.csproj b/UniverseLib/src/UniverseLib.csproj
index dd46c6f..547513b 100644
--- a/UniverseLib/src/UniverseLib.csproj
+++ b/UniverseLib/src/UniverseLib.csproj
@@ -48,8 +48,8 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -59,8 +59,8 @@
-
-
+
+
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..391ba3c
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "8.0.100",
+ "rollForward": "latestFeature"
+ }
+}
diff --git a/src/.editorconfig b/src/.editorconfig
index ee8e7c9..bf9e87a 100644
--- a/src/.editorconfig
+++ b/src/.editorconfig
@@ -1,8 +1,8 @@
[*.cs]
-#### �����X�^�C�� ####
+#### Naming styles ####
-# ���O�t�����[��
+# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
@@ -16,36 +16,32 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
-# �L���̎d�l
+# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.interface.required_modifiers =
+dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.types.required_modifiers =
+dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.non_field_members.required_modifiers =
+dotnet_naming_symbols.non_field_members.required_modifiers =
-# �����X�^�C��
+# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
-dotnet_naming_style.begins_with_i.required_suffix =
-dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
-dotnet_naming_style.pascal_case.required_prefix =
-dotnet_naming_style.pascal_case.required_suffix =
-dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
-dotnet_naming_style.pascal_case.required_prefix =
-dotnet_naming_style.pascal_case.required_suffix =
-dotnet_naming_style.pascal_case.word_separator =
-dotnet_naming_style.pascal_case.capitalization = pascal_case
csharp_using_directive_placement = outside_namespace:silent
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_var_for_built_in_types = false:error
@@ -107,54 +103,6 @@ csharp_style_prefer_readonly_struct_member = true:suggestion
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
-[*.vb]
-#### �����X�^�C�� ####
-
-# ���O�t�����[��
-
-dotnet_naming_rule.interface_should_be_i_�Ŏn�܂�.severity = suggestion
-dotnet_naming_rule.interface_should_be_i_�Ŏn�܂�.symbols = interface
-dotnet_naming_rule.interface_should_be_i_�Ŏn�܂�.style = i_�Ŏn�܂�
-
-dotnet_naming_rule.�^_should_be_�p�X�J��_�P�[�X.severity = suggestion
-dotnet_naming_rule.�^_should_be_�p�X�J��_�P�[�X.symbols = �^
-dotnet_naming_rule.�^_should_be_�p�X�J��_�P�[�X.style = �p�X�J��_�P�[�X
-
-dotnet_naming_rule.�t�B�[���h�ȊO�̃����o�[_should_be_�p�X�J��_�P�[�X.severity = suggestion
-dotnet_naming_rule.�t�B�[���h�ȊO�̃����o�[_should_be_�p�X�J��_�P�[�X.symbols = �t�B�[���h�ȊO�̃����o�[
-dotnet_naming_rule.�t�B�[���h�ȊO�̃����o�[_should_be_�p�X�J��_�P�[�X.style = �p�X�J��_�P�[�X
-
-# �L���̎d�l
-
-dotnet_naming_symbols.interface.applicable_kinds = interface
-dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
-dotnet_naming_symbols.interface.required_modifiers =
-
-dotnet_naming_symbols.�^.applicable_kinds = class, struct, interface, enum
-dotnet_naming_symbols.�^.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
-dotnet_naming_symbols.�^.required_modifiers =
-
-dotnet_naming_symbols.�t�B�[���h�ȊO�̃����o�[.applicable_kinds = property, event, method
-dotnet_naming_symbols.�t�B�[���h�ȊO�̃����o�[.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
-dotnet_naming_symbols.�t�B�[���h�ȊO�̃����o�[.required_modifiers =
-
-# �����X�^�C��
-
-dotnet_naming_style.i_�Ŏn�܂�.required_prefix = I
-dotnet_naming_style.i_�Ŏn�܂�.required_suffix =
-dotnet_naming_style.i_�Ŏn�܂�.word_separator =
-dotnet_naming_style.i_�Ŏn�܂�.capitalization = pascal_case
-
-dotnet_naming_style.�p�X�J��_�P�[�X.required_prefix =
-dotnet_naming_style.�p�X�J��_�P�[�X.required_suffix =
-dotnet_naming_style.�p�X�J��_�P�[�X.word_separator =
-dotnet_naming_style.�p�X�J��_�P�[�X.capitalization = pascal_case
-
-dotnet_naming_style.�p�X�J��_�P�[�X.required_prefix =
-dotnet_naming_style.�p�X�J��_�P�[�X.required_suffix =
-dotnet_naming_style.�p�X�J��_�P�[�X.word_separator =
-dotnet_naming_style.�p�X�J��_�P�[�X.capitalization = pascal_case
-
[*.{cs,vb}]
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
@@ -193,5 +141,3 @@ dotnet_style_predefined_type_for_member_access = true:silent
dotnet_diagnostic.CA1836.severity = warning
dotnet_diagnostic.CA1852.severity = suggestion
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
-
-
diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs
index aa9e41e..e4dba81 100644
--- a/src/ExplorerCore.cs
+++ b/src/ExplorerCore.cs
@@ -21,6 +21,9 @@ namespace UnityExplorer;
public static class ExplorerCore
{
public const string NAME = "UnityExplorer";
+ // Upstream UnityExplorer base version this fork is built on. This value is
+ // logged at runtime and drives the assembly version. The Data Center fork's
+ // own release series (v1.x) is tracked separately in CHANGELOG.md.
public const string VERSION = "4.13.5";
public const string AUTHOR = "Sinai, yukieiji";
public const string GUID = "com.sinai.unityexplorer";
diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj
index 409c5e3..8a325dc 100644
--- a/src/UnityExplorer.csproj
+++ b/src/UnityExplorer.csproj
@@ -134,8 +134,8 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -153,8 +153,8 @@
-
-
+
+
diff --git a/tests/UnityExplorer.Tests/IOUtilityTests.cs b/tests/UnityExplorer.Tests/IOUtilityTests.cs
new file mode 100644
index 0000000..31038b8
--- /dev/null
+++ b/tests/UnityExplorer.Tests/IOUtilityTests.cs
@@ -0,0 +1,31 @@
+using System.IO;
+using System.Linq;
+using UniverseLib.Utility;
+using Xunit;
+
+namespace UnityExplorer.Tests
+{
+ public class IOUtilityTests
+ {
+ [Fact]
+ public void EnsureValidFilename_StripsInvalidCharacters()
+ {
+ char[] invalid = Path.GetInvalidFileNameChars();
+ // Skip on the unusual platform where no characters are considered invalid.
+ if (invalid.Length == 0)
+ return;
+
+ string dirty = "sa" + invalid[0] + "ve" + invalid[invalid.Length - 1] + "d.log";
+ string cleaned = IOUtility.EnsureValidFilename(dirty);
+
+ Assert.DoesNotContain(cleaned, c => invalid.Contains(c));
+ Assert.Equal("saved.log", cleaned);
+ }
+
+ [Fact]
+ public void EnsureValidFilename_LeavesCleanNameUnchanged()
+ {
+ Assert.Equal("report_2026.txt", IOUtility.EnsureValidFilename("report_2026.txt"));
+ }
+ }
+}
diff --git a/tests/UnityExplorer.Tests/MiscUtilityTests.cs b/tests/UnityExplorer.Tests/MiscUtilityTests.cs
new file mode 100644
index 0000000..a867d2e
--- /dev/null
+++ b/tests/UnityExplorer.Tests/MiscUtilityTests.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Text;
+using UniverseLib.Utility;
+using Xunit;
+
+namespace UnityExplorer.Tests
+{
+ public class MiscUtilityTests
+ {
+ [Theory]
+ [InlineData("Hello World", "hello", true)]
+ [InlineData("Hello World", "WORLD", true)]
+ [InlineData("Hello World", "Hello World", true)]
+ [InlineData("Hello World", "", true)]
+ [InlineData("Hello World", "xyz", false)]
+ [InlineData("abc", "abcd", false)]
+ public void ContainsIgnoreCase_MatchesCaseInsensitively(string haystack, string needle, bool expected)
+ {
+ Assert.Equal(expected, haystack.ContainsIgnoreCase(needle));
+ }
+
+ [Flags]
+ private enum SampleFlags
+ {
+ None = 0,
+ A = 1,
+ B = 2,
+ C = 4,
+ }
+
+ // Called via the static class explicitly: Enum defines a built-in
+ // HasFlag(Enum) instance method, so instance-style calls would bind to
+ // the BCL rather than to MiscUtility's extension.
+ [Fact]
+ public void HasFlag_ReturnsTrue_WhenFlagPresent()
+ {
+ SampleFlags value = SampleFlags.A | SampleFlags.C;
+ Assert.True(MiscUtility.HasFlag(value, SampleFlags.A));
+ Assert.True(MiscUtility.HasFlag(value, SampleFlags.C));
+ Assert.True(MiscUtility.HasFlag(value, SampleFlags.A | SampleFlags.C));
+ }
+
+ [Fact]
+ public void HasFlag_ReturnsFalse_WhenFlagAbsent()
+ {
+ SampleFlags value = SampleFlags.A | SampleFlags.C;
+ Assert.False(MiscUtility.HasFlag(value, SampleFlags.B));
+ Assert.False(MiscUtility.HasFlag(value, SampleFlags.A | SampleFlags.B));
+ }
+
+ [Theory]
+ [InlineData("Hello World", "World", true)]
+ [InlineData("Hello World", "Hello", false)]
+ [InlineData("Hello", "Hello", true)]
+ [InlineData("Hi", "Hello", false)]
+ [InlineData("", "x", false)]
+ public void StringBuilderEndsWith_DetectsSuffix(string content, string suffix, bool expected)
+ {
+ StringBuilder sb = new StringBuilder(content);
+ Assert.Equal(expected, sb.EndsWith(suffix));
+ }
+ }
+}
diff --git a/tests/UnityExplorer.Tests/UnityExplorer.Tests.csproj b/tests/UnityExplorer.Tests/UnityExplorer.Tests.csproj
new file mode 100644
index 0000000..d677cfe
--- /dev/null
+++ b/tests/UnityExplorer.Tests/UnityExplorer.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8.0
+ disable
+ disable
+ false
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+