6 Commits

Author SHA1 Message Date
omegaleo c38bc89fa7 migrated workflows to gitea
Publish Nuget Packages / publish (OmegaLeo.HelperLib) (release) Successful in 56s
Publish Nuget Packages / publish (OmegaLeo.HelperLib.Changelog) (release) Successful in 55s
Publish Nuget Packages / publish (OmegaLeo.HelperLib.Documentation) (release) Successful in 1m1s
Publish Nuget Packages / publish (OmegaLeo.HelperLib.Game) (release) Successful in 53s
Publish Nuget Packages / publish (OmegaLeo.HelperLib.Shared) (release) Successful in 40s
2026-10-24 11:48:51 +01:00
omegaleo c745fb18bd updated SteamWorks library 2026-10-24 11:38:40 +01:00
omegaleo 91cdba0fef Added Serializable to Changelog for Unity 2026-08-16 23:06:28 +01:00
omegaleo cc5070e772 Delete docs directory 2026-05-15 22:05:26 +01:00
omegaleo 69fed717b5 Delete docs/css/site.css?v=AK2YraxbvzgrpO6A1NqAHflCP7fbweSjdyWpd3ERsLE.css 2026-05-15 22:05:18 +01:00
omegaleo 2972e59e00 Dev (#14)
* Fixed dependency

* Updates to NeoDictionary

* Changes to NeoDictionary

* Started working on a Git implementation to integrate into Game Engines in the future

* Added commit functionality to the git library

* New changes

* Added select for NeoDictionary.cs

* Fixed issue with previous new method

* Added a Benchmarking Tool to be used mostly in game engines

* Added IndexOf to NeoDictionary.cs

* Added new method for BenchmarkUtility.cs

* added an instacedobject class

* Added a new shuffle method for lists

* fixed return type

* Added new Changelog generator

* Added new Game logic library to assist with game projects

* Added Web Project

* Added Nuget package configs

* Add workflow for publishing game library NuGet package

* Add README for OmegaLeo's Helper Library

* Updated Website to reflect all libraries

* Fixed missing library in Changelog

* Added Contributing and Code of Conduct

* Fixed web project

* new documentation changes

* fix packages

* Fixed DocumentationHelper package dependencies

* Added Serilog

* Added logging

* Fixed documentation

* Added logging for cache

* fixed documentation

* Updated README.md

* Update README.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update OmegaLeo.HelperLib.Web/Controllers/HomeController.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update OmegaLeo.HelperLib.Web/Controllers/HomeController.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix error

* changes for Github Packages

* Cleanup

* Started working on a SteamWorks integration

* Added logging

* Started working on a Steam library

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-15 22:04:51 +01:00
27 changed files with 1556 additions and 2485 deletions
@@ -1,5 +1,5 @@
# This workflow will build a .NET project # This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net # Runs on Gitea Actions (GitHub Actions compatible syntax): https://docs.gitea.com/usage/actions/overview
name: .NET name: .NET
+30
View File
@@ -0,0 +1,30 @@
name: Publish to Gitea Packages
on:
workflow_dispatch:
inputs:
tag_name:
description: 'Version tag (e.g., 1.0.0)'
required: true
type: string
run-name: "Publish version v${{ inputs.tag_name }} to Gitea Packages"
jobs:
publish:
runs-on: ubuntu-latest
strategy:
matrix:
project:
- OmegaLeo.HelperLib
- OmegaLeo.HelperLib.Changelog
- OmegaLeo.HelperLib.Game
- OmegaLeo.HelperLib.Documentation
- OmegaLeo.HelperLib.Shared
steps:
- uses: actions/checkout@v4
- name: Setup .Net Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Create Nuget Package
run: dotnet pack "./${{ matrix.project }}/${{ matrix.project }}.csproj" -c Release /p:Version=${{ inputs.tag_name }}
- name: Publish to Gitea Packages
run: dotnet nuget push ./${{ matrix.project }}/bin/Release/${{ matrix.project }}.${{ inputs.tag_name }}.nupkg --api-key ${{ secrets.TOKEN }} --source ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/nuget/index.json --skip-duplicate
+37
View File
@@ -0,0 +1,37 @@
name: Publish Nuget Packages
on:
release:
types: [published]
run-name: "Publish version v${{ github.event.release.tag_name }} to nuget"
jobs:
publish:
runs-on: ubuntu-latest
strategy:
matrix:
project:
- OmegaLeo.HelperLib
- OmegaLeo.HelperLib.Changelog
- OmegaLeo.HelperLib.Game
- OmegaLeo.HelperLib.Documentation
- OmegaLeo.HelperLib.Shared
env:
VERSION: ${{ github.event.release.tag_name }}
PACKAGE: ./${{ matrix.project }}/bin/Release/${{ matrix.project }}.${{ github.event.release.tag_name }}.nupkg
steps:
- uses: actions/checkout@v4
- name: Setup .Net Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Create Nuget Package
run: dotnet pack "./${{ matrix.project }}/${{ matrix.project }}.csproj" -c Release /p:Version=$VERSION /p:PackageReleaseNotes="See ${{ github.server_url }}/${{ github.repository }}/releases/tag/$VERSION"
- name: Publish Nuget Package
run: dotnet nuget push "$PACKAGE" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Publish to Gitea Packages
run: dotnet nuget push "$PACKAGE" --api-key ${{ secrets.TOKEN }} --source ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/nuget/index.json --skip-duplicate
- name: Upload assets to release
run: |
curl --fail --silent --show-error \
-H "Authorization: token ${{ secrets.TOKEN }}" \
-F "attachment=@$PACKAGE" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/${{ github.event.release.id }}/assets?name=${{ matrix.project }}.$VERSION.nupkg"
-77
View File
@@ -1,77 +0,0 @@
name: Publish to GitHub Packages
on:
workflow_dispatch:
inputs:
tag_name:
description: 'Version tag (e.g., 1.0.0)'
required: true
type: string
run-name: "Publish version v${{ inputs.tag_name }} to GitHub Packages"
permissions:
contents: read
packages: write
jobs:
publish-helper-lib:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib/OmegaLeo.HelperLib.csproj" -c Release /p:Version=${{ inputs.tag_name }}
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib/bin/Release/OmegaLeo.HelperLib.${{ inputs.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
publish-changelog-lib:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib.Changelog/OmegaLeo.HelperLib.Changelog.csproj" -c Release /p:Version=${{ inputs.tag_name }}
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib.Changelog/bin/Release/OmegaLeo.HelperLib.Changelog.${{ inputs.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
publish-game-lib:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib.Game/OmegaLeo.HelperLib.Game.csproj" -c Release /p:Version=${{ inputs.tag_name }}
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib.Game/bin/Release/OmegaLeo.HelperLib.Game.${{ inputs.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
publish-doc-lib:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib.Documentation/OmegaLeo.HelperLib.Documentation.csproj" -c Release /p:Version=${{ inputs.tag_name }}
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib.Documentation/bin/Release/OmegaLeo.HelperLib.Documentation.${{ inputs.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
publish-shared-lib:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib.Shared/OmegaLeo.HelperLib.Shared.csproj" -c Release /p:Version=${{ inputs.tag_name }}
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib.Shared/bin/Release/OmegaLeo.HelperLib.Shared.${{ inputs.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
-149
View File
@@ -1,149 +0,0 @@
name: Publish Nuget Packages
on:
release:
types: [created]
run-name: "Publish version v${{github.event.release.tag_name}} to nuget"
jobs:
get-release:
name: Get Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Get release
id: get_release
uses: bruceadams/get-release@v1.2.3
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
outputs:
upload_url: ${{ steps.get_release.outputs.upload_url }}
publish-helper-lib:
runs-on: ubuntu-latest
needs: [get-release]
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Update Project Version
run: sed -i "s/\(<Version>\([0-9]\+\.\)\{2\}\)\([0-9]\+\)/\1${{ github.event.release.tag_name }}/" "./OmegaLeo.HelperLib/OmegaLeo.HelperLib.csproj"
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib/OmegaLeo.HelperLib.csproj" -c Release /p:Version=${{ github.event.release.tag_name }} /p:PackageReleaseNotes="See https://github.com/omegaleo/OmegaLeo.HelperLib/releases/tag/${{ github.event.release.tag_name }}"
- name: Publish Nuget Package
run: dotnet nuget push ./OmegaLeo.HelperLib/bin/Release/OmegaLeo.HelperLib.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.nuget_api_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib/bin/Release/OmegaLeo.HelperLib.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
- name: Upload assets to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
upload_url: ${{ needs.get-release.outputs.upload_url }}
asset_path: ./OmegaLeo.HelperLib/bin/Release/OmegaLeo.HelperLib.${{ github.event.release.tag_name }}.nupkg
asset_name: OmegaLeo.HelperLib.${{ github.event.release.tag_name }}.nupkg
asset_content_type: application/zip
publish-changelog-lib:
runs-on: ubuntu-latest
needs: [ get-release ]
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Update Project Version
run: sed -i "s/\(<Version>\([0-9]\+\.\)\{2\}\)\([0-9]\+\)/\1${{ github.event.release.tag_name }}/" "./OmegaLeo.HelperLib.Changelog/OmegaLeo.HelperLib.Changelog.csproj"
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib.Changelog/OmegaLeo.HelperLib.Changelog.csproj" -c Release /p:Version=${{ github.event.release.tag_name }} /p:PackageReleaseNotes="See https://github.com/omegaleo/OmegaLeo.HelperLib/releases/tag/${{ github.event.release.tag_name }}"
- name: Publish Nuget Package
run: dotnet nuget push ./OmegaLeo.HelperLib.Changelog/bin/Release/OmegaLeo.HelperLib.Changelog.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.nuget_api_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib.Changelog/bin/Release/OmegaLeo.HelperLib.Changelog.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
- name: Upload assets to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
upload_url: ${{ needs.get-release.outputs.upload_url }}
asset_path: ./OmegaLeo.HelperLib.Changelog/bin/Release/OmegaLeo.HelperLib.Changelog.${{ github.event.release.tag_name }}.nupkg
asset_name: OmegaLeo.HelperLib.Changelog.${{ github.event.release.tag_name }}.nupkg
asset_content_type: application/zip
publish-game-lib:
runs-on: ubuntu-latest
needs: [ get-release ]
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Update Project Version
run: sed -i "s/\(<Version>\([0-9]\+\.\)\{2\}\)\([0-9]\+\)/\1${{ github.event.release.tag_name }}/" "./OmegaLeo.HelperLib.Game/OmegaLeo.HelperLib.Game.csproj"
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib.Game/OmegaLeo.HelperLib.Game.csproj" -c Release /p:Version=${{ github.event.release.tag_name }} /p:PackageReleaseNotes="See https://github.com/omegaleo/OmegaLeo.HelperLib/releases/tag/${{ github.event.release.tag_name }}"
- name: Publish Nuget Package
run: dotnet nuget push ./OmegaLeo.HelperLib.Game/bin/Release/OmegaLeo.HelperLib.Game.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.nuget_api_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib.Game/bin/Release/OmegaLeo.HelperLib.Game.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
- name: Upload assets to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
upload_url: ${{ needs.get-release.outputs.upload_url }}
asset_path: ./OmegaLeo.HelperLib.Game/bin/Release/OmegaLeo.HelperLib.Game.${{ github.event.release.tag_name }}.nupkg
asset_name: OmegaLeo.HelperLib.Game.${{ github.event.release.tag_name }}.nupkg
asset_content_type: application/zip
publish-doc-lib:
runs-on: ubuntu-latest
needs: [ get-release ]
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Update Project Version
run: sed -i "s/\(<Version>\([0-9]\+\.\)\{2\}\)\([0-9]\+\)/\1${{ github.event.release.tag_name }}/" "./OmegaLeo.HelperLib.Documentation/OmegaLeo.HelperLib.Documentation.csproj"
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib.Documentation/OmegaLeo.HelperLib.Documentation.csproj" -c Release /p:Version=${{ github.event.release.tag_name }} /p:PackageReleaseNotes="See https://github.com/omegaleo/OmegaLeo.HelperLib/releases/tag/${{ github.event.release.tag_name }}"
- name: Publish Nuget Package
run: dotnet nuget push ./OmegaLeo.HelperLib.Documentation/bin/Release/OmegaLeo.HelperLib.Documentation.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.nuget_api_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib.Documentation/bin/Release/OmegaLeo.HelperLib.Documentation.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
- name: Upload assets to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
upload_url: ${{ needs.get-release.outputs.upload_url }}
asset_path: ./OmegaLeo.HelperLib.Documentation/bin/Release/OmegaLeo.HelperLib.Documentation.${{ github.event.release.tag_name }}.nupkg
asset_name: OmegaLeo.HelperLib.Documentation.${{ github.event.release.tag_name }}.nupkg
asset_content_type: application/zip
publish-shared-lib:
runs-on: ubuntu-latest
needs: [ get-release ]
steps:
- uses: actions/checkout@v2
- name: Setup .Net Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
- name: Update Project Version
run: sed -i "s/\(<Version>\([0-9]\+\.\)\{2\}\)\([0-9]\+\)/\1${{ github.event.release.tag_name }}/" "./OmegaLeo.HelperLib.Shared/OmegaLeo.HelperLib.Shared.csproj"
- name: Create Nuget Package
run: dotnet pack "./OmegaLeo.HelperLib.Shared/OmegaLeo.HelperLib.Shared.csproj" -c Release /p:Version=${{ github.event.release.tag_name }} /p:PackageReleaseNotes="See https://github.com/omegaleo/OmegaLeo.HelperLib/releases/tag/${{ github.event.release.tag_name }}"
- name: Publish Nuget Package
run: dotnet nuget push ./OmegaLeo.HelperLib.Shared/bin/Release/OmegaLeo.HelperLib.Shared.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.nuget_api_key }} --source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Publish to GitHub Packages
run: dotnet nuget push ./OmegaLeo.HelperLib.Shared/bin/Release/OmegaLeo.HelperLib.Shared.${{ github.event.release.tag_name }}.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/omegaleo/index.json --skip-duplicate
- name: Upload assets to release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
upload_url: ${{ needs.get-release.outputs.upload_url }}
asset_path: ./OmegaLeo.HelperLib.Shared/bin/Release/OmegaLeo.HelperLib.Shared.${{ github.event.release.tag_name }}.nupkg
asset_name: OmegaLeo.HelperLib.Shared.${{ github.event.release.tag_name }}.nupkg
asset_content_type: application/zip
@@ -1,5 +1,8 @@
using System;
namespace OmegaLeo.HelperLib.Changelog.Models namespace OmegaLeo.HelperLib.Changelog.Models
{ {
[Serializable]
public class Changelog public class Changelog
{ {
public string Target; public string Target;
+114 -3
View File
@@ -4,8 +4,11 @@ Lightweight Steamworks helper library for C# game projects.
This package provides a simple Steam API wrapper for common tasks like initialization, callback processing, player name lookup, and achievements. This package provides a simple Steam API wrapper for common tasks like initialization, callback processing, player name lookup, and achievements.
## Features ## Features
- Simple Steam initialization via `SteamManager.Instance` - Simple Steam initialization via `SteamManager.Instance` (uses `SteamAPI_InitFlat` on SDK 1.58+ and reports the error message in `InitErrorMessage`)
- Per-frame callback runner with `SteamManager.Update()` - Configurable native library location with `SteamManager.ConfigureLibraryPath()` (Unity `Plugins/x86_64`, custom folders)
- `SteamManager.RestartAppIfNecessary()` for launching through Steam
- Per-frame manual callback dispatch with `SteamManager.Update()`: callback subscriptions, `OverlayActivated` event and async call results (`SteamManager.Instance.Callbacks`)
- Ownership, DLC and language queries via `SteamManager.Instance.Apps` (ISteamApps)
- Safe shutdown with `SteamManager.Shutdown()` - Safe shutdown with `SteamManager.Shutdown()`
- Player persona name access with `SteamManager.GetSteamName()` - Player persona name access with `SteamManager.GetSteamName()`
- Achievement helpers via `AchievementManager` - Achievement helpers via `AchievementManager`
@@ -89,11 +92,119 @@ var session = steam.Ugc.BeginUpdateSession(480, 123456789012345678UL)
.WithContentFolder("/absolute/path/to/content") .WithContentFolder("/absolute/path/to/content")
.WithPreviewFile("/absolute/path/to/preview.png"); .WithPreviewFile("/absolute/path/to/preview.png");
var submitCallHandle2 = session.Submit("Updated content", out var failedStep2); var submitCallHandle2 = session.Submit("Updated content", out var failedStep2);
// Ownership / language (ISteamApps)
bool ownsDemo = steam.Apps.BIsSubscribedApp(480);
bool hasDlc = steam.Apps.BIsDlcInstalled(123456);
string language = steam.Apps.GetCurrentGameLanguage(); // "english", "portuguese", ...
``` ```
## Callbacks and async call results
`SteamManager.Update()` runs Steam's manual dispatch loop (`SteamAPI_ManualDispatch_*`). Callbacks and async call results are delivered from `Update()`, on the thread that calls it. If the loaded `steam_api` has no manual dispatch exports, or you call `SteamManager.ConfigureManualDispatch(false)` before `Instance`, `Update()` falls back to `SteamAPI_RunCallbacks` and no events or results are delivered. The library never runs both.
```cs
var steam = SteamManager.Instance;
// Typed overlay event (GameOverlayActivated_t)
steam.OverlayActivated += overlay => Console.WriteLine($"Overlay open: {overlay.Active}");
// Any callback by id, with the raw payload
IDisposable sub = steam.Callbacks.Subscribe(SteamCallbackIds.GameOverlayActivated, data =>
{
bool active = data.ReadBoolean(0);
});
sub.Dispose(); // unsubscribe
// Workshop results: callback style...
steam.Ugc.SubmitItemUpdate(updateHandle, "Patch notes", result =>
{
if (result.IsSuccess) Console.WriteLine($"Uploaded {result.PublishedFileId}");
if (result.UserNeedsToAcceptWorkshopLegalAgreement) { /* show the agreement link */ }
});
steam.Ugc.CreateItem(480, 0 /* k_EWorkshopFileTypeCommunity */, created => { /* created.PublishedFileId */ });
steam.Ugc.SendQueryUgcRequest(queryHandle, query => { /* query.NumResultsReturned */ });
// ...or awaitable (keep calling Update() while waiting)
var query = await steam.Ugc.SendQueryUgcRequestAsync(queryHandle);
// Any other SteamAPICall_t handle
var raw = await steam.Callbacks.WaitForCallResultAsync(apiCallHandle);
if (!raw.IoFailure) { int eResult = raw.ReadInt32(0); }
```
Callback structs use 8-byte packing on Windows and 4-byte packing on Linux, Steam Deck and macOS, which moves 64-bit fields in some structs. The typed results handle this. When you parse a raw payload yourself, check `SteamCallbackDispatcher.UsesLargeCallbackPack`.
## Using in Unity
The library has no Unity references; add the DLLs and the Steam native libraries (`steam_api64.dll` for Windows, `libsteam_api.so` for Linux) to `Assets/Plugins/x86_64/` and set their platforms in the import settings. Keep all Steam code behind your Steam-off define (for example `DISABLESTEAMWORKS`) so itch / Microsoft Store builds don't touch it.
```cs
#if !DISABLESTEAMWORKS
using System.IO;
using OmegaLeo.HelperLib.Steamworks;
using UnityEngine;
public class SteamBootstrap : MonoBehaviour
{
private const uint AppId = 480; // your AppId (full game and demo have different ids)
private void Awake()
{
DontDestroyOnLoad(gameObject);
// Player: <Game>_Data/Plugins/x86_64 (some Linux layouts use <Game>_Data/Plugins).
// Editor: Application.dataPath is Assets/, so this finds Assets/Plugins/x86_64.
SteamManager.ConfigureLibraryPath(Path.Combine(Application.dataPath, "Plugins", "x86_64"));
SteamManager.ConfigureLibraryPath(Path.Combine(Application.dataPath, "Plugins"));
#if !UNITY_EDITOR
// Relaunch through Steam when started from the exe. Call before ConfigureAppId.
if (SteamManager.RestartAppIfNecessary(AppId))
{
Application.Quit();
return;
}
#endif
#if UNITY_EDITOR || DEVELOPMENT_BUILD
// Editor/dev only: writes steam_appid.txt. Shipped builds get the AppId from Steam.
SteamManager.ConfigureAppId(AppId, writeSteamAppIdFile: true);
#endif
var steam = SteamManager.Instance;
if (!steam.IsSteamworksInitialized)
{
Debug.LogWarning($"Steam not available: {steam.InitErrorMessage}");
return;
}
steam.OverlayActivated += OnOverlayActivated;
}
private void Update()
{
SteamManager.Instance.Update(); // every frame, main thread
}
private void OnOverlayActivated(SteamOverlayActivated overlay)
{
Time.timeScale = overlay.Active ? 0f : 1f; // or your own pause logic
}
private void OnApplicationQuit()
{
SteamManager.Instance.OverlayActivated -= OnOverlayActivated;
SteamManager.Instance.Shutdown();
}
}
#endif
```
Pass an `ILogger` to `SteamManager.ConfigureLogger` (a small adapter around `Debug.Log`) to see which library path loaded and why init failed.
## Notes ## Notes
- Steam must be running and your app must be launched in a valid Steam context. - Steam must be running and your app must be launched in a valid Steam context.
- Configure your AppId before first access to `SteamManager.Instance`. - Configure your AppId, library path and dispatch mode before first access to `SteamManager.Instance`.
- `ConfigureAppId(appId)` only sets the `SteamAppId` / `SteamGameId` environment variables. Use `ConfigureAppId(appId, writeSteamAppIdFile: true)` to also write `steam_appid.txt` (development only; a shipped build with that file skips `RestartAppIfNecessary`).
- `SteamManager.Update()` should be called continuously during runtime. - `SteamManager.Update()` should be called continuously during runtime.
- If Steam is unavailable, `GetSteamName()` falls back to `"Player"`. - If Steam is unavailable, `GetSteamName()` falls back to `"Player"`.
@@ -0,0 +1,480 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OmegaLeo.HelperLib.Shared.Attributes;
namespace OmegaLeo.HelperLib.Steamworks
{
[Documentation(nameof(SteamCallbackIds), "Steamworks callback ids (the k_iCallback value of each callback struct) used by this library.", null, null)]
public static class SteamCallbackIds
{
[Documentation(nameof(GameOverlayActivated), "GameOverlayActivated_t (k_iSteamFriendsCallbacks + 31).", null, null)]
public const int GameOverlayActivated = 331;
[Documentation(nameof(SteamApiCallCompleted), "SteamAPICallCompleted_t (k_iSteamUtilsCallbacks + 3). Handled internally to deliver async call results.", null, null)]
public const int SteamApiCallCompleted = 703;
[Documentation(nameof(SteamUgcQueryCompleted), "SteamUGCQueryCompleted_t (k_iSteamUGCCallbacks + 1), the result of SendQueryUGCRequest.", null, null)]
public const int SteamUgcQueryCompleted = 3401;
[Documentation(nameof(CreateItemResult), "CreateItemResult_t (k_iSteamUGCCallbacks + 3), the result of CreateItem.", null, null)]
public const int CreateItemResult = 3403;
[Documentation(nameof(SubmitItemUpdateResult), "SubmitItemUpdateResult_t (k_iSteamUGCCallbacks + 4), the result of SubmitItemUpdate.", null, null)]
public const int SubmitItemUpdateResult = 3404;
}
[Documentation(nameof(SteamCallbackPayload), "Base class for a copied Steam callback payload with little-endian field readers. Offsets follow the native struct layout for the current platform (see SteamCallbackDispatcher.UsesLargeCallbackPack).", null, null)]
public abstract class SteamCallbackPayload
{
protected SteamCallbackPayload(byte[] payload)
{
Payload = payload ?? Array.Empty<byte>();
}
[Documentation(nameof(Payload), "Raw bytes of the native callback struct, copied before Steam frees it.", null, null)]
public byte[] Payload { get; }
[Documentation(nameof(ReadByte), "Reads a byte at the given offset, or 0 when out of range.", new[] { "offset: Byte offset in the payload." }, null)]
public byte ReadByte(int offset)
{
return offset >= 0 && offset < Payload.Length ? Payload[offset] : (byte)0;
}
[Documentation(nameof(ReadBoolean), "Reads a one-byte C++ bool at the given offset.", new[] { "offset: Byte offset in the payload." }, null)]
public bool ReadBoolean(int offset)
{
return ReadByte(offset) != 0;
}
[Documentation(nameof(ReadInt32), "Reads a 32-bit signed integer at the given offset, or 0 when out of range.", new[] { "offset: Byte offset in the payload." }, null)]
public int ReadInt32(int offset)
{
return HasBytes(offset, 4) ? BitConverter.ToInt32(Payload, offset) : 0;
}
[Documentation(nameof(ReadUInt32), "Reads a 32-bit unsigned integer at the given offset, or 0 when out of range.", new[] { "offset: Byte offset in the payload." }, null)]
public uint ReadUInt32(int offset)
{
return HasBytes(offset, 4) ? BitConverter.ToUInt32(Payload, offset) : 0;
}
[Documentation(nameof(ReadUInt64), "Reads a 64-bit unsigned integer at the given offset, or 0 when out of range.", new[] { "offset: Byte offset in the payload." }, null)]
public ulong ReadUInt64(int offset)
{
return HasBytes(offset, 8) ? BitConverter.ToUInt64(Payload, offset) : 0;
}
[Documentation(nameof(ReadString), "Reads a NUL-terminated UTF-8 char array field.", new[] { "offset: Byte offset of the char array.", "maxLength: Size of the char array in the native struct." }, null)]
public string ReadString(int offset, int maxLength)
{
if (offset < 0 || offset >= Payload.Length || maxLength <= 0)
{
return string.Empty;
}
var end = Math.Min(Payload.Length, offset + maxLength);
var length = 0;
while (offset + length < end && Payload[offset + length] != 0)
{
length++;
}
return Encoding.UTF8.GetString(Payload, offset, length);
}
private bool HasBytes(int offset, int size)
{
return offset >= 0 && offset + size <= Payload.Length;
}
}
[Documentation(nameof(SteamCallbackData), "A Steam callback received through manual dispatch.", null, null)]
public sealed class SteamCallbackData : SteamCallbackPayload
{
internal SteamCallbackData(int callbackId, int steamUser, byte[] payload) : base(payload)
{
CallbackId = callbackId;
SteamUser = steamUser;
}
[Documentation(nameof(CallbackId), "The callback id (k_iCallback) of the native struct.", null, null)]
public int CallbackId { get; }
[Documentation(nameof(SteamUser), "The HSteamUser the callback was posted for.", null, null)]
public int SteamUser { get; }
}
[Documentation(nameof(SteamApiCallResult), "The completed result of an asynchronous Steam API call (SteamAPICall_t).", null, null)]
public sealed class SteamApiCallResult : SteamCallbackPayload
{
internal SteamApiCallResult(ulong apiCall, int callbackId, bool ioFailure, byte[] payload) : base(payload)
{
ApiCall = apiCall;
CallbackId = callbackId;
IoFailure = ioFailure;
}
[Documentation(nameof(ApiCall), "The SteamAPICall_t handle this result belongs to.", null, null)]
public ulong ApiCall { get; }
[Documentation(nameof(CallbackId), "The callback id of the result struct (for example SteamCallbackIds.SubmitItemUpdateResult).", null, null)]
public int CallbackId { get; }
[Documentation(nameof(IoFailure), "True when Steam reported a failure fetching the result, or Steam was shut down before it completed. The payload is then not valid.", null, null)]
public bool IoFailure { get; }
internal static SteamApiCallResult Failed(ulong apiCall)
{
return new SteamApiCallResult(apiCall, 0, true, Array.Empty<byte>());
}
}
[Documentation(nameof(SteamOverlayActivated), "Data from GameOverlayActivated_t, raised when the Steam overlay opens or closes.", null, null)]
public readonly struct SteamOverlayActivated
{
internal SteamOverlayActivated(bool active, bool userInitiated, uint appId)
{
Active = active;
UserInitiated = userInitiated;
AppId = appId;
}
[Documentation(nameof(Active), "True when the overlay was opened, false when it was closed.", null, null)]
public bool Active { get; }
[Documentation(nameof(UserInitiated), "True when the user opened the overlay (as opposed to the game or Steam).", null, null)]
public bool UserInitiated { get; }
[Documentation(nameof(AppId), "The AppId the overlay was activated for.", null, null)]
public uint AppId { get; }
}
[Documentation(nameof(SteamCallbackDispatcher), "Runs the Steam manual callback dispatch loop and delivers callbacks and async call results. Access it through SteamManager.Instance.Callbacks; it is pumped by SteamManager.Update().", null,
@"```csharp
var steam = SteamManager.Instance;
steam.Callbacks.OverlayActivated += overlay => Pause(overlay.Active);
using var sub = steam.Callbacks.Subscribe(SteamCallbackIds.GameOverlayActivated, data => Log(data.ReadBoolean(0)));
var result = await steam.Callbacks.WaitForCallResultAsync(apiCallHandle);
```")]
public sealed class SteamCallbackDispatcher
{
private readonly object _sync = new object();
private readonly Dictionary<int, List<Action<SteamCallbackData>>> _handlers = new Dictionary<int, List<Action<SteamCallbackData>>>();
private readonly Dictionary<ulong, List<Action<SteamApiCallResult>>> _pendingCallResults = new Dictionary<ulong, List<Action<SteamApiCallResult>>>();
internal SteamCallbackDispatcher()
{
}
[Documentation(nameof(UsesLargeCallbackPack), "True when Steam callback structs use 8-byte packing (Windows). Linux, Steam Deck and macOS use 4-byte packing, which moves 64-bit fields in some structs.", null, null)]
public static bool UsesLargeCallbackPack => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
[Documentation(nameof(IsManualDispatchActive), "True when manual dispatch is running, so callbacks and async call results are delivered.", null, null)]
public bool IsManualDispatchActive => SteamNativeApi.IsManualDispatchActive;
[Documentation(nameof(PendingCallResultCount), "Number of async call handles still waiting for a result.", null, null)]
public int PendingCallResultCount
{
get
{
lock (_sync)
{
return _pendingCallResults.Count;
}
}
}
[Documentation(nameof(OverlayActivated), "Raised from SteamManager.Update() when the Steam overlay opens or closes (GameOverlayActivated_t). Use it to pause the game.", null, null)]
public event Action<SteamOverlayActivated> OverlayActivated;
[Documentation(nameof(Subscribe), "Subscribes to every callback with the given id. The handler receives a copy of the raw payload. Dispose the returned object to unsubscribe.", new[]
{
"callbackId: Callback id (k_iCallback), see SteamCallbackIds.",
"handler: Called from SteamManager.Update() for each matching callback."
}, null)]
public IDisposable Subscribe(int callbackId, Action<SteamCallbackData> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
lock (_sync)
{
if (!_handlers.TryGetValue(callbackId, out var list))
{
list = new List<Action<SteamCallbackData>>();
_handlers[callbackId] = list;
}
list.Add(handler);
}
return new Subscription(() => Unsubscribe(callbackId, handler));
}
[Documentation(nameof(Unsubscribe), "Removes a handler added with Subscribe.", new[] { "callbackId: Callback id the handler was registered for.", "handler: The handler to remove." }, null)]
public bool Unsubscribe(int callbackId, Action<SteamCallbackData> handler)
{
lock (_sync)
{
if (!_handlers.TryGetValue(callbackId, out var list) || !list.Remove(handler))
{
return false;
}
if (list.Count == 0)
{
_handlers.Remove(callbackId);
}
return true;
}
}
[Documentation(nameof(RegisterCallResult), "Registers a handler for the result of an async Steam API call. The handler runs once, from SteamManager.Update(). Returns false when the handle is invalid or manual dispatch is not active.", new[]
{
"apiCall: SteamAPICall_t handle returned by an async Steam API method.",
"onCompleted: Called with the result (check IoFailure)."
}, null)]
public bool RegisterCallResult(ulong apiCall, Action<SteamApiCallResult> onCompleted)
{
if (onCompleted == null)
{
throw new ArgumentNullException(nameof(onCompleted));
}
if (apiCall == 0 || !IsManualDispatchActive)
{
return false;
}
lock (_sync)
{
if (!_pendingCallResults.TryGetValue(apiCall, out var list))
{
list = new List<Action<SteamApiCallResult>>();
_pendingCallResults[apiCall] = list;
}
list.Add(onCompleted);
}
return true;
}
[Documentation(nameof(WaitForCallResultAsync), "Returns a task that completes with the result of an async Steam API call. Keep calling SteamManager.Update() while waiting. Faults with InvalidOperationException when manual dispatch is not active.", new[]
{
"apiCall: SteamAPICall_t handle returned by an async Steam API method.",
"cancellationToken: Cancels the wait (the Steam call itself keeps running)."
}, null)]
public Task<SteamApiCallResult> WaitForCallResultAsync(ulong apiCall, CancellationToken cancellationToken = default)
{
if (apiCall == 0)
{
return Task.FromException<SteamApiCallResult>(new ArgumentException("The Steam API call handle is invalid.", nameof(apiCall)));
}
var completion = new TaskCompletionSource<SteamApiCallResult>(TaskCreationOptions.RunContinuationsAsynchronously);
Action<SteamApiCallResult> handler = result => completion.TrySetResult(result);
if (!RegisterCallResult(apiCall, handler))
{
return Task.FromException<SteamApiCallResult>(new InvalidOperationException("Steam manual callback dispatch is not active, so async call results cannot be delivered."));
}
if (cancellationToken.CanBeCanceled)
{
var registration = cancellationToken.Register(() =>
{
RemoveCallResult(apiCall, handler);
completion.TrySetCanceled(cancellationToken);
});
completion.Task.ContinueWith(_ => registration.Dispose(), TaskScheduler.Default);
}
return completion.Task;
}
internal void RunFrame()
{
if (!SteamNativeApi.IsManualDispatchActive)
{
return;
}
SteamNativeApi.ManualDispatchRunFrame();
while (SteamNativeApi.ManualDispatchGetNextCallback(out var message))
{
try
{
HandleCallback(message);
}
catch (Exception e)
{
SteamManager.Logger.LogError(e, "An exception occurred while handling Steam callback {CallbackId}.", message.CallbackId);
}
finally
{
SteamNativeApi.ManualDispatchFreeLastCallback();
}
}
}
internal void FailPendingCallResults()
{
List<KeyValuePair<ulong, List<Action<SteamApiCallResult>>>> pending;
lock (_sync)
{
pending = new List<KeyValuePair<ulong, List<Action<SteamApiCallResult>>>>(_pendingCallResults);
_pendingCallResults.Clear();
}
foreach (var entry in pending)
{
InvokeCallResultHandlers(entry.Value, SteamApiCallResult.Failed(entry.Key));
}
}
private void HandleCallback(SteamNativeCallbackMessage message)
{
var payload = CopyPayload(message.Param, message.ParamSize);
var data = new SteamCallbackData(message.CallbackId, message.SteamUser, payload);
if (message.CallbackId == SteamCallbackIds.SteamApiCallCompleted)
{
HandleApiCallCompleted(data);
}
else if (message.CallbackId == SteamCallbackIds.GameOverlayActivated)
{
// GameOverlayActivated_t { uint8 m_bActive; bool m_bUserInitiated; AppId_t m_nAppID; uint32 m_dwOverlayPID; }
try
{
OverlayActivated?.Invoke(new SteamOverlayActivated(data.ReadBoolean(0), data.ReadBoolean(1), data.ReadUInt32(4)));
}
catch (Exception e)
{
SteamManager.Logger.LogError(e, "An OverlayActivated handler threw.");
}
}
Action<SteamCallbackData>[] handlers = null;
lock (_sync)
{
if (_handlers.TryGetValue(message.CallbackId, out var list))
{
handlers = list.ToArray();
}
}
if (handlers == null)
{
return;
}
foreach (var handler in handlers)
{
try
{
handler(data);
}
catch (Exception e)
{
SteamManager.Logger.LogError(e, "A subscriber threw while handling Steam callback {CallbackId}.", message.CallbackId);
}
}
}
private void HandleApiCallCompleted(SteamCallbackData data)
{
// SteamAPICallCompleted_t { SteamAPICall_t m_hAsyncCall; int m_iCallback; uint32 m_cubParam; } - same layout under pack 4 and 8.
var apiCall = data.ReadUInt64(0);
var expectedCallback = data.ReadInt32(8);
var resultSize = (int)data.ReadUInt32(12);
List<Action<SteamApiCallResult>> handlers;
lock (_sync)
{
if (!_pendingCallResults.TryGetValue(apiCall, out handlers))
{
return;
}
_pendingCallResults.Remove(apiCall);
}
var buffer = Marshal.AllocHGlobal(Math.Max(resultSize, 1));
SteamApiCallResult result;
try
{
var fetched = SteamNativeApi.ManualDispatchGetApiCallResult(apiCall, buffer, resultSize, expectedCallback, out var failed);
result = fetched
? new SteamApiCallResult(apiCall, expectedCallback, failed, CopyPayload(buffer, resultSize))
: new SteamApiCallResult(apiCall, expectedCallback, true, Array.Empty<byte>());
}
finally
{
Marshal.FreeHGlobal(buffer);
}
InvokeCallResultHandlers(handlers, result);
}
private void RemoveCallResult(ulong apiCall, Action<SteamApiCallResult> handler)
{
lock (_sync)
{
if (_pendingCallResults.TryGetValue(apiCall, out var list) && list.Remove(handler) && list.Count == 0)
{
_pendingCallResults.Remove(apiCall);
}
}
}
private static void InvokeCallResultHandlers(List<Action<SteamApiCallResult>> handlers, SteamApiCallResult result)
{
foreach (var handler in handlers)
{
try
{
handler(result);
}
catch (Exception e)
{
SteamManager.Logger.LogError(e, "A handler threw while handling the result of Steam API call {ApiCall}.", result.ApiCall);
}
}
}
private static byte[] CopyPayload(IntPtr source, int size)
{
if (source == IntPtr.Zero || size <= 0)
{
return Array.Empty<byte>();
}
var payload = new byte[size];
Marshal.Copy(source, payload, 0, size);
return payload;
}
private sealed class Subscription : IDisposable
{
private Action _dispose;
public Subscription(Action dispose)
{
_dispose = dispose;
}
public void Dispose()
{
Interlocked.Exchange(ref _dispose, null)?.Invoke();
}
}
}
}
@@ -356,6 +356,59 @@ namespace OmegaLeo.HelperLib.Steamworks
} }
} }
[Documentation(nameof(SteamAppsManager), "Provides ownership, DLC and language queries from ISteamApps.", null,
@"```csharp
var apps = SteamManager.Instance.Apps;
if (apps.BIsSubscribedApp(5498630)) { /* user owns the demo */ }
var language = apps.GetCurrentGameLanguage(); // e.g. ""english""
```")]
public class SteamAppsManager
{
public bool IsAvailable => SteamNativeApi.IsSteamAppsAvailable();
[Documentation(nameof(BIsSubscribed), "Checks whether the current user owns (is subscribed to) the running app.", null, null)]
public bool BIsSubscribed()
{
return SteamNativeApi.AppsIsSubscribed();
}
[Documentation(nameof(BIsSubscribedApp), "Checks whether the current user owns the given app, for example a related demo or another of your games.",new[] { "appId: AppId to check." }, null)]
public bool BIsSubscribedApp(uint appId)
{
return SteamNativeApi.AppsIsSubscribedApp(appId);
}
[Documentation(nameof(BIsDlcInstalled), "Checks whether the user owns the given DLC and it is installed.", new[] { "appId: DLC AppId." }, null)]
public bool BIsDlcInstalled(uint appId)
{
return SteamNativeApi.AppsIsDlcInstalled(appId);
}
[Documentation(nameof(BIsAppInstalled), "Checks whether the given app is installed on this machine.", new[] { "appId: AppId to check." }, null)]
public bool BIsAppInstalled(uint appId)
{
return SteamNativeApi.AppsIsAppInstalled(appId);
}
[Documentation(nameof(GetCurrentGameLanguage), "Gets the language the user selected for this game in Steam (API language code such as \"english\"), or an empty string when unavailable.", null, null)]
public string GetCurrentGameLanguage()
{
return SteamNativeApi.AppsGetCurrentGameLanguage();
}
[Documentation(nameof(GetAvailableGameLanguages), "Gets a comma-separated list of the languages the game supports.", null, null)]
public string GetAvailableGameLanguages()
{
return SteamNativeApi.AppsGetAvailableGameLanguages();
}
[Documentation(nameof(GetAppBuildId), "Gets the Steam build id of the installed depot.", null, null)]
public int GetAppBuildId()
{
return SteamNativeApi.AppsGetAppBuildId();
}
}
[Documentation(nameof(SteamUtilsManager), "Provides access checks for ISteamUtils.", null, null)] [Documentation(nameof(SteamUtilsManager), "Provides access checks for ISteamUtils.", null, null)]
public class SteamUtilsManager public class SteamUtilsManager
{ {
+102 -9
View File
@@ -5,11 +5,13 @@ using OmegaLeo.HelperLib.Shared.Attributes;
namespace OmegaLeo.HelperLib.Steamworks namespace OmegaLeo.HelperLib.Steamworks
{ {
[Documentation(nameof(SteamManager), "Manages Steamworks initialization, callback polling, and shutdown for applications using the Steam API.", null, [Documentation(nameof(SteamManager), "Manages Steamworks initialization, callback dispatch, and shutdown for applications using the Steam API.", null,
@"```csharp @"```csharp
SteamManager.ConfigureLogger(logger); SteamManager.ConfigureLogger(logger);
SteamManager.ConfigureAppId(480); SteamManager.ConfigureLibraryPath(pluginFolder);
if (SteamManager.RestartAppIfNecessary(480)) { Quit(); return; }
var steam = SteamManager.Instance; var steam = SteamManager.Instance;
steam.OverlayActivated += overlay => Pause(overlay.Active);
steam.Update(); steam.Update();
```")] ```")]
public class SteamManager public class SteamManager
@@ -17,6 +19,8 @@ steam.Update();
private static SteamManager _instance; private static SteamManager _instance;
private static uint? _configuredAppId; private static uint? _configuredAppId;
private static ILogger _logger = NullLogger.Instance; private static ILogger _logger = NullLogger.Instance;
private static readonly SteamCallbackDispatcher CallbackDispatcher = new SteamCallbackDispatcher();
private static readonly SteamAppsManager AppsManager = new SteamAppsManager();
private static readonly SteamUgcManager UgcManager = new SteamUgcManager(); private static readonly SteamUgcManager UgcManager = new SteamUgcManager();
private static readonly SteamRemotePlayManager RemotePlayManager = new SteamRemotePlayManager(); private static readonly SteamRemotePlayManager RemotePlayManager = new SteamRemotePlayManager();
private static readonly SteamControllerManager ControllerManager = new SteamControllerManager(); private static readonly SteamControllerManager ControllerManager = new SteamControllerManager();
@@ -41,14 +45,76 @@ steam.Update();
internal static ILogger Logger => _logger; internal static ILogger Logger => _logger;
internal static SteamCallbackDispatcher Dispatcher => CallbackDispatcher;
[Documentation(nameof(ConfigureLogger), "Configures the logger used by Steamworks helper classes.", new[] { "logger: ILogger instance to receive Steamworks logs." }, null)] [Documentation(nameof(ConfigureLogger), "Configures the logger used by Steamworks helper classes.", new[] { "logger: ILogger instance to receive Steamworks logs." }, null)]
public static void ConfigureLogger(ILogger logger) public static void ConfigureLogger(ILogger logger)
{ {
_logger = logger ?? NullLogger.Instance; _logger = logger ?? NullLogger.Instance;
} }
[Documentation(nameof(ConfigureAppId), "Configures the Steam AppId used during initialization. Must be called before accessing the singleton instance.", new[] { "appId: Steam application identifier." }, null)] [Documentation(nameof(ConfigureLibraryPath), "Adds a folder (or full file path) to search first for the Steam API native library (steam_api64.dll / libsteam_api.so / libsteam_api.dylib). Can be called several times; paths are tried in order, then the default locations. Must be called before Instance or RestartAppIfNecessary. Returns false when the library is already loaded.", new[] { "directoryOrFile: Folder containing the native library, or the full path to it." },
@"```csharp
SteamManager.ConfigureLibraryPath(Path.Combine(gameDataFolder, ""Plugins"", ""x86_64""));
```")]
public static bool ConfigureLibraryPath(string directoryOrFile)
{
if (!SteamNativeApi.AddLibrarySearchPath(directoryOrFile))
{
_logger.LogWarning("ConfigureLibraryPath ignored for '{SteamLibraryPath}': the path is empty or the Steam API library is already loaded.", directoryOrFile);
return false;
}
return true;
}
[Documentation(nameof(ConfigureManualDispatch), "Enables or disables manual callback dispatch (enabled by default). When disabled, Update() uses SteamAPI_RunCallbacks and no callback events or async call results are delivered. Must be called before Instance.", new[] { "enabled: True to use manual dispatch." }, null)]
public static bool ConfigureManualDispatch(bool enabled)
{
return _instance == null && SteamNativeApi.ConfigureManualDispatch(enabled);
}
[Documentation(nameof(RestartAppIfNecessary), "Calls SteamAPI_RestartAppIfNecessary. Returns true when the game was not launched through Steam and Steam is relaunching it: quit immediately in that case. Call it before Instance and before ConfigureAppId (steam_appid.txt, and possibly the SteamAppId environment variable, suppress the relaunch). Returns false when the native library cannot be loaded.", new[] { "appId: Your Steam AppId." },
@"```csharp
if (SteamManager.RestartAppIfNecessary(480))
{
Quit();
return;
}
```")]
public static bool RestartAppIfNecessary(uint appId)
{
if (appId == 0)
{
return false;
}
if (_configuredAppId.HasValue)
{
_logger.LogWarning("RestartAppIfNecessary was called after ConfigureAppId; the AppId environment variables or steam_appid.txt may stop Steam from relaunching the game.");
}
var restarting = SteamNativeApi.RestartAppIfNecessary(appId);
if (restarting)
{
_logger.LogInformation("Steam is relaunching the game through the Steam client; the current process should exit.");
}
return restarting;
}
[Documentation(nameof(ConfigureAppId), "Configures the Steam AppId used during initialization by setting the SteamAppId/SteamGameId environment variables. Does not write steam_appid.txt. Must be called before accessing the singleton instance.", new[] { "appId: Steam application identifier." }, null)]
public static bool ConfigureAppId(uint appId) public static bool ConfigureAppId(uint appId)
{
return ConfigureAppId(appId, false);
}
[Documentation(nameof(ConfigureAppId), "Configures the Steam AppId used during initialization. Optionally writes steam_appid.txt next to the executable and in the working directory (useful in editors and development builds; leave false for shipped builds). Must be called before accessing the singleton instance.", new[]
{
"appId: Steam application identifier.",
"writeSteamAppIdFile: True to also write steam_appid.txt."
}, null)]
public static bool ConfigureAppId(uint appId, bool writeSteamAppIdFile)
{ {
if (appId == 0) if (appId == 0)
{ {
@@ -62,13 +128,29 @@ steam.Update();
} }
_configuredAppId = appId; _configuredAppId = appId;
SteamNativeApi.ConfigureAppId(appId); SteamNativeApi.ConfigureAppId(appId, writeSteamAppIdFile);
return true; return true;
} }
[Documentation(nameof(IsSteamworksInitialized), "Indicates whether Steamworks is initialized and ready for API calls.", null, null)] [Documentation(nameof(IsSteamworksInitialized), "Indicates whether Steamworks is initialized and ready for API calls.", null, null)]
public bool IsSteamworksInitialized { get; private set; } public bool IsSteamworksInitialized { get; private set; }
[Documentation(nameof(InitErrorMessage), "The reason initialization failed, as reported by SteamAPI_InitFlat when available. Empty after a successful init.", null, null)]
public string InitErrorMessage { get; private set; } = string.Empty;
[Documentation(nameof(Callbacks), "Provides callback subscriptions and async call results through manual dispatch.", null, null)]
public SteamCallbackDispatcher Callbacks => CallbackDispatcher;
[Documentation(nameof(OverlayActivated), "Raised from Update() when the Steam overlay opens or closes. Same as Callbacks.OverlayActivated.", null, null)]
public event Action<SteamOverlayActivated> OverlayActivated
{
add => CallbackDispatcher.OverlayActivated += value;
remove => CallbackDispatcher.OverlayActivated -= value;
}
[Documentation(nameof(Apps), "Provides access to ISteamApps bindings (ownership, DLC, language).", null, null)]
public SteamAppsManager Apps => AppsManager;
[Documentation(nameof(Ugc), "Provides access to Steam Workshop (UGC) helper operations.", null, null)] [Documentation(nameof(Ugc), "Provides access to Steam Workshop (UGC) helper operations.", null, null)]
public SteamUgcManager Ugc => UgcManager; public SteamUgcManager Ugc => UgcManager;
@@ -134,11 +216,12 @@ steam.Update();
if (SteamNativeApi.Initialize(_configuredAppId)) if (SteamNativeApi.Initialize(_configuredAppId))
{ {
IsSteamworksInitialized = true; IsSteamworksInitialized = true;
_logger.LogInformation("Steamworks initialized successfully."); _logger.LogInformation("Steamworks initialized successfully (manual callback dispatch: {ManualDispatchActive}).", SteamNativeApi.IsManualDispatchActive);
} }
else else
{ {
_logger.LogWarning("Steamworks initialization failed. Ensure Steam is running and AppId context is valid."); InitErrorMessage = SteamNativeApi.LastInitErrorMessage;
_logger.LogWarning("Steamworks initialization failed: {InitErrorMessage} Ensure Steam is running and AppId context is valid.", InitErrorMessage);
} }
} }
catch (Exception e) catch (Exception e)
@@ -148,20 +231,30 @@ steam.Update();
} }
// This must be called from your game's main loop (e.g., Update in Unity/Godot) // This must be called from your game's main loop (e.g., Update in Unity/Godot)
[Documentation(nameof(Update), "Executes Steamworks callbacks. Call every frame from your game loop.", null, null)] [Documentation(nameof(Update), "Executes Steamworks callbacks (manual dispatch, or SteamAPI_RunCallbacks when manual dispatch is unavailable) and delivers events and async call results. Call every frame from your game loop's main thread.", null, null)]
public void Update() public void Update()
{ {
if (IsSteamworksInitialized) if (!IsSteamworksInitialized)
{
return;
}
if (SteamNativeApi.IsManualDispatchActive)
{
CallbackDispatcher.RunFrame();
}
else
{ {
SteamNativeApi.RunCallbacks(); SteamNativeApi.RunCallbacks();
} }
} }
[Documentation(nameof(Shutdown), "Shuts down Steamworks and clears the initialized state.", null, null)] [Documentation(nameof(Shutdown), "Shuts down Steamworks and clears the initialized state. Pending async call results complete with IoFailure = true.", null, null)]
public void Shutdown() public void Shutdown()
{ {
if (IsSteamworksInitialized) if (IsSteamworksInitialized)
{ {
CallbackDispatcher.FailPendingCallResults();
SteamNativeApi.Shutdown(); SteamNativeApi.Shutdown();
IsSteamworksInitialized = false; IsSteamworksInitialized = false;
} }
+487 -19
View File
@@ -2,18 +2,70 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
namespace OmegaLeo.HelperLib.Steamworks namespace OmegaLeo.HelperLib.Steamworks
{ {
internal readonly struct SteamNativeCallbackMessage
{
public SteamNativeCallbackMessage(int steamUser, int callbackId, IntPtr param, int paramSize)
{
SteamUser = steamUser;
CallbackId = callbackId;
Param = param;
ParamSize = paramSize;
}
public int SteamUser { get; }
public int CallbackId { get; }
public IntPtr Param { get; }
public int ParamSize { get; }
}
internal static class SteamNativeApi internal static class SteamNativeApi
{ {
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)] [return: MarshalAs(UnmanagedType.I1)]
private delegate bool SteamApiInitDelegate(); private delegate bool SteamApiInitDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int SteamApiInitFlatDelegate(IntPtr errorMessage);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private delegate bool SteamApiRestartAppIfNecessaryDelegate(uint appId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void SteamApiVoidDelegate(); private delegate void SteamApiVoidDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int SteamApiGetHSteamPipeDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void SteamApiManualDispatchPipeDelegate(int steamPipe);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private delegate bool SteamApiManualDispatchGetNextCallbackDelegate(int steamPipe, IntPtr callbackMessage);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private delegate bool SteamApiManualDispatchGetApiCallResultDelegate(int steamPipe, ulong apiCall, IntPtr callbackBuffer, int callbackSize, int callbackExpected, [MarshalAs(UnmanagedType.I1)] out bool failed);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private delegate bool SteamAppsBoolDelegate(IntPtr steamApps);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private delegate bool SteamAppsAppIdBoolDelegate(IntPtr steamApps, uint appId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr SteamAppsStringDelegate(IntPtr steamApps);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int SteamAppsGetAppBuildIdDelegate(IntPtr steamApps);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr SteamFriendsAccessorDelegate(); private delegate IntPtr SteamFriendsAccessorDelegate();
@@ -289,13 +341,41 @@ namespace OmegaLeo.HelperLib.Steamworks
private static readonly object SyncRoot = new object(); private static readonly object SyncRoot = new object();
// SteamErrMsg is a char[1024] buffer in steam_api_common.h.
private const int SteamErrMsgSize = 1024;
// Large enough for CallbackMsg_t on 32-bit and 64-bit (24 bytes on x64).
private const int CallbackMessageBufferSize = 64;
private static IntPtr _libraryHandle; private static IntPtr _libraryHandle;
private static bool _libraryLoaded; private static bool _libraryLoaded;
private static bool _loadFailureLogged;
private static bool _initialized; private static bool _initialized;
private static bool _manualDispatchEnabled = true;
private static bool _manualDispatchActive;
private static int _steamPipe;
private static IntPtr _callbackMessageBuffer;
private static readonly List<string> CustomLibraryPaths = new List<string>();
private static SteamApiInitFlatDelegate _steamApiInitFlat;
private static SteamApiInitDelegate _steamApiInit; private static SteamApiInitDelegate _steamApiInit;
private static SteamApiRestartAppIfNecessaryDelegate _steamApiRestartAppIfNecessary;
private static SteamApiVoidDelegate _steamApiShutdown; private static SteamApiVoidDelegate _steamApiShutdown;
private static SteamApiVoidDelegate _steamApiRunCallbacks; private static SteamApiVoidDelegate _steamApiRunCallbacks;
private static SteamApiGetHSteamPipeDelegate _steamApiGetHSteamPipe;
private static SteamApiVoidDelegate _steamApiManualDispatchInit;
private static SteamApiManualDispatchPipeDelegate _steamApiManualDispatchRunFrame;
private static SteamApiManualDispatchGetNextCallbackDelegate _steamApiManualDispatchGetNextCallback;
private static SteamApiManualDispatchPipeDelegate _steamApiManualDispatchFreeLastCallback;
private static SteamApiManualDispatchGetApiCallResultDelegate _steamApiManualDispatchGetApiCallResult;
private static SteamAppsBoolDelegate _steamAppsIsSubscribed;
private static SteamAppsAppIdBoolDelegate _steamAppsIsSubscribedApp;
private static SteamAppsAppIdBoolDelegate _steamAppsIsDlcInstalled;
private static SteamAppsAppIdBoolDelegate _steamAppsIsAppInstalled;
private static SteamAppsStringDelegate _steamAppsGetCurrentGameLanguage;
private static SteamAppsStringDelegate _steamAppsGetAvailableGameLanguages;
private static SteamAppsGetAppBuildIdDelegate _steamAppsGetAppBuildId;
private static SteamFriendsAccessorDelegate _steamFriendsAccessor; private static SteamFriendsAccessorDelegate _steamFriendsAccessor;
private static SteamUserStatsAccessorDelegate _steamUserStatsAccessor; private static SteamUserStatsAccessorDelegate _steamUserStatsAccessor;
private static SteamUgcAccessorDelegate _steamUgcAccessor; private static SteamUgcAccessorDelegate _steamUgcAccessor;
@@ -385,6 +465,7 @@ namespace OmegaLeo.HelperLib.Steamworks
private static readonly Dictionary<string, SteamInterfaceAccessorDelegate> AdditionalInterfaceAccessors = new Dictionary<string, SteamInterfaceAccessorDelegate>(); private static readonly Dictionary<string, SteamInterfaceAccessorDelegate> AdditionalInterfaceAccessors = new Dictionary<string, SteamInterfaceAccessorDelegate>();
private static readonly Dictionary<string, IntPtr> AdditionalInterfacePointers = new Dictionary<string, IntPtr>(); private static readonly Dictionary<string, IntPtr> AdditionalInterfacePointers = new Dictionary<string, IntPtr>();
private const string InterfaceSteamApps = "SteamApps";
private const string InterfaceSteamController = "SteamController"; private const string InterfaceSteamController = "SteamController";
private const string InterfaceSteamGameCoordinator = "SteamGameCoordinator"; private const string InterfaceSteamGameCoordinator = "SteamGameCoordinator";
private const string InterfaceSteamGameServer = "SteamGameServer"; private const string InterfaceSteamGameServer = "SteamGameServer";
@@ -403,13 +484,53 @@ namespace OmegaLeo.HelperLib.Steamworks
public static bool IsAvailable => EnsureLoaded(); public static bool IsAvailable => EnsureLoaded();
public static bool IsManualDispatchActive => _manualDispatchActive;
public static string LastInitErrorMessage { get; private set; } = string.Empty;
private static ILogger Logger => SteamManager.Logger;
public static bool AddLibrarySearchPath(string directoryOrFile)
{
if (string.IsNullOrWhiteSpace(directoryOrFile))
{
return false;
}
lock (SyncRoot)
{
if (_libraryLoaded)
{
return false;
}
CustomLibraryPaths.Add(directoryOrFile);
_loadFailureLogged = false;
return true;
}
}
public static bool ConfigureManualDispatch(bool enabled)
{
lock (SyncRoot)
{
if (_initialized)
{
return false;
}
_manualDispatchEnabled = enabled;
return true;
}
}
public static bool Initialize(uint? appId = null) public static bool Initialize(uint? appId = null)
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
if (appId.HasValue && appId.Value > 0) if (appId.HasValue && appId.Value > 0)
{ {
ConfigureAppId(appId.Value); ApplyAppIdEnvironment(appId.Value);
} }
if (_initialized) if (_initialized)
@@ -419,25 +540,39 @@ namespace OmegaLeo.HelperLib.Steamworks
if (!EnsureLoaded()) if (!EnsureLoaded())
{ {
LastInitErrorMessage = "The Steam API native library could not be loaded.";
return false; return false;
} }
if (_steamApiInit == null) _initialized = InvokeSteamApiInit();
{
return false;
}
_initialized = _steamApiInit();
if (_initialized) if (_initialized)
{ {
CacheInterfaces(); CacheInterfaces();
TryEnableManualDispatch();
} }
return _initialized; return _initialized;
} }
} }
public static void ConfigureAppId(uint appId) public static bool RestartAppIfNecessary(uint appId)
{
if (!EnsureLoaded())
{
Logger.LogError("SteamAPI_RestartAppIfNecessary skipped: the Steam API native library could not be loaded.");
return false;
}
if (_steamApiRestartAppIfNecessary == null)
{
Logger.LogWarning("SteamAPI_RestartAppIfNecessary export not found in the loaded Steam API library.");
return false;
}
return _steamApiRestartAppIfNecessary(appId);
}
public static void ConfigureAppId(uint appId, bool writeSteamAppIdFile)
{ {
if (appId == 0) if (appId == 0)
{ {
@@ -447,7 +582,10 @@ namespace OmegaLeo.HelperLib.Steamworks
lock (SyncRoot) lock (SyncRoot)
{ {
ApplyAppIdEnvironment(appId); ApplyAppIdEnvironment(appId);
TryWriteSteamAppIdFile(appId); if (writeSteamAppIdFile)
{
TryWriteSteamAppIdFile(appId);
}
} }
} }
@@ -462,6 +600,14 @@ namespace OmegaLeo.HelperLib.Steamworks
_steamApiShutdown?.Invoke(); _steamApiShutdown?.Invoke();
_initialized = false; _initialized = false;
_manualDispatchActive = false;
_steamPipe = 0;
if (_callbackMessageBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(_callbackMessageBuffer);
_callbackMessageBuffer = IntPtr.Zero;
}
_steamFriends = IntPtr.Zero; _steamFriends = IntPtr.Zero;
_steamUserStats = IntPtr.Zero; _steamUserStats = IntPtr.Zero;
_steamUgc = IntPtr.Zero; _steamUgc = IntPtr.Zero;
@@ -472,7 +618,8 @@ namespace OmegaLeo.HelperLib.Steamworks
public static void RunCallbacks() public static void RunCallbacks()
{ {
if (!_initialized) // SteamAPI_RunCallbacks must never run once manual dispatch is in use.
if (!_initialized || _manualDispatchActive)
{ {
return; return;
} }
@@ -480,6 +627,152 @@ namespace OmegaLeo.HelperLib.Steamworks
_steamApiRunCallbacks?.Invoke(); _steamApiRunCallbacks?.Invoke();
} }
public static void ManualDispatchRunFrame()
{
if (!_manualDispatchActive)
{
return;
}
_steamApiManualDispatchRunFrame(_steamPipe);
}
public static bool ManualDispatchGetNextCallback(out SteamNativeCallbackMessage message)
{
message = default;
if (!_manualDispatchActive || !_steamApiManualDispatchGetNextCallback(_steamPipe, _callbackMessageBuffer))
{
return false;
}
// CallbackMsg_t { HSteamUser m_hSteamUser; int m_iCallback; uint8 *m_pubParam; int m_cubParam; }
// The pointer lands on offset 8 under both pack 4 and pack 8, so the layout is the same on every platform.
message = new SteamNativeCallbackMessage(
Marshal.ReadInt32(_callbackMessageBuffer, 0),
Marshal.ReadInt32(_callbackMessageBuffer, 4),
Marshal.ReadIntPtr(_callbackMessageBuffer, 8),
Marshal.ReadInt32(_callbackMessageBuffer, 8 + IntPtr.Size));
return true;
}
public static void ManualDispatchFreeLastCallback()
{
if (!_manualDispatchActive)
{
return;
}
_steamApiManualDispatchFreeLastCallback(_steamPipe);
}
public static bool ManualDispatchGetApiCallResult(ulong apiCall, IntPtr callbackBuffer, int callbackSize, int callbackExpected, out bool failed)
{
failed = true;
if (!_manualDispatchActive)
{
return false;
}
return _steamApiManualDispatchGetApiCallResult(_steamPipe, apiCall, callbackBuffer, callbackSize, callbackExpected, out failed);
}
private static bool InvokeSteamApiInit()
{
LastInitErrorMessage = string.Empty;
if (_steamApiInitFlat != null)
{
var errorBuffer = Marshal.AllocHGlobal(SteamErrMsgSize);
try
{
Marshal.WriteByte(errorBuffer, 0, 0);
var result = _steamApiInitFlat(errorBuffer);
if (result == 0)
{
Logger.LogDebug("Steamworks initialized through SteamAPI_InitFlat.");
return true;
}
var message = Marshal.PtrToStringAnsi(errorBuffer) ?? string.Empty;
LastInitErrorMessage = string.IsNullOrWhiteSpace(message) ? DescribeInitResult(result) : message;
Logger.LogWarning("SteamAPI_InitFlat failed with {InitResult} ({InitResultName}): {InitErrorMessage}", result, DescribeInitResult(result), LastInitErrorMessage);
return false;
}
finally
{
Marshal.FreeHGlobal(errorBuffer);
}
}
if (_steamApiInit != null)
{
if (_steamApiInit())
{
Logger.LogDebug("Steamworks initialized through the legacy SteamAPI_Init export.");
return true;
}
LastInitErrorMessage = "SteamAPI_Init returned false (this SDK version does not report a reason).";
return false;
}
LastInitErrorMessage = "No SteamAPI_InitFlat, SteamAPI_Init or SteamAPI_InitSafe export was found.";
Logger.LogError(LastInitErrorMessage);
return false;
}
private static string DescribeInitResult(int result)
{
// ESteamAPIInitResult
switch (result)
{
case 0: return "OK";
case 1: return "FailedGeneric";
case 2: return "NoSteamClient";
case 3: return "VersionMismatch";
default: return "Unknown";
}
}
private static void TryEnableManualDispatch()
{
if (!_manualDispatchEnabled)
{
Logger.LogInformation("Manual callback dispatch disabled; using SteamAPI_RunCallbacks.");
return;
}
if (_steamApiGetHSteamPipe == null ||
_steamApiManualDispatchInit == null ||
_steamApiManualDispatchRunFrame == null ||
_steamApiManualDispatchGetNextCallback == null ||
_steamApiManualDispatchFreeLastCallback == null ||
_steamApiManualDispatchGetApiCallResult == null)
{
Logger.LogWarning("Steam manual dispatch exports not found; falling back to SteamAPI_RunCallbacks. Callback events and async call results will not be delivered.");
return;
}
var steamPipe = _steamApiGetHSteamPipe();
if (steamPipe == 0)
{
Logger.LogWarning("SteamAPI_GetHSteamPipe returned an invalid pipe; falling back to SteamAPI_RunCallbacks.");
return;
}
_steamApiManualDispatchInit();
_steamPipe = steamPipe;
if (_callbackMessageBuffer == IntPtr.Zero)
{
_callbackMessageBuffer = Marshal.AllocHGlobal(CallbackMessageBufferSize);
}
_manualDispatchActive = true;
Logger.LogDebug("Steam manual callback dispatch enabled.");
}
public static string GetPersonaName() public static string GetPersonaName()
{ {
if (!EnsureReady()) if (!EnsureReady())
@@ -784,6 +1077,8 @@ namespace OmegaLeo.HelperLib.Steamworks
return _steamRemotePlayGetSessionClientResolution?.Invoke(_steamRemotePlay, sessionId, out resolutionX, out resolutionY) ?? false; return _steamRemotePlayGetSessionClientResolution?.Invoke(_steamRemotePlay, sessionId, out resolutionX, out resolutionY) ?? false;
} }
public static bool IsSteamAppsAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamApps);
public static bool IsSteamControllerAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamController); public static bool IsSteamControllerAvailable() => EnsureAdditionalInterfaceReady(InterfaceSteamController);
public static bool IsSteamFriendsAvailable() => EnsureFriendsReady(); public static bool IsSteamFriendsAvailable() => EnsureFriendsReady();
@@ -928,6 +1223,85 @@ namespace OmegaLeo.HelperLib.Steamworks
return _steamUtilsIsOverlayEnabled?.Invoke(steamUtils) ?? false; return _steamUtilsIsOverlayEnabled?.Invoke(steamUtils) ?? false;
} }
public static bool AppsIsSubscribed()
{
if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps))
{
return false;
}
var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps);
return _steamAppsIsSubscribed?.Invoke(steamApps) ?? false;
}
public static bool AppsIsSubscribedApp(uint appId)
{
if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps))
{
return false;
}
var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps);
return _steamAppsIsSubscribedApp?.Invoke(steamApps, appId) ?? false;
}
public static bool AppsIsDlcInstalled(uint appId)
{
if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps))
{
return false;
}
var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps);
return _steamAppsIsDlcInstalled?.Invoke(steamApps, appId) ?? false;
}
public static bool AppsIsAppInstalled(uint appId)
{
if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps))
{
return false;
}
var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps);
return _steamAppsIsAppInstalled?.Invoke(steamApps, appId) ?? false;
}
public static string AppsGetCurrentGameLanguage()
{
if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps))
{
return string.Empty;
}
var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps);
var languagePtr = _steamAppsGetCurrentGameLanguage?.Invoke(steamApps) ?? IntPtr.Zero;
return languagePtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringAnsi(languagePtr) ?? string.Empty;
}
public static string AppsGetAvailableGameLanguages()
{
if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps))
{
return string.Empty;
}
var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps);
var languagesPtr = _steamAppsGetAvailableGameLanguages?.Invoke(steamApps) ?? IntPtr.Zero;
return languagesPtr == IntPtr.Zero ? string.Empty : Marshal.PtrToStringAnsi(languagesPtr) ?? string.Empty;
}
public static int AppsGetAppBuildId()
{
if (!EnsureAdditionalInterfaceReady(InterfaceSteamApps))
{
return 0;
}
var steamApps = GetAdditionalInterfacePointer(InterfaceSteamApps);
return _steamAppsGetAppBuildId?.Invoke(steamApps) ?? 0;
}
public static bool RemoteStorageFileExists(string fileName) public static bool RemoteStorageFileExists(string fileName)
{ {
if (!EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage)) if (!EnsureAdditionalInterfaceReady(InterfaceSteamRemoteStorage))
@@ -1486,35 +1860,66 @@ namespace OmegaLeo.HelperLib.Steamworks
return true; return true;
} }
var failures = new List<string>();
foreach (var candidate in GetLibraryCandidates()) foreach (var candidate in GetLibraryCandidates())
{ {
try try
{ {
_libraryHandle = LoadUnmanagedLibrary(candidate); var handle = LoadUnmanagedLibrary(candidate);
_libraryLoaded = _libraryHandle != IntPtr.Zero; if (handle != IntPtr.Zero)
if (_libraryLoaded)
{ {
_libraryHandle = handle;
_libraryLoaded = true;
BindDelegates(); BindDelegates();
Logger.LogInformation("Loaded Steam API native library from '{SteamLibraryPath}'.", candidate);
return true; return true;
} }
failures.Add($"{candidate} ({GetLastLoadError()})");
} }
catch catch (Exception e)
{ {
// Try the next candidate. // Try the next candidate.
failures.Add($"{candidate} ({e.GetType().Name}: {e.Message})");
} }
} }
if (!_loadFailureLogged)
{
_loadFailureLogged = true;
Logger.LogError(
"Could not load the Steam API native library '{SteamLibraryName}'. Call SteamManager.ConfigureLibraryPath with the folder that contains it. Tried: {SteamLibraryCandidates}",
GetLibraryFileName(),
string.Join("; ", failures));
}
return false; return false;
} }
} }
private static void BindDelegates() private static void BindDelegates()
{ {
// SDK 1.58+ exposes SteamAPI_InitFlat (with an error message); older SDKs only have SteamAPI_Init/InitSafe.
_steamApiInitFlat = Bind<SteamApiInitFlatDelegate>("SteamAPI_InitFlat");
_steamApiInit = _steamApiInit =
Bind<SteamApiInitDelegate>("SteamAPI_Init") Bind<SteamApiInitDelegate>("SteamAPI_Init")
?? Bind<SteamApiInitDelegate>("SteamAPI_InitSafe"); ?? Bind<SteamApiInitDelegate>("SteamAPI_InitSafe");
_steamApiRestartAppIfNecessary = Bind<SteamApiRestartAppIfNecessaryDelegate>("SteamAPI_RestartAppIfNecessary");
_steamApiShutdown = Bind<SteamApiVoidDelegate>("SteamAPI_Shutdown"); _steamApiShutdown = Bind<SteamApiVoidDelegate>("SteamAPI_Shutdown");
_steamApiRunCallbacks = Bind<SteamApiVoidDelegate>("SteamAPI_RunCallbacks"); _steamApiRunCallbacks = Bind<SteamApiVoidDelegate>("SteamAPI_RunCallbacks");
_steamApiGetHSteamPipe = Bind<SteamApiGetHSteamPipeDelegate>("SteamAPI_GetHSteamPipe");
_steamApiManualDispatchInit = Bind<SteamApiVoidDelegate>("SteamAPI_ManualDispatch_Init");
_steamApiManualDispatchRunFrame = Bind<SteamApiManualDispatchPipeDelegate>("SteamAPI_ManualDispatch_RunFrame");
_steamApiManualDispatchGetNextCallback = Bind<SteamApiManualDispatchGetNextCallbackDelegate>("SteamAPI_ManualDispatch_GetNextCallback");
_steamApiManualDispatchFreeLastCallback = Bind<SteamApiManualDispatchPipeDelegate>("SteamAPI_ManualDispatch_FreeLastCallback");
_steamApiManualDispatchGetApiCallResult = Bind<SteamApiManualDispatchGetApiCallResultDelegate>("SteamAPI_ManualDispatch_GetAPICallResult");
_steamAppsIsSubscribed = Bind<SteamAppsBoolDelegate>("SteamAPI_ISteamApps_BIsSubscribed");
_steamAppsIsSubscribedApp = Bind<SteamAppsAppIdBoolDelegate>("SteamAPI_ISteamApps_BIsSubscribedApp");
_steamAppsIsDlcInstalled = Bind<SteamAppsAppIdBoolDelegate>("SteamAPI_ISteamApps_BIsDlcInstalled");
_steamAppsIsAppInstalled = Bind<SteamAppsAppIdBoolDelegate>("SteamAPI_ISteamApps_BIsAppInstalled");
_steamAppsGetCurrentGameLanguage = Bind<SteamAppsStringDelegate>("SteamAPI_ISteamApps_GetCurrentGameLanguage");
_steamAppsGetAvailableGameLanguages = Bind<SteamAppsStringDelegate>("SteamAPI_ISteamApps_GetAvailableGameLanguages");
_steamAppsGetAppBuildId = Bind<SteamAppsGetAppBuildIdDelegate>("SteamAPI_ISteamApps_GetAppBuildId");
_steamFriendsAccessor = BindInterfaceAccessor<SteamFriendsAccessorDelegate>("SteamFriends", "ISteamFriends"); _steamFriendsAccessor = BindInterfaceAccessor<SteamFriendsAccessorDelegate>("SteamFriends", "ISteamFriends");
_steamUserStatsAccessor = BindInterfaceAccessor<SteamUserStatsAccessorDelegate>("SteamUserStats", "ISteamUserStats"); _steamUserStatsAccessor = BindInterfaceAccessor<SteamUserStatsAccessorDelegate>("SteamUserStats", "ISteamUserStats");
_steamUgcAccessor = BindInterfaceAccessor<SteamUgcAccessorDelegate>("SteamUGC", "ISteamUGC"); _steamUgcAccessor = BindInterfaceAccessor<SteamUgcAccessorDelegate>("SteamUGC", "ISteamUGC");
@@ -1597,6 +2002,7 @@ namespace OmegaLeo.HelperLib.Steamworks
_steamUserStatsSetStatInt32 = Bind<SteamUserStatsSetStatInt32Delegate>("SteamAPI_ISteamUserStats_SetStatInt32"); _steamUserStatsSetStatInt32 = Bind<SteamUserStatsSetStatInt32Delegate>("SteamAPI_ISteamUserStats_SetStatInt32");
// Additional interface accessors requested for broad API coverage. // Additional interface accessors requested for broad API coverage.
BindAdditionalInterfaceAccessor(InterfaceSteamApps, "SteamApps", "ISteamApps");
BindAdditionalInterfaceAccessor(InterfaceSteamController, "SteamController", "ISteamController"); BindAdditionalInterfaceAccessor(InterfaceSteamController, "SteamController", "ISteamController");
BindAdditionalInterfaceAccessor(InterfaceSteamGameCoordinator, "SteamGameCoordinator", "ISteamGameCoordinator"); BindAdditionalInterfaceAccessor(InterfaceSteamGameCoordinator, "SteamGameCoordinator", "ISteamGameCoordinator");
BindAdditionalInterfaceAccessor(InterfaceSteamGameServer, "SteamGameServer", "ISteamGameServer"); BindAdditionalInterfaceAccessor(InterfaceSteamGameServer, "SteamGameServer", "ISteamGameServer");
@@ -1735,13 +2141,38 @@ namespace OmegaLeo.HelperLib.Steamworks
{ {
var libraryName = GetLibraryFileName(); var libraryName = GetLibraryFileName();
var runtimeIdentifier = GetRuntimeIdentifier(); var runtimeIdentifier = GetRuntimeIdentifier();
var candidates = new List<string>();
return new[] // Paths from SteamManager.ConfigureLibraryPath are tried first, in the order they were added.
foreach (var customPath in CustomLibraryPaths)
{ {
Path.Combine(AppContext.BaseDirectory, "runtimes", runtimeIdentifier, "native", libraryName), var isFile = File.Exists(customPath) ||
Path.Combine(AppContext.BaseDirectory, libraryName), string.Equals(Path.GetFileName(customPath), libraryName, StringComparison.OrdinalIgnoreCase);
libraryName candidates.Add(isFile ? customPath : Path.Combine(customPath, libraryName));
}; }
candidates.Add(Path.Combine(AppContext.BaseDirectory, "runtimes", runtimeIdentifier, "native", libraryName));
candidates.Add(Path.Combine(AppContext.BaseDirectory, libraryName));
candidates.Add(libraryName);
return candidates.ToArray();
}
private static string GetLastLoadError()
{
try
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()).Message;
}
var errorPtr = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? DlerrorMac() : DlerrorLinux();
return errorPtr == IntPtr.Zero ? "unknown error" : Marshal.PtrToStringAnsi(errorPtr) ?? "unknown error";
}
catch (Exception e)
{
return e.Message;
}
} }
private static string GetRuntimeIdentifier() private static string GetRuntimeIdentifier()
@@ -1779,6 +2210,31 @@ namespace OmegaLeo.HelperLib.Steamworks
var appIdValue = appId.ToString(); var appIdValue = appId.ToString();
Environment.SetEnvironmentVariable("SteamAppId", appIdValue); Environment.SetEnvironmentVariable("SteamAppId", appIdValue);
Environment.SetEnvironmentVariable("SteamGameId", appIdValue); Environment.SetEnvironmentVariable("SteamGameId", appIdValue);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
// Some runtimes keep Environment.SetEnvironmentVariable managed-only on Unix,
// so mirror the values into the native environment that steam_api reads.
try
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
SetEnvMac("SteamAppId", appIdValue, 1);
SetEnvMac("SteamGameId", appIdValue, 1);
}
else
{
SetEnvLinux("SteamAppId", appIdValue, 1);
SetEnvLinux("SteamGameId", appIdValue, 1);
}
}
catch (Exception e)
{
Logger.LogDebug(e, "Could not set the native SteamAppId environment variables.");
}
} }
private static void TryWriteSteamAppIdFile(uint appId) private static void TryWriteSteamAppIdFile(uint appId)
@@ -1856,6 +2312,18 @@ namespace OmegaLeo.HelperLib.Steamworks
[DllImport("libSystem.B.dylib", EntryPoint = "dlsym")] [DllImport("libSystem.B.dylib", EntryPoint = "dlsym")]
private static extern IntPtr DlsymMac(IntPtr handle, string symbol); private static extern IntPtr DlsymMac(IntPtr handle, string symbol);
[DllImport("libdl.so.2", EntryPoint = "dlerror")]
private static extern IntPtr DlerrorLinux();
[DllImport("libSystem.B.dylib", EntryPoint = "dlerror")]
private static extern IntPtr DlerrorMac();
[DllImport("libc.so.6", EntryPoint = "setenv")]
private static extern int SetEnvLinux(string name, string value, int overwrite);
[DllImport("libSystem.B.dylib", EntryPoint = "setenv")]
private static extern int SetEnvMac(string name, string value, int overwrite);
private const int RtldNow = 2; private const int RtldNow = 2;
} }
@@ -0,0 +1,121 @@
using OmegaLeo.HelperLib.Shared.Attributes;
namespace OmegaLeo.HelperLib.Steamworks
{
[Documentation(nameof(SteamUgcSubmitItemUpdateResult), "Result of ISteamUGC::SubmitItemUpdate (SubmitItemUpdateResult_t).", null, null)]
public sealed class SteamUgcSubmitItemUpdateResult
{
[Documentation(nameof(Result), "Numeric EResult value (1 = OK).", null, null)]
public int Result { get; private set; }
[Documentation(nameof(UserNeedsToAcceptWorkshopLegalAgreement), "True when the user must accept the Workshop legal agreement before the item becomes visible.", null, null)]
public bool UserNeedsToAcceptWorkshopLegalAgreement { get; private set; }
[Documentation(nameof(PublishedFileId), "The updated Workshop item ID.", null, null)]
public ulong PublishedFileId { get; private set; }
[Documentation(nameof(IoFailure), "True when the result could not be retrieved; the other fields are then not valid.", null, null)]
public bool IoFailure { get; private set; }
[Documentation(nameof(IsSuccess), "True when the result was retrieved and EResult is OK.", null, null)]
public bool IsSuccess => !IoFailure && Result == 1;
internal static SteamUgcSubmitItemUpdateResult FromCallResult(SteamApiCallResult callResult)
{
// SubmitItemUpdateResult_t { EResult m_eResult; bool m_bUserNeedsToAcceptWorkshopLegalAgreement; PublishedFileId_t m_nPublishedFileId; }
// Offsets 0/4/8 under both pack 4 and pack 8.
return new SteamUgcSubmitItemUpdateResult
{
IoFailure = IsFailure(callResult, SteamCallbackIds.SubmitItemUpdateResult, 16),
Result = callResult.ReadInt32(0),
UserNeedsToAcceptWorkshopLegalAgreement = callResult.ReadBoolean(4),
PublishedFileId = callResult.ReadUInt64(8)
};
}
internal static bool IsFailure(SteamApiCallResult callResult, int expectedCallbackId, int minimumSize)
{
return callResult.IoFailure || callResult.CallbackId != expectedCallbackId || callResult.Payload.Length < minimumSize;
}
}
[Documentation(nameof(SteamUgcCreateItemResult), "Result of ISteamUGC::CreateItem (CreateItemResult_t).", null, null)]
public sealed class SteamUgcCreateItemResult
{
[Documentation(nameof(Result), "Numeric EResult value (1 = OK).", null, null)]
public int Result { get; private set; }
[Documentation(nameof(PublishedFileId), "The new Workshop item ID. Pass it to StartItemUpdate to upload content.", null, null)]
public ulong PublishedFileId { get; private set; }
[Documentation(nameof(UserNeedsToAcceptWorkshopLegalAgreement), "True when the user must accept the Workshop legal agreement before the item becomes visible.", null, null)]
public bool UserNeedsToAcceptWorkshopLegalAgreement { get; private set; }
[Documentation(nameof(IoFailure), "True when the result could not be retrieved; the other fields are then not valid.", null, null)]
public bool IoFailure { get; private set; }
[Documentation(nameof(IsSuccess), "True when the result was retrieved and EResult is OK.", null, null)]
public bool IsSuccess => !IoFailure && Result == 1;
internal static SteamUgcCreateItemResult FromCallResult(SteamApiCallResult callResult)
{
// CreateItemResult_t { EResult m_eResult; PublishedFileId_t m_nPublishedFileId; bool m_bUserNeedsToAcceptWorkshopLegalAgreement; }
// Pack 8 (Windows): offsets 0/8/16, size 24. Pack 4 (Linux/macOS): offsets 0/4/12, size 16.
var large = SteamCallbackDispatcher.UsesLargeCallbackPack;
var idOffset = large ? 8 : 4;
var legalOffset = large ? 16 : 12;
return new SteamUgcCreateItemResult
{
IoFailure = SteamUgcSubmitItemUpdateResult.IsFailure(callResult, SteamCallbackIds.CreateItemResult, legalOffset + 1),
Result = callResult.ReadInt32(0),
PublishedFileId = callResult.ReadUInt64(idOffset),
UserNeedsToAcceptWorkshopLegalAgreement = callResult.ReadBoolean(legalOffset)
};
}
}
[Documentation(nameof(SteamUgcQueryCompletedResult), "Result of ISteamUGC::SendQueryUGCRequest (SteamUGCQueryCompleted_t). Release the query handle with ReleaseQueryUgcRequest when done.", null, null)]
public sealed class SteamUgcQueryCompletedResult
{
[Documentation(nameof(QueryHandle), "The UGC query handle the results belong to.", null, null)]
public ulong QueryHandle { get; private set; }
[Documentation(nameof(Result), "Numeric EResult value (1 = OK).", null, null)]
public int Result { get; private set; }
[Documentation(nameof(NumResultsReturned), "Number of items returned in this page.", null, null)]
public uint NumResultsReturned { get; private set; }
[Documentation(nameof(TotalMatchingResults), "Total number of items matching the query.", null, null)]
public uint TotalMatchingResults { get; private set; }
[Documentation(nameof(CachedData), "True when the results came from the local cache.", null, null)]
public bool CachedData { get; private set; }
[Documentation(nameof(NextCursor), "Cursor for the next page (cursor-based queries only).", null, null)]
public string NextCursor { get; private set; } = string.Empty;
[Documentation(nameof(IoFailure), "True when the result could not be retrieved; the other fields are then not valid.", null, null)]
public bool IoFailure { get; private set; }
[Documentation(nameof(IsSuccess), "True when the result was retrieved and EResult is OK.", null, null)]
public bool IsSuccess => !IoFailure && Result == 1;
internal static SteamUgcQueryCompletedResult FromCallResult(SteamApiCallResult callResult)
{
// SteamUGCQueryCompleted_t { UGCQueryHandle_t m_handle; EResult m_eResult; uint32 m_unNumResultsReturned;
// uint32 m_unTotalMatchingResults; bool m_bCachedData; char m_rgchNextCursor[256]; }
// Offsets 0/8/12/16/20/21 under both pack 4 and pack 8.
return new SteamUgcQueryCompletedResult
{
IoFailure = SteamUgcSubmitItemUpdateResult.IsFailure(callResult, SteamCallbackIds.SteamUgcQueryCompleted, 21),
QueryHandle = callResult.ReadUInt64(0),
Result = callResult.ReadInt32(8),
NumResultsReturned = callResult.ReadUInt32(12),
TotalMatchingResults = callResult.ReadUInt32(16),
CachedData = callResult.ReadBoolean(20),
NextCursor = callResult.ReadString(21, 256)
};
}
}
}
@@ -1,3 +1,4 @@
using System;
using OmegaLeo.HelperLib.Shared.Attributes; using OmegaLeo.HelperLib.Shared.Attributes;
namespace OmegaLeo.HelperLib.Steamworks namespace OmegaLeo.HelperLib.Steamworks
@@ -68,6 +69,17 @@ namespace OmegaLeo.HelperLib.Steamworks
{ {
return _ugcManager.UpdateItem(_consumerAppId, _publishedFileId, _options, changeNote, out failedStep); return _ugcManager.UpdateItem(_consumerAppId, _publishedFileId, _options, changeNote, out failedStep);
} }
[Documentation(nameof(Submit), "Submits the fluent update session and calls onCompleted with the upload result from SteamManager.Update().", new[]
{
"changeNote: Changelog note for this update.",
"onCompleted: Called once with the upload result.",
"failedStep: Name of the failed step if submission returns 0."
}, null)]
public ulong Submit(string changeNote, Action<SteamUgcSubmitItemUpdateResult> onCompleted, out string failedStep)
{
return _ugcManager.UpdateItem(_consumerAppId, _publishedFileId, _options, changeNote, onCompleted, out failedStep);
}
} }
} }
@@ -1,3 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OmegaLeo.HelperLib.Shared.Attributes; using OmegaLeo.HelperLib.Shared.Attributes;
namespace OmegaLeo.HelperLib.Steamworks namespace OmegaLeo.HelperLib.Steamworks
@@ -113,6 +117,22 @@ var items = ugc.GetSubscribedItems();
return submitHandle; return submitHandle;
} }
[Documentation(nameof(UpdateItem), "Starts an item update, applies option fields, submits it, and calls onCompleted with the upload result from SteamManager.Update().", new[]
{
"consumerAppId: Your game AppId.",
"publishedFileId: Existing workshop item ID.",
"options: Options object containing fields to update.",
"changeNote: Changelog note for submission.",
"onCompleted: Called once with the upload result.",
"failedStep: Name of the failed step when the call returns 0."
}, null)]
public ulong UpdateItem(uint consumerAppId, ulong publishedFileId, SteamUgcItemUpdateOptions options, string changeNote, Action<SteamUgcSubmitItemUpdateResult> onCompleted, out string failedStep)
{
var submitHandle = UpdateItem(consumerAppId, publishedFileId, options, changeNote, out failedStep);
RegisterResult(submitHandle, onCompleted, SteamUgcSubmitItemUpdateResult.FromCallResult, nameof(SubmitItemUpdate));
return submitHandle;
}
[Documentation(nameof(CreateItem), "Creates a new Workshop item and returns a Steam API call handle.", new[] [Documentation(nameof(CreateItem), "Creates a new Workshop item and returns a Steam API call handle.", new[]
{ {
"consumerAppId: Your game AppId.", "consumerAppId: Your game AppId.",
@@ -123,6 +143,30 @@ var items = ugc.GetSubscribedItems();
return SteamNativeApi.CreateItem(consumerAppId, fileType); return SteamNativeApi.CreateItem(consumerAppId, fileType);
} }
[Documentation(nameof(CreateItem), "Creates a new Workshop item and calls onCompleted with the CreateItemResult_t from SteamManager.Update(). Returns the Steam API call handle, or 0 on failure.", new[]
{
"consumerAppId: Your game AppId.",
"fileType: Numeric EWorkshopFileType value.",
"onCompleted: Called once with the new item ID and result."
}, null)]
public ulong CreateItem(uint consumerAppId, uint fileType, Action<SteamUgcCreateItemResult> onCompleted)
{
var apiCall = CreateItem(consumerAppId, fileType);
RegisterResult(apiCall, onCompleted, SteamUgcCreateItemResult.FromCallResult, nameof(CreateItem));
return apiCall;
}
[Documentation(nameof(CreateItemAsync), "Creates a new Workshop item and returns a task with the CreateItemResult_t. Keep calling SteamManager.Update() while waiting.", new[]
{
"consumerAppId: Your game AppId.",
"fileType: Numeric EWorkshopFileType value.",
"cancellationToken: Cancels the wait (the Steam call keeps running)."
}, null)]
public Task<SteamUgcCreateItemResult> CreateItemAsync(uint consumerAppId, uint fileType, CancellationToken cancellationToken = default)
{
return AwaitResult(CreateItem(consumerAppId, fileType), SteamUgcCreateItemResult.FromCallResult, nameof(CreateItem), cancellationToken);
}
[Documentation(nameof(StartItemUpdate), "Starts an update transaction for an existing Workshop item.", new[] [Documentation(nameof(StartItemUpdate), "Starts an update transaction for an existing Workshop item.", new[]
{ {
"consumerAppId: Your game AppId.", "consumerAppId: Your game AppId.",
@@ -181,7 +225,31 @@ var items = ugc.GetSubscribedItems();
return SteamNativeApi.SubmitItemUpdate(updateHandle, changeNote); return SteamNativeApi.SubmitItemUpdate(updateHandle, changeNote);
} }
[Documentation(nameof(GetItemUpdateProgress), "Gets progress state for a pending item update upload.", new[] { "updateHandle: Handle from StartItemUpdate.", "bytesProcessed: Uploaded bytes output.", "bytesTotal: Total bytes output." }, null)] [Documentation(nameof(SubmitItemUpdate), "Submits a pending workshop item update and calls onCompleted with the SubmitItemUpdateResult_t from SteamManager.Update(). Returns the Steam API call handle, or 0 when submission failed.", new[]
{
"updateHandle: Handle from StartItemUpdate.",
"changeNote: Changelog note for the update.",
"onCompleted: Called once with the upload result."
}, null)]
public ulong SubmitItemUpdate(ulong updateHandle, string changeNote, Action<SteamUgcSubmitItemUpdateResult> onCompleted)
{
var apiCall = SubmitItemUpdate(updateHandle, changeNote);
RegisterResult(apiCall, onCompleted, SteamUgcSubmitItemUpdateResult.FromCallResult, nameof(SubmitItemUpdate));
return apiCall;
}
[Documentation(nameof(SubmitItemUpdateAsync), "Submits a pending workshop item update and returns a task with the SubmitItemUpdateResult_t. Keep calling SteamManager.Update() while waiting.", new[]
{
"updateHandle: Handle from StartItemUpdate.",
"changeNote: Changelog note for the update.",
"cancellationToken: Cancels the wait (the upload keeps running)."
}, null)]
public Task<SteamUgcSubmitItemUpdateResult> SubmitItemUpdateAsync(ulong updateHandle, string changeNote, CancellationToken cancellationToken = default)
{
return AwaitResult(SubmitItemUpdate(updateHandle, changeNote), SteamUgcSubmitItemUpdateResult.FromCallResult, nameof(SubmitItemUpdate), cancellationToken);
}
[Documentation(nameof(GetItemUpdateProgress),"Gets progress state for a pending item update upload.", new[] { "updateHandle: Handle from StartItemUpdate.", "bytesProcessed: Uploaded bytes output.", "bytesTotal: Total bytes output." }, null)]
public uint GetItemUpdateProgress(ulong updateHandle, out ulong bytesProcessed, out ulong bytesTotal) public uint GetItemUpdateProgress(ulong updateHandle, out ulong bytesProcessed, out ulong bytesTotal)
{ {
return SteamNativeApi.GetItemUpdateProgress(updateHandle, out bytesProcessed, out bytesTotal); return SteamNativeApi.GetItemUpdateProgress(updateHandle, out bytesProcessed, out bytesTotal);
@@ -206,7 +274,29 @@ var items = ugc.GetSubscribedItems();
return SteamNativeApi.SendQueryUgcRequest(queryHandle); return SteamNativeApi.SendQueryUgcRequest(queryHandle);
} }
[Documentation(nameof(ReleaseQueryUgcRequest), "Releases a UGC query handle after you are done with query results.", new[] { "queryHandle: UGC query handle to release." }, null)] [Documentation(nameof(SendQueryUgcRequest), "Submits a UGC query and calls onCompleted with the SteamUGCQueryCompleted_t from SteamManager.Update(). Returns the Steam API call handle, or 0 on failure.", new[]
{
"queryHandle: UGC query handle returned from CreateQueryAllUgcRequest.",
"onCompleted: Called once with the query result."
}, null)]
public ulong SendQueryUgcRequest(ulong queryHandle, Action<SteamUgcQueryCompletedResult> onCompleted)
{
var apiCall = SendQueryUgcRequest(queryHandle);
RegisterResult(apiCall, onCompleted, SteamUgcQueryCompletedResult.FromCallResult, nameof(SendQueryUgcRequest));
return apiCall;
}
[Documentation(nameof(SendQueryUgcRequestAsync), "Submits a UGC query and returns a task with the SteamUGCQueryCompleted_t. Keep calling SteamManager.Update() while waiting.", new[]
{
"queryHandle: UGC query handle returned from CreateQueryAllUgcRequest.",
"cancellationToken: Cancels the wait (the query keeps running)."
}, null)]
public Task<SteamUgcQueryCompletedResult> SendQueryUgcRequestAsync(ulong queryHandle, CancellationToken cancellationToken = default)
{
return AwaitResult(SendQueryUgcRequest(queryHandle), SteamUgcQueryCompletedResult.FromCallResult, nameof(SendQueryUgcRequest), cancellationToken);
}
[Documentation(nameof(ReleaseQueryUgcRequest),"Releases a UGC query handle after you are done with query results.", new[] { "queryHandle: UGC query handle to release." }, null)]
public bool ReleaseQueryUgcRequest(ulong queryHandle) public bool ReleaseQueryUgcRequest(ulong queryHandle)
{ {
return SteamNativeApi.ReleaseQueryUgcRequest(queryHandle); return SteamNativeApi.ReleaseQueryUgcRequest(queryHandle);
@@ -235,6 +325,30 @@ var items = ugc.GetSubscribedItems();
{ {
return SteamNativeApi.UnsubscribeItem(publishedFileId); return SteamNativeApi.UnsubscribeItem(publishedFileId);
} }
private static void RegisterResult<T>(ulong apiCall, Action<T> onCompleted, Func<SteamApiCallResult, T> convert, string operation)
{
if (apiCall == 0 || onCompleted == null)
{
return;
}
if (!SteamManager.Dispatcher.RegisterCallResult(apiCall, result => onCompleted(convert(result))))
{
SteamManager.Logger.LogWarning("{Operation} was submitted but its result will not be delivered because Steam manual callback dispatch is not active.", operation);
}
}
private static async Task<T> AwaitResult<T>(ulong apiCall, Func<SteamApiCallResult, T> convert, string operation, CancellationToken cancellationToken)
{
if (apiCall == 0)
{
throw new InvalidOperationException($"{operation} did not return a Steam API call handle. Ensure Steamworks is initialized.");
}
var result = await SteamManager.Dispatcher.WaitForCallResultAsync(apiCall, cancellationToken).ConfigureAwait(false);
return convert(result);
}
} }
} }
-1
View File
@@ -1 +0,0 @@
library.omegaleo.dev
-225
View File
@@ -1,225 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Changelog Library - OmegaLeo Helper Libraries</title>
<link rel="stylesheet" href="../../lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="../../css/site.css%3Fv=AK2YraxbvzgrpO6A1NqAHflCP7fbweSjdyWpd3ERsLE.css" />
</head>
<body>
<header b-pqzonzw7gm>
<nav b-pqzonzw7gm class="navbar navbar-expand-sm navbar-dark bg-dark border-bottom box-shadow mb-3">
<div b-pqzonzw7gm class="container-fluid">
<a class="navbar-brand" href="../../index.html">
OmegaLeo Helper Libraries
</a>
<button b-pqzonzw7gm class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse">
<span b-pqzonzw7gm class="navbar-toggler-icon"></span>
</button>
<div b-pqzonzw7gm class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul b-pqzonzw7gm class="navbar-nav flex-grow-1">
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="../../index.html">Home</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="core.html">Core</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="changelog.html">Changelog</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="game.html">Game Dev</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="documentation.html">Documentation</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="shared.html">Shared Components</a>
</li>
</ul>
<ul b-pqzonzw7gm class="navbar-nav ms-auto">
<li b-pqzonzw7gm class="nav-item">
<a b-pqzonzw7gm class="nav-link" href="https://github.com/omegaleo/HelperLib" target="_blank" rel="noopener noreferrer" aria-label="GitHub repository">
<i b-pqzonzw7gm class="bi bi-github" style="font-size:1.25rem;"></i> View on Github
</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<main b-pqzonzw7gm role="main" class="pb-3">
<div class="container-fluid mt-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="../../index.html">Home</a></li>
<li class="breadcrumb-item active">Changelog Library</li>
</ol>
</nav>
<div class="row">
<div class="col-md-3">
<div class="sticky-top" style="top: 20px;">
<div class="card">
<div class="card-header">
<h5>Changelog Library</h5>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="changelog.html#overview">
<i class="bi bi-info-circle me-2"></i>Overview
</a>
<a class="list-group-item list-group-item-action" href="changelog.html#namespace-OmegaLeo-HelperLib-Changelog">
<i class="bi bi-folder me-2"></i>OmegaLeo.HelperLib.Changelog
</a>
<a class="list-group-item list-group-item-action" href="changelog.html#changelog">
<i class="bi bi-clock-history me-2"></i>Changelog
</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div id="overview" class="mb-5">
<h2>Changelog Library</h2>
<p class="lead">Tools for managing and generating changelogs</p>
<hr />
</div>
<div id="namespace-OmegaLeo-HelperLib-Changelog" class="mb-5">
<h3>OmegaLeo.HelperLib.Changelog</h3>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">ChangelogHelper</h4>
<small class="text-muted">OmegaLeo.HelperLib.Changelog</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ChangelogHelper</code></td>
<td><p>Helper class to generate changelog markdown from ChangelogAttributes in the provided Assemblies.</p>
<br/>
</td>
</tr>
<tr>
<td><code>GetChangelogMarkdown</code></td>
<td><p>Generates a markdown formatted changelog from the changelog attributes in the provided assemblies.</p>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">ChangelogTool</h4>
<small class="text-muted">OmegaLeo.HelperLib.Changelog</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ChangelogTool</code></td>
<td><p>Provides methods to extract changelog information from assembly attributes.</p>
<br/>
</td>
</tr>
<tr>
<td><code>ChangelogTool.ExtractChangelog</code></td>
<td><p>Extracts changelog entries from the specified assembly.</p>
<br/>
</td>
</tr>
<tr>
<td><code>ChangelogTool.GetMarkdown</code></td>
<td><p>Generates a markdown formatted changelog from the assembly's changelog attributes.</p>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div id="changelog" class="mb-5">
<h3>Changelog</h3>
<div class="card">
<div class="card-body">
<h2>Version 1.2.0</h2>
<p><strong>OmegaLeo.HelperLib.Changelog.Helpers</strong></p>
<ul>
<li><strong>ChangelogHelper</strong>:
<ul>
<li>Created ChangelogHelper to generate markdown from changelog attributes.</li>
</ul>
</li>
</ul>
<h2>Version 1.0.0</h2>
<p><strong>OmegaLeo.HelperLib.Changelog.Tools</strong></p>
<ul>
<li><strong>ChangelogTool</strong>:
<ul>
<li>Created the class ChangelogTool to extract changelogs via attributes</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.sticky-top {
max-height: calc(100vh - 40px);
overflow-y: auto;
}
code {
background-color: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.9em;
}
.table th {
background-color: #f8f9fa;
}
</style>
</main>
<script src="../../lib/jquery/dist/jquery.min.js"></script>
<script src="../../lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../../js/site.js%3Fv=y_Ngd8W21UpuCUhEqGbUKDEqFnNfTjAfb24GhJJGUyM"></script>
</body>
</html>
-924
View File
@@ -1,924 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Core Library - OmegaLeo Helper Libraries</title>
<link rel="stylesheet" href="../../lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="../../css/site.css%3Fv=AK2YraxbvzgrpO6A1NqAHflCP7fbweSjdyWpd3ERsLE.css" />
</head>
<body>
<header b-pqzonzw7gm>
<nav b-pqzonzw7gm class="navbar navbar-expand-sm navbar-dark bg-dark border-bottom box-shadow mb-3">
<div b-pqzonzw7gm class="container-fluid">
<a class="navbar-brand" href="../../index.html">
OmegaLeo Helper Libraries
</a>
<button b-pqzonzw7gm class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse">
<span b-pqzonzw7gm class="navbar-toggler-icon"></span>
</button>
<div b-pqzonzw7gm class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul b-pqzonzw7gm class="navbar-nav flex-grow-1">
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="../../index.html">Home</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="core.html">Core</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="changelog.html">Changelog</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="game.html">Game Dev</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="documentation.html">Documentation</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="shared.html">Shared Components</a>
</li>
</ul>
<ul b-pqzonzw7gm class="navbar-nav ms-auto">
<li b-pqzonzw7gm class="nav-item">
<a b-pqzonzw7gm class="nav-link" href="https://github.com/omegaleo/HelperLib" target="_blank" rel="noopener noreferrer" aria-label="GitHub repository">
<i b-pqzonzw7gm class="bi bi-github" style="font-size:1.25rem;"></i> View on Github
</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<main b-pqzonzw7gm role="main" class="pb-3">
<div class="container-fluid mt-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="../../index.html">Home</a></li>
<li class="breadcrumb-item active">Core Library</li>
</ol>
</nav>
<div class="row">
<div class="col-md-3">
<div class="sticky-top" style="top: 20px;">
<div class="card">
<div class="card-header">
<h5>Core Library</h5>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="core.html#overview">
<i class="bi bi-info-circle me-2"></i>Overview
</a>
<a class="list-group-item list-group-item-action" href="core.html#namespace-OmegaLeo-HelperLib">
<i class="bi bi-folder me-2"></i>OmegaLeo.HelperLib
</a>
<a class="list-group-item list-group-item-action" href="core.html#changelog">
<i class="bi bi-clock-history me-2"></i>Changelog
</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div id="overview" class="mb-5">
<h2>Core Library</h2>
<p class="lead">Core utilities and helper functions for general C# development</p>
<hr />
</div>
<div id="namespace-OmegaLeo-HelperLib" class="mb-5">
<h3>OmegaLeo.HelperLib</h3>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">ArrayExtensions</h4>
<small class="text-muted">OmegaLeo.HelperLib</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Array Extensions</code></td>
<td><p>Set of extension methods to help with Arrays.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Next</code></td>
<td><p>Method to obtain the next object in the array by passing the current index.
Note: Idea obtained from TaroDev's video on things to do in Unity - https://youtu.be/Ic5ux-tpkCE?t=302</p>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">BenchmarkUtility</h4>
<small class="text-muted">OmegaLeo.HelperLib</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>BenchmarkUtility</code></td>
<td><p>Utility class for benchmarking code execution time.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
BenchmarkUtility.Start(<span style="color:#D69D85;">&quot;MyBenchmark&quot;</span>);
<span style="color:#57A64A;">// Code to benchmark</span>
BenchmarkUtility.Stop(<span style="color:#D69D85;">&quot;MyBenchmark&quot;</span>);
<span style="color:#569CD6;">var</span> results = BenchmarkUtility.GetResults(<span style="color:#D69D85;">&quot;MyBenchmark&quot;</span>);
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Record</code></td>
<td><p>Records the execution time of the provided action and returns the elapsed time in milliseconds.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> time = BenchmarkUtility.Record(() =&gt;
{
<span style="color:#57A64A;">// Code to benchmark</span>
});
Console.WriteLine($<span style="color:#D69D85;">&quot;Elapsed time: {time} ms&quot;</span>);
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>RecordAndSaveToResults</code></td>
<td><p>Records the execution time of the provided action, saves it under the given key, and returns the elapsed time in milliseconds.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> time = BenchmarkUtility.RecordAndSaveToResults(<span style="color:#D69D85;">&quot;MyBenchmark&quot;</span>, () =&gt;
{
<span style="color:#57A64A;">// Code to benchmark</span>
});
Console.WriteLine($<span style="color:#D69D85;">&quot;Elapsed time: {time} ms&quot;</span>);
<span style="color:#569CD6;">var</span> results = BenchmarkUtility.GetResults(<span style="color:#D69D85;">&quot;MyBenchmark&quot;</span>);
<span style="color:#569CD6;">var</span> averageTime = results.Average();
Console.WriteLine($<span style="color:#D69D85;">&quot;Average time: {averageTime} ms&quot;</span>);
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Start</code></td>
<td><p>Starts or restarts the stopwatch for the given key.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Stop</code></td>
<td><p>Stops the stopwatch for the given key and records the elapsed time.</p>
<br/>
</td>
</tr>
<tr>
<td><code>GetResults</code></td>
<td><p>Retrieves the list of recorded times for the given key.</p>
<br/>
</td>
</tr>
<tr>
<td><code>ClearResults</code></td>
<td><p>Clears all recorded benchmark results.</p>
<br/>
</td>
</tr>
<tr>
<td><code>GetAllResults</code></td>
<td><p>Retrieves all recorded benchmark results.</p>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">FloatExtensions</h4>
<small class="text-muted">OmegaLeo.HelperLib</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>FloatExtensions</code></td>
<td><p>Provides extension methods for the float data type.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Round</code></td>
<td><p>Rounds a float value to the specified number of decimal places.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> originalValue = <span style="color:#B5CEA8;">3</span>.14159f;
<span style="color:#569CD6;">var</span> roundedValue = originalValue.Round(<span style="color:#B5CEA8;">2</span>); <span style="color:#57A64A;">// roundedValue will be 3.14</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>RoundToInt</code></td>
<td><p>Rounds a float value to the nearest integer and returns it as an int.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> originalValue = <span style="color:#B5CEA8;">3</span>.14159f;
<span style="color:#569CD6;">var</span> roundedValue = originalValue.RoundToInt(); <span style="color:#57A64A;">// roundedValue will be 3</span>
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">IListExtensions</h4>
<small class="text-muted">OmegaLeo.HelperLib</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>IListExtensions</code></td>
<td><p>Extension methods for IList<T> to provide additional functionalities such as swapping, replacing, random selection, and shuffling.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Swap</code></td>
<td><p>Swaps the elements at the specified indices in the list.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> myList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt; { <span style="color:#B5CEA8;">1</span>, <span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">3</span>, <span style="color:#B5CEA8;">4</span> };
myList.Swap(<span style="color:#B5CEA8;">0</span>, <span style="color:#B5CEA8;">2</span>); <span style="color:#57A64A;">// myList is now { 3, 2, 1, 4 }</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Swap</code></td>
<td><p>Swaps the elements at the specified indices in the list.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> myList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt; { <span style="color:#B5CEA8;">1</span>, <span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">3</span>, <span style="color:#B5CEA8;">4</span> };
myList.Swap(<span style="color:#B5CEA8;">0</span>, <span style="color:#B5CEA8;">2</span>); <span style="color:#57A64A;">// myList is now { 3, 2, 1, 4 }</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Replace</code></td>
<td><p>Replaces the first occurrence of a specified value in the list with a new value.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> myList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt; { <span style="color:#B5CEA8;">1</span>, <span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">3</span>, <span style="color:#B5CEA8;">4</span> };
myList.Replace(<span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">5</span>); <span style="color:#57A64A;">// myList is now { 1, 5, 3, 4 }</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Random</code></td>
<td><p>Selects a random element from the list.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> myList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt; { <span style="color:#B5CEA8;">1</span>, <span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">3</span>, <span style="color:#B5CEA8;">4</span> };
<span style="color:#569CD6;">var</span> randomElement = myList.Random(); <span style="color:#57A64A;">// randomElement could be any of 1, 2, 3, or 4</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Random</code></td>
<td><p>Selects a specified number of unique random elements from the list.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> myList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt; { <span style="color:#B5CEA8;">1</span>, <span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">3</span>, <span style="color:#B5CEA8;">4</span> };
<span style="color:#569CD6;">var</span> randomElements = myList.Random(<span style="color:#B5CEA8;">2</span>); <span style="color:#57A64A;">// randomElements could be any two of 1, 2, 3, or 4</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Next&lt;T&gt;</code></td>
<td><p>Method to obtain the next object in the list by passing the current index.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> myList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt; { <span style="color:#B5CEA8;">1</span>, <span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">3</span>,<span style="color:#B5CEA8;">4</span> };
<span style="color:#569CD6;">var</span> currentIndex = <span style="color:#B5CEA8;">0</span>;
<span style="color:#569CD6;">var</span> nextItem = myList.Next(<span style="color:#569CD6;">ref</span> currentIndex); <span style="color:#57A64A;">// nextItem will be 1, currentIndex will be 1</span>
<span style="color:#569CD6;">var</span> nextItem2 = myList.Next(<span style="color:#569CD6;">ref</span> currentIndex); <span style="color:#57A64A;">// nextItem2 will be 2, currentIndex will be 2</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Shuffle&lt;T&gt;</code></td>
<td><p>Shuffles the elements of the list in place using a cryptographic random number generator.</p>
<p>Code obtained from https://stackoverflow.com/a/1262619</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> myList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt; { <span style="color:#B5CEA8;">1</span>, <span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">3</span>, <span style="color:#B5CEA8;">4</span> };
myList.Shuffle(); <span style="color:#57A64A;">// myList could now be { 3, 1, 4, 2 } (example output)</span>
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">InstancedObject`1</h4>
<small class="text-muted">OmegaLeo.HelperLib</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>InstancedObject&lt;T&gt; Class</code></td>
<td><p>A generic base class that ensures only one instance of a derived class exists at any time.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">public</span> <span style="color:#569CD6;">class</span> MySingleton : InstancedObject&lt;MySingleton&gt;
{
<span style="color:#57A64A;">// Your class implementation here</span>
}
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">MathExtensions</h4>
<small class="text-muted">OmegaLeo.HelperLib</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>MathExtensions</code></td>
<td><p>Provides extension methods for mathematical operations.</p>
<br/>
</td>
</tr>
<tr>
<td><code>AverageWithNullValidation</code></td>
<td><p>Calculates the average of a list of integers, returning 0 if the list is empty.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> numbers = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt; { <span style="color:#B5CEA8;">1</span>, <span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">3</span>, <span style="color:#B5CEA8;">4</span> };
<span style="color:#569CD6;">int</span> average = numbers.AverageWithNullValidation(); <span style="color:#57A64A;">// average will be 2 (rounded down)</span>
<span style="color:#569CD6;">var</span> emptyList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt;();
<span style="color:#569CD6;">int</span> averageEmpty = emptyList.AverageWithNullValidation(); <span style="color:#57A64A;">// averageEmpty will be 0</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>AverageWithNullValidation</code></td>
<td><p>Calculates the average of a list of doubles, returning 0.0 if the list is empty.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> numbers = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">double</span>&gt; { <span style="color:#B5CEA8;">1</span>.<span style="color:#B5CEA8;">5</span>, <span style="color:#B5CEA8;">2</span>.<span style="color:#B5CEA8;">5</span>, <span style="color:#B5CEA8;">3</span>.<span style="color:#B5CEA8;">5</span> };
<span style="color:#569CD6;">double</span> average = numbers.AverageWithNullValidation(); <span style="color:#57A64A;">// average will be 2.5</span>
<span style="color:#569CD6;">var</span> emptyList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">double</span>&gt;();
<span style="color:#569CD6;">double</span> averageEmpty = emptyList.AverageWithNullValidation(); <span style="color:#57A64A;">// averageEmpty will be 0.0</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>AverageWithNullValidation</code></td>
<td><p>Calculates the average of a list of floats, returning 0.0f if the list is empty.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> numbers = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">float</span>&gt; { <span style="color:#B5CEA8;">1</span>.5f, <span style="color:#B5CEA8;">2</span>.5f, <span style="color:#B5CEA8;">3</span>.5f };
<span style="color:#569CD6;">float</span> average = numbers.AverageWithNullValidation(); <span style="color:#57A64A;">// average will be 2.5f</span>
<span style="color:#569CD6;">var</span> emptyList = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">float</span>&gt;();
<span style="color:#569CD6;">float</span> averageEmpty = emptyList.AverageWithNullValidation(); <span style="color:#57A64A;">// averageEmpty will be 0.0f</span>
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">NeoDictionary`2</h4>
<small class="text-muted">OmegaLeo.HelperLib</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>NeoDictionary</code></td>
<td><p>Dictionary like class created to make it easier to display dictionaries in game engines like Unity</p>
<br/>
</td>
</tr>
<tr>
<td><code>TryGetValue</code></td>
<td><p>Tries to get the value from the NeoDictionary for the given key.</p>
<br/>
</td>
</tr>
<tr>
<td><code>TryGetValueFromIndex</code></td>
<td><p>Tries to get the value from the NeoDictionary at the given index.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Add</code></td>
<td><p>Adds a new NeoDictionaryItem to the NeoDictionary.</p>
<br/>
</td>
</tr>
<tr>
<td><code>AddRange</code></td>
<td><p>Adds a range of NeoDictionaryItems from another NeoDictionary to the NeoDictionary.</p>
<br/>
</td>
</tr>
<tr>
<td><code>AddRange</code></td>
<td><p>Adds a range of NeoDictionaryItems to the NeoDictionary.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Any</code></td>
<td><p>Checks if the NeoDictionary has any items.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Any</code></td>
<td><p>Checks if any item in the NeoDictionary satisfies a condition.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Where</code></td>
<td><p>Filters the NeoDictionary items based on a predicate.</p>
<br/>
</td>
</tr>
<tr>
<td><code>FirstOrDefault</code></td>
<td><p>Returns the first item of the NeoDictionary or a default value if the NeoDictionary is empty.</p>
<br/>
</td>
</tr>
<tr>
<td><code>FirstOrDefault</code></td>
<td><p>Returns the first item of the NeoDictionary that satisfies a condition or a default value if no such item is found.</p>
<br/>
</td>
</tr>
<tr>
<td><code>LastOrDefault</code></td>
<td><p>Returns the last item of the NeoDictionary or a default value if the NeoDictionary is empty.</p>
<br/>
</td>
</tr>
<tr>
<td><code>LastOrDefault</code></td>
<td><p>Returns the last item of the NeoDictionary that satisfies a condition or a default value if no such item is found.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Select</code></td>
<td><p>Projects each item of the NeoDictionary into a new form.</p>
<br/>
</td>
</tr>
<tr>
<td><code>IndexOf</code></td>
<td><p>Returns the index of the given NeoDictionaryItem.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Remove</code></td>
<td><p>Removes the item with the given key.</p>
<br/>
</td>
</tr>
<tr>
<td><code>RemoveAt</code></td>
<td><p>Removes the item at the given index.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Replace</code></td>
<td><p>Replaces the value for the given key if it exists.</p>
<br/>
</td>
</tr>
<tr>
<td><code>HasKey</code></td>
<td><p>Checks if the NeoDictionary contains the given key.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Clear</code></td>
<td><p>Clears all items from the NeoDictionary.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Sort</code></td>
<td><p>Sorts the NeoDictionary items using the given comparison.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Count</code></td>
<td><p>Returns the number of items in the NeoDictionary.</p>
<br/>
</td>
</tr>
<tr>
<td><code>ForEach</code></td>
<td><p>Performs the given action on each item in the NeoDictionary.</p>
<br/>
</td>
</tr>
<tr>
<td><code>ToList</code></td>
<td><p>Converts the NeoDictionary items to a List.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Clone</code></td>
<td><p>Creates a shallow copy of the NeoDictionary.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Reverse</code></td>
<td><p>Reverses the order of the items in the NeoDictionary.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Insert</code></td>
<td><p>Inserts an item at the given index.</p>
<br/>
</td>
</tr>
<tr>
<td><code>InsertRange</code></td>
<td><p>Inserts a range of items starting from the given index.</p>
<br/>
</td>
</tr>
<tr>
<td><code>RemoveRange</code></td>
<td><p>Removes a range of items starting from the given index.</p>
<br/>
</td>
</tr>
<tr>
<td><code>RemoveAll</code></td>
<td><p>Removes all items matching the given predicate.</p>
<br/>
</td>
</tr>
<tr>
<td><code>FindIndex</code></td>
<td><p>Finds the index of the first item matching the given predicate.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Find</code></td>
<td><p>Finds the first item matching the given predicate.</p>
<br/>
</td>
</tr>
<tr>
<td><code>FindAll</code></td>
<td><p>Finds all items matching the given predicate.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Merge</code></td>
<td><p>Merges another NeoDictionary into this one, ignoring duplicate keys.</p>
<br/>
</td>
</tr>
<tr>
<td><code>ToDictionarySafe</code></td>
<td><p>Converts the NeoDictionary to a standard Dictionary while safely handling duplicate keys by ignoring them.</p>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">StringExtensions</h4>
<small class="text-muted">OmegaLeo.HelperLib</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>StringExtensions</code></td>
<td><p>Provides extension methods for the string data type.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Truncate</code></td>
<td><p>Truncates the string to a specified maximum number of characters, appending '...' if truncation occurs.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> longString = <span style="color:#D69D85;">&quot;This is a very long string that needs to be truncated.&quot;</span>;
<span style="color:#569CD6;">var</span> truncatedString = longString.Truncate(<span style="color:#B5CEA8;">20</span>); <span style="color:#57A64A;">// truncatedString will be &quot;This is a very long...&quot;</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>IsNullOrEmpty</code></td>
<td><p>Checks if the string is null or empty.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">string</span> myString = <span style="color:#D69D85;">&quot;&quot;</span>;
<span style="color:#569CD6;">bool</span> isNullOrEmpty = myString.IsNullOrEmpty(); <span style="color:#57A64A;">// isNullOrEmpty will be true</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>IsNotNullOrEmpty</code></td>
<td><p>Checks if the string is not null or empty.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">string</span> myString = <span style="color:#D69D85;">&quot;Hello&quot;</span>;
<span style="color:#569CD6;">bool</span> isNotNullOrEmpty = myString.IsNotNullOrEmpty(); <span style="color:#57A64A;">// isNotNullOrEmpty will be true</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>AnyMatch</code></td>
<td><p>Checks if the string matches any of the provided search strings, ignoring case.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">string</span> myString = <span style="color:#D69D85;">&quot;Hello&quot;</span>;
<span style="color:#569CD6;">bool</span> matches = myString.AnyMatch(<span style="color:#D69D85;">&quot;hi&quot;</span>, <span style="color:#D69D85;">&quot;hello&quot;</span>, <span style="color:#D69D85;">&quot;greetings&quot;</span>); <span style="color:#57A64A;">// matches will be true</span>
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Reverse</code></td>
<td><p>Reverses the characters in the string.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">string</span> myString = <span style="color:#D69D85;">&quot;Hello&quot;</span>;
<span style="color:#569CD6;">string</span> reversedString = myString.Reverse(); <span style="color:#57A64A;">// reversedString will be &quot;olleH&quot;</span>
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div id="changelog" class="mb-5">
<h3>Changelog</h3>
<div class="card">
<div class="card-body">
<h2>Version 1.2.0</h2>
<h3>January 28, 2026</h3>
<p><strong>OmegaLeo.HelperLib.Extensions</strong></p>
<ul>
<li><strong>ArrayExtensions</strong>:
<ul>
<li>Fixed root namespace to OmegaLeo.HelperLib.Extensions.</li>
</ul>
</li>
<li><strong>FloatExtensions</strong>:
<ul>
<li>Fixed root namespace to OmegaLeo.HelperLib.Extensions.</li>
</ul>
</li>
<li><strong>IListExtensions</strong>:
<ul>
<li>Fixed root namespace to OmegaLeo.HelperLib.Extensions.</li>
</ul>
</li>
<li><strong>MathExtensions</strong>:
<ul>
<li>Fixed root namespace to OmegaLeo.HelperLib.Extensions.</li>
</ul>
</li>
<li><strong>StringExtensions</strong>:
<ul>
<li>Fixed root namespace to OmegaLeo.HelperLib.Extensions.</li>
</ul>
</li>
</ul>
<p><strong>OmegaLeo.HelperLib.Helpers</strong></p>
<ul>
<li><strong>BenchmarkUtility</strong>:
<ul>
<li>Fixed root namespace to OmegaLeo.HelperLib.Helpers.</li>
</ul>
</li>
</ul>
<p><strong>OmegaLeo.HelperLib.Models</strong></p>
<ul>
<li><strong>InstancedObject</strong>:
<ul>
<li>Fixed root namespace to OmegaLeo.HelperLib.Models.</li>
</ul>
</li>
<li><strong>NeoDictionary</strong>:
<ul>
<li>Fixed root namespace to OmegaLeo.HelperLib.Models.</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.sticky-top {
max-height: calc(100vh - 40px);
overflow-y: auto;
}
code {
background-color: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.9em;
}
.table th {
background-color: #f8f9fa;
}
</style>
</main>
<script src="../../lib/jquery/dist/jquery.min.js"></script>
<script src="../../lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../../js/site.js%3Fv=y_Ngd8W21UpuCUhEqGbUKDEqFnNfTjAfb24GhJJGUyM"></script>
</body>
</html>
-187
View File
@@ -1,187 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documentation Library - OmegaLeo Helper Libraries</title>
<link rel="stylesheet" href="../../lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="../../css/site.css%3Fv=AK2YraxbvzgrpO6A1NqAHflCP7fbweSjdyWpd3ERsLE.css" />
</head>
<body>
<header b-pqzonzw7gm>
<nav b-pqzonzw7gm class="navbar navbar-expand-sm navbar-dark bg-dark border-bottom box-shadow mb-3">
<div b-pqzonzw7gm class="container-fluid">
<a class="navbar-brand" href="../../index.html">
OmegaLeo Helper Libraries
</a>
<button b-pqzonzw7gm class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse">
<span b-pqzonzw7gm class="navbar-toggler-icon"></span>
</button>
<div b-pqzonzw7gm class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul b-pqzonzw7gm class="navbar-nav flex-grow-1">
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="../../index.html">Home</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="core.html">Core</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="changelog.html">Changelog</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="game.html">Game Dev</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="documentation.html">Documentation</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="shared.html">Shared Components</a>
</li>
</ul>
<ul b-pqzonzw7gm class="navbar-nav ms-auto">
<li b-pqzonzw7gm class="nav-item">
<a b-pqzonzw7gm class="nav-link" href="https://github.com/omegaleo/HelperLib" target="_blank" rel="noopener noreferrer" aria-label="GitHub repository">
<i b-pqzonzw7gm class="bi bi-github" style="font-size:1.25rem;"></i> View on Github
</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<main b-pqzonzw7gm role="main" class="pb-3">
<div class="container-fluid mt-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="../../index.html">Home</a></li>
<li class="breadcrumb-item active">Documentation Library</li>
</ol>
</nav>
<div class="row">
<div class="col-md-3">
<div class="sticky-top" style="top: 20px;">
<div class="card">
<div class="card-header">
<h5>Documentation Library</h5>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="documentation.html#overview">
<i class="bi bi-info-circle me-2"></i>Overview
</a>
<a class="list-group-item list-group-item-action" href="documentation.html#namespace-OmegaLeo-HelperLib-Documentation">
<i class="bi bi-folder me-2"></i>OmegaLeo.HelperLib.Documentation
</a>
<a class="list-group-item list-group-item-action" href="documentation.html#changelog">
<i class="bi bi-clock-history me-2"></i>Changelog
</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div id="overview" class="mb-5">
<h2>Documentation Library</h2>
<p class="lead">Tools for generating documentation from code attributes</p>
<hr />
</div>
<div id="namespace-OmegaLeo-HelperLib-Documentation" class="mb-5">
<h3>OmegaLeo.HelperLib.Documentation</h3>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">DocumentationHelperTool</h4>
<small class="text-muted">OmegaLeo.HelperLib.Documentation</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GenerateDocumentation (bool generateForPackageAssembly)</code></td>
<td><p>Generates a List of objects of type DocumentationStructure that contain the following fields:<br />
<strong>AssemblyName</strong>: Name of the main Assembly, used to identify the root namespace<br />
<strong>ClassName</strong>: Name of the class, used to identify the upper level object<br />
<strong>Title</strong>: Title what we're generating documentation for<br />
<strong>Description</strong>: Description of what we're generating documentation for, this can contain usage examples and can use the args array to pass names(e.g.: This method uses this methodology)<br />
<strong>Args</strong>: Array of strings that describe the parameters of the method or class<br />
<strong>CodeExample</strong>: A code example of how to use the method or class</p>
<p><em>Note: If generateForPackageAssembly is set to true, this will generate documentation for the library as well.</em></p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
DocumentationHelperTool.GenerateDocumentation(<span style="color:#569CD6;">true</span>); <span style="color:#57A64A;">// Generates documentation for all assemblies including the DocumentationHelper.Library package assembly</span>
DocumentationHelperTool.GenerateDocumentation(<span style="color:#569CD6;">false</span>); <span style="color:#57A64A;">// Generates documentation for all assemblies excluding the DocumentationHelper.Library package assembly</span>
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div id="changelog" class="mb-5">
<h3>Changelog</h3>
<div class="card">
<div class="card-body">
<h2>Version 1.2.0</h2>
<h3>January 26, 2026</h3>
<p><strong>OmegaLeo.HelperLib.Documentation.Helpers</strong></p>
<ul>
<li><strong>DocumentationHelperTool</strong>:
<ul>
<li>Migrated DocumentationHelper library from NetFlow.DocumentationHelper.Library to it's own package in OmegaLeo.HelperLib</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.sticky-top {
max-height: calc(100vh - 40px);
overflow-y: auto;
}
code {
background-color: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.9em;
}
.table th {
background-color: #f8f9fa;
}
</style>
</main>
<script src="../../lib/jquery/dist/jquery.min.js"></script>
<script src="../../lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../../js/site.js%3Fv=y_Ngd8W21UpuCUhEqGbUKDEqFnNfTjAfb24GhJJGUyM"></script>
</body>
</html>
-416
View File
@@ -1,416 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Game Development Library - OmegaLeo Helper Libraries</title>
<link rel="stylesheet" href="../../lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="../../css/site.css%3Fv=AK2YraxbvzgrpO6A1NqAHflCP7fbweSjdyWpd3ERsLE.css" />
</head>
<body>
<header b-pqzonzw7gm>
<nav b-pqzonzw7gm class="navbar navbar-expand-sm navbar-dark bg-dark border-bottom box-shadow mb-3">
<div b-pqzonzw7gm class="container-fluid">
<a class="navbar-brand" href="../../index.html">
OmegaLeo Helper Libraries
</a>
<button b-pqzonzw7gm class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse">
<span b-pqzonzw7gm class="navbar-toggler-icon"></span>
</button>
<div b-pqzonzw7gm class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul b-pqzonzw7gm class="navbar-nav flex-grow-1">
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="../../index.html">Home</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="core.html">Core</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="changelog.html">Changelog</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="game.html">Game Dev</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="documentation.html">Documentation</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="shared.html">Shared Components</a>
</li>
</ul>
<ul b-pqzonzw7gm class="navbar-nav ms-auto">
<li b-pqzonzw7gm class="nav-item">
<a b-pqzonzw7gm class="nav-link" href="https://github.com/omegaleo/HelperLib" target="_blank" rel="noopener noreferrer" aria-label="GitHub repository">
<i b-pqzonzw7gm class="bi bi-github" style="font-size:1.25rem;"></i> View on Github
</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<main b-pqzonzw7gm role="main" class="pb-3">
<div class="container-fluid mt-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="../../index.html">Home</a></li>
<li class="breadcrumb-item active">Game Development Library</li>
</ol>
</nav>
<div class="row">
<div class="col-md-3">
<div class="sticky-top" style="top: 20px;">
<div class="card">
<div class="card-header">
<h5>Game Development Library</h5>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="game.html#overview">
<i class="bi bi-info-circle me-2"></i>Overview
</a>
<a class="list-group-item list-group-item-action" href="game.html#namespace-OmegaLeo-HelperLib-Game">
<i class="bi bi-folder me-2"></i>OmegaLeo.HelperLib.Game
</a>
<a class="list-group-item list-group-item-action" href="game.html#changelog">
<i class="bi bi-clock-history me-2"></i>Changelog
</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div id="overview" class="mb-5">
<h2>Game Development Library</h2>
<p class="lead">Game development utilities for Unity and Godot</p>
<hr />
</div>
<div id="namespace-OmegaLeo-HelperLib-Game" class="mb-5">
<h3>OmegaLeo.HelperLib.Game</h3>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">CommandConsole</h4>
<small class="text-muted">OmegaLeo.HelperLib.Game</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>RegisterCommand</code></td>
<td><p>Registers a new command with the console.</p>
<br/>
</td>
</tr>
<tr>
<td><code>RegisterCommand</code></td>
<td><p>Registers a new command with the console using a delegate.</p>
<br/>
</td>
</tr>
<tr>
<td><code>UnregisterCommand</code></td>
<td><p>Removes a command from the console.</p>
<br/>
</td>
</tr>
<tr>
<td><code>ExecuteCommand</code></td>
<td><p>Executes a command string.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Output</code></td>
<td><p>Outputs a message to the console.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Error</code></td>
<td><p>Outputs an error message to the console.</p>
<br/>
</td>
</tr>
<tr>
<td><code>ClearHistory</code></td>
<td><p>Clears command history.</p>
<br/>
</td>
</tr>
<tr>
<td><code>GetAutoComplete</code></td>
<td><p>Gets autocomplete suggestions for partial input.</p>
<br/>
</td>
</tr>
<tr>
<td><code>RegisterDefaultCommands</code></td>
<td><p>Registers built-in commands.</p>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">DelayedActionHandler</h4>
<small class="text-muted">OmegaLeo.HelperLib.Game</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>DelayedActionHandler</code></td>
<td><p>Handler for managing delayed actions, allowing cancellation and status checking.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> action = <span style="color:#569CD6;">new</span> DelayedAction(...);
<span style="color:#569CD6;">var</span> handler = <span style="color:#569CD6;">new</span> DelayedActionHandler(action);
</pre></div>
</div>
</td>
</tr>
<tr>
<td><code>Cancel</code></td>
<td><p>Cancels the delayed action if it has not yet executed.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
handler.Cancel();
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">DelayedActionManager</h4>
<small class="text-muted">OmegaLeo.HelperLib.Game</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>PendingActionsCount</code></td>
<td><p>Gets the count of pending delayed actions.</p>
<br/>
</td>
</tr>
<tr>
<td><code>RunAfter</code></td>
<td><p>Schedules an action to run after a specified delay.</p>
<br/>
</td>
</tr>
<tr>
<td><code>RunRepeating</code></td>
<td><p>Schedules an action to run repeatedly at specified intervals.</p>
<br/>
</td>
</tr>
<tr>
<td><code>RunAfterCancellable</code></td>
<td><p>Schedules an action to run after a delay and returns a handler to cancel it if needed.</p>
<br/>
</td>
</tr>
<tr>
<td><code>CancelAll</code></td>
<td><p>Cancels all pending delayed actions.</p>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">ListExtensions</h4>
<small class="text-muted">OmegaLeo.HelperLib.Game</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Shuffle</code></td>
<td><p>Shuffles the elements of the list in place using the provided RandomNumberGenerator.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
<span style="color:#569CD6;">var</span> list = <span style="color:#569CD6;">new</span> List&lt;<span style="color:#569CD6;">int</span>&gt; { <span style="color:#B5CEA8;">1</span>, <span style="color:#B5CEA8;">2</span>, <span style="color:#B5CEA8;">3</span>, <span style="color:#B5CEA8;">4</span>};
<span style="color:#569CD6;">var</span> rng = <span style="color:#569CD6;">new</span> RandomNumberGenerator();
<span style="color:#57A64A;">// List before: [1, 2, 3, 4]</span>
list.Shuffle(rng);
<span style="color:#57A64A;">// List after: [3, 1, 4, 2] (example output)</span>
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">SaveManager</h4>
<small class="text-muted">OmegaLeo.HelperLib.Game</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Save</code></td>
<td><p>Saves data to a JSON file.</p>
<br/>
</td>
</tr>
<tr>
<td><code>Load</code></td>
<td><p>Loads data from a JSON file.</p>
<br/>
</td>
</tr>
<tr>
<td><code>TryLoad</code></td>
<td><p>Attempts to load data from a JSON file, returning a success flag.</p>
<br/>
</td>
</tr>
<tr>
<td><code>SaveExists</code></td>
<td><p>Checks if a save file exists.</p>
<br/>
</td>
</tr>
<tr>
<td><code>DeleteSave</code></td>
<td><p>Deletes a save file.</p>
<br/>
</td>
</tr>
<tr>
<td><code>GetAllSaveFiles</code></td>
<td><p>Retrieves all save files in the save directory.</p>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">WeightedPicker</h4>
<small class="text-muted">OmegaLeo.HelperLib.Game</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>WeightedPicker.Pick</code></td>
<td><p>Picks a random item from a dictionary of items with associated weights.</p>
<br/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.sticky-top {
max-height: calc(100vh - 40px);
overflow-y: auto;
}
code {
background-color: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.9em;
}
.table th {
background-color: #f8f9fa;
}
</style>
</main>
<script src="../../lib/jquery/dist/jquery.min.js"></script>
<script src="../../lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../../js/site.js%3Fv=y_Ngd8W21UpuCUhEqGbUKDEqFnNfTjAfb24GhJJGUyM"></script>
</body>
</html>
-235
View File
@@ -1,235 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Shared Library - OmegaLeo Helper Libraries</title>
<link rel="stylesheet" href="../../lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="../../css/site.css%3Fv=AK2YraxbvzgrpO6A1NqAHflCP7fbweSjdyWpd3ERsLE.css" />
</head>
<body>
<header b-pqzonzw7gm>
<nav b-pqzonzw7gm class="navbar navbar-expand-sm navbar-dark bg-dark border-bottom box-shadow mb-3">
<div b-pqzonzw7gm class="container-fluid">
<a class="navbar-brand" href="../../index.html">
OmegaLeo Helper Libraries
</a>
<button b-pqzonzw7gm class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse">
<span b-pqzonzw7gm class="navbar-toggler-icon"></span>
</button>
<div b-pqzonzw7gm class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul b-pqzonzw7gm class="navbar-nav flex-grow-1">
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="../../index.html">Home</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="core.html">Core</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="changelog.html">Changelog</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="game.html">Game Dev</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="documentation.html">Documentation</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="shared.html">Shared Components</a>
</li>
</ul>
<ul b-pqzonzw7gm class="navbar-nav ms-auto">
<li b-pqzonzw7gm class="nav-item">
<a b-pqzonzw7gm class="nav-link" href="https://github.com/omegaleo/HelperLib" target="_blank" rel="noopener noreferrer" aria-label="GitHub repository">
<i b-pqzonzw7gm class="bi bi-github" style="font-size:1.25rem;"></i> View on Github
</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<main b-pqzonzw7gm role="main" class="pb-3">
<div class="container-fluid mt-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="../../index.html">Home</a></li>
<li class="breadcrumb-item active">Shared Library</li>
</ol>
</nav>
<div class="row">
<div class="col-md-3">
<div class="sticky-top" style="top: 20px;">
<div class="card">
<div class="card-header">
<h5>Shared Library</h5>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="shared.html#overview">
<i class="bi bi-info-circle me-2"></i>Overview
</a>
<a class="list-group-item list-group-item-action" href="shared.html#namespace-OmegaLeo-HelperLib-Shared">
<i class="bi bi-folder me-2"></i>OmegaLeo.HelperLib.Shared
</a>
<a class="list-group-item list-group-item-action" href="shared.html#changelog">
<i class="bi bi-clock-history me-2"></i>Changelog
</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div id="overview" class="mb-5">
<h2>Shared Library</h2>
<p class="lead">Library of shared attributes and utilities used across other OmegaLeo.HelperLib packages</p>
<hr />
</div>
<div id="namespace-OmegaLeo-HelperLib-Shared" class="mb-5">
<h3>OmegaLeo.HelperLib.Shared</h3>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">ChangelogAttribute</h4>
<small class="text-muted">OmegaLeo.HelperLib.Shared</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ChangelogAttribute</code></td>
<td><p>Attribute to annotate classes, methods, or properties with changelog information.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
[Changelog(<span style="color:#D69D85;">&quot;1.0.0&quot;</span>, <span style="color:#D69D85;">&quot;Created ChangelogAttribute to be used to register changes in the code.&quot;</span>)]
<span style="color:#569CD6;">public</span> <span style="color:#569CD6;">class</span> ChangelogAttribute : Attribute
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h4 class="mb-0">DocumentationAttribute</h4>
<small class="text-muted">OmegaLeo.HelperLib.Shared</small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Member</th>
<th>Documentation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>DocumentationAttribute</code></td>
<td><p>Attribute to annotate classes, methods, or properties with documentation information.</p>
<br/>
<strong>Example:</strong><br/>
<div class="example-code mb-2">
<div style="color:#DADADA;background-color:#1E1E1E;"><pre>
[Documentation(
<span style="color:#D69D85;">&quot;MyMethod&quot;</span>,
<span style="color:#D69D85;">&quot;This method does something important.&quot;</span>,
<span style="color:#569CD6;">new</span> <span style="color:#569CD6;">string</span>[] { <span style="color:#D69D85;">&quot;param1: The first parameter.&quot;</span>, <span style="color:#D69D85;">&quot;param2: The second parameter.&quot;</span> },
<span style="color:#D69D85;">&quot;MyMethod(param1, param2);&quot;</span>
)]
<span style="color:#569CD6;">public</span> <span style="color:#569CD6;">void</span> MyMethod(<span style="color:#569CD6;">string</span> param1, <span style="color:#569CD6;">int</span> param2)
{
<span style="color:#57A64A;">// Method implementation</span>
}
</pre></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div id="changelog" class="mb-5">
<h3>Changelog</h3>
<div class="card">
<div class="card-body">
<h2>Version 1.2.1</h2>
<h3>January 28, 2026</h3>
<p><strong>OmegaLeo.HelperLib.Shared.Attributes</strong></p>
<ul>
<li><strong>ChangelogAttribute</strong>:
<ul>
<li>Created documentation for ChangelogAttribute.</li>
</ul>
</li>
<li><strong>DocumentationAttribute</strong>:
<ul>
<li>Created documentation for DocumentationAttribute.</li>
</ul>
</li>
</ul>
<h2>Version 1.0.0</h2>
<p><strong>OmegaLeo.HelperLib.Shared.Attributes</strong></p>
<ul>
<li><strong>ChangelogAttribute</strong>:
<ul>
<li>Created ChangelogAttribute to be used to register changes in the code.</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.sticky-top {
max-height: calc(100vh - 40px);
overflow-y: auto;
}
code {
background-color: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.9em;
}
.table th {
background-color: #f8f9fa;
}
</style>
</main>
<script src="../../lib/jquery/dist/jquery.min.js"></script>
<script src="../../lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../../js/site.js%3Fv=y_Ngd8W21UpuCUhEqGbUKDEqFnNfTjAfb24GhJJGUyM"></script>
</body>
</html>
@@ -1,40 +0,0 @@
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}
pre {
overflow: auto;
padding: 1rem;
background-color: #495057;
position: relative;
color: #f2f2f2;
}
code {
background-color: transparent !important;
}
.example-code > div {
border-radius: 0.5rem !important;
background-color: #495057 !important;
}
-177
View File
@@ -1,177 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OmegaLeo Helper Libraries - OmegaLeo Helper Libraries</title>
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="css/site.css%3Fv=AK2YraxbvzgrpO6A1NqAHflCP7fbweSjdyWpd3ERsLE.css" />
</head>
<body>
<header b-pqzonzw7gm>
<nav b-pqzonzw7gm class="navbar navbar-expand-sm navbar-dark bg-dark border-bottom box-shadow mb-3">
<div b-pqzonzw7gm class="container-fluid">
<a class="navbar-brand" href="index.html">
OmegaLeo Helper Libraries
</a>
<button b-pqzonzw7gm class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse">
<span b-pqzonzw7gm class="navbar-toggler-icon"></span>
</button>
<div b-pqzonzw7gm class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul b-pqzonzw7gm class="navbar-nav flex-grow-1">
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="index.html">Home</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="Home/Library/core.html">Core</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="Home/Library/changelog.html">Changelog</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="Home/Library/game.html">Game Dev</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="Home/Library/documentation.html">Documentation</a>
</li>
<li b-pqzonzw7gm class="nav-item">
<a class="nav-link" href="Home/Library/shared.html">Shared Components</a>
</li>
</ul>
<ul b-pqzonzw7gm class="navbar-nav ms-auto">
<li b-pqzonzw7gm class="nav-item">
<a b-pqzonzw7gm class="nav-link" href="https://github.com/omegaleo/HelperLib" target="_blank" rel="noopener noreferrer" aria-label="GitHub repository">
<i b-pqzonzw7gm class="bi bi-github" style="font-size:1.25rem;"></i> View on Github
</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<main b-pqzonzw7gm role="main" class="pb-3">
<div class="container mt-5">
<div class="text-center mb-5">
<h1 class="display-4">OmegaLeo Helper Libraries</h1>
<p class="lead">Comprehensive C# libraries for game development and general utilities</p>
</div>
<div class="row g-4">
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title">Core Library</h5>
<p class="card-text">Core utilities and helper functions for general C# development</p>
<p class="text-muted small">
8 classes documented
</p>
<a href="https://www.nuget.org/packages/OmegaLeo.HelperLib"
target="_blank"
class="btn btn-sm btn-outline-primary">
<i class="bi bi-box-seam"></i> View on NuGet
</a>
</div>
<div class="card-footer">
<a class="btn btn-primary w-100" href="Home/Library/core.html">
View Documentation
</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title">Changelog Library</h5>
<p class="card-text">Tools for managing and generating changelogs</p>
<p class="text-muted small">
2 classes documented
</p>
<a href="https://www.nuget.org/packages/OmegaLeo.HelperLib.Changelog"
target="_blank"
class="btn btn-sm btn-outline-primary">
<i class="bi bi-box-seam"></i> View on NuGet
</a>
</div>
<div class="card-footer">
<a class="btn btn-primary w-100" href="Home/Library/changelog.html">
View Documentation
</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title">Game Development Library</h5>
<p class="card-text">Game development utilities for Unity and Godot</p>
<p class="text-muted small">
6 classes documented
</p>
<a href="https://www.nuget.org/packages/OmegaLeo.HelperLib.Game"
target="_blank"
class="btn btn-sm btn-outline-primary">
<i class="bi bi-box-seam"></i> View on NuGet
</a>
</div>
<div class="card-footer">
<a class="btn btn-primary w-100" href="Home/Library/game.html">
View Documentation
</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title">Documentation Library</h5>
<p class="card-text">Tools for generating documentation from code attributes</p>
<p class="text-muted small">
1 classes documented
</p>
<a href="https://www.nuget.org/packages/OmegaLeo.HelperLib.Documentation"
target="_blank"
class="btn btn-sm btn-outline-primary">
<i class="bi bi-box-seam"></i> View on NuGet
</a>
</div>
<div class="card-footer">
<a class="btn btn-primary w-100" href="Home/Library/documentation.html">
View Documentation
</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title">Shared Library</h5>
<p class="card-text">Library of shared attributes and utilities used across other OmegaLeo.HelperLib packages</p>
<p class="text-muted small">
2 classes documented
</p>
<a href="https://www.nuget.org/packages/OmegaLeo.HelperLib.Shared"
target="_blank"
class="btn btn-sm btn-outline-primary">
<i class="bi bi-box-seam"></i> View on NuGet
</a>
</div>
<div class="card-footer">
<a class="btn btn-primary w-100" href="Home/Library/shared.html">
View Documentation
</a>
</div>
</div>
</div>
</div>
</div>
</main>
<script src="lib/jquery/dist/jquery.min.js"></script>
<script src="lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="js/site.js%3Fv=y_Ngd8W21UpuCUhEqGbUKDEqFnNfTjAfb24GhJJGUyM"></script>
</body>
</html>
@@ -1,4 +0,0 @@
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long