diff --git a/API/API.projitems b/API/API.projitems
index d49934f..c72f357 100644
--- a/API/API.projitems
+++ b/API/API.projitems
@@ -51,9 +51,17 @@
+
+
+
+
+
+
+
-
+
+
\ No newline at end of file
diff --git a/API/Utilities/CoroutineUtils.cs b/API/Utilities/CoroutineUtils.cs
new file mode 100644
index 0000000..8bd3eda
--- /dev/null
+++ b/API/Utilities/CoroutineUtils.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections;
+using UnityEngine;
+
+namespace KKAPI.Utilities
+{
+ ///
+ /// Utility methods for working with coroutines.
+ ///
+ public static class CoroutineUtils
+ {
+ ///
+ /// Create a coroutine that calls the appendCoroutine after baseCoroutine finishes
+ ///
+ public static IEnumerator AppendCo(this IEnumerator baseCoroutine, IEnumerator appendCoroutine)
+ {
+ return ComposeCoroutine(baseCoroutine, appendCoroutine);
+ }
+
+ ///
+ /// Create a coroutine that calls the yieldInstruction after baseCoroutine finishes.
+ /// Useless on its own, append further coroutines to run after this.
+ ///
+ public static IEnumerator AppendCo(this IEnumerator baseCoroutine, YieldInstruction yieldInstruction)
+ {
+ return new object[] { baseCoroutine, yieldInstruction }.GetEnumerator();
+ }
+
+ ///
+ /// Create a coroutine that calls each of the actions in order after base coroutine finishes.
+ /// One action is called per frame. First action is called right after the coroutine finishes.
+ ///
+ public static IEnumerator AppendCo(this IEnumerator baseCoroutine, params Action[] actions)
+ {
+ return ComposeCoroutine(baseCoroutine, CreateCoroutine(actions));
+ }
+
+ ///
+ /// Create a coroutine that calls each of the action delegates on consecutive frames.
+ /// One action is called per frame. First action is called right away. There is no frame skip after the last action.
+ ///
+ public static IEnumerator CreateCoroutine(params Action[] actions)
+ {
+ var first = true;
+ foreach (var action in actions)
+ {
+ if (first)
+ first = false;
+ else
+ yield return null;
+
+ action();
+ }
+ }
+
+ ///
+ /// Create a coroutine that calls each of the supplied coroutines in order.
+ ///
+ public static IEnumerator ComposeCoroutine(params IEnumerator[] coroutine)
+ {
+ return coroutine.GetEnumerator();
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/Utilities/Extensions.cs b/API/Utilities/Extensions.cs
new file mode 100644
index 0000000..930aa9a
--- /dev/null
+++ b/API/Utilities/Extensions.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace KKAPI.Utilities
+{
+ ///
+ /// General utility extensions that don't fit in other categories.
+ ///
+ public static class Extensions
+ {
+ ///
+ /// Wrap this dictionary in a read-only wrapper that will prevent any changes to it.
+ /// Warning: Any reference types inside the dictionary can still be modified.
+ ///
+ public static ReadOnlyDictionary ToReadOnlyDictionary(this IDictionary original)
+ {
+ return new ReadOnlyDictionary(original);
+ }
+
+ ///
+ /// Mark GameObject of this Component as ignored by AutoTranslator. Prevents AutoTranslator from trying to translate custom UI elements.
+ ///
+ public static void MarkXuaIgnored(this Component target)
+ {
+ if (target == null) throw new ArgumentNullException(nameof(target));
+ target.gameObject.name += "(XUAIGNORE)";
+ }
+ }
+}
diff --git a/API/Utilities/HSceneUtils.cs b/API/Utilities/HSceneUtils.cs
new file mode 100644
index 0000000..2aab92b
--- /dev/null
+++ b/API/Utilities/HSceneUtils.cs
@@ -0,0 +1,52 @@
+using System;
+
+namespace KKAPI.Utilities
+{
+ ///
+ /// Utility methods for working with H Scenes / main game.
+ ///
+ public static class HSceneUtils
+ {
+#if KK
+ ///
+ /// Get the heroine that is currently in leading position in the h scene.
+ /// In 3P returns the heroine the cum options affect. Outside of 3P it gets the single heroine.
+ ///
+ public static SaveData.Heroine GetLeadingHeroine(this HFlag hFlag)
+ {
+ if (hFlag == null) throw new ArgumentNullException(nameof(hFlag));
+ return hFlag.lstHeroine[GetLeadingHeroineId(hFlag)];
+ }
+
+ ///
+ /// Get the heroine that is currently in leading position in the h scene.
+ /// In 3P returns the heroine the cum options affect. Outside of 3P it gets the single heroine.
+ ///
+ public static SaveData.Heroine GetLeadingHeroine(this HSprite hSprite)
+ {
+ if (hSprite == null) throw new ArgumentNullException(nameof(hSprite));
+ return GetLeadingHeroine(hSprite.flags);
+ }
+
+ ///
+ /// Get ID of the heroine that is currently in leading position in the h scene. 0 is the main heroine, 1 is the "tag along".
+ /// In 3P returns the heroine the cum options affect. Outside of 3P it gets the single heroine.
+ ///
+ public static int GetLeadingHeroineId(this HFlag hFlag)
+ {
+ if (hFlag == null) throw new ArgumentNullException(nameof(hFlag));
+ return hFlag.mode == HFlag.EMode.houshi3P || hFlag.mode == HFlag.EMode.sonyu3P ? hFlag.nowAnimationInfo.id % 2 : 0;
+ }
+
+ ///
+ /// Get ID of the heroine that is currently in leading position in the h scene. 0 is the main heroine, 1 is the "tag along".
+ /// In 3P returns the heroine the cum options affect. Outside of 3P it gets the single heroine.
+ ///
+ public static int GetLeadingHeroineId(this HSprite hSprite)
+ {
+ if (hSprite == null) throw new ArgumentNullException(nameof(hSprite));
+ return GetLeadingHeroineId(hSprite.flags);
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/API/Utilities/IMGUIUtils.cs b/API/Utilities/IMGUIUtils.cs
new file mode 100644
index 0000000..524398a
--- /dev/null
+++ b/API/Utilities/IMGUIUtils.cs
@@ -0,0 +1,104 @@
+using UnityEngine;
+
+namespace KKAPI.Utilities
+{
+ ///
+ /// Utility methods for working with IMGUI / OnGui.
+ ///
+ public static class IMGUIUtils
+ {
+ private static Texture2D SolidBoxTex { get; set; }
+
+ ///
+ /// Draw a gray non-transparent GUI.Box at the specified rect. Use before a window or other controls to get rid of
+ /// the default transparency and make the GUI easier to read.
+ ///
+ public static void DrawSolidBox(Rect boxRect)
+ {
+ if (SolidBoxTex == null)
+ {
+ var windowBackground = new Texture2D(1, 1, TextureFormat.ARGB32, false);
+ windowBackground.SetPixel(0, 0, new Color(0.84f, 0.84f, 0.84f));
+ windowBackground.Apply();
+ SolidBoxTex = windowBackground;
+ }
+
+ // It's necessary to make a new GUIStyle here or the texture doesn't show up
+ GUI.Box(boxRect, GUIContent.none, new GUIStyle { normal = new GUIStyleState { background = SolidBoxTex } });
+ }
+
+ public static void DrawLabelWithOutline(Rect rect, string text, GUIStyle style, Color outColor, Color inColor, float size)
+ {
+ var halfSize = size * 0.5F;
+
+ var backupGuiColor = GUI.color;
+ var backupTextColor = style.normal.textColor;
+
+ style.normal.textColor = outColor;
+ GUI.color = outColor;
+
+ rect.x -= halfSize;
+ GUI.Label(rect, text, style);
+
+ rect.x += size;
+ GUI.Label(rect, text, style);
+
+ rect.x -= halfSize;
+ rect.y -= halfSize;
+ GUI.Label(rect, text, style);
+
+ rect.y += size;
+ GUI.Label(rect, text, style);
+
+ rect.y -= halfSize;
+ style.normal.textColor = inColor;
+ GUI.color = backupGuiColor;
+ GUI.Label(rect, text, style);
+
+ style.normal.textColor = backupTextColor;
+ }
+
+ public static void DrawLabelWithShadow(Rect rect, GUIContent content, GUIStyle style, Color txtColor, Color shadowColor, Vector2 direction)
+ {
+ var backupColor = style.normal.textColor;
+
+ style.normal.textColor = shadowColor;
+ rect.x += direction.x;
+ rect.y += direction.y;
+ GUI.Label(rect, content, style);
+
+ style.normal.textColor = txtColor;
+ rect.x -= direction.x;
+ rect.y -= direction.y;
+ GUI.Label(rect, content, style);
+
+ style.normal.textColor = backupColor;
+ }
+
+ public static void DrawLayoutLabelWithShadow(GUIContent content, GUIStyle style, Color txtColor, Color shadowColor, Vector2 direction, params GUILayoutOption[] options)
+ {
+ DrawLabelWithShadow(GUILayoutUtility.GetRect(content, style, options), content, style, txtColor, shadowColor, direction);
+ }
+
+ public static bool DrawButtonWithShadow(Rect r, GUIContent content, GUIStyle style, float shadowAlpha, Vector2 direction)
+ {
+ var letters = new GUIStyle(style);
+ letters.normal.background = null;
+ letters.hover.background = null;
+ letters.active.background = null;
+
+ var result = GUI.Button(r, content, style);
+
+ var color = r.Contains(Event.current.mousePosition) ? letters.hover.textColor : letters.normal.textColor;
+
+ DrawLabelWithShadow(r, content, letters, color, new Color(0f, 0f, 0f, shadowAlpha), direction);
+
+ return result;
+ }
+
+ public static bool DrawLayoutButtonWithShadow(GUIContent content, GUIStyle style, float shadowAlpha, Vector2 direction, params GUILayoutOption[] options)
+ {
+ return DrawButtonWithShadow(GUILayoutUtility.GetRect(content, style, options), content, style, shadowAlpha, direction);
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/Utilities/Utils.cs b/API/Utilities/ReadOnlyDictionary.cs
similarity index 86%
rename from API/Utilities/Utils.cs
rename to API/Utilities/ReadOnlyDictionary.cs
index 9b957a7..b2274b1 100644
--- a/API/Utilities/Utils.cs
+++ b/API/Utilities/ReadOnlyDictionary.cs
@@ -6,18 +6,6 @@
namespace KKAPI.Utilities
{
- public static class Extensions
- {
- ///
- /// Wrap this dictionary in a read-only wrapper that will prevent any changes to it.
- /// Warning: Any reference types inside the dictionary can still be modified.
- ///
- public static ReadOnlyDictionary ToReadOnlyDictionary(this IDictionary original)
- {
- return new ReadOnlyDictionary(original);
- }
- }
-
///
/// Read-only dictionary wrapper. Will protect the base dictionary from being changed.
/// Warning: Any reference types inside the dictionary can still be modified.
@@ -127,4 +115,4 @@ private static Exception ReadOnlyException()
return new NotSupportedException("This dictionary is read-only");
}
}
-}
+}
\ No newline at end of file
diff --git a/API/Utilities/ResourceUtils.cs b/API/Utilities/ResourceUtils.cs
new file mode 100644
index 0000000..5f53c85
--- /dev/null
+++ b/API/Utilities/ResourceUtils.cs
@@ -0,0 +1,46 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace KKAPI.Utilities
+{
+ ///
+ /// Utility methods for working with embedded resources.
+ ///
+ public static class ResourceUtils
+ {
+ ///
+ /// Read all bytes starting at current position and ending at the end of the stream.
+ ///
+ public static byte[] ReadAllBytes(this Stream input)
+ {
+ var buffer = new byte[16 * 1024];
+ using (var ms = new MemoryStream())
+ {
+ int read;
+ while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
+ ms.Write(buffer, 0, read);
+ return ms.ToArray();
+ }
+ }
+
+ ///
+ /// Get a file set as "Embedded Resource" from the assembly that is calling this code, or optionally from a specified assembly.
+ /// The filename is matched to the end of the resource path, no need to give the full path.
+ /// If 0 or more than 1 resources match the provided filename, an exception is thrown.
+ /// For example if you have a file "ProjectRoot\Resources\icon.png" set as "Embedded Resource", you can use this to load it by
+ /// doing GetEmbeddedResource("icon.png"), assuming that no other embedded files have the same name.
+ ///
+ public static byte[] GetEmbeddedResource(string resourceFileName, Assembly containingAssembly = null)
+ {
+ if (containingAssembly == null)
+ containingAssembly = Assembly.GetCallingAssembly();
+
+ var resourceName = containingAssembly.GetManifestResourceNames().Single(str => str.EndsWith(resourceFileName));
+
+ using (var stream = containingAssembly.GetManifestResourceStream(resourceName))
+ return ReadAllBytes(stream ?? throw new InvalidOperationException($"The resource {resourceFileName} was not found"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/Utilities/TextUtils.cs b/API/Utilities/TextUtils.cs
new file mode 100644
index 0000000..236cd08
--- /dev/null
+++ b/API/Utilities/TextUtils.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace KKAPI.Utilities
+{
+ ///
+ /// Utility methods for working with text.
+ ///
+ public static class TextUtils
+ {
+ ///
+ /// Convert PascalCase to Sentence case.
+ ///
+ public static string PascalCaseToSentenceCase(this string str)
+ {
+ if (str == null) throw new ArgumentNullException(nameof(str));
+ return Regex.Replace(str, "[a-z][A-Z]", m => $"{m.Value[0]} {char.ToLower(m.Value[1])}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/Utilities/TextureUtils.cs b/API/Utilities/TextureUtils.cs
new file mode 100644
index 0000000..5589516
--- /dev/null
+++ b/API/Utilities/TextureUtils.cs
@@ -0,0 +1,57 @@
+using System;
+using UnityEngine;
+
+namespace KKAPI.Utilities
+{
+ ///
+ /// Utility methods for working with texture objects.
+ ///
+ public static class TextureUtils
+ {
+ ///
+ /// Copy this texture inside a new editable Texture2D.
+ ///
+ /// Texture to copy
+ /// Format of the copy
+ /// Copy has mipmaps
+ public static Texture2D ToTexture2D(this Texture tex, TextureFormat format = TextureFormat.ARGB32, bool mipMaps = false)
+ {
+ var rt = RenderTexture.GetTemporary(tex.width, tex.height);
+ var prev = RenderTexture.active;
+ RenderTexture.active = rt;
+
+ GL.Clear(true, true, Color.clear);
+
+ Graphics.Blit(tex, rt);
+
+ var t = new Texture2D(tex.width, tex.height, format, mipMaps);
+ t.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
+ t.Apply(false);
+
+ RenderTexture.active = prev;
+ RenderTexture.ReleaseTemporary(rt);
+ return t;
+ }
+
+ ///
+ /// Create texture from an image stored in a byte array, for example a png file read from disk.
+ ///
+ public static Texture2D LoadTexture(this byte[] texBytes, TextureFormat format = TextureFormat.ARGB32, bool mipMaps = false)
+ {
+ if (texBytes == null) throw new ArgumentNullException(nameof(texBytes));
+
+ var tex = new Texture2D(2, 2, format, mipMaps);
+ tex.LoadImage(texBytes);
+ return tex;
+ }
+
+ ///
+ /// Create a sprite based on this texture.
+ ///
+ public static Sprite ToSprite(this Texture2D texture)
+ {
+ if (texture == null) throw new ArgumentNullException(nameof(texture));
+ return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/Utilities/WindowsStringComparer.cs b/API/Utilities/WindowsStringComparer.cs
new file mode 100644
index 0000000..c197d55
--- /dev/null
+++ b/API/Utilities/WindowsStringComparer.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace KKAPI.Utilities
+{
+ ///
+ /// String comparer that is equivalent to the one used by Windows Explorer to sort files (e.g. 2 will go before 10, unlike normal compare).
+ ///
+ ///
+ public class WindowsStringComparer : IComparer
+ {
+ [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
+ private static extern int StrCmpLogicalW(String x, String y);
+
+ ///
+ /// Compare two strings with rules used by Windows Explorer to logically sort files.
+ ///
+ public int Compare(string x, string y)
+ {
+ return StrCmpLogicalW(x, y);
+ }
+
+ ///
+ /// Compare two strings with rules used by Windows Explorer to logically sort files.
+ ///
+ public static int LogicalCompare(string x, string y)
+ {
+ return StrCmpLogicalW(x, y);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ECAPI/ECAPI.csproj b/ECAPI/ECAPI.csproj
index 411b956..1be27c8 100644
--- a/ECAPI/ECAPI.csproj
+++ b/ECAPI/ECAPI.csproj
@@ -87,6 +87,14 @@
..\..\..\EC\EC_CorePlugins\lib\UnityEngine.CoreModule.dll
False
+
+ ..\..\..\EC\EC_CorePlugins\lib\UnityEngine.ImageConversionModule.dll
+ False
+
+
+ ..\..\..\EC\EC_CorePlugins\lib\UnityEngine.IMGUIModule.dll
+ False
+
..\..\..\EC\EC_CorePlugins\lib\UnityEngine.UI.dll
False
diff --git a/doc/Home.md b/doc/Home.md
index 6997774..de21a90 100644
--- a/doc/Home.md
+++ b/doc/Home.md
@@ -82,9 +82,16 @@
## [KKAPI.Utilities](KKAPI.Utilities.md)
+- [`CoroutineUtils`](KKAPI.Utilities.md#coroutineutils)
- [`Extensions`](KKAPI.Utilities.md#extensions)
+- [`HSceneUtils`](KKAPI.Utilities.md#hsceneutils)
+- [`IMGUIUtils`](KKAPI.Utilities.md#imguiutils)
- [`OpenFileDialog`](KKAPI.Utilities.md#openfiledialog)
- [`ReadOnlyDictionary`](KKAPI.Utilities.md#readonlydictionarytkey-tvalue)
- [`RecycleBinUtil`](KKAPI.Utilities.md#recyclebinutil)
+- [`ResourceUtils`](KKAPI.Utilities.md#resourceutils)
+- [`TextureUtils`](KKAPI.Utilities.md#textureutils)
+- [`TextUtils`](KKAPI.Utilities.md#textutils)
- [`ThreadingHelper`](KKAPI.Utilities.md#threadinghelper)
+- [`WindowsStringComparer`](KKAPI.Utilities.md#windowsstringcomparer)
diff --git a/doc/KKAPI.Utilities.md b/doc/KKAPI.Utilities.md
index fa79ee7..125c0ab 100644
--- a/doc/KKAPI.Utilities.md
+++ b/doc/KKAPI.Utilities.md
@@ -1,5 +1,25 @@
+## `CoroutineUtils`
+
+Utility methods for working with coroutines.
+```csharp
+public static class KKAPI.Utilities.CoroutineUtils
+
+```
+
+Static Methods
+
+| Type | Name | Summary |
+| --- | --- | --- |
+| `IEnumerator` | AppendCo(this `IEnumerator` baseCoroutine, `IEnumerator` appendCoroutine) | Create a coroutine that calls the appendCoroutine after baseCoroutine finishes |
+| `IEnumerator` | AppendCo(this `IEnumerator` baseCoroutine, `YieldInstruction` yieldInstruction) | Create a coroutine that calls the appendCoroutine after baseCoroutine finishes |
+| `IEnumerator` | AppendCo(this `IEnumerator` baseCoroutine, `Action[]` actions) | Create a coroutine that calls the appendCoroutine after baseCoroutine finishes |
+| `IEnumerator` | ComposeCoroutine(`IEnumerator[]` coroutine) | Create a coroutine that calls each of the supplied coroutines in order. |
+| `IEnumerator` | CreateCoroutine(`Action[]` actions) | Create a coroutine that calls each of the action delegates on consecutive frames. One action is called per frame. First action is called right away. There is no frame skip after the last action. |
+
+
## `Extensions`
+General utility extensions that don't fit in other categories.
```csharp
public static class KKAPI.Utilities.Extensions
@@ -9,9 +29,48 @@ Static Methods
| Type | Name | Summary |
| --- | --- | --- |
+| `void` | MarkXuaIgnored(this `Component` target) | Mark GameObject of this Component as ignored by AutoTranslator. Prevents AutoTranslator from trying to translate custom UI elements. |
| `ReadOnlyDictionary` | ToReadOnlyDictionary(this `IDictionary` original) | Wrap this dictionary in a read-only wrapper that will prevent any changes to it. Warning: Any reference types inside the dictionary can still be modified. |
+## `HSceneUtils`
+
+Utility methods for working with H Scenes / main game.
+```csharp
+public static class KKAPI.Utilities.HSceneUtils
+
+```
+
+Static Methods
+
+| Type | Name | Summary |
+| --- | --- | --- |
+| `Heroine` | GetLeadingHeroine(this `HFlag` hFlag) | Get the heroine that is currently in leading position in the h scene. In 3P returns the heroine the cum options affect. Outside of 3P it gets the single heroine. |
+| `Heroine` | GetLeadingHeroine(this `HSprite` hSprite) | Get the heroine that is currently in leading position in the h scene. In 3P returns the heroine the cum options affect. Outside of 3P it gets the single heroine. |
+| `Int32` | GetLeadingHeroineId(this `HFlag` hFlag) | Get ID of the heroine that is currently in leading position in the h scene. 0 is the main heroine, 1 is the "tag along". In 3P returns the heroine the cum options affect. Outside of 3P it gets the single heroine. |
+| `Int32` | GetLeadingHeroineId(this `HSprite` hSprite) | Get ID of the heroine that is currently in leading position in the h scene. 0 is the main heroine, 1 is the "tag along". In 3P returns the heroine the cum options affect. Outside of 3P it gets the single heroine. |
+
+
+## `IMGUIUtils`
+
+Utility methods for working with IMGUI / OnGui.
+```csharp
+public static class KKAPI.Utilities.IMGUIUtils
+
+```
+
+Static Methods
+
+| Type | Name | Summary |
+| --- | --- | --- |
+| `Boolean` | DrawButtonWithShadow(`Rect` r, `GUIContent` content, `GUIStyle` style, `Single` shadowAlpha, `Vector2` direction) | |
+| `void` | DrawLabelWithOutline(`Rect` rect, `String` text, `GUIStyle` style, `Color` outColor, `Color` inColor, `Single` size) | |
+| `void` | DrawLabelWithShadow(`Rect` rect, `GUIContent` content, `GUIStyle` style, `Color` txtColor, `Color` shadowColor, `Vector2` direction) | |
+| `Boolean` | DrawLayoutButtonWithShadow(`GUIContent` content, `GUIStyle` style, `Single` shadowAlpha, `Vector2` direction, `GUILayoutOption[]` options) | |
+| `void` | DrawLayoutLabelWithShadow(`GUIContent` content, `GUIStyle` style, `Color` txtColor, `Color` shadowColor, `Vector2` direction, `GUILayoutOption[]` options) | |
+| `void` | DrawSolidBox(`Rect` boxRect) | Draw a gray non-transparent GUI.Box at the specified rect. Use before a window or other controls to get rid of the default transparency and make the GUI easier to read. |
+
+
## `OpenFileDialog`
Gives access to the Windows open file dialog. http://www.pinvoke.net/default.aspx/comdlg32/GetOpenFileName.html http://www.pinvoke.net/default.aspx/Structures/OpenFileName.html http://www.pinvoke.net/default.aspx/Enums/OpenSaveFileDialgueFlags.html https://social.msdn.microsoft.com/Forums/en-US/2f4dd95e-5c7b-4f48-adfc-44956b350f38/getopenfilename-for-multiple-files?forum=csharpgeneral
@@ -83,6 +142,54 @@ Static Methods
| `Boolean` | MoveToRecycleBin(`String` path) | Send file to recycle bin |
+## `ResourceUtils`
+
+Utility methods for working with embedded resources.
+```csharp
+public static class KKAPI.Utilities.ResourceUtils
+
+```
+
+Static Methods
+
+| Type | Name | Summary |
+| --- | --- | --- |
+| `Byte[]` | GetEmbeddedResource(`String` resourceFileName, `Assembly` containingAssembly = null) | Get a file set as "Embedded Resource" from the assembly that is calling this code, or optionally from a specified assembly. The filename is matched to the end of the resource path, no need to give the full path. If 0 or more than 1 resources match the provided filename, an exception is thrown. For example if you have a file "ProjectRoot\Resources\icon.png" set as "Embedded Resource", you can use this to load it by doing GetEmbeddedResource("icon.png"), assuming that no other embedded files have the same name.
|
+| `Byte[]` | ReadAllBytes(this `Stream` input) | Read all bytes starting at current position and ending at the end of the stream. |
+
+
+## `TextureUtils`
+
+Utility methods for working with texture objects.
+```csharp
+public static class KKAPI.Utilities.TextureUtils
+
+```
+
+Static Methods
+
+| Type | Name | Summary |
+| --- | --- | --- |
+| `Texture2D` | LoadTexture(this `Byte[]` texBytes, `TextureFormat` format = ARGB32, `Boolean` mipMaps = False) | Create texture from an image stored in a byte array, for example a png file read from disk. |
+| `Sprite` | ToSprite(this `Texture2D` texture) | Create a sprite based on this texture. |
+| `Texture2D` | ToTexture2D(this `Texture` tex, `TextureFormat` format = ARGB32, `Boolean` mipMaps = False) | Copy this texture inside a new editable Texture2D. |
+
+
+## `TextUtils`
+
+Utility methods for working with text.
+```csharp
+public static class KKAPI.Utilities.TextUtils
+
+```
+
+Static Methods
+
+| Type | Name | Summary |
+| --- | --- | --- |
+| `String` | PascalCaseToSentenceCase(this `String` str) | Convert PascalCase to Sentence case. |
+
+
## `ThreadingHelper`
Provides methods for running code on other threads and synchronizing with the main thread.
@@ -100,3 +207,26 @@ Static Methods
| `void` | StartSyncInvoke(`Action` action) | Queue the delegate to be invoked on the main unity thread. Use to synchronize your threads. |
+## `WindowsStringComparer`
+
+String comparer that is equivalent to the one used by Windows Explorer to sort files (e.g. 2 will go before 10, unlike normal compare).
+```csharp
+public class KKAPI.Utilities.WindowsStringComparer
+ : IComparer
+
+```
+
+Methods
+
+| Type | Name | Summary |
+| --- | --- | --- |
+| `Int32` | Compare(`String` x, `String` y) | Compare two strings with rules used by Windows Explorer to logically sort files. |
+
+
+Static Methods
+
+| Type | Name | Summary |
+| --- | --- | --- |
+| `Int32` | LogicalCompare(`String` x, `String` y) | Compare two strings with rules used by Windows Explorer to logically sort files. |
+
+