Skip to content
Open
26 changes: 19 additions & 7 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
# SecretAPI ![Downloads](https://img.shields.io/github/downloads/Misfiy/SecretAPI/total)
* SecretAPI is a plugin that extends [LabAPI](https://github.com/northwood-studios/LabAPI) by providing extra features to help devs.

# Dependencies
- SecretAPI relies on Harmony to implement features
- You can find the latest version [here](https://github.com/pardeike/Harmony/releases/latest)
- You should download Harmony-Fat and put the **0harmony.dll** (found in /net48) into your dependencies folder!

# Features
- CollectionExtensions: Extensions to provide utility for collections like lists and arrays.
- RoomExtensions: Extensions to help with room specific tasks, like checking if a room is safe to teleport to.
- HarmonyExtensions: Extensions to provide more utility to Harmony patching, like adding some updated Harmony features which can't be utilised, i.e. patching by category.
- CustomPlayerEffect: Create custom status effects using the base-game system.
- IRegister: Handle auto registering certain plugin features inheriting this interface and then running `IRegister.RegisterAll(Assembly)` in your plugin's initialise method.
- CallOnLoadAttribute: Allows calling static initialize methods on enable via ``CallOnLoadAttribute.Load(Assembly)``
- CustomSetting: Server Specific Settings without the management hassle, control everything for 1 setting in 1 class, including permissions. A better setting system overall.
- **CustomPlayerEffect**: Create custom status effects using the base-game system.
- BlastResistance: Grants 0.5% resistance to explosion damage
- Depleted: Disables stamina and its regen
- Energized: Disables stamina usage
- **IRegister**: Handle auto registering certain plugin features inheriting this interface and then running `IRegister.RegisterAll(Assembly)` in your plugin's initialize method.
- **CallOnLoadAttribute**: Allows calling static initialize methods on enable via ``CallOnLoadAttribute.Load(Assembly)``
- **CustomSetting**: Server Specific Settings without the management hassle, control everything for 1 setting in 1 class, including permissions. A better setting system overall.
- **PrefabStore** and **PrefabManager**: Designed to help access prefabs in a clean way
- Extensions:
- CodeMatcherExtensions: Extensions related to ``HarmonyLib.CodeMatcher``.
- CollectionExtensions: Extensions to provide utility for collections like lists and arrays.
- MirrorExtensions: Extensions providing help with fake-syncing ``SyncVar``, ``SyncList`` and ``Rpc``
- ReflectionExtensions: Extensions related to C# Reflection
- RoomExtensions: Extensions to help with room specific tasks, like checking if a room is safe to teleport to.

# Examples
You can find examples inside the `SecretAPI.Examples` folder above, this contains some example settings, patches using categories and some more.
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<Version>3.5.0-beta1</Version>
<Version>3.5.0-beta.3</Version> <!-- BetaX = Incorrect (3.5.0-beta3) | Beta.X = Correct (3.5.0-beta.3) -->
</PropertyGroup>

<PropertyGroup Label="Nuget">
Expand Down
18 changes: 15 additions & 3 deletions SecretAPI.Examples/ExampleEntry.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
namespace SecretAPI.Examples;
/*
Temp until 4.0
*/
#pragma warning disable SA1200
global using HarmonyPatchCategoryAttribute = HarmonyLib.HarmonyPatchCategory;
#pragma warning restore SA1200

namespace SecretAPI.Examples;

using System;
using System.Reflection;
using HarmonyLib;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
using SecretAPI.Examples.Settings;
using SecretAPI.Extensions;
using SecretAPI.Features.UserSettings;
using UserSettings.ServerSpecific;

Expand All @@ -31,11 +38,16 @@ public class ExampleEntry : Plugin
/// <inheritdoc/>
public override Version RequiredApiVersion { get; } = new(LabApi.Features.LabApiProperties.CompiledVersion);

/// <summary>
/// Gets the <see cref="Assembly"/> of SecretAPI.Examples.
/// </summary>
internal Assembly Assembly { get; } = typeof(ExampleEntry).Assembly;

/// <inheritdoc/>
public override void Enable()
{
CustomSetting.Register(new ExampleKeybindSetting(), new ExampleDropdownSetting(), new ExampleButtonSetting(), new ExampleFakeSyncButton());
harmony.PatchCategory(nameof(ExampleFakeSyncButton));
harmony.PatchCategory(Assembly, nameof(ExampleFakeSyncButton));
}

/// <inheritdoc/>
Expand Down
8 changes: 3 additions & 5 deletions SecretAPI.Examples/Patches/ExampleAmmoPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@ private static void Prefix()

// gets called after the original method is called
// We grab the method params of ammoType and player, the names must be correct
// ref __result will become a reference to the return value
#pragma warning disable SA1313
private static void Postfix(ItemType ammoType, ReferenceHub player, ref ushort __result)
#pragma warning restore SA1313
// ref result will become a reference to the return value
private static void Postfix(ItemType ammoType, ReferenceHub player, [HarmonyArgument("__result")] ref ushort result)
{
// make sure we don't modify the max ammo if its not the correct ammo type or the player hasn't been fake synced
if (ammoType != ExampleFakeSyncButton.AmmoFakeSync || !ExampleFakeSyncButton.FakeSyncs.TryGetValue(player, out ushort sync))
return;

__result = sync;
result = sync;
}
}
5 changes: 1 addition & 4 deletions SecretAPI.Examples/SecretAPI.Examples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@

<ItemGroup>
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.2" PrivateAssets="all" />
<PackageReference Include="Lib.Harmony" Version="2.2.2" />
<!--
<PackageReference Include="Northwood.LabAPI" Version="1.1.4" IncludeAssets="All" PrivateAssets="All" />
-->
<PackageReference Include="Lib.Harmony" Version="2.4.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" IncludeAssets="All" PrivateAssets="All" />
</ItemGroup>

Expand Down
4 changes: 3 additions & 1 deletion SecretAPI/Attributes/HarmonyPatchCategory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
/// Category handling for <see cref="HarmonyExtensions"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class HarmonyPatchCategory : Attribute
[Obsolete("Please use HarmonyLib.HarmonyPatchCategoryAttribute - This will be removed in 4.0")]
public class HarmonyPatchCategory : HarmonyLib.HarmonyPatchCategory
{
/// <summary>
/// Initializes a new instance of the <see cref="HarmonyPatchCategory"/> class.
/// </summary>
/// <param name="category">The category of the patch.</param>
public HarmonyPatchCategory(string category)
: base(category)
{
Category = category;
}
Expand Down
12 changes: 12 additions & 0 deletions SecretAPI/Config.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace SecretAPI;

/// <summary>
/// Handles config for SecretAPI.
/// </summary>
public sealed class Config
{
/// <summary>
/// Gets or sets a value indicating whether PatchClassProcessor.ReportException should be edited to log instead of throwing exception.
/// </summary>
public bool LogHarmonyReplaceThrow { get; set; } = false;
}
11 changes: 4 additions & 7 deletions SecretAPI/Extensions/CodeMatcherExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,8 @@ public static class CodeMatcherExtensions
/// <param name="localType">The <see cref="Type"/> of the local to declare.</param>
/// <param name="localBuilder">The <see cref="LocalBuilder"/> declared.</param>
/// <returns>The current <see cref="CodeMatcher"/>.</returns>
public CodeMatcher DeclareLocal(Type localType, out LocalBuilder localBuilder)
{
localBuilder = matcher.generator.DeclareLocal(localType);
return matcher;
}
[Obsolete("Use CodeMatcher::DeclareLocal(Type, out LocalBuilder)")]
public CodeMatcher DeclareLocal(Type localType, out LocalBuilder localBuilder) => matcher.DeclareLocal(localType, out localBuilder);

/// <summary>
/// Declares a local to be used of a certain <see cref="Type"/>.
Expand All @@ -39,7 +36,7 @@ public CodeMatcher DeclareLocal(Type localType, out LocalBuilder localBuilder)
/// <returns>The current <see cref="CodeMatcher"/>.</returns>
public CodeMatcher DeclareLocal(Type localType, out int localIndex)
{
DeclareLocal(matcher, localType, out LocalBuilder builder);
matcher.DeclareLocal(localType, out LocalBuilder builder);
localIndex = builder.LocalIndex;
return matcher;
}
Expand All @@ -63,7 +60,7 @@ public CodeMatcher GetFirstLabel(out Label label)
/// <returns>The current <see cref="CodeMatcher"/>.</returns>
public CodeMatcher GetFirstLabelAt(int position, out Label label)
{
label = matcher.codes[position].labels.FirstOrDefault();
label = matcher.Instructions()[position].labels.FirstOrDefault();
return matcher;
}
}
Expand Down
17 changes: 11 additions & 6 deletions SecretAPI/Extensions/HarmonyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,43 @@ public static class HarmonyExtensions
/// </summary>
/// <param name="category">The category to patch.</param>
/// <param name="assembly">The assembly to find patches in.</param>
[Obsolete("Use Harmony::PatchCategory(Assembly, string)")]
public void PatchCategory(string category, Assembly? assembly = null)
{
assembly ??= Assembly.GetCallingAssembly();
harmony.PatchCategory(assembly, category);

assembly.GetTypes().Where(type =>
/*assembly.GetTypes().Where(type =>
{
IEnumerable<HarmonyPatchCategory> categories = type.GetCustomAttributes<HarmonyPatchCategory>();
return categories.Any(c => c.Category == category);
return categories.Any(c => c.info.category == category);
})
.Do(type => SafePatch(harmony, type));
.Do(type => SafePatch(harmony, type));*/
}

/// <summary>
/// Patches all patches that don't have a <see cref="HarmonyPatchCategory"/>.
/// </summary>
/// <param name="assembly">The assembly to look for patches.</param>
[Obsolete("Use Harmony::PatchAllUncategorized(Assembly)")]
public void PatchAllNoCategory(Assembly? assembly = null)
{
assembly ??= Assembly.GetCallingAssembly();
harmony.PatchAllUncategorized(assembly);

assembly.GetTypes().Where(type =>
/*assembly.GetTypes().Where(type =>
{
IEnumerable<HarmonyPatchCategory> categories = type.GetCustomAttributes<HarmonyPatchCategory>();
return !categories.Any();
})
.Do(type => SafePatch(harmony, type));
.Do(type => SafePatch(harmony, type));*/
}

/// <summary>
/// Attempts to safely patch a <see cref="Type"/>, logging any errors.
/// </summary>
/// <param name="type">The <see cref="Type"/> to attempt to patch.</param>
[Obsolete]
public void SafePatch(Type type)
{
try
Expand All @@ -61,7 +66,7 @@ public void SafePatch(Type type)
}
catch (Exception ex)
{
Logger.Error($"[HarmonyExtensions] failed to safely patch {harmony.Id} ({type.FullName}): {ex}");
Logger.Error($"[HarmonyExtensions] Failed to safely patch {harmony.Id} ({type.FullName}): {ex}");
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions SecretAPI/Features/UserSettings/CustomSetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using LabApi.Features.Wrappers;
using Mirror;
using NorthwoodLib.Pools;
using SecretAPI.Extensions;

/// <summary>
/// Wraps <see cref="ServerSpecificSettingBase"/>.
Expand All @@ -23,7 +22,7 @@ public abstract class CustomSetting : ISetting<ServerSpecificSettingBase>

static CustomSetting()
{
SecretApi.Harmony.PatchCategory(nameof(CustomSetting), SecretApi.Assembly);
SecretApi.Harmony.PatchCategory(SecretApi.Assembly, nameof(CustomSetting));

ServerSpecificSettingsSync.SendOnJoinFilter = null;
ServerSpecificSettingsSync.DefinedSettings ??= []; // fix null ref
Expand Down
37 changes: 37 additions & 0 deletions SecretAPI/Patches/Features/HarmonyLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace SecretAPI.Patches.Features;

using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using HarmonyLib;
using LabApi.Features.Console;

/// <summary>
/// Changes <see cref="PatchClassProcessor"/>'s ReportException to log instead of throwing the exception.
/// </summary>
[HarmonyPatch(typeof(PatchClassProcessor), "ReportException")]
internal static class HarmonyLogger
{
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
CodeMatcher matcher = new(instructions);

while (matcher.MatchEndForward(new CodeMatch(OpCodes.Throw)).IsValid)
{
CodeInstruction callInstruction = CodeInstruction.Call(typeof(HarmonyLogger), nameof(Log));
callInstruction.labels.AddRange(matcher.Labels);
callInstruction.blocks.AddRange(matcher.Blocks);

matcher.SetInstruction(callInstruction);
matcher.InsertAfter(new CodeInstruction(OpCodes.Ret));
matcher.Advance();
}

return matcher.InstructionEnumeration();
}

private static void Log(Exception exception)
{
Logger.Error($"Prevented Harmony Exception: {exception.Message}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@
[HarmonyPatch(typeof(ServerSpecificSettingBase), nameof(ServerSpecificSettingBase.OriginalDefinition), MethodType.Getter)]
internal static class SettingsOriginalDefinitionFix
{
#pragma warning disable SA1313 // Parameter '__result' should begin with lower-case letter
private static void Postfix(ServerSpecificSettingBase __instance, ref ServerSpecificSettingBase __result)
#pragma warning restore SA1313
private static void Postfix([HarmonyArgument("__instance")] ServerSpecificSettingBase instance, [HarmonyArgument("__result")] ref ServerSpecificSettingBase result)
{
// Prevent handling non SecretAPI settings.
if (__result != null)
if (result != null)
return;

__result = CustomSetting.Get(__instance.GetType(), __instance.SettingId)?.Base ?? null!;
result = CustomSetting.Get(instance.GetType(), instance.SettingId)?.Base ?? null!;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@
[HarmonyPatch(typeof(ServerSpecificSettingsSync), nameof(ServerSpecificSettingsSync.ServerPrevalidateClientResponse))]
internal static class SettingsSyncValidateFix
{
#pragma warning disable SA1313 // Parameter '__result' should begin with lower-case letter
private static void Postfix(SSSClientResponse msg, ref bool __result)
#pragma warning restore SA1313
private static void Postfix(SSSClientResponse msg, [HarmonyArgument("__result")] ref bool result)
{
// prevent overriding already validated settings
if (__result)
if (result)
return;

__result = CustomSetting.Get(msg.SettingType, msg.Id) != null;
result = CustomSetting.Get(msg.SettingType, msg.Id) != null;
}
}
12 changes: 7 additions & 5 deletions SecretAPI/SecretAPI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<TargetFramework>net48</TargetFramework>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
<ImplicitUsings>false</ImplicitUsings>
</PropertyGroup>

<PropertyGroup Label="Nuget">
Expand All @@ -23,10 +25,7 @@

<ItemGroup>
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.2" PrivateAssets="all" />
<!--
<PackageReference Include="Northwood.LabAPI" Version="1.1.4" IncludeAssets="All" PrivateAssets="All" />
-->
<PackageReference Include="Lib.Harmony" Version="2.2.2" Publicize="true" />
<PackageReference Include="Lib.Harmony" Version="2.4.2"/> <!-- Do not publicize Harmony as it will publicize internal monomod references -->
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" IncludeAssets="All" PrivateAssets="All" />
</ItemGroup>

Expand All @@ -40,11 +39,14 @@
<Reference Include="Assembly-CSharp-firstpass" />
<Reference Include="Pooling" />
<Reference Include="Mirror" />
<Reference Include="mscorlib" />
<Reference Include="UnityEngine" />
<Reference Include="UnityEngine.CoreModule" />
<Reference Include="UnityEngine.PhysicsModule" />
<Reference Include="Unity.TextMeshPro" />

<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>

</Project>
Loading
Loading