using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using dnlib.DotNet.Writer;
using dnlib.IO;
using dnlib.Utils;

namespace Crysome.Obfuscator;

public static class CryObfuscator
{
	private static readonly Random _rnd = new Random();

	private static readonly char[] _nameChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();

	public static void Obfuscate(string inputPath, string outputPath, Action<string> log = null)
	{
		//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01be: Expected O, but got Unknown
		if (log == null)
		{
			log = delegate
			{
			};
		}
		if (string.IsNullOrEmpty(inputPath) || !File.Exists(inputPath))
		{
			log("ERROR: Input file not found: " + inputPath);
			throw new FileNotFoundException("Input file not found", inputPath);
		}
		string text = Path.GetDirectoryName(Path.GetFullPath(inputPath)) ?? ".";
		ModuleContext val = ModuleDef.CreateModuleContext();
		string[] array = new string[2]
		{
			text,
			Path.Combine(text, "..")
		};
		foreach (string text2 in array)
		{
			if (!string.IsNullOrEmpty(text2) && Directory.Exists(text2))
			{
				try
				{
					((object)val.AssemblyResolver).GetType().GetMethod("AddSearchDirectory", new Type[1] { typeof(string) })?.Invoke(val.AssemblyResolver, new object[1] { Path.GetFullPath(text2) });
				}
				catch
				{
				}
			}
		}
		try
		{
			ModuleDefMD val2 = ModuleDefMD.Load(inputPath, val);
			try
			{
				log("Obfuscating: " + inputPath);
				RenameObfuscation((ModuleDef)(object)val2, log);
				try
				{
					StringEncryption((ModuleDef)(object)val2, log);
				}
				catch (Exception ex)
				{
					log("String encryption skipped: " + ex.Message);
				}
				try
				{
					ControlFlowObfuscation((ModuleDef)(object)val2, log);
				}
				catch (Exception ex2)
				{
					log("Control flow skipped: " + ex2.Message);
				}
				try
				{
					AddJunkNops((ModuleDef)(object)val2, log);
				}
				catch (Exception ex3)
				{
					log("Junk NOPs skipped: " + ex3.Message);
				}
				try
				{
					ObfuscateCosturaEmbeddedAssemblies((ModuleDef)(object)val2, val, log);
				}
				catch (Exception ex4)
				{
					log("Costura embedded obfuscation skipped: " + ex4.Message);
				}
				ModuleWriterOptions val3 = new ModuleWriterOptions((ModuleDef)(object)val2);
				((ModuleDef)val2).Write(outputPath, val3);
				log("Written: " + outputPath);
			}
			finally
			{
				((IDisposable)val2)?.Dispose();
			}
		}
		catch (Exception ex5)
		{
			log("Obfuscation error: " + ex5.Message);
			if (ex5.InnerException != null)
			{
				log("  Inner: " + ex5.InnerException.Message);
			}
			throw;
		}
	}

	private static string RandomName(int len = 12)
	{
		StringBuilder stringBuilder = new StringBuilder(len);
		for (int i = 0; i < len; i++)
		{
			stringBuilder.Append(_nameChars[_rnd.Next(_nameChars.Length)]);
		}
		return stringBuilder.ToString();
	}

	private static bool ShouldRename(ITypeDefOrRef type)
	{
		if (type == null)
		{
			return false;
		}
		string text = ((IFullName)type).FullName ?? "";
		if (text.StartsWith("System.") || text.StartsWith("Microsoft.") || text.StartsWith("dnlib."))
		{
			return false;
		}
		if (text.StartsWith("<") || text.Contains("AssemblyInfo"))
		{
			return false;
		}
		if (text.IndexOf("Costura", StringComparison.OrdinalIgnoreCase) >= 0)
		{
			return false;
		}
		if (text.IndexOf("Loader", StringComparison.OrdinalIgnoreCase) >= 0 && text.IndexOf("Costura", StringComparison.OrdinalIgnoreCase) >= 0)
		{
			return false;
		}
		return true;
	}

