VarTextSecondary = new("--color-text-secondary");
diff --git a/Assets/com.IvanMurzak.meta b/Assets/com.IvanMurzak.meta
new file mode 100644
index 00000000..d5791038
--- /dev/null
+++ b/Assets/com.IvanMurzak.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cd76a1d460524d746bee1cf9a5a2151e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer.meta b/Assets/com.IvanMurzak/AI Game Dev Installer.meta
new file mode 100644
index 00000000..abd45873
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f9b8ea68f545b1442b81375cc40c1d22
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.Manifest.cs b/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.Manifest.cs
new file mode 100644
index 00000000..5a3368e9
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.Manifest.cs
@@ -0,0 +1,167 @@
+/*
+┌──────────────────────────────────────────────────────────────────┐
+│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
+│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
+│ Copyright (c) 2025 Ivan Murzak │
+│ Licensed under the Apache License, Version 2.0. │
+│ See the LICENSE file in the project root for more information. │
+└──────────────────────────────────────────────────────────────────┘
+*/
+#nullable enable
+using System.IO;
+using System.Linq;
+using UnityEngine;
+using com.IvanMurzak.Unity.MCP.Installer.SimpleJSON;
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("com.IvanMurzak.Unity.MCP.Installer.Tests")]
+namespace com.IvanMurzak.Unity.MCP.Installer
+{
+ public static partial class Installer
+ {
+ static string ManifestPath => Path.Combine(Application.dataPath, "../Packages/manifest.json");
+
+ // Property names
+ public const string Dependencies = "dependencies";
+ public const string ScopedRegistries = "scopedRegistries";
+ public const string Name = "name";
+ public const string Url = "url";
+ public const string Scopes = "scopes";
+
+ // Property values
+ public const string RegistryName = "package.openupm.com";
+ public const string RegistryUrl = "https://package.openupm.com";
+ public static readonly string[] PackageIds = new string[] {
+ "com.ivanmurzak", // Ivan Murzak's OpenUPM packages
+ "extensions.unity", // Ivan Murzak's OpenUPM packages (older)
+ "org.nuget.com.ivanmurzak", // Ivan Murzak's NuGet packages
+ "org.nuget.microsoft", // Microsoft NuGet packages
+ "org.nuget.system", // Microsoft NuGet packages
+ "org.nuget.r3" // R3 package NuGet package
+ };
+
+ ///
+ /// Determines if the version should be updated. Only update if installer version is higher than current version.
+ ///
+ /// Current package version string
+ /// Installer version string
+ /// True if version should be updated (installer version is higher), false otherwise
+
+ internal static bool ShouldUpdateVersion(string currentVersion, string installerVersion)
+ {
+ if (string.IsNullOrEmpty(currentVersion))
+ return true; // No current version, should install
+
+ if (string.IsNullOrEmpty(installerVersion))
+ return false; // No installer version, don't change
+
+ try
+ {
+ // Try to parse as System.Version (semantic versioning)
+ var current = new System.Version(currentVersion);
+ var installer = new System.Version(installerVersion);
+
+ // Only update if installer version is higher than current version
+ return installer > current;
+ }
+ catch (System.Exception)
+ {
+ Debug.LogWarning($"Failed to parse versions '{currentVersion}' or '{installerVersion}' as System.Version.");
+ // If version parsing fails, fall back to string comparison
+ // This ensures we don't break if version format is unexpected
+ return string.Compare(installerVersion, currentVersion, System.StringComparison.OrdinalIgnoreCase) > 0;
+ }
+ }
+
+ public static void AddScopedRegistryIfNeeded(string manifestPath, int indent = 2)
+ {
+ if (!File.Exists(manifestPath))
+ {
+ Debug.LogError($"{manifestPath} not found!");
+ return;
+ }
+ var jsonText = File.ReadAllText(manifestPath)
+ .Replace("{ }", "{\n}")
+ .Replace("{}", "{\n}")
+ .Replace("[ ]", "[\n]")
+ .Replace("[]", "[\n]");
+
+ var manifestJson = JSONObject.Parse(jsonText);
+ if (manifestJson == null)
+ {
+ Debug.LogError($"Failed to parse {manifestPath} as JSON.");
+ return;
+ }
+
+ var modified = false;
+
+ // --- Add scoped registries if needed
+ var scopedRegistries = manifestJson[ScopedRegistries];
+ if (scopedRegistries == null)
+ {
+ manifestJson[ScopedRegistries] = new JSONArray();
+ modified = true;
+ }
+
+ // --- Add OpenUPM registry if needed
+ var openUpmRegistry = scopedRegistries!.Linq
+ .Select(kvp => kvp.Value)
+ .Where(r => r.Linq
+ .Any(p => p.Key == Name && p.Value == RegistryName))
+ .FirstOrDefault();
+
+ if (openUpmRegistry == null)
+ {
+ scopedRegistries.Add(openUpmRegistry = new JSONObject
+ {
+ [Name] = RegistryName,
+ [Url] = RegistryUrl,
+ [Scopes] = new JSONArray()
+ });
+ modified = true;
+ }
+
+ // --- Add missing scopes
+ var scopes = openUpmRegistry[Scopes];
+ if (scopes == null)
+ {
+ openUpmRegistry[Scopes] = scopes = new JSONArray();
+ modified = true;
+ }
+ foreach (var packageId in PackageIds)
+ {
+ var existingScope = scopes!.Linq
+ .Select(kvp => kvp.Value)
+ .Where(value => value == packageId)
+ .FirstOrDefault();
+ if (existingScope == null)
+ {
+ scopes.Add(packageId);
+ modified = true;
+ }
+ }
+
+ // --- Package Dependency (Version-aware installation)
+ // Only update version if installer version is higher than current version
+ // This prevents downgrades when users manually update to newer versions
+ var dependencies = manifestJson[Dependencies];
+ if (dependencies == null)
+ {
+ manifestJson[Dependencies] = dependencies = new JSONObject();
+ modified = true;
+ }
+
+ // Only update version if installer version is higher than current version
+ var currentVersion = dependencies[PackageId];
+ if (currentVersion == null || ShouldUpdateVersion(currentVersion, Version))
+ {
+ dependencies[PackageId] = Version;
+ modified = true;
+ }
+
+ // --- Write changes back to manifest
+ if (modified)
+ File.WriteAllText(manifestPath, manifestJson.ToString(indent).Replace("\" : ", "\": "));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.Manifest.cs.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.Manifest.cs.meta
new file mode 100644
index 00000000..42a2d6d5
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.Manifest.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 888edfe7699cca7478a863e50be3566d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.cs b/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.cs
new file mode 100644
index 00000000..add5b934
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.cs
@@ -0,0 +1,28 @@
+/*
+┌──────────────────────────────────────────────────────────────────┐
+│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
+│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
+│ Copyright (c) 2025 Ivan Murzak │
+│ Licensed under the Apache License, Version 2.0. │
+│ See the LICENSE file in the project root for more information. │
+└──────────────────────────────────────────────────────────────────┘
+*/
+#nullable enable
+using UnityEditor;
+
+namespace com.IvanMurzak.Unity.MCP.Installer
+{
+ [InitializeOnLoad]
+ public static partial class Installer
+ {
+ public const string PackageId = "com.ivanmurzak.unity.mcp";
+ public const string Version = "0.48.1";
+
+ static Installer()
+ {
+#if !IVAN_MURZAK_INSTALLER_PROJECT
+ AddScopedRegistryIfNeeded(ManifestPath);
+#endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.cs.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.cs.meta
new file mode 100644
index 00000000..4658c219
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Installer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b81812993f383a34a950af84d081945d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/PackageExporter.cs b/Assets/com.IvanMurzak/AI Game Dev Installer/PackageExporter.cs
new file mode 100644
index 00000000..89b4f717
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/PackageExporter.cs
@@ -0,0 +1,27 @@
+using UnityEngine;
+using UnityEditor;
+using System.IO;
+
+namespace com.IvanMurzak.Unity.MCP.Installer
+{
+ public static class PackageExporter
+ {
+ public static void ExportPackage()
+ {
+ var packagePath = "Assets/com.IvanMurzak/AI Game Dev Installer";
+ var outputPath = "build/AI-Game-Dev-Installer.unitypackage";
+
+ // Ensure build directory exists
+ var buildDir = Path.GetDirectoryName(outputPath);
+ if (!Directory.Exists(buildDir))
+ {
+ Directory.CreateDirectory(buildDir);
+ }
+
+ // Export the package
+ AssetDatabase.ExportPackage(packagePath, outputPath, ExportPackageOptions.Recurse);
+
+ Debug.Log($"Package exported to: {outputPath}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/PackageExporter.cs.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/PackageExporter.cs.meta
new file mode 100644
index 00000000..5844408d
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/PackageExporter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b1b6d684272a1e24097921598fd2cd0e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/README.md b/Assets/com.IvanMurzak/AI Game Dev Installer/README.md
new file mode 100644
index 00000000..f04193b3
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/README.md
@@ -0,0 +1,289 @@
+
+
✨ AI Game Developer — Unity MCP
+
+[](https://hub.docker.com/r/ivanmurzakdev/unity-mcp-server)
+[](https://modelcontextprotocol.io/introduction)
+[](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml)
+[](https://u3d.as/3wsw)
+[](https://unity.com/releases/editor/archive)
+[](https://unity.com/releases/editor/archive)
+[](https://openupm.com/packages/com.ivanmurzak.unity.mcp/)
+[](https://discord.gg/cfbdMZX99G)
+[](https://github.com/IvanMurzak/Unity-MCP/stargazers)
+[](https://github.com/IvanMurzak/Unity-MCP/blob/main/LICENSE)
+[](https://stand-with-ukraine.pp.ua)
+
+AI helper which does wide range of tasks in Unity Editor and even in a running game compiled to any platform. It connects to AI using TCP connection, that is why it is so flexible.
+
+💬 **Join our community:** [Discord Server](https://discord.gg/cfbdMZX99G) - Ask questions, showcase your work, and connect with other developers!
+
+
+
+
+
+
+ Made with AI — samples (click to see)
+
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+  |
+  |
+
+
+
+
+
+## Features for a human
+
+- ✅ Chat with AI like with a human
+- ✅ Local and Remote usage supported
+- ✅ `stdio` and `streamableHttp` protocols supported
+- ✅ Wide range of default [AI tools](https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/ai-tools.md)
+- ✅ Use `Description` attribute in C# code to provide detailed information for `class`, `field`, `property` or `method`.
+- ✅ Customizable reflection convertors, inspired by `System.Text.Json` convertors
+ - do you have something extremely custom in your project? Make custom reflection convertor to let LLM be able to read and write into that data
+- ✅ Remote AI units setup using docker containers,
+ - make a team of AI workers which work on your project simultaneously
+
+## Features for LLM
+
+- ✅ Agent ready tools, find anything you need in 1-2 steps
+- ✅ Instant C# code compilation & execution using `Roslyn`, iterate faster
+- ✅ Assets access (read / write), C# scripts access (read / write)
+- ✅ Well described positive and negative feedback for proper understanding of an issue
+- ✅ Provide references to existed objects for the instant C# code using `Reflection`
+- ✅ Get full access to entire project data in a readable shape using `Reflection`
+- ✅ Populate & Modify any granular piece of data in the project using `Reflection`
+- ✅ Find any `method` in the entire codebase, including compiled DLL files using `Reflection`
+- ✅ Call any `method` in the entire codebase using `Reflection`
+- ✅ Provide any property into `method` call, even if it is a reference to existed object in memory using `Reflection` and advanced reflection convertors
+- ✅ Unity API instantly available for usage, even if Unity changes something you will get fresh API using `Reflection`.
+- ✅ Get access to human readable description of any `class`, `method`, `field`, `property` by reading it's `Description` attribute.
+
+### Stability status
+
+| Unity Version | Editmode | Playmode | Standalone |
+| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 2022.3.62f3 | [](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) |
+| 2023.2.22f1 | [](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) |
+| 6000.3.1f1 | [](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) | [](https://github.com/IvanMurzak/Unity-MCP/actions/workflows/release.yml) |
+
+## Requirements
+
+> [!IMPORTANT]
+> **Project path cannot contain spaces**
+>
+> - ✅ `C:/MyProjects/Project`
+> - ❌ `C:/My Projects/Project`
+
+### Install `MCP Client`
+
+Choose `MCP Client` you prefer, don't need to install all of them. This is will be your main chat window to talk with LLM.
+
+- [Claude Code](https://github.com/anthropics/claude-code)
+- [Claude Desktop](https://claude.ai/download)
+- [GitHub Copilot in VS Code](https://code.visualstudio.com/docs/copilot/overview)
+- [Cursor](https://www.cursor.com/)
+- [Windsurf](https://windsurf.com)
+- Any other supported
+
+> MCP protocol is quite universal, that is why you may any MCP client you prefer, it will work as smooth as anyone else. The only important thing, that the MCP client has to support dynamic tool update.
+
+# Installation
+
+## Step 1: Install `Unity Plugin`
+
+- **[⬇️ Download Installer](https://github.com/IvanMurzak/Unity-MCP/releases/download/0.17.2/AI-Game-Dev-Installer.unitypackage)**
+- **📂 Import installer into Unity project**
+ > - You may use double click on the file - Unity will open it
+ > - OR: You may open Unity Editor first, then click on `Assets/Import Package/Custom Package`, then choose the file
+
+
+ Alternative: Install Unity Plugin via OpenUPM
+
+- [Install OpenUPM-CLI](https://github.com/openupm/openupm-cli#installation)
+- Open command line in Unity project folder
+- Run the command
+
+```bash
+openupm add com.ivanmurzak.unity.mcp
+```
+
+
+
+## Step 2: Configure `MCP Client`
+
+### Automatic configuration
+
+- Open Unity project
+- Open `Window/AI Connector (Unity-MCP)`
+- Click `Configure` at your MCP client
+
+
+
+> If MCP client is not in the list, use the raw JSON below in the window, to inject it into your MCP client. Read instructions for your MCP client how to do that.
+
+### Manual configuration
+
+If Automatic configuration doesn't work for you for any reason. Use JSON from `AI Connector (Unity-MCP)` window to configure any `MCP Client` on your own.
+
+
+ Add Unity-MCP to Claude Code (Windows)
+
+ Replace `unityProjectPath` with your real project path
+
+ ```bash
+ claude mcp add Unity-MCP "/Library/mcp-server/win-x64/unity-mcp-server.exe" client-transport=stdio
+ ```
+
+
+
+
+ Add Unity-MCP to Claude Code (MacOS Apple-Silicon)
+
+ Replace `unityProjectPath` with your real project path
+
+ ```bash
+ claude mcp add Unity-MCP "/Library/mcp-server/osx-arm64/unity-mcp-server" client-transport=stdio
+ ```
+
+
+
+
+ Add Unity-MCP to Claude Code (MacOS Apple-Intel)
+
+ Replace `unityProjectPath` with your real project path
+
+ ```bash
+ claude mcp add Unity-MCP "/Library/mcp-server/osx-x64/unity-mcp-server" client-transport=stdio
+ ```
+
+
+
+
+ Add Unity-MCP to Claude Code (Linux x64)
+
+ Replace `unityProjectPath` with your real project path
+
+ ```bash
+ claude mcp add Unity-MCP "/Library/mcp-server/linux-x64/unity-mcp-server" client-transport=stdio
+ ```
+
+
+
+
+ Add Unity-MCP to Claude Code (Linux arm64)
+
+ Replace `unityProjectPath` with your real project path
+
+ ```bash
+ claude mcp add Unity-MCP "/Library/mcp-server/linux-arm64/unity-mcp-server" client-transport=stdio
+ ```
+
+
+
+---
+
+# Use AI
+
+Talk with AI (LLM) in your `MCP Client`. Ask it to do anything you want. As better you describe your task / idea - as better it will do the job.
+
+Some `MCP Clients` allow to chose different LLM models. Take an eye on it, some model may work much better.
+
+ ```text
+ Explain my scene hierarchy
+ ```
+
+ ```text
+ Create 3 cubes in a circle with radius 2
+ ```
+
+ ```text
+ Create metallic golden material and attach it to a sphere gameObject
+ ```
+
+> Make sure `Agent` mode is turned on in MCP client
+
+---
+
+# How it works
+
+**[Unity-MCP](https://github.com/IvanMurzak/Unity-MCP)** is a bridge between LLM and Unity. It exposes and explains to LLM Unity's tools. LLM understands the interface and utilizes the tools in the way a user asks.
+
+Connect **[Unity-MCP](https://github.com/IvanMurzak/Unity-MCP)** to LLM client such as [Claude](https://claude.ai/download) or [Cursor](https://www.cursor.com/) using integrated `AI Connector` window. Custom clients are supported as well.
+
+The project is designed to let developers to add custom tools soon. After that the next goal is to enable the same features in player's build. For not it works only in Unity Editor.
+
+The system is extensible: you can define custom `tool`s directly in your Unity project codebase, exposing new capabilities to the AI or automation clients. This makes Unity-MCP a flexible foundation for building advanced workflows, rapid prototyping, or integrating AI-driven features into your development process.
+
+---
+
+# Advanced MCP server setup
+
+Unity-MCP server supports many different launch options and docker docker deployment. Both transport protocol are supported `streamableHttp` and `stdio`. [Read more...](https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/mcp-server.md)
+
+# Add custom `tool`
+
+> ⚠️ It only works with MCP client that supports dynamic tool list update.
+
+Unity-MCP is designed to support custom `tool` development by project owner. MCP server takes data from Unity plugin and exposes it to a Client. So anyone in the MCP communication chain would receive the information about a new `tool`. Which LLM may decide to call at some point.
+
+To add a custom `tool` you need:
+
+1. To have a class with attribute `McpPluginToolType`.
+2. To have a method in the class with attribute `McpPluginTool`.
+3. [optional] Add `Description` attribute to each method argument to let LLM to understand it.
+4. [optional] Use `string? optional = null` properties with `?` and default value to mark them as `optional` for LLM.
+
+> Take a look that the line `MainThread.Instance.Run(() =>` it allows to run the code in Main thread which is needed to interact with Unity API. If you don't need it and running the tool in background thread is fine for the tool, don't use Main thread for efficiency purpose.
+
+```csharp
+[McpPluginToolType]
+public class Tool_GameObject
+{
+ [McpPluginTool
+ (
+ "MyCustomTask",
+ Title = "Create a new GameObject"
+ )]
+ [Description("Explain here to LLM what is this, when it should be called.")]
+ public string CustomTask
+ (
+ [Description("Explain to LLM what is this.")]
+ string inputData
+ )
+ {
+ // do anything in background thread
+
+ return MainThread.Instance.Run(() =>
+ {
+ // do something in main thread if needed
+
+ return $"[Success] Operation completed.";
+ });
+ }
+}
+```
+
+# Add custom in-game `tool`
+
+> ⚠️ Not yet supported. The work is in progress
+
+---
+
+# Contribution 💙💛
+
+Contribution is highly appreciated. Brings your ideas and lets make the game development as simple as never before! Do you have an idea of a new `tool`, feature or did you spot a bug and know how to fix it.
+
+1. 👉 [Fork the project](https://github.com/IvanMurzak/Unity-MCP/fork)
+2. Clone the fork and open the `./Unity-MCP-Plugin` folder in Unity
+3. Implement new things in the project, commit, push it to GitHub
+4. Create Pull Request targeting original [Unity-MCP](https://github.com/IvanMurzak/Unity-MCP) repository, `main` branch.
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/README.md.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/README.md.meta
new file mode 100644
index 00000000..8552e047
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: badb67057628e444fb9ecfbf2246ad10
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/SimpleJSON.cs b/Assets/com.IvanMurzak/AI Game Dev Installer/SimpleJSON.cs
new file mode 100644
index 00000000..225cde3c
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/SimpleJSON.cs
@@ -0,0 +1,1434 @@
+/* * * * *
+ * A simple JSON Parser / builder
+ * ------------------------------
+ *
+ * It mainly has been written as a simple JSON parser. It can build a JSON string
+ * from the node-tree, or generate a node tree from any valid JSON string.
+ *
+ * Written by Bunny83
+ * 2012-06-09
+ *
+ * Changelog now external. See Changelog.txt
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2012-2022 Markus Göbel (Bunny83)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * * * * */
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+
+namespace com.IvanMurzak.Unity.MCP.Installer.SimpleJSON
+{
+ public enum JSONNodeType
+ {
+ Array = 1,
+ Object = 2,
+ String = 3,
+ Number = 4,
+ NullValue = 5,
+ Boolean = 6,
+ None = 7,
+ Custom = 0xFF,
+ }
+ public enum JSONTextMode
+ {
+ Compact,
+ Indent
+ }
+
+ public abstract partial class JSONNode
+ {
+ #region Enumerators
+ public struct Enumerator
+ {
+ private enum Type { None, Array, Object }
+ private Type type;
+ private Dictionary.Enumerator m_Object;
+ private List.Enumerator m_Array;
+ public bool IsValid { get { return type != Type.None; } }
+ public Enumerator(List.Enumerator aArrayEnum)
+ {
+ type = Type.Array;
+ m_Object = default(Dictionary.Enumerator);
+ m_Array = aArrayEnum;
+ }
+ public Enumerator(Dictionary.Enumerator aDictEnum)
+ {
+ type = Type.Object;
+ m_Object = aDictEnum;
+ m_Array = default(List.Enumerator);
+ }
+ public KeyValuePair Current
+ {
+ get
+ {
+ if (type == Type.Array)
+ return new KeyValuePair(string.Empty, m_Array.Current);
+ else if (type == Type.Object)
+ return m_Object.Current;
+ return new KeyValuePair(string.Empty, null);
+ }
+ }
+ public bool MoveNext()
+ {
+ if (type == Type.Array)
+ return m_Array.MoveNext();
+ else if (type == Type.Object)
+ return m_Object.MoveNext();
+ return false;
+ }
+ }
+ public struct ValueEnumerator
+ {
+ private Enumerator m_Enumerator;
+ public ValueEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { }
+ public ValueEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { }
+ public ValueEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; }
+ public JSONNode Current { get { return m_Enumerator.Current.Value; } }
+ public bool MoveNext() { return m_Enumerator.MoveNext(); }
+ public ValueEnumerator GetEnumerator() { return this; }
+ }
+ public struct KeyEnumerator
+ {
+ private Enumerator m_Enumerator;
+ public KeyEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { }
+ public KeyEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { }
+ public KeyEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; }
+ public string Current { get { return m_Enumerator.Current.Key; } }
+ public bool MoveNext() { return m_Enumerator.MoveNext(); }
+ public KeyEnumerator GetEnumerator() { return this; }
+ }
+
+ public class LinqEnumerator : IEnumerator>, IEnumerable>
+ {
+ private JSONNode m_Node;
+ private Enumerator m_Enumerator;
+ internal LinqEnumerator(JSONNode aNode)
+ {
+ m_Node = aNode;
+ if (m_Node != null)
+ m_Enumerator = m_Node.GetEnumerator();
+ }
+ public KeyValuePair Current { get { return m_Enumerator.Current; } }
+ object IEnumerator.Current { get { return m_Enumerator.Current; } }
+ public bool MoveNext() { return m_Enumerator.MoveNext(); }
+
+ public void Dispose()
+ {
+ m_Node = null;
+ m_Enumerator = new Enumerator();
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ return new LinqEnumerator(m_Node);
+ }
+
+ public void Reset()
+ {
+ if (m_Node != null)
+ m_Enumerator = m_Node.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return new LinqEnumerator(m_Node);
+ }
+ }
+
+ #endregion Enumerators
+
+ #region common interface
+
+ public static bool forceASCII = false; // Use Unicode by default
+ public static bool longAsString = false; // lazy creator creates a JSONString instead of JSONNumber
+ public static bool allowLineComments = true; // allow "//"-style comments at the end of a line
+
+ public abstract JSONNodeType Tag { get; }
+
+ public virtual JSONNode this[int aIndex] { get { return null; } set { } }
+
+ public virtual JSONNode this[string aKey] { get { return null; } set { } }
+
+ public virtual string Value { get { return ""; } set { } }
+
+ public virtual int Count { get { return 0; } }
+
+ public virtual bool IsNumber { get { return false; } }
+ public virtual bool IsString { get { return false; } }
+ public virtual bool IsBoolean { get { return false; } }
+ public virtual bool IsNull { get { return false; } }
+ public virtual bool IsArray { get { return false; } }
+ public virtual bool IsObject { get { return false; } }
+
+ public virtual bool Inline { get { return false; } set { } }
+
+ public virtual void Add(string aKey, JSONNode aItem)
+ {
+ }
+ public virtual void Add(JSONNode aItem)
+ {
+ Add("", aItem);
+ }
+
+ public virtual JSONNode Remove(string aKey)
+ {
+ return null;
+ }
+
+ public virtual JSONNode Remove(int aIndex)
+ {
+ return null;
+ }
+
+ public virtual JSONNode Remove(JSONNode aNode)
+ {
+ return aNode;
+ }
+ public virtual void Clear() { }
+
+ public virtual JSONNode Clone()
+ {
+ return null;
+ }
+
+ public virtual IEnumerable Children
+ {
+ get
+ {
+ yield break;
+ }
+ }
+
+ public IEnumerable DeepChildren
+ {
+ get
+ {
+ foreach (var C in Children)
+ foreach (var D in C.DeepChildren)
+ yield return D;
+ }
+ }
+
+ public virtual bool HasKey(string aKey)
+ {
+ return false;
+ }
+
+ public virtual JSONNode GetValueOrDefault(string aKey, JSONNode aDefault)
+ {
+ return aDefault;
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ WriteToStringBuilder(sb, 0, 0, JSONTextMode.Compact);
+ return sb.ToString();
+ }
+
+ public virtual string ToString(int aIndent)
+ {
+ StringBuilder sb = new StringBuilder();
+ WriteToStringBuilder(sb, 0, aIndent, JSONTextMode.Indent);
+ return sb.ToString();
+ }
+ internal abstract void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode);
+
+ public abstract Enumerator GetEnumerator();
+ public IEnumerable> Linq { get { return new LinqEnumerator(this); } }
+ public KeyEnumerator Keys { get { return new KeyEnumerator(GetEnumerator()); } }
+ public ValueEnumerator Values { get { return new ValueEnumerator(GetEnumerator()); } }
+
+ #endregion common interface
+
+ #region typecasting properties
+
+
+ public virtual double AsDouble
+ {
+ get
+ {
+ double v = 0.0;
+ if (double.TryParse(Value, NumberStyles.Float, CultureInfo.InvariantCulture, out v))
+ return v;
+ return 0.0;
+ }
+ set
+ {
+ Value = value.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+
+ public virtual int AsInt
+ {
+ get { return (int)AsDouble; }
+ set { AsDouble = value; }
+ }
+
+ public virtual float AsFloat
+ {
+ get { return (float)AsDouble; }
+ set { AsDouble = value; }
+ }
+
+ public virtual bool AsBool
+ {
+ get
+ {
+ bool v = false;
+ if (bool.TryParse(Value, out v))
+ return v;
+ return !string.IsNullOrEmpty(Value);
+ }
+ set
+ {
+ Value = (value) ? "true" : "false";
+ }
+ }
+
+ public virtual long AsLong
+ {
+ get
+ {
+ long val = 0;
+ if (long.TryParse(Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out val))
+ return val;
+ return 0L;
+ }
+ set
+ {
+ Value = value.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+
+ public virtual ulong AsULong
+ {
+ get
+ {
+ ulong val = 0;
+ if (ulong.TryParse(Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out val))
+ return val;
+ return 0;
+ }
+ set
+ {
+ Value = value.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+
+ public virtual JSONArray AsArray
+ {
+ get
+ {
+ return this as JSONArray;
+ }
+ }
+
+ public virtual JSONObject AsObject
+ {
+ get
+ {
+ return this as JSONObject;
+ }
+ }
+
+
+ #endregion typecasting properties
+
+ #region operators
+
+ public static implicit operator JSONNode(string s)
+ {
+ return (s == null) ? (JSONNode)JSONNull.CreateOrGet() : new JSONString(s);
+ }
+ public static implicit operator string(JSONNode d)
+ {
+ return (d == null) ? null : d.Value;
+ }
+
+ public static implicit operator JSONNode(double n)
+ {
+ return new JSONNumber(n);
+ }
+ public static implicit operator double(JSONNode d)
+ {
+ return (d == null) ? 0 : d.AsDouble;
+ }
+
+ public static implicit operator JSONNode(float n)
+ {
+ return new JSONNumber(n);
+ }
+ public static implicit operator float(JSONNode d)
+ {
+ return (d == null) ? 0 : d.AsFloat;
+ }
+
+ public static implicit operator JSONNode(int n)
+ {
+ return new JSONNumber(n);
+ }
+ public static implicit operator int(JSONNode d)
+ {
+ return (d == null) ? 0 : d.AsInt;
+ }
+
+ public static implicit operator JSONNode(long n)
+ {
+ if (longAsString)
+ return new JSONString(n.ToString(CultureInfo.InvariantCulture));
+ return new JSONNumber(n);
+ }
+ public static implicit operator long(JSONNode d)
+ {
+ return (d == null) ? 0L : d.AsLong;
+ }
+
+ public static implicit operator JSONNode(ulong n)
+ {
+ if (longAsString)
+ return new JSONString(n.ToString(CultureInfo.InvariantCulture));
+ return new JSONNumber(n);
+ }
+ public static implicit operator ulong(JSONNode d)
+ {
+ return (d == null) ? 0 : d.AsULong;
+ }
+
+ public static implicit operator JSONNode(bool b)
+ {
+ return new JSONBool(b);
+ }
+ public static implicit operator bool(JSONNode d)
+ {
+ return (d == null) ? false : d.AsBool;
+ }
+
+ public static implicit operator JSONNode(KeyValuePair aKeyValue)
+ {
+ return aKeyValue.Value;
+ }
+
+ public static bool operator ==(JSONNode a, object b)
+ {
+ if (ReferenceEquals(a, b))
+ return true;
+ bool aIsNull = a is JSONNull || ReferenceEquals(a, null) || a is JSONLazyCreator;
+ bool bIsNull = b is JSONNull || ReferenceEquals(b, null) || b is JSONLazyCreator;
+ if (aIsNull && bIsNull)
+ return true;
+ return !aIsNull && a.Equals(b);
+ }
+
+ public static bool operator !=(JSONNode a, object b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ReferenceEquals(this, obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ #endregion operators
+
+ [ThreadStatic]
+ private static StringBuilder m_EscapeBuilder;
+ internal static StringBuilder EscapeBuilder
+ {
+ get
+ {
+ if (m_EscapeBuilder == null)
+ m_EscapeBuilder = new StringBuilder();
+ return m_EscapeBuilder;
+ }
+ }
+ internal static string Escape(string aText)
+ {
+ var sb = EscapeBuilder;
+ sb.Length = 0;
+ if (sb.Capacity < aText.Length + aText.Length / 10)
+ sb.Capacity = aText.Length + aText.Length / 10;
+ foreach (char c in aText)
+ {
+ switch (c)
+ {
+ case '\\':
+ sb.Append("\\\\");
+ break;
+ case '\"':
+ sb.Append("\\\"");
+ break;
+ case '\n':
+ sb.Append("\\n");
+ break;
+ case '\r':
+ sb.Append("\\r");
+ break;
+ case '\t':
+ sb.Append("\\t");
+ break;
+ case '\b':
+ sb.Append("\\b");
+ break;
+ case '\f':
+ sb.Append("\\f");
+ break;
+ default:
+ if (c < ' ' || (forceASCII && c > 127))
+ {
+ ushort val = c;
+ sb.Append("\\u").Append(val.ToString("X4"));
+ }
+ else
+ sb.Append(c);
+ break;
+ }
+ }
+ string result = sb.ToString();
+ sb.Length = 0;
+ return result;
+ }
+
+ private static JSONNode ParseElement(string token, bool quoted)
+ {
+ if (quoted)
+ return token;
+ if (token.Length <= 5)
+ {
+ string tmp = token.ToLower();
+ if (tmp == "false" || tmp == "true")
+ return tmp == "true";
+ if (tmp == "null")
+ return JSONNull.CreateOrGet();
+ }
+ double val;
+ if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out val))
+ return val;
+ else
+ return token;
+ }
+
+ public static JSONNode Parse(string aJSON)
+ {
+ Stack stack = new Stack();
+ JSONNode ctx = null;
+ int i = 0;
+ StringBuilder Token = new StringBuilder();
+ string TokenName = "";
+ bool QuoteMode = false;
+ bool TokenIsQuoted = false;
+ bool HasNewlineChar = false;
+ while (i < aJSON.Length)
+ {
+ switch (aJSON[i])
+ {
+ case '{':
+ if (QuoteMode)
+ {
+ Token.Append(aJSON[i]);
+ break;
+ }
+ stack.Push(new JSONObject());
+ if (ctx != null)
+ {
+ ctx.Add(TokenName, stack.Peek());
+ }
+ TokenName = "";
+ Token.Length = 0;
+ ctx = stack.Peek();
+ HasNewlineChar = false;
+ break;
+
+ case '[':
+ if (QuoteMode)
+ {
+ Token.Append(aJSON[i]);
+ break;
+ }
+
+ stack.Push(new JSONArray());
+ if (ctx != null)
+ {
+ ctx.Add(TokenName, stack.Peek());
+ }
+ TokenName = "";
+ Token.Length = 0;
+ ctx = stack.Peek();
+ HasNewlineChar = false;
+ break;
+
+ case '}':
+ case ']':
+ if (QuoteMode)
+ {
+
+ Token.Append(aJSON[i]);
+ break;
+ }
+ if (stack.Count == 0)
+ throw new Exception("JSON Parse: Too many closing brackets");
+
+ stack.Pop();
+ if (Token.Length > 0 || TokenIsQuoted)
+ ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted));
+ if (ctx != null)
+ ctx.Inline = !HasNewlineChar;
+ TokenIsQuoted = false;
+ TokenName = "";
+ Token.Length = 0;
+ if (stack.Count > 0)
+ ctx = stack.Peek();
+ break;
+
+ case ':':
+ if (QuoteMode)
+ {
+ Token.Append(aJSON[i]);
+ break;
+ }
+ TokenName = Token.ToString();
+ Token.Length = 0;
+ TokenIsQuoted = false;
+ break;
+
+ case '"':
+ QuoteMode ^= true;
+ TokenIsQuoted |= QuoteMode;
+ break;
+
+ case ',':
+ if (QuoteMode)
+ {
+ Token.Append(aJSON[i]);
+ break;
+ }
+ if (Token.Length > 0 || TokenIsQuoted)
+ ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted));
+ TokenIsQuoted = false;
+ TokenName = "";
+ Token.Length = 0;
+ TokenIsQuoted = false;
+ break;
+
+ case '\r':
+ case '\n':
+ HasNewlineChar = true;
+ break;
+
+ case ' ':
+ case '\t':
+ if (QuoteMode)
+ Token.Append(aJSON[i]);
+ break;
+
+ case '\\':
+ ++i;
+ if (QuoteMode)
+ {
+ char C = aJSON[i];
+ switch (C)
+ {
+ case 't':
+ Token.Append('\t');
+ break;
+ case 'r':
+ Token.Append('\r');
+ break;
+ case 'n':
+ Token.Append('\n');
+ break;
+ case 'b':
+ Token.Append('\b');
+ break;
+ case 'f':
+ Token.Append('\f');
+ break;
+ case 'u':
+ {
+ string s = aJSON.Substring(i + 1, 4);
+ Token.Append((char)int.Parse(
+ s,
+ System.Globalization.NumberStyles.AllowHexSpecifier));
+ i += 4;
+ break;
+ }
+ default:
+ Token.Append(C);
+ break;
+ }
+ }
+ break;
+ case '/':
+ if (allowLineComments && !QuoteMode && i + 1 < aJSON.Length && aJSON[i + 1] == '/')
+ {
+ while (++i < aJSON.Length && aJSON[i] != '\n' && aJSON[i] != '\r') ;
+ break;
+ }
+ Token.Append(aJSON[i]);
+ break;
+ case '\uFEFF': // remove / ignore BOM (Byte Order Mark)
+ break;
+
+ default:
+ Token.Append(aJSON[i]);
+ break;
+ }
+ ++i;
+ }
+ if (QuoteMode)
+ {
+ throw new Exception("JSON Parse: Quotation marks seems to be messed up.");
+ }
+ if (ctx == null)
+ return ParseElement(Token.ToString(), TokenIsQuoted);
+ return ctx;
+ }
+
+ }
+ // End of JSONNode
+
+ public partial class JSONArray : JSONNode
+ {
+ private List m_List = new List();
+ private bool inline = false;
+ public override bool Inline
+ {
+ get { return inline; }
+ set { inline = value; }
+ }
+
+ public override JSONNodeType Tag { get { return JSONNodeType.Array; } }
+ public override bool IsArray { get { return true; } }
+ public override Enumerator GetEnumerator() { return new Enumerator(m_List.GetEnumerator()); }
+
+ public override JSONNode this[int aIndex]
+ {
+ get
+ {
+ if (aIndex < 0 || aIndex >= m_List.Count)
+ return new JSONLazyCreator(this);
+ return m_List[aIndex];
+ }
+ set
+ {
+ if (value == null)
+ value = JSONNull.CreateOrGet();
+ if (aIndex < 0 || aIndex >= m_List.Count)
+ m_List.Add(value);
+ else
+ m_List[aIndex] = value;
+ }
+ }
+
+ public override JSONNode this[string aKey]
+ {
+ get { return new JSONLazyCreator(this); }
+ set
+ {
+ if (value == null)
+ value = JSONNull.CreateOrGet();
+ m_List.Add(value);
+ }
+ }
+
+ public override int Count
+ {
+ get { return m_List.Count; }
+ }
+
+ public override void Add(string aKey, JSONNode aItem)
+ {
+ if (aItem == null)
+ aItem = JSONNull.CreateOrGet();
+ m_List.Add(aItem);
+ }
+
+ public override JSONNode Remove(int aIndex)
+ {
+ if (aIndex < 0 || aIndex >= m_List.Count)
+ return null;
+ JSONNode tmp = m_List[aIndex];
+ m_List.RemoveAt(aIndex);
+ return tmp;
+ }
+
+ public override JSONNode Remove(JSONNode aNode)
+ {
+ m_List.Remove(aNode);
+ return aNode;
+ }
+
+ public override void Clear()
+ {
+ m_List.Clear();
+ }
+
+ public override JSONNode Clone()
+ {
+ var node = new JSONArray();
+ node.m_List.Capacity = m_List.Capacity;
+ foreach (var n in m_List)
+ {
+ if (n != null)
+ node.Add(n.Clone());
+ else
+ node.Add(null);
+ }
+ return node;
+ }
+
+ public override IEnumerable Children
+ {
+ get
+ {
+ foreach (JSONNode N in m_List)
+ yield return N;
+ }
+ }
+
+
+ internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
+ {
+ aSB.Append('[');
+ int count = m_List.Count;
+ if (inline)
+ aMode = JSONTextMode.Compact;
+ for (int i = 0; i < count; i++)
+ {
+ if (i > 0)
+ aSB.Append(',');
+ if (aMode == JSONTextMode.Indent)
+ aSB.AppendLine();
+
+ if (aMode == JSONTextMode.Indent)
+ aSB.Append(' ', aIndent + aIndentInc);
+ m_List[i].WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode);
+ }
+ if (aMode == JSONTextMode.Indent)
+ aSB.AppendLine().Append(' ', aIndent);
+ aSB.Append(']');
+ }
+ }
+ // End of JSONArray
+
+ public partial class JSONObject : JSONNode
+ {
+ private Dictionary m_Dict = new Dictionary();
+
+ private bool inline = false;
+ public override bool Inline
+ {
+ get { return inline; }
+ set { inline = value; }
+ }
+
+ public override JSONNodeType Tag { get { return JSONNodeType.Object; } }
+ public override bool IsObject { get { return true; } }
+
+ public override Enumerator GetEnumerator() { return new Enumerator(m_Dict.GetEnumerator()); }
+
+
+ public override JSONNode this[string aKey]
+ {
+ get
+ {
+ if (m_Dict.TryGetValue(aKey, out JSONNode outJsonNode))
+ return outJsonNode;
+ else
+ return new JSONLazyCreator(this, aKey);
+ }
+ set
+ {
+ if (value == null)
+ value = JSONNull.CreateOrGet();
+ if (m_Dict.ContainsKey(aKey))
+ m_Dict[aKey] = value;
+ else
+ m_Dict.Add(aKey, value);
+ }
+ }
+
+ public override JSONNode this[int aIndex]
+ {
+ get
+ {
+ if (aIndex < 0 || aIndex >= m_Dict.Count)
+ return null;
+ return m_Dict.ElementAt(aIndex).Value;
+ }
+ set
+ {
+ if (value == null)
+ value = JSONNull.CreateOrGet();
+ if (aIndex < 0 || aIndex >= m_Dict.Count)
+ return;
+ string key = m_Dict.ElementAt(aIndex).Key;
+ m_Dict[key] = value;
+ }
+ }
+
+ public override int Count
+ {
+ get { return m_Dict.Count; }
+ }
+
+ public override void Add(string aKey, JSONNode aItem)
+ {
+ if (aItem == null)
+ aItem = JSONNull.CreateOrGet();
+
+ if (aKey != null)
+ {
+ if (m_Dict.ContainsKey(aKey))
+ m_Dict[aKey] = aItem;
+ else
+ m_Dict.Add(aKey, aItem);
+ }
+ else
+ m_Dict.Add(Guid.NewGuid().ToString(), aItem);
+ }
+
+ public override JSONNode Remove(string aKey)
+ {
+ if (!m_Dict.ContainsKey(aKey))
+ return null;
+ JSONNode tmp = m_Dict[aKey];
+ m_Dict.Remove(aKey);
+ return tmp;
+ }
+
+ public override JSONNode Remove(int aIndex)
+ {
+ if (aIndex < 0 || aIndex >= m_Dict.Count)
+ return null;
+ var item = m_Dict.ElementAt(aIndex);
+ m_Dict.Remove(item.Key);
+ return item.Value;
+ }
+
+ public override JSONNode Remove(JSONNode aNode)
+ {
+ try
+ {
+ var item = m_Dict.Where(k => k.Value == aNode).First();
+ m_Dict.Remove(item.Key);
+ return aNode;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ public override void Clear()
+ {
+ m_Dict.Clear();
+ }
+
+ public override JSONNode Clone()
+ {
+ var node = new JSONObject();
+ foreach (var n in m_Dict)
+ {
+ node.Add(n.Key, n.Value.Clone());
+ }
+ return node;
+ }
+
+ public override bool HasKey(string aKey)
+ {
+ return m_Dict.ContainsKey(aKey);
+ }
+
+ public override JSONNode GetValueOrDefault(string aKey, JSONNode aDefault)
+ {
+ JSONNode res;
+ if (m_Dict.TryGetValue(aKey, out res))
+ return res;
+ return aDefault;
+ }
+
+ public override IEnumerable Children
+ {
+ get
+ {
+ foreach (KeyValuePair N in m_Dict)
+ yield return N.Value;
+ }
+ }
+
+ internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
+ {
+ aSB.Append('{');
+ bool first = true;
+ if (inline)
+ aMode = JSONTextMode.Compact;
+ foreach (var k in m_Dict)
+ {
+ if (!first)
+ aSB.Append(',');
+ first = false;
+ if (aMode == JSONTextMode.Indent)
+ aSB.AppendLine();
+ if (aMode == JSONTextMode.Indent)
+ aSB.Append(' ', aIndent + aIndentInc);
+ aSB.Append('\"').Append(Escape(k.Key)).Append('\"');
+ if (aMode == JSONTextMode.Compact)
+ aSB.Append(':');
+ else
+ aSB.Append(" : ");
+ k.Value.WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode);
+ }
+ if (aMode == JSONTextMode.Indent)
+ aSB.AppendLine().Append(' ', aIndent);
+ aSB.Append('}');
+ }
+
+ }
+ // End of JSONObject
+
+ public partial class JSONString : JSONNode
+ {
+ private string m_Data;
+
+ public override JSONNodeType Tag { get { return JSONNodeType.String; } }
+ public override bool IsString { get { return true; } }
+
+ public override Enumerator GetEnumerator() { return new Enumerator(); }
+
+
+ public override string Value
+ {
+ get { return m_Data; }
+ set
+ {
+ m_Data = value;
+ }
+ }
+
+ public JSONString(string aData)
+ {
+ m_Data = aData;
+ }
+ public override JSONNode Clone()
+ {
+ return new JSONString(m_Data);
+ }
+
+ internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
+ {
+ aSB.Append('\"').Append(Escape(m_Data)).Append('\"');
+ }
+ public override bool Equals(object obj)
+ {
+ if (base.Equals(obj))
+ return true;
+ string s = obj as string;
+ if (s != null)
+ return m_Data == s;
+ JSONString s2 = obj as JSONString;
+ if (s2 != null)
+ return m_Data == s2.m_Data;
+ return false;
+ }
+ public override int GetHashCode()
+ {
+ return m_Data.GetHashCode();
+ }
+ public override void Clear()
+ {
+ m_Data = "";
+ }
+ }
+ // End of JSONString
+
+ public partial class JSONNumber : JSONNode
+ {
+ private double m_Data;
+
+ public override JSONNodeType Tag { get { return JSONNodeType.Number; } }
+ public override bool IsNumber { get { return true; } }
+ public override Enumerator GetEnumerator() { return new Enumerator(); }
+
+ public override string Value
+ {
+ get { return m_Data.ToString(CultureInfo.InvariantCulture); }
+ set
+ {
+ double v;
+ if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out v))
+ m_Data = v;
+ }
+ }
+
+ public override double AsDouble
+ {
+ get { return m_Data; }
+ set { m_Data = value; }
+ }
+ public override long AsLong
+ {
+ get { return (long)m_Data; }
+ set { m_Data = value; }
+ }
+ public override ulong AsULong
+ {
+ get { return (ulong)m_Data; }
+ set { m_Data = value; }
+ }
+
+ public JSONNumber(double aData)
+ {
+ m_Data = aData;
+ }
+
+ public JSONNumber(string aData)
+ {
+ Value = aData;
+ }
+
+ public override JSONNode Clone()
+ {
+ return new JSONNumber(m_Data);
+ }
+
+ internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
+ {
+ aSB.Append(Value.ToString(CultureInfo.InvariantCulture));
+ }
+ private static bool IsNumeric(object value)
+ {
+ return value is int || value is uint
+ || value is float || value is double
+ || value is decimal
+ || value is long || value is ulong
+ || value is short || value is ushort
+ || value is sbyte || value is byte;
+ }
+ public override bool Equals(object obj)
+ {
+ if (obj == null)
+ return false;
+ if (base.Equals(obj))
+ return true;
+ JSONNumber s2 = obj as JSONNumber;
+ if (s2 != null)
+ return m_Data == s2.m_Data;
+ if (IsNumeric(obj))
+ return Convert.ToDouble(obj) == m_Data;
+ return false;
+ }
+ public override int GetHashCode()
+ {
+ return m_Data.GetHashCode();
+ }
+ public override void Clear()
+ {
+ m_Data = 0;
+ }
+ }
+ // End of JSONNumber
+
+ public partial class JSONBool : JSONNode
+ {
+ private bool m_Data;
+
+ public override JSONNodeType Tag { get { return JSONNodeType.Boolean; } }
+ public override bool IsBoolean { get { return true; } }
+ public override Enumerator GetEnumerator() { return new Enumerator(); }
+
+ public override string Value
+ {
+ get { return m_Data.ToString(); }
+ set
+ {
+ bool v;
+ if (bool.TryParse(value, out v))
+ m_Data = v;
+ }
+ }
+ public override bool AsBool
+ {
+ get { return m_Data; }
+ set { m_Data = value; }
+ }
+
+ public JSONBool(bool aData)
+ {
+ m_Data = aData;
+ }
+
+ public JSONBool(string aData)
+ {
+ Value = aData;
+ }
+
+ public override JSONNode Clone()
+ {
+ return new JSONBool(m_Data);
+ }
+
+ internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
+ {
+ aSB.Append((m_Data) ? "true" : "false");
+ }
+ public override bool Equals(object obj)
+ {
+ if (obj == null)
+ return false;
+ if (obj is bool)
+ return m_Data == (bool)obj;
+ return false;
+ }
+ public override int GetHashCode()
+ {
+ return m_Data.GetHashCode();
+ }
+ public override void Clear()
+ {
+ m_Data = false;
+ }
+ }
+ // End of JSONBool
+
+ public partial class JSONNull : JSONNode
+ {
+ static JSONNull m_StaticInstance = new JSONNull();
+ public static bool reuseSameInstance = true;
+ public static JSONNull CreateOrGet()
+ {
+ if (reuseSameInstance)
+ return m_StaticInstance;
+ return new JSONNull();
+ }
+ private JSONNull() { }
+
+ public override JSONNodeType Tag { get { return JSONNodeType.NullValue; } }
+ public override bool IsNull { get { return true; } }
+ public override Enumerator GetEnumerator() { return new Enumerator(); }
+
+ public override string Value
+ {
+ get { return "null"; }
+ set { }
+ }
+ public override bool AsBool
+ {
+ get { return false; }
+ set { }
+ }
+
+ public override JSONNode Clone()
+ {
+ return CreateOrGet();
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (object.ReferenceEquals(this, obj))
+ return true;
+ return (obj is JSONNull);
+ }
+ public override int GetHashCode()
+ {
+ return 0;
+ }
+
+ internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
+ {
+ aSB.Append("null");
+ }
+ }
+ // End of JSONNull
+
+ internal partial class JSONLazyCreator : JSONNode
+ {
+ private JSONNode m_Node = null;
+ private string m_Key = null;
+ public override JSONNodeType Tag { get { return JSONNodeType.None; } }
+ public override Enumerator GetEnumerator() { return new Enumerator(); }
+
+ public JSONLazyCreator(JSONNode aNode)
+ {
+ m_Node = aNode;
+ m_Key = null;
+ }
+
+ public JSONLazyCreator(JSONNode aNode, string aKey)
+ {
+ m_Node = aNode;
+ m_Key = aKey;
+ }
+
+ private T Set(T aVal) where T : JSONNode
+ {
+ if (m_Key == null)
+ m_Node.Add(aVal);
+ else
+ m_Node.Add(m_Key, aVal);
+ m_Node = null; // Be GC friendly.
+ return aVal;
+ }
+
+ public override JSONNode this[int aIndex]
+ {
+ get { return new JSONLazyCreator(this); }
+ set { Set(new JSONArray()).Add(value); }
+ }
+
+ public override JSONNode this[string aKey]
+ {
+ get { return new JSONLazyCreator(this, aKey); }
+ set { Set(new JSONObject()).Add(aKey, value); }
+ }
+
+ public override void Add(JSONNode aItem)
+ {
+ Set(new JSONArray()).Add(aItem);
+ }
+
+ public override void Add(string aKey, JSONNode aItem)
+ {
+ Set(new JSONObject()).Add(aKey, aItem);
+ }
+
+ public static bool operator ==(JSONLazyCreator a, object b)
+ {
+ if (b == null)
+ return true;
+ return System.Object.ReferenceEquals(a, b);
+ }
+
+ public static bool operator !=(JSONLazyCreator a, object b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null)
+ return true;
+ return System.Object.ReferenceEquals(this, obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return 0;
+ }
+
+ public override int AsInt
+ {
+ get { Set(new JSONNumber(0)); return 0; }
+ set { Set(new JSONNumber(value)); }
+ }
+
+ public override float AsFloat
+ {
+ get { Set(new JSONNumber(0.0f)); return 0.0f; }
+ set { Set(new JSONNumber(value)); }
+ }
+
+ public override double AsDouble
+ {
+ get { Set(new JSONNumber(0.0)); return 0.0; }
+ set { Set(new JSONNumber(value)); }
+ }
+
+ public override long AsLong
+ {
+ get
+ {
+ if (longAsString)
+ Set(new JSONString("0"));
+ else
+ Set(new JSONNumber(0.0));
+ return 0L;
+ }
+ set
+ {
+ if (longAsString)
+ Set(new JSONString(value.ToString(CultureInfo.InvariantCulture)));
+ else
+ Set(new JSONNumber(value));
+ }
+ }
+
+ public override ulong AsULong
+ {
+ get
+ {
+ if (longAsString)
+ Set(new JSONString("0"));
+ else
+ Set(new JSONNumber(0.0));
+ return 0L;
+ }
+ set
+ {
+ if (longAsString)
+ Set(new JSONString(value.ToString(CultureInfo.InvariantCulture)));
+ else
+ Set(new JSONNumber(value));
+ }
+ }
+
+ public override bool AsBool
+ {
+ get { Set(new JSONBool(false)); return false; }
+ set { Set(new JSONBool(value)); }
+ }
+
+ public override JSONArray AsArray
+ {
+ get { return Set(new JSONArray()); }
+ }
+
+ public override JSONObject AsObject
+ {
+ get { return Set(new JSONObject()); }
+ }
+ internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
+ {
+ aSB.Append("null");
+ }
+ }
+ // End of JSONLazyCreator
+
+ public static class JSON
+ {
+ public static JSONNode Parse(string aJSON)
+ {
+ return JSONNode.Parse(aJSON);
+ }
+ }
+}
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/SimpleJSON.cs.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/SimpleJSON.cs.meta
new file mode 100644
index 00000000..dbfcd905
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/SimpleJSON.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9fbdd5383371516498e4fd0ba51f3845
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests.meta
new file mode 100644
index 00000000..be63c6cd
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 75e3d8b42387c80479161a91b0a310f8
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files.meta
new file mode 100644
index 00000000..67b9c3a8
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a6babde583d2f114c9f1756e71e0ac7d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/Correct.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/Correct.meta
new file mode 100644
index 00000000..9c215cbd
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/Correct.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d82937626fc136e489d066690bcccd3d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/Correct/correct_manifest.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/Correct/correct_manifest.json
new file mode 100644
index 00000000..2334066a
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/Correct/correct_manifest.json
@@ -0,0 +1,33 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": [
+ {
+ "name": "package.openupm.com",
+ "url": "https://package.openupm.com",
+ "scopes": [
+ "com.ivanmurzak",
+ "extensions.unity",
+ "org.nuget.com.ivanmurzak",
+ "org.nuget.microsoft",
+ "org.nuget.system",
+ "org.nuget.r3"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/Correct/correct_manifest.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/Correct/correct_manifest.json.meta
new file mode 100644
index 00000000..b423bad1
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/Correct/correct_manifest.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 3d5b79cfeddb2f04f8bb70ff73000dd7
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_1.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_1.json
new file mode 100644
index 00000000..f94ad813
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_1.json
@@ -0,0 +1,20 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": []
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_1.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_1.json.meta
new file mode 100644
index 00000000..7b35a7fb
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_1.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 16c14e4a40eafcb418d9966639fefded
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_2.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_2.json
new file mode 100644
index 00000000..f94ad813
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_2.json
@@ -0,0 +1,20 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": []
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_2.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_2.json.meta
new file mode 100644
index 00000000..0a950ccf
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_empty_2.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ddb8439b851dacf458f1c25cf9895ddc
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_gone.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_gone.json
new file mode 100644
index 00000000..bcdb7a66
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_gone.json
@@ -0,0 +1,19 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ }
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_gone.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_gone.json.meta
new file mode 100644
index 00000000..31fe8232
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopedregistries_gone.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 0dc4a1c3c2fa1b141b5f60b9f16fd725
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_empty.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_empty.json
new file mode 100644
index 00000000..c556ab4e
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_empty.json
@@ -0,0 +1,26 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": [
+ {
+ "name": "package.openupm.com",
+ "url": "https://package.openupm.com",
+ "scopes": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_empty.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_empty.json.meta
new file mode 100644
index 00000000..b42d6b12
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_empty.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 43ffd2c523a1fa643b984aeb51def7d9
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_gone.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_gone.json
new file mode 100644
index 00000000..5b0829dc
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_gone.json
@@ -0,0 +1,25 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": [
+ {
+ "name": "package.openupm.com",
+ "url": "https://package.openupm.com"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_gone.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_gone.json.meta
new file mode 100644
index 00000000..1d8b787c
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_gone.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 30d120107a842394eb6b6d35500b70c0
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_1.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_1.json
new file mode 100644
index 00000000..ecd20928
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_1.json
@@ -0,0 +1,32 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": [
+ {
+ "name": "package.openupm.com",
+ "url": "https://package.openupm.com",
+ "scopes": [
+ "com.ivanmurzak",
+ "extensions.unity",
+ "org.nuget.com.ivanmurzak",
+ "org.nuget.microsoft",
+ "org.nuget.system"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_1.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_1.json.meta
new file mode 100644
index 00000000..bef8f462
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_1.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 610ca5c6100bb9042937f4d5cf7c6160
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_2.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_2.json
new file mode 100644
index 00000000..dca084fa
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_2.json
@@ -0,0 +1,31 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": [
+ {
+ "name": "package.openupm.com",
+ "url": "https://package.openupm.com",
+ "scopes": [
+ "com.ivanmurzak",
+ "extensions.unity",
+ "org.nuget.com.ivanmurzak",
+ "org.nuget.microsoft"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_2.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_2.json.meta
new file mode 100644
index 00000000..016c5d92
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_2.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9d0579095e1400946814f12371bae605
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_3.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_3.json
new file mode 100644
index 00000000..5a2d1a03
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_3.json
@@ -0,0 +1,30 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": [
+ {
+ "name": "package.openupm.com",
+ "url": "https://package.openupm.com",
+ "scopes": [
+ "com.ivanmurzak",
+ "extensions.unity",
+ "org.nuget.com.ivanmurzak"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_3.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_3.json.meta
new file mode 100644
index 00000000..9431daf4
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_3.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 69acf9fb81183764fabb07cae63a55a1
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_4.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_4.json
new file mode 100644
index 00000000..1ce5ba42
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_4.json
@@ -0,0 +1,29 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": [
+ {
+ "name": "package.openupm.com",
+ "url": "https://package.openupm.com",
+ "scopes": [
+ "com.ivanmurzak",
+ "extensions.unity"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_4.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_4.json.meta
new file mode 100644
index 00000000..a0584b33
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_4.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 6ba3279b66694744399935ac96421b6f
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_5.json b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_5.json
new file mode 100644
index 00000000..18b1858d
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_5.json
@@ -0,0 +1,28 @@
+{
+ "dependencies": {
+ "com.unity.ide.visualstudio": "2.0.23",
+ "com.unity.test-framework": "1.1.33",
+ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "9.0.7",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "9.0.7",
+ "org.nuget.microsoft.bcl.memory": "9.0.7",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.13.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting": "9.0.7",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "9.0.7",
+ "org.nuget.microsoft.extensions.logging.abstractions": "9.0.7",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "9.0.7",
+ "PACKAGE_ID": "PACKAGE_VERSION"
+ },
+ "scopedRegistries": [
+ {
+ "name": "package.openupm.com",
+ "url": "https://package.openupm.com",
+ "scopes": [
+ "com.ivanmurzak"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_5.json.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_5.json.meta
new file mode 100644
index 00000000..3d78acd8
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files/scopes_partial_5.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 112f17f3f25d40e469604e858090eacd
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/ManifestInstallerTests.cs b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/ManifestInstallerTests.cs
new file mode 100644
index 00000000..9a5e53e9
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/ManifestInstallerTests.cs
@@ -0,0 +1,71 @@
+/*
+┌──────────────────────────────────────────────────────────────────┐
+│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
+│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
+│ Copyright (c) 2025 Ivan Murzak │
+│ Licensed under the Apache License, Version 2.0. │
+│ See the LICENSE file in the project root for more information. │
+└──────────────────────────────────────────────────────────────────┘
+*/
+using System.IO;
+using NUnit.Framework;
+using UnityEngine;
+
+namespace com.IvanMurzak.Unity.MCP.Installer.Tests
+{
+ public class ManifestInstallerTests
+ {
+ const string PackageIdTag = "PACKAGE_ID";
+ const string PackageVersionTag = "PACKAGE_VERSION";
+ const string FilesRoot = "Assets/com.IvanMurzak/AI Game Dev Installer/Tests/Files";
+ const string FilesCopyRoot = "Temp/com.IvanMurzak/AI Game Dev Installer/Tests/Files";
+ static string CorrectManifestPath => $"{FilesRoot}/Correct/correct_manifest.json";
+
+ [SetUp]
+ public void SetUp()
+ {
+ Debug.Log($"[{nameof(ManifestInstallerTests)}] SetUp");
+ Directory.CreateDirectory(FilesCopyRoot);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ Debug.Log($"[{nameof(ManifestInstallerTests)}] TearDown");
+
+ // var files = Directory.GetFiles(FilesCopyRoot, "*.json", SearchOption.TopDirectoryOnly);
+ // foreach (var file in files)
+ // File.Delete(file);
+ }
+
+ [Test]
+ public void All()
+ {
+ var files = Directory.GetFiles(FilesRoot, "*.json", SearchOption.TopDirectoryOnly);
+ var correctManifest = File.ReadAllText(CorrectManifestPath)
+ .Replace(PackageVersionTag, Installer.Version)
+ .Replace(PackageIdTag, Installer.PackageId);
+
+ foreach (var file in files)
+ {
+ Debug.Log($"Found JSON file: {file}");
+
+ // Copy the file
+ var fileCopy = Path.Combine(FilesCopyRoot, Path.GetFileName(file));
+ File.Copy(file, fileCopy, overwrite: true);
+
+ // Arrange
+ File.WriteAllText(fileCopy, File.ReadAllText(fileCopy)
+ .Replace(PackageVersionTag, Installer.Version)
+ .Replace(PackageIdTag, Installer.PackageId));
+
+ // Act
+ Installer.AddScopedRegistryIfNeeded(fileCopy);
+
+ // Assert
+ var modifiedManifest = File.ReadAllText(fileCopy);
+ Assert.AreEqual(correctManifest, modifiedManifest, $"Modified manifest from {file} does not match the correct manifest.");
+ }
+ }
+ }
+}
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/ManifestInstallerTests.cs.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/ManifestInstallerTests.cs.meta
new file mode 100644
index 00000000..d6b9096c
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/ManifestInstallerTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4f0df7dc33708f34ca0ab943a7176b3d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/VersionComparisonTests.cs b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/VersionComparisonTests.cs
new file mode 100644
index 00000000..c9003239
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/VersionComparisonTests.cs
@@ -0,0 +1,232 @@
+/*
+┌──────────────────────────────────────────────────────────────────┐
+│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
+│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
+│ Copyright (c) 2025 Ivan Murzak │
+│ Licensed under the Apache License, Version 2.0. │
+│ See the LICENSE file in the project root for more information. │
+└──────────────────────────────────────────────────────────────────┘
+*/
+using System.IO;
+using NUnit.Framework;
+using com.IvanMurzak.Unity.MCP.Installer.SimpleJSON;
+
+namespace com.IvanMurzak.Unity.MCP.Installer.Tests
+{
+ public class VersionComparisonTests
+ {
+ const string TestManifestPath = "Temp/com.IvanMurzak/Unity.MCP.Installer.Tests/test_manifest.json";
+ const string PackageId = "com.ivanmurzak.unity.mcp";
+
+ [SetUp]
+ public void SetUp()
+ {
+ var dir = Path.GetDirectoryName(TestManifestPath);
+ if (!Directory.Exists(dir))
+ Directory.CreateDirectory(dir);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ if (File.Exists(TestManifestPath))
+ File.Delete(TestManifestPath);
+ }
+
+ [Test]
+ public void ShouldUpdateVersion_PatchVersionHigher_ReturnsTrue()
+ {
+ // Act & Assert
+ Assert.IsTrue(
+ condition: Installer.ShouldUpdateVersion(
+ currentVersion: "1.5.1",
+ installerVersion: "1.5.2"
+ ),
+ message: "Should update when patch version is higher"
+ );
+ }
+
+ [Test]
+ public void ShouldUpdateVersion_PatchVersionLower_ReturnsFalse()
+ {
+ // Act & Assert
+ Assert.IsFalse(
+ condition: Installer.ShouldUpdateVersion(
+ currentVersion: "1.5.2",
+ installerVersion: "1.5.1"
+ ),
+ message: "Should not downgrade when patch version is lower"
+ );
+ }
+
+ [Test]
+ public void ShouldUpdateVersion_MinorVersionHigher_ReturnsTrue()
+ {
+ // Act & Assert
+ Assert.IsTrue(
+ condition: Installer.ShouldUpdateVersion(
+ currentVersion: "1.5.0",
+ installerVersion: "1.6.0"
+ ),
+ message: "Should update when minor version is higher"
+ );
+ }
+
+ [Test]
+ public void ShouldUpdateVersion_MinorVersionLower_ReturnsFalse()
+ {
+ // Act & Assert
+ Assert.IsFalse(
+ condition: Installer.ShouldUpdateVersion(
+ currentVersion: "1.6.0",
+ installerVersion: "1.5.0"
+ ),
+ message: "Should not downgrade when minor version is lower"
+ );
+ }
+
+ [Test]
+ public void ShouldUpdateVersion_MajorVersionHigher_ReturnsTrue()
+ {
+ // Act & Assert
+ Assert.IsTrue(
+ condition: Installer.ShouldUpdateVersion(
+ currentVersion: "1.5.0",
+ installerVersion: "2.0.0"
+ ),
+ message: "Should update when major version is higher"
+ );
+ }
+
+ [Test]
+ public void ShouldUpdateVersion_MajorVersionLower_ReturnsFalse()
+ {
+ // Act & Assert
+ Assert.IsFalse(
+ condition: Installer.ShouldUpdateVersion(
+ currentVersion: "2.0.0",
+ installerVersion: "1.5.0"
+ ),
+ message: "Should not downgrade when major version is lower"
+ );
+ }
+
+ [Test]
+ public void ShouldUpdateVersion_SameVersion_ReturnsFalse()
+ {
+ // Act & Assert
+ Assert.IsFalse(
+ condition: Installer.ShouldUpdateVersion(
+ currentVersion: "1.5.2",
+ installerVersion: "1.5.2"
+ ),
+ message: "Should not update when versions are the same"
+ );
+ }
+
+ [Test]
+ public void ShouldUpdateVersion_EmptyCurrentVersion_ReturnsTrue()
+ {
+ // Act & Assert
+ Assert.IsTrue(
+ condition: Installer.ShouldUpdateVersion(
+ currentVersion: "",
+ installerVersion: "1.5.2"
+ ),
+ message: "Should install when no current version exists"
+ );
+ }
+
+ [Test]
+ public void ShouldUpdateVersion_NullCurrentVersion_ReturnsTrue()
+ {
+ // Act & Assert
+ Assert.IsTrue(
+ condition: Installer.ShouldUpdateVersion(
+ currentVersion: null,
+ installerVersion: "1.5.2"
+ ),
+ message: "Should install when current version is null"
+ );
+ }
+
+ [Test]
+ public void AddScopedRegistryIfNeeded_PreventVersionDowngrade_Integration()
+ {
+ // Arrange - Create manifest with higher version
+ var versionParts = Installer.Version.Split('.');
+ var majorVersion = int.Parse(versionParts[0]);
+ var higherVersion = $"{majorVersion + 10}.0.0";
+ var manifest = new JSONObject
+ {
+ [Installer.Dependencies] = new JSONObject
+ {
+ [PackageId] = higherVersion
+ },
+ [Installer.ScopedRegistries] = new JSONArray()
+ };
+ File.WriteAllText(TestManifestPath, manifest.ToString(2));
+
+ // Act - Run installer (should NOT downgrade)
+ Installer.AddScopedRegistryIfNeeded(TestManifestPath);
+
+ // Assert - Version should remain unchanged
+ var updatedContent = File.ReadAllText(TestManifestPath);
+ var updatedManifest = JSONObject.Parse(updatedContent);
+ var actualVersion = updatedManifest[Installer.Dependencies][PackageId];
+
+ Assert.AreEqual(higherVersion, actualVersion.ToString().Trim('"'),
+ "Version should not be downgraded from higher version");
+ }
+
+ [Test]
+ public void AddScopedRegistryIfNeeded_AllowVersionUpgrade_Integration()
+ {
+ // Arrange - Create manifest with lower version (0.0.1 is always lower)
+ var lowerVersion = "0.0.1";
+ var manifest = new JSONObject
+ {
+ [Installer.Dependencies] = new JSONObject
+ {
+ [PackageId] = lowerVersion
+ },
+ [Installer.ScopedRegistries] = new JSONArray()
+ };
+ File.WriteAllText(TestManifestPath, manifest.ToString(2));
+
+ // Act - Run installer (should upgrade)
+ Installer.AddScopedRegistryIfNeeded(TestManifestPath);
+
+ // Assert - Version should be upgraded to installer version
+ var updatedContent = File.ReadAllText(TestManifestPath);
+ var updatedManifest = JSONObject.Parse(updatedContent);
+ var actualVersion = updatedManifest[Installer.Dependencies][PackageId];
+
+ Assert.AreEqual(Installer.Version, actualVersion.ToString().Trim('"'),
+ "Version should be upgraded to installer version");
+ }
+
+ [Test]
+ public void AddScopedRegistryIfNeeded_NoExistingDependency_InstallsNewVersion()
+ {
+ // Arrange - Create manifest without the package
+ var manifest = new JSONObject
+ {
+ [Installer.Dependencies] = new JSONObject(),
+ [Installer.ScopedRegistries] = new JSONArray()
+ };
+ File.WriteAllText(TestManifestPath, manifest.ToString(2));
+
+ // Act - Run installer
+ Installer.AddScopedRegistryIfNeeded(TestManifestPath);
+
+ // Assert - Package should be added with installer version
+ var updatedContent = File.ReadAllText(TestManifestPath);
+ var updatedManifest = JSONObject.Parse(updatedContent);
+ var actualVersion = updatedManifest[Installer.Dependencies][PackageId];
+
+ Assert.AreEqual(Installer.Version, actualVersion.ToString().Trim('"'),
+ "New package should be installed with installer version");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/VersionComparisonTests.cs.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/VersionComparisonTests.cs.meta
new file mode 100644
index 00000000..93f604d9
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/VersionComparisonTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ece21aef0b677a246b2e4d410e769d3b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/com.IvanMurzak.Unity.MCP.Installer.Tests.asmdef b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/com.IvanMurzak.Unity.MCP.Installer.Tests.asmdef
new file mode 100644
index 00000000..e846fda7
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/com.IvanMurzak.Unity.MCP.Installer.Tests.asmdef
@@ -0,0 +1,22 @@
+{
+ "name": "com.IvanMurzak.Unity.MCP.Installer.Tests",
+ "rootNamespace": "com.IvanMurzak.Unity.MCP.Installer.Tests",
+ "references": [
+ "com.IvanMurzak.Unity.MCP.Installer"
+ ],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": true,
+ "precompiledReferences": [
+ "nunit.framework.dll"
+ ],
+ "autoReferenced": false,
+ "defineConstraints": [
+ "UNITY_INCLUDE_TESTS"
+ ],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/com.IvanMurzak.Unity.MCP.Installer.Tests.asmdef.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/com.IvanMurzak.Unity.MCP.Installer.Tests.asmdef.meta
new file mode 100644
index 00000000..7585df09
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/Tests/com.IvanMurzak.Unity.MCP.Installer.Tests.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 62688e1a637d08f4583b4de5fdce7fc4
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/com.IvanMurzak.Unity.MCP.Installer.asmdef b/Assets/com.IvanMurzak/AI Game Dev Installer/com.IvanMurzak.Unity.MCP.Installer.asmdef
new file mode 100644
index 00000000..6196d372
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/com.IvanMurzak.Unity.MCP.Installer.asmdef
@@ -0,0 +1,16 @@
+{
+ "name": "com.IvanMurzak.Unity.MCP.Installer",
+ "rootNamespace": "com.IvanMurzak.Unity.MCP.Installer",
+ "references": [],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": false,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Assets/com.IvanMurzak/AI Game Dev Installer/com.IvanMurzak.Unity.MCP.Installer.asmdef.meta b/Assets/com.IvanMurzak/AI Game Dev Installer/com.IvanMurzak.Unity.MCP.Installer.asmdef.meta
new file mode 100644
index 00000000..5cb06203
--- /dev/null
+++ b/Assets/com.IvanMurzak/AI Game Dev Installer/com.IvanMurzak.Unity.MCP.Installer.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ba51295843cc38b4ab50bce94c164333
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/manifest.json b/Packages/manifest.json
index 5b03c115..ab5431e9 100644
--- a/Packages/manifest.json
+++ b/Packages/manifest.json
@@ -1,6 +1,5 @@
{
"dependencies": {
- "com.coplaydev.unity-mcp": "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity",
"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",
"com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity",
"com.unity.2d.sprite": "1.0.0",
@@ -48,6 +47,21 @@
"com.unity.modules.video": "1.0.0",
"com.unity.modules.vr": "1.0.0",
"com.unity.modules.wind": "1.0.0",
- "com.unity.modules.xr": "1.0.0"
- }
-}
+ "com.unity.modules.xr": "1.0.0",
+ "com.ivanmurzak.unity.mcp": "0.48.1"
+ },
+ "scopedRegistries": [
+ {
+ "name": "package.openupm.com",
+ "url": "https://package.openupm.com",
+ "scopes": [
+ "com.ivanmurzak",
+ "extensions.unity",
+ "org.nuget.com.ivanmurzak",
+ "org.nuget.microsoft",
+ "org.nuget.system",
+ "org.nuget.r3"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json
index 945fcf6e..8fb8ba0a 100644
--- a/Packages/packages-lock.json
+++ b/Packages/packages-lock.json
@@ -1,15 +1,5 @@
{
"dependencies": {
- "com.coplaydev.unity-mcp": {
- "version": "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity",
- "depth": 0,
- "source": "git",
- "dependencies": {
- "com.unity.nuget.newtonsoft-json": "3.0.2",
- "com.unity.test-framework": "1.1.31"
- },
- "hash": "12dd9bd516aba822a9206209880a0b650f60ccfa"
- },
"com.cysharp.unitask": {
"version": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask",
"depth": 0,
@@ -24,6 +14,28 @@
"dependencies": {},
"hash": "a7c6b49a0141a5bff9b1983e38137522ef61977d"
},
+ "com.ivanmurzak.unity.mcp": {
+ "version": "0.48.1",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.modules.uielements": "1.0.0",
+ "com.unity.test-framework": "1.1.33",
+ "extensions.unity.playerprefsex": "2.1.2",
+ "org.nuget.microsoft.aspnetcore.signalr.client": "10.0.3",
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "10.0.3",
+ "org.nuget.microsoft.bcl.memory": "10.0.3",
+ "org.nuget.microsoft.codeanalysis.csharp": "4.14.0",
+ "org.nuget.microsoft.extensions.caching.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.hosting.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.logging": "10.0.3",
+ "org.nuget.microsoft.extensions.logging.abstractions": "10.0.3",
+ "org.nuget.r3": "1.3.0",
+ "org.nuget.system.text.json": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
"com.unity.2d.sprite": {
"version": "1.0.0",
"depth": 0,
@@ -247,6 +259,450 @@
},
"url": "https://packages.unity.com"
},
+ "extensions.unity.playerprefsex": {
+ "version": "2.1.2",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.aspnetcore.connections.abstractions": {
+ "version": "10.0.3",
+ "depth": 3,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.extensions.features": "10.0.3",
+ "org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
+ "org.nuget.system.io.pipelines": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.aspnetcore.http.connections.client": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.aspnetcore.http.connections.common": "10.0.3",
+ "org.nuget.microsoft.extensions.logging.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.options": "10.0.3",
+ "org.nuget.system.net.serversentevents": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.aspnetcore.http.connections.common": {
+ "version": "10.0.3",
+ "depth": 3,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.aspnetcore.connections.abstractions": "10.0.3",
+ "org.nuget.system.text.json": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.aspnetcore.signalr.client": {
+ "version": "10.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.aspnetcore.signalr.client.core": "10.0.3",
+ "org.nuget.microsoft.aspnetcore.http.connections.client": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.aspnetcore.signalr.client.core": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": "10.0.3",
+ "org.nuget.microsoft.aspnetcore.signalr.common": "10.0.3",
+ "org.nuget.microsoft.bcl.timeprovider": "10.0.3",
+ "org.nuget.microsoft.extensions.dependencyinjection": "10.0.3",
+ "org.nuget.microsoft.extensions.logging": "10.0.3",
+ "org.nuget.system.threading.channels": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.aspnetcore.signalr.common": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.aspnetcore.connections.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.options": "10.0.3",
+ "org.nuget.system.text.json": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.aspnetcore.signalr.protocols.json": {
+ "version": "10.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.aspnetcore.signalr.common": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.bcl.asyncinterfaces": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.threading.tasks.extensions": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.bcl.memory": {
+ "version": "10.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.memory": "4.6.3",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.bcl.timeprovider": {
+ "version": "10.0.3",
+ "depth": 3,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.codeanalysis.analyzers": {
+ "version": "3.11.0",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.codeanalysis.common": {
+ "version": "4.14.0",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.codeanalysis.analyzers": "3.11.0",
+ "org.nuget.system.collections.immutable": "9.0.0",
+ "org.nuget.system.memory": "4.5.5",
+ "org.nuget.system.reflection.metadata": "9.0.0",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.0.0",
+ "org.nuget.system.text.encoding.codepages": "7.0.0",
+ "org.nuget.system.threading.tasks.extensions": "4.5.4",
+ "org.nuget.system.buffers": "4.5.1",
+ "org.nuget.system.numerics.vectors": "4.5.0"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.codeanalysis.csharp": {
+ "version": "4.14.0",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.codeanalysis.common": "4.14.0",
+ "org.nuget.microsoft.codeanalysis.analyzers": "3.11.0",
+ "org.nuget.system.buffers": "4.5.1",
+ "org.nuget.system.collections.immutable": "9.0.0",
+ "org.nuget.system.memory": "4.5.5",
+ "org.nuget.system.numerics.vectors": "4.5.0",
+ "org.nuget.system.reflection.metadata": "9.0.0",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.0.0",
+ "org.nuget.system.text.encoding.codepages": "7.0.0",
+ "org.nuget.system.threading.tasks.extensions": "4.5.4"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.caching.abstractions": {
+ "version": "10.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.extensions.primitives": "10.0.3",
+ "org.nuget.system.threading.tasks.extensions": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.configuration.abstractions": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.extensions.primitives": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.dependencyinjection": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
+ "org.nuget.system.threading.tasks.extensions": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": {
+ "version": "10.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
+ "org.nuget.system.threading.tasks.extensions": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.diagnostics.abstractions": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.options": "10.0.3",
+ "org.nuget.system.diagnostics.diagnosticsource": "10.0.3",
+ "org.nuget.system.buffers": "4.6.1",
+ "org.nuget.system.memory": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.features": {
+ "version": "10.0.3",
+ "depth": 4,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.fileproviders.abstractions": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.extensions.primitives": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.hosting.abstractions": {
+ "version": "10.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
+ "org.nuget.microsoft.extensions.configuration.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.diagnostics.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.fileproviders.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.logging.abstractions": "10.0.3",
+ "org.nuget.system.threading.tasks.extensions": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.logging": {
+ "version": "10.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
+ "org.nuget.microsoft.extensions.dependencyinjection": "10.0.3",
+ "org.nuget.microsoft.extensions.logging.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.options": "10.0.3",
+ "org.nuget.system.diagnostics.diagnosticsource": "10.0.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.logging.abstractions": {
+ "version": "10.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
+ "org.nuget.system.diagnostics.diagnosticsource": "10.0.3",
+ "org.nuget.system.buffers": "4.6.1",
+ "org.nuget.system.memory": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.options": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.extensions.dependencyinjection.abstractions": "10.0.3",
+ "org.nuget.microsoft.extensions.primitives": "10.0.3",
+ "org.nuget.system.componentmodel.annotations": "5.0.0"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.microsoft.extensions.primitives": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.memory": "4.6.3",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.r3": {
+ "version": "1.3.0",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.bcl.timeprovider": "8.0.0",
+ "org.nuget.system.buffers": "4.5.1",
+ "org.nuget.system.componentmodel.annotations": "5.0.0",
+ "org.nuget.system.memory": "4.5.5",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.0.0",
+ "org.nuget.system.threading.channels": "8.0.0"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.buffers": {
+ "version": "4.6.1",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.collections.immutable": {
+ "version": "9.0.0",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.memory": "4.5.5",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.0.0"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.componentmodel.annotations": {
+ "version": "5.0.0",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.diagnostics.diagnosticsource": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.memory": "4.6.3",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.io.pipelines": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.buffers": "4.6.1",
+ "org.nuget.system.memory": "4.6.3",
+ "org.nuget.system.threading.tasks.extensions": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.memory": {
+ "version": "4.6.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.buffers": "4.6.1",
+ "org.nuget.system.numerics.vectors": "4.6.1",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.net.serversentevents": {
+ "version": "10.0.3",
+ "depth": 3,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
+ "org.nuget.system.memory": "4.6.3",
+ "org.nuget.system.threading.tasks.extensions": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.numerics.vectors": {
+ "version": "4.6.1",
+ "depth": 3,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.reflection.metadata": {
+ "version": "9.0.0",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.collections.immutable": "9.0.0",
+ "org.nuget.system.memory": "4.5.5"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.runtime.compilerservices.unsafe": {
+ "version": "6.1.2",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.text.encoding.codepages": {
+ "version": "7.0.0",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.memory": "4.5.5",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.0.0"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.text.encodings.web": {
+ "version": "10.0.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.buffers": "4.6.1",
+ "org.nuget.system.memory": "4.6.3",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.text.json": {
+ "version": "10.0.3",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
+ "org.nuget.system.io.pipelines": "10.0.3",
+ "org.nuget.system.text.encodings.web": "10.0.3",
+ "org.nuget.system.buffers": "4.6.1",
+ "org.nuget.system.memory": "4.6.3",
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.1.2",
+ "org.nuget.system.threading.tasks.extensions": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.threading.channels": {
+ "version": "10.0.3",
+ "depth": 3,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.microsoft.bcl.asyncinterfaces": "10.0.3",
+ "org.nuget.system.threading.tasks.extensions": "4.6.3"
+ },
+ "url": "https://package.openupm.com"
+ },
+ "org.nuget.system.threading.tasks.extensions": {
+ "version": "4.6.3",
+ "depth": 2,
+ "source": "registry",
+ "dependencies": {
+ "org.nuget.system.runtime.compilerservices.unsafe": "6.1.2"
+ },
+ "url": "https://package.openupm.com"
+ },
"com.unity.modules.accessibility": {
"version": "1.0.0",
"depth": 0,
diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset
index be4a7974..9812c7df 100644
--- a/ProjectSettings/PackageManagerSettings.asset
+++ b/ProjectSettings/PackageManagerSettings.asset
@@ -12,11 +12,13 @@ MonoBehaviour:
m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
m_Name:
m_EditorClassIdentifier:
- m_EnablePreviewPackages: 0
- m_EnablePackageDependencies: 0
+ m_EnablePreReleasePackages: 0
m_AdvancedSettingsExpanded: 1
m_ScopedRegistriesSettingsExpanded: 1
+ m_SeeAllPackageVersions: 0
+ m_DismissPreviewPackagesInUse: 0
oneTimeWarningShown: 0
+ oneTimeDeprecatedPopUpShown: 0
m_Registries:
- m_Id: main
m_Name:
@@ -24,20 +26,31 @@ MonoBehaviour:
m_Scopes: []
m_IsDefault: 1
m_Capabilities: 7
- m_UserSelectedRegistryName:
+ m_ConfigSource: 0
+ m_Compliance:
+ m_Status: 0
+ m_Violations: []
+ - m_Id: scoped:project:package.openupm.com
+ m_Name: package.openupm.com
+ m_Url: https://package.openupm.com
+ m_Scopes:
+ - com.ivanmurzak
+ - extensions.unity
+ - org.nuget.com.ivanmurzak
+ - org.nuget.microsoft
+ - org.nuget.system
+ - org.nuget.r3
+ m_IsDefault: 0
+ m_Capabilities: 0
+ m_ConfigSource: 4
+ m_Compliance:
+ m_Status: 0
+ m_Violations: []
+ m_UserSelectedRegistryName: package.openupm.com
m_UserAddingNewScopedRegistry: 0
m_RegistryInfoDraft:
- m_ErrorMessage:
- m_Original:
- m_Id:
- m_Name:
- m_Url:
- m_Scopes: []
- m_IsDefault: 0
- m_Capabilities: 0
m_Modified: 0
- m_Name:
- m_Url:
- m_Scopes:
- -
- m_SelectedScopeIndex: 0
+ m_ErrorMessage:
+ m_UserModificationsInstanceId: -868
+ m_OriginalInstanceId: -870
+ m_LoadAssets: 0
diff --git a/XRBase.slnx b/XRBase.slnx
index 121985aa..c4589777 100644
--- a/XRBase.slnx
+++ b/XRBase.slnx
@@ -9,10 +9,12 @@
+
+