private protected void RegisterInstructionMetadata(InstructionMetadata meta) { //generate delegate Action del = GenerateDelegate(meta); //register delegate Instruction inst = new Instruction( meta.CPUAttribute.OpCode, meta.CPUAttribute.Cycles, del ); RegisterInstruction(inst); }
private protected void FindParameters(InstructionMetadata meta) { if (meta.Parameters == null) { meta.Parameters = new List <MemoryAddressAttributeBase>(); } ParameterInfo[] parameters = meta.Method.GetParameters(); foreach (ParameterInfo parameter in parameters) { MemoryAddressAttributeBase attribute = (MemoryAddressAttributeBase)parameter.GetCustomAttribute(typeof(MemoryAddressAttributeBase), false); if (attribute != null) { if (parameter.ParameterType != typeof(int)) { ThrowInvalidParameterType(parameter.Name, "int", meta); } // add parameter to meta MemoryAddressAttributeBase param = attribute; meta.Parameters.Add(param); } else { if (parameter.ParameterType != typeof(byte)) { ThrowInvalidParameterType(parameter.Name, "byte", meta); } // add parameter to meta MemoryAddressAttributeBase param = null; meta.Parameters.Add(param); } } RegisterInstructionMetadata(meta); }
private protected Action GenerateDelegate(InstructionMetadata meta) { Action del = () => { object[] instParams = new object[meta.Parameters.Count]; for (int i = 0; i < instParams.Length; i++) { MemoryAddressAttributeBase attr = meta.Parameters[i]; if (attr == null) { instParams[i] = FetchNext(); } else { byte[] rawAddr = FetchMultiple(attr.RequiredBytes); instParams[i] = attr.Resolve(this, ref rawAddr); } } meta.Method.Invoke(meta.ClassInstance, instParams); }; return(del); }
/**** ** Assembly rewriting ****/ /// <summary>Rewrite the types referenced by an assembly.</summary> /// <param name="mod">The mod for which the assembly is being loaded.</param> /// <param name="assembly">The assembly to rewrite.</param> /// <param name="assumeCompatible">Assume the mod is compatible, even if incompatible code is detected.</param> /// <param name="loggedMessages">The messages that have already been logged for this mod.</param> /// <param name="logPrefix">A string to prefix to log messages.</param> /// <returns>Returns whether the assembly was modified.</returns> /// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception> private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, bool assumeCompatible, HashSet <string> loggedMessages, string logPrefix) { ModuleDefinition module = assembly.MainModule; string filename = $"{assembly.Name.Name}.dll"; // swap assembly references if needed (e.g. XNA => MonoGame) bool platformChanged = false; for (int i = 0; i < module.AssemblyReferences.Count; i++) { // remove old assembly reference if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name)) { this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Rewriting {filename} for OS..."); platformChanged = true; module.AssemblyReferences.RemoveAt(i); i--; } } if (platformChanged) { // add target assembly references foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values) { module.AssemblyReferences.Add(target); } // rewrite type scopes to use target assemblies IEnumerable <TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName); foreach (TypeReference type in typeReferences) { this.ChangeTypeScope(type); } } // find (and optionally rewrite) incompatible instructions bool anyRewritten = false; IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode).ToArray(); foreach (MethodDefinition method in this.GetMethods(module)) { // check method definition foreach (IInstructionHandler handler in handlers) { InstructionHandleResult result = handler.Handle(module, method, this.AssemblyMap, platformChanged); this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, assumeCompatible, filename); if (result == InstructionHandleResult.Rewritten) { anyRewritten = true; } } // check CIL instructions ILProcessor cil = method.Body.GetILProcessor(); var instructions = cil.Body.Instructions; // ReSharper disable once ForCanBeConvertedToForeach -- deliberate access by index so each handler sees replacements from previous handlers for (int offset = 0; offset < instructions.Count; offset++) { foreach (IInstructionHandler handler in handlers) { Instruction instruction = instructions[offset]; InstructionHandleResult result = handler.Handle(module, cil, instruction, this.AssemblyMap, platformChanged); this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, assumeCompatible, filename); if (result == InstructionHandleResult.Rewritten) { anyRewritten = true; } } } } return(platformChanged || anyRewritten); }
/**** ** Assembly rewriting ****/ /// <summary>Rewrite the types referenced by an assembly.</summary> /// <param name="mod">The mod for which the assembly is being loaded.</param> /// <param name="assembly">The assembly to rewrite.</param> /// <param name="loggedMessages">The messages that have already been logged for this mod.</param> /// <param name="logPrefix">A string to prefix to log messages.</param> /// <returns>Returns whether the assembly was modified.</returns> /// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception> private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, HashSet <string> loggedMessages, string logPrefix) { ModuleDefinition module = assembly.MainModule; string filename = $"{assembly.Name.Name}.dll"; // swap assembly references if needed (e.g. XNA => MonoGame) bool platformChanged = false; for (int i = 0; i < module.AssemblyReferences.Count; i++) { // remove old assembly reference if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name)) { this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Rewriting {filename} for OS..."); platformChanged = true; module.AssemblyReferences.RemoveAt(i); i--; } } if (platformChanged) { // add target assembly references foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values) { module.AssemblyReferences.Add(target); } // rewrite type scopes to use target assemblies IEnumerable <TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName); foreach (TypeReference type in typeReferences) { this.ChangeTypeScope(type); } // rewrite types using custom attributes foreach (TypeDefinition type in module.GetTypes()) { foreach (var attr in type.CustomAttributes) { foreach (var conField in attr.ConstructorArguments) { if (conField.Value is TypeReference typeRef) { this.ChangeTypeScope(typeRef); } } } } } // find or rewrite code IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode, platformChanged, this.RewriteMods).ToArray(); RecursiveRewriter rewriter = new RecursiveRewriter( module: module, rewriteType: (type, replaceWith) => { bool rewritten = false; foreach (IInstructionHandler handler in handlers) { rewritten |= handler.Handle(module, type, replaceWith); } return(rewritten); }, rewriteInstruction: (ref Instruction instruction, ILProcessor cil) => { bool rewritten = false; foreach (IInstructionHandler handler in handlers) { rewritten |= handler.Handle(module, cil, instruction); } return(rewritten); } ); bool anyRewritten = rewriter.RewriteModule(); // handle rewrite flags foreach (IInstructionHandler handler in handlers) { foreach (var flag in handler.Flags) { this.ProcessInstructionHandleResult(mod, handler, flag, loggedMessages, logPrefix, filename); } } return(platformChanged || anyRewritten); }
private protected void FindMethods(Type classType) { // try to instantiate class object instance = null; try { instance = Activator.CreateInstance(classType, this); } catch (Exception e) { if (e is MissingMethodException) { throw new MissingMethodException($"No constructor with single parameter of type 'ICpu' found in class '{classType.Name}'. Define one or manually use ICpu.Register to register instruction"); } else { throw e; } } // Find CPU instructions MethodInfo[] methods = classType.GetMethods(); foreach (MethodInfo method in methods) { CPUInstructionAttribute[] attributes = (CPUInstructionAttribute[])method.GetCustomAttributes(typeof(CPUInstructionAttribute), false); LimitCPUTypeAttribute typeLimit = (LimitCPUTypeAttribute)method.GetCustomAttribute(typeof(LimitCPUTypeAttribute), false); if (attributes.Length < 1) { continue; } // ignore instruction if it's only for one type of CPU if (typeLimit != null && typeLimit.Type != _type) { continue; } List <InstructionMetadata?> metadata = new List <InstructionMetadata?>(); foreach (var attribute in attributes) { // check if instruction has the same cpu type if (!attribute.CPUType.HasFlag(_type)) { metadata.Add(null); continue; } // generate metadata InstructionMetadata meta = new InstructionMetadata(); meta.ClassType = classType; meta.ClassInstance = instance; meta.Method = method; meta.CPUAttribute = attribute; metadata.Add(meta); } if (metadata.Count == 1) { if (metadata[0].HasValue) { FindParameters(metadata[0].Value); } } else if (metadata.Count > 1) { FindMemoryAddressParameters(method, metadata.ToArray()); } } }
private void ThrowInvalidParameterType(string parameterName, string targetType, InstructionMetadata meta) { throw new InvalidParameterTypeException($"Parameter '{parameterName}' of CPU instruction '{meta.ClassType.Name}.{meta.Method.Name}' must be of type '{targetType}'"); }
private protected void FindMemoryAddressParameters(MethodInfo method, InstructionMetadata?[] meta) { if (meta.Length < 1) { return; } // check if all attributes have memory attribute MemoryAddressAttributeBase[] memAttributes = (MemoryAddressAttributeBase[])method.GetCustomAttributes(typeof(MemoryAddressAttributeBase), false); if (memAttributes.Length > 0 && meta.Length != memAttributes.Length) { throw new Exception($"Method {method.Name} doesn't have the same number of CPUInstruction attributes ({meta.Length}) and MemoryResolver attributes ({memAttributes.Length})!"); } // check parameters ParameterInfo[] parameters = method.GetParameters(); bool[] isIntParam = new bool[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { ParameterInfo parameter = parameters[i]; if (parameter.ParameterType == typeof(int)) { isIntParam[i] = true; continue; } else if (parameter.ParameterType == typeof(byte)) { isIntParam[i] = false; continue; } else { foreach (InstructionMetadata?metadata in meta) { if (metadata.HasValue) { ThrowInvalidParameterType(parameter.Name, "int|byte", metadata.Value); } } // in case that all provided metadata is null return; } } // generate delegates for (int i = 0; i < meta.Length; i++) { if (!meta[i].HasValue) { continue; } InstructionMetadata metadata = meta[i].Value; metadata.Parameters = new List <MemoryAddressAttributeBase>(); foreach (bool isInt in isIntParam) { if (isInt) { if (i < memAttributes.Length) { metadata.Parameters.Add(memAttributes[i]); } else { ThrowInvalidParameterType(parameters[i].Name, "byte", metadata); } } else { metadata.Parameters.Add(null); } } RegisterInstructionMetadata(metadata); } }
public InstructionMetadata(InstructionMetadata metadata, Register register) : this(metadata.Attribute, metadata.FieldInfo) { Register = register; }