	private static void RenameObfuscation(ModuleDef mod, Action<string> log)
	{
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		TypeDef[] array = mod.GetTypes().ToArray();
		TypeDef[] array2 = array;
		foreach (TypeDef val in array2)
		{
			if (!(val.Namespace == "<Module>") && !val.IsGlobalModuleType && ShouldRename((ITypeDefOrRef)(object)val))
			{
				try
				{
					val.Name = (UTF8String)(RandomName(14));
					num++;
				}
				catch
				{
				}
			}
		}
		array2 = array;
		foreach (TypeDef val2 in array2)
		{
			MethodDef[] array3 = val2.Methods.ToArray();
			foreach (MethodDef val3 in array3)
			{
				if (val3.IsConstructor || (val3.Name == "Main" && val3.IsStatic))
				{
					continue;
				}
				TypeDef declaringType = val3.DeclaringType;
				if ((declaringType == null || !declaringType.IsGlobalModuleType) && !val3.IsNative && (!val3.IsVirtual || val3.Overrides.Count <= 0) && ShouldRename((ITypeDefOrRef)(object)val3.DeclaringType))
				{
					try
					{
						val3.Name = (UTF8String)(RandomName(10));
						num2++;
					}
					catch
					{
					}
				}
			}
			FieldDef[] array4 = val2.Fields.ToArray();
			foreach (FieldDef val4 in array4)
			{
				if (!val4.IsLiteral && ShouldRename((ITypeDefOrRef)(object)val4.DeclaringType))
				{
					try
					{
						val4.Name = (UTF8String)(RandomName(10));
						num3++;
					}
					catch
					{
					}
				}
			}
		}
		log($"Rename: {num} types, {num2} methods, {num3} fields");
		if (num == 0 && num2 == 0)
		{
			log("WARNING: No symbols renamed - check if stub is correct .NET assembly.");
		}
	}

	private static void StringEncryption(ModuleDef mod, Action<string> log)
	{
		MethodDef val = CreateDecryptHelper(mod);
		if (val == null)
		{
			log("String encryption: skipped");
			return;
		}
		int num = 0;
		TypeDef[] array = mod.Types.ToArray();
		for (int i = 0; i < array.Length; i++)
		{
			MethodDef[] array2 = array[i].Methods.ToArray();
			foreach (MethodDef val2 in array2)
			{
				if (!val2.HasBody || !val2.Body.HasInstructions)
				{
					continue;
				}
				IList<Instruction> instructions = val2.Body.Instructions;
				for (int k = 0; k < instructions.Count; k++)
				{
					if (instructions[k].OpCode != OpCodes.Ldstr)
					{
						continue;
					}
					string text = instructions[k].Operand as string;
					if (string.IsNullOrEmpty(text) || text.Length > 500 || text.IndexOf("CRYCFG", StringComparison.Ordinal) >= 0)
					{
						continue;
					}
					try
					{
						var (array3, num2) = XorEncrypt(text);
						instructions.RemoveAt(k);
						int num3 = k;
						instructions.Insert(num3++, Instruction.Create(OpCodes.Ldc_I4, array3.Length));
						instructions.Insert(num3++, Instruction.Create(OpCodes.Newarr, ((TypeDefOrRefSig)mod.CorLibTypes.Byte).TypeDefOrRef));
						for (int l = 0; l < array3.Length; l++)
						{
							instructions.Insert(num3++, Instruction.Create(OpCodes.Dup));
							instructions.Insert(num3++, Instruction.Create(OpCodes.Ldc_I4, l));
							instructions.Insert(num3++, Instruction.Create(OpCodes.Ldc_I4, (int)array3[l]));
							instructions.Insert(num3++, Instruction.Create(OpCodes.Stelem_I1));
						}
						instructions.Insert(num3++, Instruction.Create(OpCodes.Ldc_I4, num2));
						instructions.Insert(num3++, Instruction.Create(OpCodes.Call, (IMethod)(object)val));
						num++;
					}
					catch
					{
						break;
					}
				}
			}
		}
		log($"String encryption: {num} strings");
	}

	private static (byte[] enc, int key) XorEncrypt(string s)
	{
		int num = _rnd.Next(1, 255);
		byte[] bytes = Encoding.UTF8.GetBytes(s);
		byte[] array = new byte[bytes.Length];
		for (int i = 0; i < bytes.Length; i++)
		{
			array[i] = (byte)(bytes[i] ^ num);
		}
		return (enc: array, key: num);
	}

