pardeike/Harmony · error · Exception

Parameter does not contain a valid index

Error message

Parameter {paramRealName} does not contain a valid index

What it means

A patch parameter uses index-based argument binding (name starting with PARAM_INDEX_PREFIX, e.g. `__0`). The text after the prefix must parse as an int; Harmony throws this Exception when int.TryParse fails. It indicates a malformed index-based parameter name in the patch signature.

Solutions

  1. Use a plain decimal index after the prefix, e.g. `__0`, `__1`.
  2. Bind by original parameter name instead of index to avoid format issues.
  3. Sanitize generated patch parameter names to ensure the suffix after the prefix is digits only.

Example fix

// before
static void Postfix(int __first) { ... }

// after
static void Postfix(int __0) { ... }
Defensive patterns

Strategy: validation

Validate before calling

if (name.StartsWith("__") && !int.TryParse(name.Substring(2), out _))
    throw new ArgumentException($"Bad index parameter name: {name}");

Type guard

bool IsWellFormedIndexParam(string name) =>
    name.StartsWith("__") && int.TryParse(name.Substring(2), out var i) && i >= 0;

Try / catch

try { harmony.Patch(original, postfix: new HarmonyMethod(fix)); }
catch (Exception ex) when (ex.Message.Contains("does not contain a valid index")) { log.Error(ex); }

Prevention

When it happens

Trigger: Writing an index-prefixed patch parameter whose suffix is not a valid integer, e.g. `__abc`, `__0x1`, or a name where the prefix collides with a user parameter name that happens to start with the prefix.

Common situations: Typos in hand-written patch signatures; code generation emitting malformed names; accidentally prefixing a normal parameter name with the index marker.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of pardeike/Harmony@e7872dc170 (2026-09-15). Data as JSON: /api/errors/0039ad430c7b7111. Report an issue: GitHub.

Appendix: source

Thrown at Harmony/Internal/MethodCreatorTools.cs:384

				if (injection.argumentMode != ArgumentMode.Original && config.localVariables.TryGetValue(paramRealName, out var localBuilder))
				{
					var ldlocCode = paramType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc;
					codes.Add(new CodeInstruction(ldlocCode, localBuilder));
					continue;
				}

				int argumentIdx;
				if (injection.argumentMode == ArgumentMode.Original)
				{
					argumentIdx = Array.IndexOf(originalParameterNames, paramRealName);
					if (argumentIdx == -1)
						throw new Exception($"Parameter \"{paramRealName}\" not found in method {original.FullDescription()}");
				}
				else if (paramRealName.StartsWith(PARAM_INDEX_PREFIX, StringComparison.Ordinal))
				{
					var val = paramRealName.Substring(PARAM_INDEX_PREFIX.Length);
					if (!int.TryParse(val, out argumentIdx))
						throw new Exception($"Parameter {paramRealName} does not contain a valid index");
					if (argumentIdx < 0 || argumentIdx >= originalParameters.Length)
						throw new Exception($"No parameter found at index {argumentIdx}");
				}
				else
				{
					argumentIdx = patch.GetArgumentIndex(originalParameterNames, injection.parameterInfo);
					if (argumentIdx == -1)
					{
						var harmonyMethod = HarmonyMethodExtensions.GetMergedFromType(paramType);
						harmonyMethod.methodType ??= MethodType.Normal;
						var delegateOriginal = harmonyMethod.GetOriginalMethod();
						if (delegateOriginal is MethodInfo methodInfo)
						{
							var delegateConstructor = paramType.GetConstructor([typeof(object), typeof(IntPtr)]);
							if (delegateConstructor is not null)
							{
								if (methodInfo.IsStatic)
									codes.Add(Ldnull);

View on GitHub (pinned to e7872dc170)