/// <summary> /// Effectue le décodage des instructions /// </summary> /// <param name="bin"></param> /// <param name="offset"></param> /// <param name="context">Contexte de décodage. Par défaut, prend la valeur du mode actuel du CPU</param> /// <returns></returns> public InstructionsBin Decode(MemoryBin bin, int offset, InstructionDecodeContext context) { context.Source = bin; var decodeResult = new List<InstructionReference>(); var length = bin.Length; int originalOffset = 0; while (offset < length) { var code = bin.ReadInt1(originalOffset = offset); offset++; var match = knownInstructions[code]; var instructionReference = new InstructionReference(); instructionReference.instruction = match; instructionReference.offset = originalOffset; match.DecodeArguments(bin, ref context, ref offset, ref instructionReference); decodeResult.Add(instructionReference); } var result = new InstructionsBin() { DecodeContext = context, Instructions = decodeResult.ToArray() }; return result; }
public CPU(SnesPlatform platform) { if (platform == null) throw new ArgumentNullException("platform"); Platform = platform; // TODO vérifier les plages DirectPage = new MemoryBin(platform.Memory, 0, 256); RAM = new SnesMemoryMappingBin(platform.Memory); NativeStackBin = new MemoryBin(platform.Memory, 0x7E0000, 0x00FFFF + 1); EmulationStackBin = new MemoryBin(platform.Memory, NativeStackBin.Start + 0x100, 0xFF + 1); DecodeTable = new InstructionsDecodeTable(this); Reset(); }
/// <summary> /// Decode une unique instruction à partir de l'offset spécifié dans le bin spécifié à l'aide du contexte courrant, et la stoque dans decodedInstruction /// </summary> /// <param name="bin"></param> /// <param name="offset"></param> /// <param name="context"></param> /// <param name="decodedInstruction"></param> public void DecodeOnce(MemoryBin bin, ref int offset, ref InstructionDecodeContext context, ref InstructionReference decodedInstruction) { var code = bin.ReadInt1(offset); offset++; var match = knownInstructions[code]; decodedInstruction.instruction = match; decodedInstruction.offset = offset - 1; match.DecodeArguments(bin, ref context, ref offset, ref decodedInstruction); }
public InstructionsBin Decode(MemoryBin bin, int offset) { return Decode(bin, offset, CPU.BuildCurrentContext()); }
/// <summary> /// Exécute une seule instruction en partant de l'addresse spécifiée /// </summary> /// <param name="bin">Mémoire à utiliser pour la référence</param> /// <param name="startAddress">Adresse, ou rien pour utiliser le PC actuel</param> public void RunOnce(MemoryBin bin, int startAddress = -1) { if (bin == null) throw new ArgumentNullException("bin"); if (startAddress >= 0) platform.CPU.PC = startAddress; var refInstruction = new InstructionReference(); var context = platform.CPU.BuildCurrentContext(); platform.Decoder.DecodeOnce(bin, ref platform.CPU.PC, ref context, ref refInstruction); refInstruction.instruction.Run(refInstruction.param1, refInstruction.param2); }
private static void InitTestContext(out SnesPlatform snes, out MemoryBin romBin, out int writeOffset) { snes = new SnesPlatform(); romBin = new MemoryBin(snes.Memory, 0, snes.Memory.Length); snes.Interpreter.RethrowExecutionExceptions = true; snes.Interpreter.Trace = true; snes.Encoder.EnableInstructionValidation = true; writeOffset = 0; }
/// <summary> /// Lance un thread d'exécution qui process toutes les instructions à partir de startAddress /// </summary> /// <param name="bin">Mémoire à utiliser pour la référence</param> /// <param name="startAddress">Adresse, ou rien pour utiliser le PC actuel</param> /// <param name="separateThread">true pour lancer dans un thread séparé, false sinon</param> public void Interpret(MemoryBin bin, int startAddress = -1, bool separateThread = true) { if (bin == null) throw new ArgumentNullException("bin"); else if (interpreting) throw new InvalidOperationException("Une seule interprétation possible à la fois"); else { // Il y a clairement une RunCondition ici mais bon ... interpreting = true; stop = false; if (startAddress >= 0) platform.CPU.PC = startAddress; interpreterMemory = bin; if (separateThread) { startedInSeparateThread = true; interpretThread = new Thread(new ThreadStart(InterpretThreadFunction)); interpretThread.Name = String.Format("SNES Interpreter - {0}", DateTime.Now.ToLongTimeString()); // Si on kill le programme, ça va killer aussi le thread ... c'est bien ça interpretThread.IsBackground = true; interpretThread.Start(); } else { startedInSeparateThread = false; InterpretThreadFunction(); } } }
/// <summary> /// Ecrit une instruction. C'est un peut chiant car il faut spécifier à la fois l'instruction, l'addrMode, le type des args et les args mais bon, /// pour l'instant ça devrait faire l'affaire /// </summary> /// <param name="bin"></param> /// <param name="offset"></param> /// <param name="opCode"></param> /// <param name="addrMode"></param> /// <param name="param1Type"></param> /// <param name="param1"></param> /// <param name="param2"></param> public void Write(MemoryBin bin, ref int offset, OpCodes opCode, AddressingModes addrMode = AddressingModes.Direct, ArgumentType param1Type = ArgumentType.I1, int param1 = 0, int param2 = 0) { var originalOffset = offset; /* d'abord, on cherche l'instruction */ Instruction match_instruction = null; var match_code = from instruction in cpu.DecodeTable.KnownInstructions where instruction.Code == opCode select instruction; switch(match_code.Count()) { case 0: throw new InvalidOperationException(String.Format("Instruction not found : {0}", opCode)); case 1: { /* on se prend pas la tête sur le mode d'addr */ match_instruction = match_code.First(); break; } default: { /* on cherche le bon mode */ match_instruction = (from code_item in match_code where code_item.AddrMode == addrMode select code_item).FirstOrDefault(); if(match_instruction== null) throw new InvalidOperationException(String.Format("Instruction {0} was found, but the addrMode {1} has no binding", opCode, addrMode)); break; } } /* on écrit le code */ bin.WriteInt1(offset, match_instruction.AssociatedHexCode); offset++; /* s'il y a des arguments, on les écrit */ if (match_instruction.HaveArgs) { WriteParameter(bin, ref offset, param1Type, param1); if (addrMode == AddressingModes.BlockMove) { /* c'est le seul mode qui a deux arguments, et il a la même * taille que le premier argument * */ WriteParameter(bin, ref offset, param1Type, param2); } } if (enableInstructionValidation) { /* on vérifie l'instruction écrite */ var instructionDecode = new InstructionReference(); var context = cpu.BuildCurrentContext(); cpu.Platform.Decoder.DecodeOnce(bin, ref originalOffset, ref context, ref instructionDecode); if (instructionDecode.instruction.Code != opCode || instructionDecode.param1 != param1 || instructionDecode.param2 != param2) { throw new InvalidProgramException(String.Format("Instruction decode mismatch.\r\nEncoded:{0}({1},{2}) [{3}]\r\nDecoded:{4}({5},{6}) [{7}]", opCode, param1, param2, addrMode, instructionDecode.instruction.Code, instructionDecode.param1, instructionDecode.param2, instructionDecode.instruction.AddrMode)); } else if (originalOffset != offset) { throw new InvalidProgramException(String.Format("Instruction decode mismatch, Invalid offset. Difference : {0}", offset - originalOffset)); } } }
private void WriteParameter(MemoryBin bin, ref int offset, ArgumentType paramType, int param) { switch (paramType) { default: case ArgumentType.None: break; case ArgumentType.I1: bin.WriteInt1(offset, (byte)param); offset++; break; case ArgumentType.I2: bin.WriteInt1(offset, (byte)param); bin.WriteInt1(offset + 1, (byte)(param >> 8)); offset += 2; break; case ArgumentType.I3: bin.WriteInt1(offset, (byte)param); bin.WriteInt1(offset + 1, (byte)(param >> 8)); bin.WriteInt1(offset + 2, (byte)(param >> 16)); offset += 3; break; } }
public void WriteNativeToEmulationMode(MemoryBin romBin, ref int writeOffset) { Write(romBin, ref writeOffset, OpCodes.SEC); Write(romBin, ref writeOffset, OpCodes.XCE); /* Histoire de préserver les changements de contexte */ if (enableInstructionValidation) cpu.SwitchFromNativeToEmulationMode(); }
/// <summary> /// Utilise une instruction spéciale qui va appeler un code à un moment clef de l'exécution /// </summary> /// <remarks>ATTENTION : Il n'y a que 254 slots disponibles en l'état. Pour effacer les associations, utiliser ClearCallbackInvokes</remarks> /// <param name="bin"></param> /// <param name="offset"></param> /// <param name="callback"></param> public void WriteCallbackInvoke(MemoryBin bin, ref int offset, Action<Instruction> callback) { if (callback == null) return; int slot = 0; for(slot = 0; slot <= 254;slot++) { if (cpu.InstrumentationCallBacks[slot] == null) break; } if (slot > 254) throw new InvalidOperationException("Tous les slots de callback sont utilisés. Impossible d'encoder cette instruction"); else { cpu.InstrumentationCallBacks[slot] = callback; Write(bin, ref offset, OpCodes.WDM, param1Type: ArgumentType.I1, param1: slot + 1); } }