SboxSuperuser/Core/Prefixes/PrefixStyleText.cs

Parses the text form of an effect style used in commands ("preset:", "code:", a plain one-line script, or "b64:" plus a base64 script) and converts a style back into chunked command arguments. Encoded scripts are split into 256-character pieces so multi-line scripts fit the argument limit, with a cap of 13 chunks.

using System.Text;
using SboxSuperuser.Core.Effects;

namespace SboxSuperuser.Core.Prefixes;

/// <summary>
/// The text form of a style in commands: "preset:rain", "code:game.fire", a one-line script typed in the console,
/// or "b64:" plus a base64 script, which is how the menu sends multi-line scripts through 256-character arguments.
/// </summary>
internal static class PrefixStyleText
{
	internal const int ChunkLength = 256;
	internal const int MaximumChunks = 13;
	private const string Base64 = "b64:";

	internal static PrefixStyle Parse( string text )
	{
		text = (text ?? "").Trim();
		if ( text.StartsWith( "preset:", StringComparison.Ordinal ) ) return PrefixStyle.Create( PrefixStyleKind.Preset, text.Substring( 7 ) );
		if ( text.StartsWith( "code:", StringComparison.Ordinal ) ) return PrefixStyle.Create( PrefixStyleKind.Code, text.Substring( 5 ) );
		if ( !text.StartsWith( Base64, StringComparison.Ordinal ) ) return PrefixStyle.Create( PrefixStyleKind.Script, text );
		try
		{
			// Arguments are joined with spaces, which base64 never contains, so dropping them restores the payload.
			var payload = text.Substring( Base64.Length ).Replace( " ", "" );
			return PrefixStyle.Create( PrefixStyleKind.Script, Encoding.UTF8.GetString( Convert.FromBase64String( payload ) ) );
		}
		catch ( FormatException )
		{
			throw new ArgumentException( "The encoded effect script is damaged.", nameof( text ) );
		}
	}

	/// <summary>Command arguments that carry a style; throws when a script is too long to send.</summary>
	internal static IReadOnlyList<string> Arguments( PrefixStyle style )
	{
		switch ( style.Kind )
		{
			case PrefixStyleKind.Preset: return new[] { "preset:" + style.Value };
			case PrefixStyleKind.Code: return new[] { "code:" + style.Value };
		}
		if ( style.Value.Length > EffectProgram.MaximumSourceLength )
			throw new ArgumentException( $"Effect scripts can be at most {EffectProgram.MaximumSourceLength} characters." );
		var encoded = Base64 + Convert.ToBase64String( Encoding.UTF8.GetBytes( style.Value ) );
		var chunks = Enumerable.Range( 0, (encoded.Length + ChunkLength - 1) / ChunkLength )
			.Select( index => encoded.Substring( index * ChunkLength, Math.Min( ChunkLength, encoded.Length - index * ChunkLength ) ) ).ToArray();
		if ( chunks.Length > MaximumChunks ) throw new ArgumentException( "This effect is too long to send; shorten it or use fewer special characters." );
		return chunks;
	}
}