/// <summary> /// Parses an integer key that was stored as a string, and checks to see if the /// value falls within an acceptable range. /// </summary> /// <param name="keyStr">Integer key, in string form.</param> /// <param name="fileLen">Length of file, for range check.</param> /// <param name="fieldName">Name of field, for error messages.</param> /// <param name="report">Error report object.</param> /// <param name="intKey">Returned integer key.</param> /// <returns>True on success, false on failure.</returns> private static bool ParseValidateKey(string keyStr, int fileLen, string fieldName, FileLoadReport report, out int intKey) { if (!int.TryParse(keyStr, out intKey)) { report.Add(FileLoadItem.Type.Warning, Properties.Resources.ERR_INVALID_INT_VALUE + " (" + fieldName + ": " + keyStr + ")"); return(false); } // Shouldn't allow DisplayList.Line.HEADER_COMMENT_OFFSET on anything but // LongComment. Maybe "bool allowNegativeKeys"? if (intKey < fileLen && (intKey >= 0 || intKey == DisplayList.Line.HEADER_COMMENT_OFFSET)) { return(true); } else { report.Add(FileLoadItem.Type.Warning, Properties.Resources.ERR_INVALID_KEY_VALUE + " (" + fieldName + ": " + intKey + ")"); return(false); } }
/// <summary> /// Attempts to load the specified plugin. If the plugin is already loaded, this /// does nothing. If not, the assembly is loaded an an instance is created. /// </summary> /// <param name="scriptIdent">Script identifier.</param> /// <param name="report">Report with errors and warnings.</param> /// <returns>True on success.</returns> public bool LoadPlugin(string scriptIdent, out FileLoadReport report) { // Make sure the most recent version is compiled. string dllPath = PluginDllCache.GenerateScriptDll(scriptIdent, mProject.ProjectPathName, out report); if (dllPath == null) { return(false); } if (DomainManager == null) { if (mActivePlugins.TryGetValue(scriptIdent, out IPlugin plugin)) { return(true); } Assembly asm = Assembly.LoadFile(dllPath); plugin = PluginDllCache.ConstructIPlugin(asm); mActivePlugins.Add(scriptIdent, plugin); report = new FileLoadReport(dllPath); // empty report return(true); } else { IPlugin plugin = DomainManager.PluginMgr.LoadPlugin(dllPath, scriptIdent); return(plugin != null); } }
/// <summary> /// Creates a LocalVariableTable from a SerLocalVariableTable. /// </summary> /// <param name="serTable">Deserialized data.</param> /// <param name="contentVersion">Serialization version.</param> /// <param name="report">Error report object.</param> /// <param name="outLvt">Created LocalVariableTable</param> /// <returns>True on success.</returns> private static bool CreateLocalVariableTable(SerLocalVariableTable serTable, int contentVersion, FileLoadReport report, out LocalVariableTable outLvt) { outLvt = new LocalVariableTable(); outLvt.ClearPrevious = serTable.ClearPrevious; foreach (SerDefSymbol serDef in serTable.Variables) { // Force the "has width" field to true for local variables, because it's // non-optional there. This is really only needed for loading projects // created in v1.3, which didn't have the "has width" property. serDef.HasWidth = true; if (!CreateDefSymbol(serDef, contentVersion, report, out DefSymbol defSym)) { return(false); } if (!defSym.IsVariable) { // not expected to happen; skip it Debug.WriteLine("Found local variable with bad source: " + defSym.SymbolSource); string str = string.Format(Res.Strings.ERR_BAD_LOCAL_VARIABLE_FMT, defSym); report.Add(FileLoadItem.Type.Warning, str); continue; } outLvt.AddOrReplace(defSym); } return(true); }
private DisasmProject InstantiateProject(string dataPathName, out FileLoadReport projectLoadReport) { DisasmProject project = new DisasmProject(); // always use AppDomain sandbox projectLoadReport = null; int testNum = GetTestNum(dataPathName); if (testNum < 2000) { // create new disasm project for data file byte[] fileData; try { fileData = LoadDataFile(dataPathName); } catch (Exception ex) { ReportErrMsg(ex.Message); return(null); } project.Initialize(fileData.Length); project.PrepForNew(fileData, Path.GetFileName(dataPathName)); // no platform symbols to load } else { // deserialize project file, failing if we can't find it string projectPathName = dataPathName + ProjectFile.FILENAME_EXT; if (!ProjectFile.DeserializeFromFile(projectPathName, project, out projectLoadReport)) { ReportErrMsg(projectLoadReport.Format()); return(null); } byte[] fileData; try { fileData = LoadDataFile(dataPathName); } catch (Exception ex) { ReportErrMsg(ex.Message); return(null); } project.SetFileData(fileData, Path.GetFileName(dataPathName)); project.ProjectPathName = projectPathName; project.LoadExternalFiles(); } TaskTimer genTimer = new TaskTimer(); DebugLog genLog = new DebugLog(); genLog.SetMinPriority(DebugLog.Priority.Silent); project.Analyze(UndoableChange.ReanalysisScope.CodeAndData, genLog, genTimer); return(project); }
/// <summary> /// Prepares the DLL for the specified script, compiling it if necessary. /// </summary> /// <param name="scriptIdent">Script identifier.</param> /// <param name="projectPathName">Project file name, used for naming project-local /// files. May be empty if the project hasn't been named yet (in which case /// project-local files will cause a failure).</param> /// <param name="report">Report with errors and warnings.</param> /// <returns>Full path to DLL, or null if compilation failed.</returns> public static string GenerateScriptDll(string scriptIdent, string projectPathName, out FileLoadReport report) { ExternalFile ef = ExternalFile.CreateFromIdent(scriptIdent); if (ef == null) { Debug.Assert(false); report = new FileLoadReport("CreateFromIdent failed"); return(null); } string projectDir = string.Empty; if (!string.IsNullOrEmpty(projectPathName)) { projectDir = Path.GetDirectoryName(projectPathName); } string srcPathName = ef.GetPathName(projectDir); // Fail if the source script doesn't exist. If a previously-compiled DLL is present // we could just continue to use it, but that seems contrary to expectation, and // means that you won't notice that your project is broken until you clear out // the DLL directory. if (!File.Exists(srcPathName)) { report = new FileLoadReport(srcPathName); report.Add(FileLoadItem.Type.Error, string.Format(Properties.Resources.ERR_FILE_NOT_FOUND, srcPathName)); return(null); } string destFileName = ef.GenerateDllName(projectPathName); string destPathName = Path.Combine(GetPluginDirPath(), destFileName); // Compile if necessary. if (FileUtil.FileMissingOrOlder(destPathName, srcPathName)) { Debug.WriteLine("Compiling " + srcPathName + " to " + destPathName); Assembly asm = CompileCode(srcPathName, destPathName, out report); if (asm == null) { return(null); } } else { Debug.WriteLine("NOT recompiling " + srcPathName); report = new FileLoadReport(srcPathName); } return(destPathName); }
/// <summary> /// Compiles the script from the specified pathname into an Assembly. /// </summary> /// <param name="scriptPathName">Script pathname.</param> /// <param name="dllPathName">Full pathname for output DLL.</param> /// <param name="report">Errors and warnings reported by the compiler.</param> /// <returns>Reference to script instance, or null on failure.</returns> private static Assembly CompileCode(string scriptPathName, string dllPathName, out FileLoadReport report) { report = new FileLoadReport(scriptPathName); // To get C#6 (and later) features, a NuGet package must be installed, and // some "black magic" must be invoked. // See https://stackoverflow.com/a/40311406/294248 and nearby answers. Microsoft.CSharp.CSharpCodeProvider csProvider = new Microsoft.CSharp.CSharpCodeProvider(); CompilerParameters parms = new CompilerParameters(); // We want a DLL, not an EXE. parms.GenerateExecutable = false; // Save to disk so other AppDomain can load it. parms.GenerateInMemory = false; // Be vocal about warnings. parms.WarningLevel = 3; // Optimization is nice. parms.CompilerOptions = "/optimize"; // Output file name. Must be named appropriately so it can be found. parms.OutputAssembly = dllPathName; // Add dependencies. parms.ReferencedAssemblies.AddRange(sRefAssem); #if DEBUG // This creates a .pdb file, which allows breakpoints to work. parms.IncludeDebugInformation = true; #endif // Using the "from file" version has an advantage over the "from source" // version in that the debugger can find the source file, so things like // breakpoints work correctly. CompilerResults cr = csProvider.CompileAssemblyFromFile(parms, scriptPathName); CompilerErrorCollection cec = cr.Errors; foreach (CompilerError ce in cr.Errors) { report.Add(ce.Line, ce.Column, ce.IsWarning ? FileLoadItem.Type.Warning : FileLoadItem.Type.Error, ce.ErrorText); } if (cr.Errors.HasErrors) { return(null); } else { Debug.WriteLine("Compilation successful"); return(cr.CompiledAssembly); } }
/// <summary> /// Creates a Symbol from a SerSymbol. /// </summary> /// <param name="ssym">Deserialized data.</param> /// <param name="report">Error report object.</param> /// <param name="outSym"></param> /// <returns>True on success.</returns> private static bool CreateSymbol(SerSymbol ssym, FileLoadReport report, out Symbol outSym) { outSym = null; Symbol.Source source; Symbol.Type type; try { source = (Symbol.Source)Enum.Parse(typeof(Symbol.Source), ssym.Source); type = (Symbol.Type)Enum.Parse(typeof(Symbol.Type), ssym.Type); } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Properties.Resources.ERR_BAD_SYMBOL_ST + ": " + ssym.Source + "/" + ssym.Type); return(false); } outSym = new Symbol(ssym.Label, ssym.Value, source, type /*, ssym.IsExport*/); return(true); }
/// <summary> /// Creates a DefSymbol from a SerDefSymbol. /// </summary> /// <param name="serDefSym">Deserialized data.</param> /// <param name="contentVersion">Serialization version.</param> /// <param name="report">Error report object.</param> /// <param name="outDefSym">Created symbol.</param> /// <returns></returns> private static bool CreateDefSymbol(SerDefSymbol serDefSym, int contentVersion, FileLoadReport report, out DefSymbol outDefSym) { outDefSym = null; if (!CreateSymbol(serDefSym, report, out Symbol sym)) { return(false); } if (!CreateFormatDescriptor(serDefSym.DataDescriptor, contentVersion, report, out FormatDescriptor dfd)) { return(false); } DefSymbol.DirectionFlags direction; if (string.IsNullOrEmpty(serDefSym.Direction)) { direction = DefSymbol.DirectionFlags.ReadWrite; } else { try { direction = (DefSymbol.DirectionFlags) Enum.Parse(typeof(DefSymbol.DirectionFlags), serDefSym.Direction); } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_DEF_SYMBOL_DIR + ": " + serDefSym.Direction); return(false); } } DefSymbol.MultiAddressMask multiMask = null; if (serDefSym.MultiMask != null) { multiMask = new DefSymbol.MultiAddressMask(serDefSym.MultiMask.CompareMask, serDefSym.MultiMask.CompareValue, serDefSym.MultiMask.AddressMask); } outDefSym = DefSymbol.Create(sym, dfd, serDefSym.HasWidth, serDefSym.Comment, direction, multiMask); return(true); }
/// <summary> /// Reads the specified file and deserializes it into the project. /// </summary> /// <param name="pathName">Input path name.</param> /// <param name="proj">Project to deserialize into.</param> /// <param name="report">File load report, which may contain errors or warnings.</param> /// <returns>True on success.</returns> public static bool DeserializeFromFile(string pathName, DisasmProject proj, out FileLoadReport report) { Debug.WriteLine("Deserializing '" + pathName + "'"); report = new FileLoadReport(pathName); string serializedData; try { serializedData = File.ReadAllText(pathName); } catch (Exception ex) { report.Add(FileLoadItem.Type.Error, Properties.Resources.ERR_PROJECT_LOAD_FAIL + ": " + ex.Message); return(false); } if (serializedData.StartsWith(SerializableProjectFile1.MAGIC)) { // File is a match for SerializableProjectFile1. Strip header and deserialize. serializedData = serializedData.Substring(SerializableProjectFile1.MAGIC.Length); try { bool ok = SerializableProjectFile1.DeserializeProject(serializedData, proj, report); if (ok) { proj.UpdateCpuDef(); } return(ok); } catch (Exception ex) { // Ideally this won't happen -- errors should be caught explicitly. This // is a catch-all to keep us from crashing on expectedly bad input. report.Add(FileLoadItem.Type.Error, Properties.Resources.ERR_PROJECT_FILE_CORRUPT + ": " + ex); return(false); } } else { report.Add(FileLoadItem.Type.Error, Properties.Resources.ERR_NOT_PROJECT_FILE); return(false); } }
/// <summary> /// Attempts to load the specified plugin. If the plugin is already loaded, this /// does nothing. If not, the assembly is loaded and an instance is created. /// </summary> /// <param name="scriptIdent">Script identifier.</param> /// <param name="report">Report with errors and warnings.</param> /// <returns>True on success.</returns> public bool LoadPlugin(string scriptIdent, out FileLoadReport report) { // Make sure the most recent version is compiled. string dllPath = PluginDllCache.GenerateScriptDll(scriptIdent, mProject.ProjectPathName, out report); if (dllPath == null) { return(false); } if (DomainMgr == null) { if (mActivePlugins.TryGetValue(scriptIdent, out IPlugin plugin)) { return(true); } Assembly asm = Assembly.LoadFile(dllPath); plugin = PluginDllCache.ConstructIPlugin(asm); mActivePlugins.Add(scriptIdent, plugin); report = new FileLoadReport(dllPath); // empty report return(true); } else { CheckHealth(); IPlugin plugin = DomainMgr.PluginMgr.LoadPlugin(dllPath, scriptIdent, out string failMsg); if (plugin == null) { report.Add(FileLoadItem.Type.Error, "Failed loading plugin: " + failMsg); } else { mLoadedPlugins.Add(new LoadedPluginPath(scriptIdent, dllPath)); } return(plugin != null); } }
/// <summary> /// Creates a FormatDescriptor from a SerFormatDescriptor. /// </summary> /// <param name="sfd">Deserialized data.</param> /// <param name="report">Error report object.</param> /// <param name="dfd">Created FormatDescriptor.</param> /// <returns>True on success.</returns> private static bool CreateFormatDescriptor(SerFormatDescriptor sfd, FileLoadReport report, out FormatDescriptor dfd) { dfd = null; FormatDescriptor.Type format; FormatDescriptor.SubType subFormat; try { format = (FormatDescriptor.Type)Enum.Parse( typeof(FormatDescriptor.Type), sfd.Format); subFormat = (FormatDescriptor.SubType)Enum.Parse( typeof(FormatDescriptor.SubType), sfd.SubFormat); } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Properties.Resources.ERR_BAD_FD_FORMAT + ": " + sfd.Format + "/" + sfd.SubFormat); return(false); } if (sfd.SymbolRef == null) { dfd = FormatDescriptor.Create(sfd.Length, format, subFormat); } else { WeakSymbolRef.Part part; try { part = (WeakSymbolRef.Part)Enum.Parse( typeof(WeakSymbolRef.Part), sfd.SymbolRef.Part); } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Properties.Resources.ERR_BAD_SYMREF_PART + ": " + sfd.SymbolRef.Part); return(false); } dfd = FormatDescriptor.Create(sfd.Length, new WeakSymbolRef(sfd.SymbolRef.Label, part), format == FormatDescriptor.Type.NumericBE); } return(true); }
/// <summary> /// Deserializes an augmented JSON string into a DisasmProject. /// </summary> /// <param name="cereal">Serialized data.</param> /// <param name="proj">Project to populate.</param> /// <param name="report">Error report object.</param> /// <returns>True on success, false on fatal error.</returns> public static bool DeserializeProject(string cereal, DisasmProject proj, FileLoadReport report) { JavaScriptSerializer ser = new JavaScriptSerializer(); SerializableProjectFile1 spf; try { spf = ser.Deserialize <SerializableProjectFile1>(cereal); } catch (Exception ex) { // The deserializer seems to be stuffing the entire data stream into the // exception, which we don't really want, so cap it at 256 chars. string msg = ex.Message; if (msg.Length > 256) { msg = msg.Substring(0, 256) + " [...]"; } report.Add(FileLoadItem.Type.Error, Properties.Resources.ERR_PROJECT_FILE_CORRUPT + ": " + msg); return(false); } if (spf._ContentVersion > ProjectFile.CONTENT_VERSION) { // Post a warning. report.Add(FileLoadItem.Type.Notice, Properties.Resources.PROJECT_FROM_NEWER_APP); } if (spf.FileDataLength <= 0) { report.Add(FileLoadItem.Type.Error, Properties.Resources.ERR_BAD_FILE_LENGTH + ": " + spf.FileDataLength); return(false); } // Initialize the object and set a few simple items. proj.Initialize(spf.FileDataLength); proj.SetFileCrc((uint)spf.FileDataCrc32); // Deserialize ProjectProperties: misc items. proj.ProjectProps.CpuType = Asm65.CpuDef.GetCpuTypeFromName(spf.ProjectProps.CpuName); proj.ProjectProps.IncludeUndocumentedInstr = spf.ProjectProps.IncludeUndocumentedInstr; proj.ProjectProps.EntryFlags = Asm65.StatusFlags.FromInt(spf.ProjectProps.EntryFlags); if (Enum.TryParse <AutoLabel.Style>(spf.ProjectProps.AutoLabelStyle, out AutoLabel.Style als)) { proj.ProjectProps.AutoLabelStyle = als; } else { // unknown value, leave as default } proj.ProjectProps.AnalysisParams = new ProjectProperties.AnalysisParameters(); proj.ProjectProps.AnalysisParams.AnalyzeUncategorizedData = spf.ProjectProps.AnalysisParams.AnalyzeUncategorizedData; proj.ProjectProps.AnalysisParams.MinCharsForString = spf.ProjectProps.AnalysisParams.MinCharsForString; proj.ProjectProps.AnalysisParams.SeekNearbyTargets = spf.ProjectProps.AnalysisParams.SeekNearbyTargets; // Deserialize ProjectProperties: external file identifiers. Debug.Assert(proj.ProjectProps.PlatformSymbolFileIdentifiers.Count == 0); foreach (string str in spf.ProjectProps.PlatformSymbolFileIdentifiers) { proj.ProjectProps.PlatformSymbolFileIdentifiers.Add(str); } Debug.Assert(proj.ProjectProps.ExtensionScriptFileIdentifiers.Count == 0); foreach (string str in spf.ProjectProps.ExtensionScriptFileIdentifiers) { proj.ProjectProps.ExtensionScriptFileIdentifiers.Add(str); } // Deserialize ProjectProperties: project symbols. foreach (KeyValuePair <string, SerDefSymbol> kvp in spf.ProjectProps.ProjectSyms) { if (!CreateSymbol(kvp.Value, report, out Symbol sym)) { continue; } if (!CreateFormatDescriptor(kvp.Value.DataDescriptor, report, out FormatDescriptor dfd)) { continue; } proj.ProjectProps.ProjectSyms[sym.Label] = new DefSymbol(sym, dfd, kvp.Value.Comment); } // Deserialize address map. foreach (SerAddressMap addr in spf.AddressMap) { proj.AddrMap.Set(addr.Offset, addr.Addr); } // Deserialize type hints. Default value in new array as NoHint, so we don't // need to write those. They should not have been recorded in the file. foreach (SerTypeHintRange range in spf.TypeHints) { if (range.Low < 0 || range.High >= spf.FileDataLength || range.Low > range.High) { report.Add(FileLoadItem.Type.Warning, Properties.Resources.ERR_BAD_RANGE + ": " + Properties.Resources.PROJECT_FIELD_TYPE_HINT + " +" + range.Low.ToString("x6") + " - +" + range.High.ToString("x6")); continue; } CodeAnalysis.TypeHint hint; try { hint = (CodeAnalysis.TypeHint)Enum.Parse( typeof(CodeAnalysis.TypeHint), range.Hint); } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Properties.Resources.ERR_BAD_TYPE_HINT + ": " + range.Hint); continue; } for (int i = range.Low; i <= range.High; i++) { proj.TypeHints[i] = hint; } } // Deserialize status flag overrides. foreach (KeyValuePair <string, int> kvp in spf.StatusFlagOverrides) { if (!ParseValidateKey(kvp.Key, spf.FileDataLength, Properties.Resources.PROJECT_FIELD_STATUS_FLAGS, report, out int intKey)) { continue; } proj.StatusFlagOverrides[intKey] = Asm65.StatusFlags.FromInt(kvp.Value); } // Deserialize comments. foreach (KeyValuePair <string, string> kvp in spf.Comments) { if (!ParseValidateKey(kvp.Key, spf.FileDataLength, Properties.Resources.PROJECT_FIELD_COMMENT, report, out int intKey)) { continue; } proj.Comments[intKey] = kvp.Value; } // Deserialize long comments. foreach (KeyValuePair <string, SerMultiLineComment> kvp in spf.LongComments) { if (!ParseValidateKey(kvp.Key, spf.FileDataLength, Properties.Resources.PROJECT_FIELD_LONG_COMMENT, report, out int intKey)) { continue; } proj.LongComments[intKey] = new MultiLineComment(kvp.Value.Text, kvp.Value.BoxMode, kvp.Value.MaxWidth); } // Deserialize notes. foreach (KeyValuePair <string, SerMultiLineComment> kvp in spf.Notes) { if (!ParseValidateKey(kvp.Key, spf.FileDataLength, Properties.Resources.PROJECT_FIELD_NOTE, report, out int intKey)) { continue; } proj.Notes[intKey] = new MultiLineComment(kvp.Value.Text, Color.FromArgb(kvp.Value.BackgroundColor)); } // Deserialize user-defined labels. SortedList <string, string> labelDupCheck = new SortedList <string, string>(spf.UserLabels.Count); foreach (KeyValuePair <string, SerSymbol> kvp in spf.UserLabels) { if (!ParseValidateKey(kvp.Key, spf.FileDataLength, Properties.Resources.PROJECT_FIELD_USER_LABEL, report, out int intKey)) { continue; } Symbol.Source source; Symbol.Type type; try { source = (Symbol.Source)Enum.Parse(typeof(Symbol.Source), kvp.Value.Source); type = (Symbol.Type)Enum.Parse(typeof(Symbol.Type), kvp.Value.Type); if (source != Symbol.Source.User) { // User labels are always source=user. I don't think it really matters, // but best to keep junk out. throw new Exception("wrong source for user label"); } } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Properties.Resources.ERR_BAD_SYMBOL_ST + ": " + kvp.Value.Source + "/" + kvp.Value.Type); continue; } // Check for duplicate labels. We only want to compare label strings, so we // can't test UserLabels.ContainsValue (which might be a linear search anyway). // Dump the labels into a sorted list. if (labelDupCheck.ContainsKey(kvp.Value.Label)) { report.Add(FileLoadItem.Type.Warning, string.Format(Properties.Resources.ERR_DUPLICATE_LABEL, kvp.Value.Label, intKey)); continue; } labelDupCheck.Add(kvp.Value.Label, string.Empty); proj.UserLabels[intKey] = new Symbol(kvp.Value.Label, kvp.Value.Value, source, type); } // Deserialize operand format descriptors. foreach (KeyValuePair <string, SerFormatDescriptor> kvp in spf.OperandFormats) { if (!ParseValidateKey(kvp.Key, spf.FileDataLength, Properties.Resources.PROJECT_FIELD_OPERAND_FORMAT, report, out int intKey)) { continue; } if (!CreateFormatDescriptor(kvp.Value, report, out FormatDescriptor dfd)) { continue; } if (intKey < 0 || intKey + dfd.Length > spf.FileDataLength) { report.Add(FileLoadItem.Type.Warning, string.Format(Properties.Resources.ERR_BAD_FD, intKey)); continue; } // TODO(maybe): check to see if the descriptor straddles an address change. // Not fatal but it'll make things look weird. proj.OperandFormats[intKey] = dfd; } return(true); }
private DisasmProject InstantiateProject(string dataPathName, out FileLoadReport projectLoadReport) { DisasmProject project = new DisasmProject(); // always use AppDomain sandbox projectLoadReport = null; int testNum = GetTestNum(dataPathName); CpuDef.CpuType cpuType = GetCpuTypeFromNum(testNum); if (testNum < 20000) { // create new disasm project for data file byte[] fileData; try { fileData = LoadDataFile(dataPathName); } catch (Exception ex) { ReportErrMsg(ex.Message); return(null); } project.Initialize(fileData.Length); project.ProjectProps.CpuType = cpuType; project.ProjectProps.IncludeUndocumentedInstr = true; project.ProjectProps.TwoByteBrk = false; project.UpdateCpuDef(); project.PrepForNew(fileData, Path.GetFileName(dataPathName)); // no platform symbols to load } else { // deserialize project file, failing if we can't find it string projectPathName = dataPathName + ProjectFile.FILENAME_EXT; if (!ProjectFile.DeserializeFromFile(projectPathName, project, out projectLoadReport)) { ReportErrMsg(projectLoadReport.Format()); return(null); } byte[] fileData; try { fileData = LoadDataFile(dataPathName); } catch (Exception ex) { ReportErrMsg(ex.Message); return(null); } FileLoadReport unused = new FileLoadReport("test"); project.SetFileData(fileData, Path.GetFileName(dataPathName), ref unused); project.ProjectPathName = projectPathName; string extMsgs = project.LoadExternalFiles(); if (!string.IsNullOrEmpty(extMsgs)) { ReportErrMsg(extMsgs); // keep going } if (project.ProjectProps.CpuType != cpuType) { ReportErrMsg("Mismatch CPU type for test " + testNum + ": project wants " + project.ProjectProps.CpuType); // keep going } } TaskTimer genTimer = new TaskTimer(); DebugLog genLog = new DebugLog(); genLog.SetMinPriority(DebugLog.Priority.Silent); project.Analyze(UndoableChange.ReanalysisScope.CodeAndData, genLog, genTimer); return(project); }
/// <summary> /// Prepares the DLL for the specified script, compiling it if necessary. /// </summary> /// <param name="scriptIdent">Script identifier.</param> /// <param name="projectPathName">Project file name, used for naming project-local /// files. May be empty if the project hasn't been named yet (in which case /// project-local files will cause a failure).</param> /// <param name="report">Report with errors and warnings.</param> /// <returns>Full path to DLL, or null if compilation failed.</returns> public static string GenerateScriptDll(string scriptIdent, string projectPathName, out FileLoadReport report) { ExternalFile ef = ExternalFile.CreateFromIdent(scriptIdent); if (ef == null) { Debug.Assert(false); report = new FileLoadReport("CreateFromIdent failed"); return(null); } string projectDir = string.Empty; if (!string.IsNullOrEmpty(projectPathName)) { projectDir = Path.GetDirectoryName(projectPathName); } string srcPathName = ef.GetPathName(projectDir); // Fail if the source script doesn't exist. If a previously-compiled DLL is present // we could just continue to use it, but that seems contrary to expectation, and // means that you won't notice that your project is broken until you clear out // the DLL directory. if (!File.Exists(srcPathName)) { report = new FileLoadReport(srcPathName); report.Add(FileLoadItem.Type.Error, string.Format(Res.Strings.ERR_FILE_NOT_FOUND_FMT, srcPathName)); return(null); } string destFileName = ef.GenerateDllName(projectPathName); string destPathName = Path.Combine(GetPluginDirPath(), destFileName); // Compile if necessary. We do this if the source code is newer, or if the // DLLs that plugins depend on have been updated. (We're checking the dates on // the DLLs the app uses, not the copy the plugins use, but earlier we made sure // that they were the same. This test doesn't handle the case where the DLLs // get rolled back, but that's probably not interesting for us.) bool needCompile = FileUtil.IsFileMissingOrOlder(destPathName, srcPathName) || FileUtil.IsFileMissingOrOlder(destPathName, typeof(PluginCommon.PluginManager).Assembly.Location) || FileUtil.IsFileMissingOrOlder(destPathName, typeof(CommonUtil.CRC32).Assembly.Location); if (needCompile) { Debug.WriteLine("Compiling " + srcPathName + " to " + destPathName); Assembly asm = CompileCode(srcPathName, destPathName, out report); if (asm == null) { return(null); } } else { Debug.WriteLine("NOT recompiling " + srcPathName); report = new FileLoadReport(srcPathName); } return(destPathName); }
/// <summary> /// Loads platform symbols. /// </summary> /// <param name="fileIdent">Relative pathname of file to open.</param> /// <param name="report">Report of warnings and errors.</param> /// <returns>True on success (no errors), false on failure.</returns> public bool LoadFromFile(string fileIdent, string projectDir, out FileLoadReport report) { // These files shouldn't be enormous. Do it the easy way. report = new FileLoadReport(fileIdent); ExternalFile ef = ExternalFile.CreateFromIdent(fileIdent); if (ef == null) { report.Add(FileLoadItem.Type.Error, CommonUtil.Properties.Resources.ERR_FILE_NOT_FOUND + ": " + fileIdent); return(false); } string pathName = ef.GetPathName(projectDir); if (pathName == null) { report.Add(FileLoadItem.Type.Error, Properties.Resources.ERR_BAD_IDENT + ": " + fileIdent); return(false); } string[] lines; try { lines = File.ReadAllLines(pathName); } catch (IOException ioe) { Debug.WriteLine("Platform symbol load failed: " + ioe); report.Add(FileLoadItem.Type.Error, CommonUtil.Properties.Resources.ERR_FILE_NOT_FOUND + ": " + pathName); return(false); } string tag = string.Empty; int lineNum = 0; foreach (string line in lines) { lineNum++; // first line is line 1, says Vim and VisualStudio if (string.IsNullOrEmpty(line) || line[0] == ';') { // ignore } else if (line[0] == '*') { if (line.StartsWith(TAG_CMD)) { tag = ParseTag(line); } else { // Do something clever with *SYNOPSIS? Debug.WriteLine("CMD: " + line); } } else { MatchCollection matches = sNameValueRegex.Matches(line); if (matches.Count == 1) { //Debug.WriteLine("GOT '" + matches[0].Groups[1] + "' " + // matches[0].Groups[2] + " '" + matches[0].Groups[3] + "'"); string label = matches[0].Groups[1].Value; bool isConst = (matches[0].Groups[2].Value[0] == '='); string badParseMsg; int value, numBase; bool parseOk; if (isConst) { // Allow various numeric options, and preserve the value. parseOk = Asm65.Number.TryParseInt(matches[0].Groups[3].Value, out value, out numBase); badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_NUMERIC_CONSTANT; } else { // Allow things like "05/1000". Always hex. numBase = 16; parseOk = Asm65.Address.ParseAddress(matches[0].Groups[3].Value, (1 << 24) - 1, out value); badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_ADDRESS; } if (!parseOk) { report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning, badParseMsg); } else { string comment = matches[0].Groups[4].Value; if (comment.Length > 0) { // remove ';' comment = comment.Substring(1); } FormatDescriptor.SubType subType = FormatDescriptor.GetSubTypeForBase(numBase); DefSymbol symDef = new DefSymbol(label, value, Symbol.Source.Platform, isConst ? Symbol.Type.Constant : Symbol.Type.ExternalAddr, subType, comment, tag); if (mSymbols.ContainsKey(label)) { // This is very easy to do -- just define the same symbol twice // in the same file. We don't really need to do anything about // it though. Debug.WriteLine("NOTE: stomping previous definition of " + label); } mSymbols[label] = symDef; } } else { report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning, CommonUtil.Properties.Resources.ERR_SYNTAX); } } } return(!report.HasErrors); }
/// <summary> /// Loads platform symbols. /// </summary> /// <param name="fileIdent">External file identifier of symbol file.</param> /// <param name="projectDir">Full path to project directory.</param> /// <param name="loadOrdinal">Platform file load order.</param> /// <param name="report">Report of warnings and errors.</param> /// <returns>True on success (no errors), false on failure.</returns> public bool LoadFromFile(string fileIdent, string projectDir, int loadOrdinal, out FileLoadReport report) { report = new FileLoadReport(fileIdent); ExternalFile ef = ExternalFile.CreateFromIdent(fileIdent); if (ef == null) { report.Add(FileLoadItem.Type.Error, CommonUtil.Properties.Resources.ERR_FILE_NOT_FOUND + ": " + fileIdent); return(false); } string pathName = ef.GetPathName(projectDir); if (pathName == null) { report.Add(FileLoadItem.Type.Error, Res.Strings.ERR_BAD_IDENT + ": " + fileIdent); return(false); } // These files shouldn't be enormous. Just read the entire thing into a string array. string[] lines; try { lines = File.ReadAllLines(pathName); } catch (IOException ioe) { Debug.WriteLine("Platform symbol load failed: " + ioe); report.Add(FileLoadItem.Type.Error, CommonUtil.Properties.Resources.ERR_FILE_NOT_FOUND + ": " + pathName); return(false); } string tag = string.Empty; DefSymbol.MultiAddressMask multiMask = null; int lineNum = 0; foreach (string line in lines) { lineNum++; // first line is line 1, says Vim and VisualStudio if (string.IsNullOrEmpty(line) || line[0] == ';') { // ignore } else if (line[0] == '*') { if (line.StartsWith(TAG_CMD)) { tag = ParseTag(line); } else if (line.StartsWith(MULTI_MASK_CMD)) { if (!ParseMask(line, out multiMask, out string badMaskMsg)) { report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning, badMaskMsg); } //Debug.WriteLine("Mask is now " + mask.ToString("x6")); } else { // Do something clever with *SYNOPSIS? Debug.WriteLine("Ignoring CMD: " + line); } } else { MatchCollection matches = sNameValueRegex.Matches(line); if (matches.Count == 1) { string label = matches[0].Groups[GROUP_NAME].Value; char typeAndDir = matches[0].Groups[GROUP_TYPE].Value[0]; bool isConst = (typeAndDir == '='); DefSymbol.DirectionFlags direction = DefSymbol.DirectionFlags.ReadWrite; if (typeAndDir == '<') { direction = DefSymbol.DirectionFlags.Read; } else if (typeAndDir == '>') { direction = DefSymbol.DirectionFlags.Write; } string badParseMsg; int value, numBase; bool parseOk; string valueStr = matches[0].Groups[GROUP_VALUE].Value; if (isConst) { // Allow various numeric options, and preserve the value. We // don't limit the value range. parseOk = Asm65.Number.TryParseInt(valueStr, out value, out numBase); badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_NUMERIC_CONSTANT; } else if (valueStr.ToUpperInvariant().Equals(ERASE_VALUE_STR)) { parseOk = true; value = ERASE_VALUE; numBase = 10; badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_ADDRESS; } else { // Allow things like "05/1000". Always hex. numBase = 16; parseOk = Asm65.Address.ParseAddress(valueStr, (1 << 24) - 1, out value); // limit to positive 24-bit values parseOk &= (value >= 0 && value < 0x01000000); badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_ADDRESS; } int width = -1; string widthStr = matches[0].Groups[GROUP_WIDTH].Value; if (parseOk && !string.IsNullOrEmpty(widthStr)) { parseOk = Asm65.Number.TryParseInt(widthStr, out width, out int ignoredBase); if (parseOk) { if (width < DefSymbol.MIN_WIDTH || width > DefSymbol.MAX_WIDTH) { parseOk = false; badParseMsg = Res.Strings.ERR_INVALID_WIDTH; } } else { badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_NUMERIC_CONSTANT; } } if (parseOk && multiMask != null && !isConst) { // We need to ensure that all possible values fit within the mask. // We don't test AddressValue here, because it's okay for the // canonical value to be outside the masked range. int testWidth = (width > 0) ? width : 1; for (int testValue = value; testValue < value + testWidth; testValue++) { if ((testValue & multiMask.CompareMask) != multiMask.CompareValue) { parseOk = false; badParseMsg = Res.Strings.ERR_VALUE_INCOMPATIBLE_WITH_MASK; Debug.WriteLine("Mask FAIL: value=" + value.ToString("x6") + " width=" + width + " testValue=" + testValue.ToString("x6") + " mask=" + multiMask); break; } } } if (!parseOk) { report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning, badParseMsg); } else { string comment = matches[0].Groups[GROUP_COMMENT].Value; if (comment.Length > 0) { // remove ';' comment = comment.Substring(1); } FormatDescriptor.SubType subType = FormatDescriptor.GetSubTypeForBase(numBase); DefSymbol symDef = new DefSymbol(label, value, Symbol.Source.Platform, isConst ? Symbol.Type.Constant : Symbol.Type.ExternalAddr, subType, width, width > 0, comment, direction, multiMask, tag, loadOrdinal, fileIdent); if (mSymbols.ContainsKey(label)) { // This is very easy to do -- just define the same symbol twice // in the same file. We don't really need to do anything about // it though. Debug.WriteLine("NOTE: stomping previous definition of " + label); } mSymbols[label] = symDef; } } else { report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning, CommonUtil.Properties.Resources.ERR_SYNTAX); } } } return(!report.HasErrors); }
/// <summary> /// Creates a FormatDescriptor from a SerFormatDescriptor. /// </summary> /// <param name="sfd">Deserialized data.</param> /// <param name="version">Serialization version (CONTENT_VERSION).</param> /// <param name="report">Error report object.</param> /// <param name="dfd">Created FormatDescriptor.</param> /// <returns>True on success.</returns> private static bool CreateFormatDescriptor(SerFormatDescriptor sfd, int version, FileLoadReport report, out FormatDescriptor dfd) { dfd = null; FormatDescriptor.Type format; FormatDescriptor.SubType subFormat; if ("String".Equals(sfd.Format)) { // File version 1 used a different set of enumerated values for defining strings. // Parse it out here. Debug.Assert(version <= 1); subFormat = FormatDescriptor.SubType.ASCII_GENERIC; if ("None".Equals(sfd.SubFormat)) { format = FormatDescriptor.Type.StringGeneric; } else if ("Reverse".Equals(sfd.SubFormat)) { format = FormatDescriptor.Type.StringReverse; } else if ("CString".Equals(sfd.SubFormat)) { format = FormatDescriptor.Type.StringNullTerm; } else if ("L8String".Equals(sfd.SubFormat)) { format = FormatDescriptor.Type.StringL8; } else if ("L16String".Equals(sfd.SubFormat)) { format = FormatDescriptor.Type.StringL16; } else if ("Dci".Equals(sfd.SubFormat)) { format = FormatDescriptor.Type.StringDci; } else if ("DciReverse".Equals(sfd.SubFormat)) { // No longer supported. Nobody ever used this but the regression tests, // though, so there's no reason to handle this nicely. format = FormatDescriptor.Type.Dense; subFormat = FormatDescriptor.SubType.None; } else { // No idea what this is; output as dense hex. format = FormatDescriptor.Type.Dense; subFormat = FormatDescriptor.SubType.None; } Debug.WriteLine("Found v1 string, fmt=" + format + ", sub=" + subFormat); dfd = FormatDescriptor.Create(sfd.Length, format, subFormat); return(dfd != null); } try { format = (FormatDescriptor.Type)Enum.Parse( typeof(FormatDescriptor.Type), sfd.Format); if (version <= 1 && "Ascii".Equals(sfd.SubFormat)) { // File version 1 used "Ascii" for all character data in numeric operands. // It applied to both low and high ASCII. subFormat = FormatDescriptor.SubType.ASCII_GENERIC; Debug.WriteLine("Found v1 char, fmt=" + sfd.Format + ", sub=" + sfd.SubFormat); } else { subFormat = (FormatDescriptor.SubType)Enum.Parse( typeof(FormatDescriptor.SubType), sfd.SubFormat); } } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_FD_FORMAT + ": " + sfd.Format + "/" + sfd.SubFormat); return(false); } if (sfd.SymbolRef == null) { dfd = FormatDescriptor.Create(sfd.Length, format, subFormat); } else { WeakSymbolRef.Part part; try { part = (WeakSymbolRef.Part)Enum.Parse( typeof(WeakSymbolRef.Part), sfd.SymbolRef.Part); } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_SYMREF_PART + ": " + sfd.SymbolRef.Part); return(false); } dfd = FormatDescriptor.Create(sfd.Length, new WeakSymbolRef(sfd.SymbolRef.Label, part), format == FormatDescriptor.Type.NumericBE); } return(dfd != null); }