	private static MethodDef CreateDecryptHelper(ModuleDef mod)
	{
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00de: Expected O, but got Unknown
		//IL_0111: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Expected O, but got Unknown
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_0142: Expected O, but got Unknown
		//IL_0154: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Expected O, but got Unknown
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0165: Expected O, but got Unknown
		//IL_04cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_04d7: Expected O, but got Unknown
		//IL_04d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_04dc: Expected O, but got Unknown
		//IL_04ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_04f9: Expected O, but got Unknown
		//IL_050c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0516: Expected O, but got Unknown
		//IL_0511: Unknown result type (might be due to invalid IL or missing references)
		//IL_051b: Expected O, but got Unknown
		//IL_052e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0538: Expected O, but got Unknown
		Importer val = new Importer(mod);
		ITypeDefOrRef val2 = val.Import(typeof(byte[]));
		IMethod val3 = val.Import((MethodBase)typeof(Encoding).GetMethod("GetString", new Type[1] { typeof(byte[]) }));
		MethodInfo methodInfo = typeof(Encoding).GetProperty("UTF8")?.GetGetMethod();
		if (methodInfo == null)
		{
			return null;
		}
		IMethod val4 = val.Import((MethodBase)methodInfo);
		MethodSig val5 = MethodSig.CreateStatic((TypeSig)(object)mod.CorLibTypes.String, (TypeSig)(object)mod.CorLibTypes.Object, (TypeSig)(object)mod.CorLibTypes.Int32);
		TypeDefUser val6 = new TypeDefUser((UTF8String)(""), (UTF8String)("C" + RandomName(10)), ((TypeDefOrRefSig)mod.CorLibTypes.Object).TypeDefOrRef);
		((TypeDef)val6).Attributes = (dnlib.DotNet.TypeAttributes)384;
		mod.Types.Add((TypeDef)(object)val6);
		MethodDefUser val7 = new MethodDefUser((UTF8String)("X" + RandomName(6)), val5, (dnlib.DotNet.MethodImplAttributes)0, (dnlib.DotNet.MethodAttributes)17);
		((TypeDef)val6).Methods.Add((MethodDef)(object)val7);
		((MethodDef)val7).ParamDefs.Add((ParamDef)new ParamDefUser((UTF8String)("a"), (ushort)1));
		((MethodDef)val7).ParamDefs.Add((ParamDef)new ParamDefUser((UTF8String)("k"), (ushort)2));
		CilBody val8 = new CilBody();
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
		val8.Instructions.Add(Instruction.Create(OpCodes.Castclass, val2));
		val8.Instructions.Add(Instruction.Create(OpCodes.Stloc_0));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldarg_1));
		val8.Instructions.Add(Instruction.Create(OpCodes.Stloc_1));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_0));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldlen));
		val8.Instructions.Add(Instruction.Create(OpCodes.Conv_I4));
		val8.Instructions.Add(Instruction.Create(OpCodes.Newarr, ((TypeDefOrRefSig)mod.CorLibTypes.Byte).TypeDefOrRef));
		val8.Instructions.Add(Instruction.Create(OpCodes.Stloc_2));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4_0));
		val8.Instructions.Add(Instruction.Create(OpCodes.Stloc_3));
		Instruction val9 = Instruction.Create(OpCodes.Nop);
		val8.Instructions.Add(val9);
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_3));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_0));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldlen));
		val8.Instructions.Add(Instruction.Create(OpCodes.Conv_I4));
		Instruction val10 = Instruction.Create(OpCodes.Nop);
		val8.Instructions.Add(Instruction.Create(OpCodes.Bge, val10));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_2));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_3));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_0));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_3));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldelem_U1));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_1));
		val8.Instructions.Add(Instruction.Create(OpCodes.Xor));
		val8.Instructions.Add(Instruction.Create(OpCodes.Conv_U1));
		val8.Instructions.Add(Instruction.Create(OpCodes.Stelem_I1));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_3));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4_1));
		val8.Instructions.Add(Instruction.Create(OpCodes.Add));
		val8.Instructions.Add(Instruction.Create(OpCodes.Stloc_3));
		val8.Instructions.Add(Instruction.Create(OpCodes.Br, val9));
		val8.Instructions.Add(val10);
		val8.Instructions.Add(Instruction.Create(OpCodes.Call, val4));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ldloc_2));
		val8.Instructions.Add(Instruction.Create(OpCodes.Callvirt, val3));
		val8.Instructions.Add(Instruction.Create(OpCodes.Ret));
		val8.InitLocals = true;
		val8.Variables.Add(new Local((TypeSig)new SZArraySig((TypeSig)(object)mod.CorLibTypes.Byte)));
		val8.Variables.Add(new Local((TypeSig)(object)mod.CorLibTypes.Int32));
		val8.Variables.Add(new Local((TypeSig)new SZArraySig((TypeSig)(object)mod.CorLibTypes.Byte)));
		val8.Variables.Add(new Local((TypeSig)(object)mod.CorLibTypes.Int32));
		((MethodDef)val7).Body = val8;
		return (MethodDef)(object)val7;
	}

	private static void ControlFlowObfuscation(ModuleDef mod, Action<string> log)
	{
		int num = 0;
		foreach (TypeDef type in mod.Types)
		{
			foreach (MethodDef method in type.Methods)
			{
				if (!method.HasBody || !method.Body.HasInstructions || method.Body.Instructions.Count < 4)
				{
					continue;
				}
				try
				{
					IList<Instruction> instructions = method.Body.Instructions;
					int num2 = instructions.Count - 1;
					while (num2 > 0 && num2 % 3 == 0)
					{
						if (instructions[num2].OpCode != OpCodes.Ret)
						{
							instructions.Insert(num2, Instruction.Create(OpCodes.Ldc_I4, _rnd.Next(0, 2)));
							instructions.Insert(num2 + 1, Instruction.Create(OpCodes.Pop));
							num++;
							break;
						}
						num2--;
					}
				}
				catch
				{
				}
			}
		}
		log($"Control flow: {num} methods");
	}

	private static void ObfuscateCosturaEmbeddedAssemblies(ModuleDef mod, ModuleContext modCtx, Action<string> log)
	{
		//IL_017a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0184: Expected O, but got Unknown
		//IL_0267: Unknown result type (might be due to invalid IL or missing references)
		//IL_026c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0276: Expected O, but got Unknown
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		List<(EmbeddedResource, byte[])> list = new List<(EmbeddedResource, byte[])>();
		Resource[] array = ((IEnumerable<Resource>)mod.Resources).ToArray();
		foreach (Resource obj in array)
		{
			EmbeddedResource val = (EmbeddedResource)(object)((obj is EmbeddedResource) ? obj : null);
			if (val == null)
			{
				continue;
			}
			string text = ((object)((Resource)val).Name)?.ToString() ?? "";
			if (text.IndexOf("costura", StringComparison.OrdinalIgnoreCase) < 0 || !text.EndsWith(".compressed", StringComparison.OrdinalIgnoreCase))
			{
				continue;
			}
			try
			{
				DataReader val2 = val.CreateReader();
				byte[] array2 = val2.ReadBytes((int)val.Length);
				if (array2 == null || array2.Length == 0)
				{
					continue;
				}
				byte[] array3;
				using (MemoryStream stream = new MemoryStream(array2))
				{
					using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress);
					using MemoryStream memoryStream = new MemoryStream();
					deflateStream.CopyTo(memoryStream);
					array3 = memoryStream.ToArray();
				}
				ModuleDefMD val3 = ModuleDefMD.Load(array3, modCtx);
				try
				{
					RenameObfuscation((ModuleDef)(object)val3, delegate
					{
					});
					try
					{
						StringEncryption((ModuleDef)(object)val3, delegate
						{
						});
					}
					catch
					{
					}
					try
					{
						AddJunkNops((ModuleDef)(object)val3, delegate
						{
						});
					}
					catch
					{
					}
					using MemoryStream memoryStream2 = new MemoryStream();
					((ModuleDef)val3).Write((Stream)memoryStream2, new ModuleWriterOptions((ModuleDef)(object)val3));
					byte[] array4 = memoryStream2.ToArray();
					using MemoryStream memoryStream3 = new MemoryStream();
					using (DeflateStream deflateStream2 = new DeflateStream(memoryStream3, CompressionLevel.Fastest))
					{
						deflateStream2.Write(array4, 0, array4.Length);
					}
					list.Add((val, memoryStream3.ToArray()));
				}
				finally
				{
					((IDisposable)val3)?.Dispose();
				}
			}
			catch (Exception ex)
			{
				log("Failed to obfuscate embedded " + text + ": " + ex.Message);
			}
		}
		foreach (var (val4, array5) in list)
		{
			((LazyList<Resource>)(object)mod.Resources).Remove((Resource)(object)val4);
			((LazyList<Resource>)(object)mod.Resources).Add((Resource)new EmbeddedResource(((Resource)val4).Name, array5, ((Resource)val4).Attributes));
		}
		if (list.Count > 0)
		{
			log($"Obfuscated {list.Count} Costura embedded assembly(ies).");
		}
	}

	private static void AddJunkNops(ModuleDef mod, Action<string> log)
	{
		int num = 0;
		foreach (TypeDef type in mod.Types)
		{
			foreach (MethodDef method in type.Methods)
			{
				if (!method.HasBody || !method.Body.HasInstructions)
				{
					continue;
				}
				try
				{
					IList<Instruction> instructions = method.Body.Instructions;
					for (int num2 = instructions.Count - 1; num2 > 0; num2 -= 2)
					{
						if (instructions[num2].OpCode != OpCodes.Ret)
						{
							for (int i = 0; i < 2; i++)
							{
								instructions.Insert(num2, Instruction.Create(OpCodes.Nop));
							}
							num++;
							break;
						}
					}
				}
				catch
				{
				}
			}
		}
		log($"Junk NOPs: {num} methods");
	}
}
