mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08cd768260 | |||
| 8acaaf528c | |||
| 6e881a74e1 | |||
| cc1bc44e69 | |||
| c7b85518ab | |||
| bb039a1445 | |||
| 8fc142d316 | |||
| b0123b3f83 | |||
| 2ec17aee9b | |||
| ec9731e1e5 | |||
| a06382c27e | |||
| e013638c5a | |||
| 70ab82f1f7 | |||
| c5896689cf | |||
| 67719dd93e | |||
| 258e141017 | |||
| dbdda55065 | |||
| a43320af1f | |||
| 35bbd40f5d | |||
| c1cb4b0b19 | |||
| 2379853faa | |||
| 5e1aff180f |
@@ -110,11 +110,6 @@ jobs:
|
||||
name: linux-aarch64
|
||||
build: zigbuild
|
||||
features: default
|
||||
- platform: ubuntu-latest
|
||||
target: armv7-unknown-linux-musleabi
|
||||
name: linux-armv7l
|
||||
build: zigbuild
|
||||
features: default
|
||||
- platform: windows-latest
|
||||
target: i686-pc-windows-msvc
|
||||
name: windows-x86
|
||||
|
||||
Generated
+521
-629
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -13,7 +13,7 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "2.0.0"
|
||||
version = "2.2.1"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -11,7 +11,6 @@ description = """
|
||||
A local diffing tool for decompilation projects.
|
||||
"""
|
||||
publish = false
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
@@ -29,3 +28,6 @@ supports-color = "3.0"
|
||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
mimalloc = "0.1"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
fn main() {
|
||||
let output = std::process::Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.expect("Failed to execute git");
|
||||
let rev = String::from_utf8(output.stdout).expect("Failed to parse git output");
|
||||
println!("cargo:rustc-env=GIT_COMMIT_SHA={rev}");
|
||||
println!("cargo:rustc-rerun-if-changed=.git/HEAD");
|
||||
}
|
||||
@@ -31,10 +31,9 @@ where T: FromArgs
|
||||
Ok(v) => {
|
||||
if v.version {
|
||||
println!(
|
||||
"{} {} {}",
|
||||
"{} {}",
|
||||
command_name.first().unwrap_or(&""),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
env!("GIT_COMMIT_SHA"),
|
||||
);
|
||||
std::process::exit(0);
|
||||
} else {
|
||||
|
||||
@@ -199,7 +199,7 @@ fn report_object(
|
||||
.unwrap_or_default(),
|
||||
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
||||
};
|
||||
let mut measures = Measures::default();
|
||||
let mut measures = Measures { total_units: 1, ..Default::default() };
|
||||
let mut sections = vec![];
|
||||
let mut functions = vec![];
|
||||
|
||||
@@ -237,7 +237,7 @@ fn report_object(
|
||||
}
|
||||
|
||||
for (symbol, symbol_diff) in section.symbols.iter().zip(§ion_diff.symbols) {
|
||||
if symbol.size == 0 {
|
||||
if symbol.size == 0 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
|
||||
continue;
|
||||
}
|
||||
if let Some(existing_functions) = &mut existing_functions {
|
||||
@@ -280,6 +280,7 @@ fn report_object(
|
||||
if metadata.complete.unwrap_or(false) {
|
||||
measures.complete_code = measures.total_code;
|
||||
measures.complete_data = measures.total_data;
|
||||
measures.complete_units = 1;
|
||||
}
|
||||
measures.calc_fuzzy_match_percent();
|
||||
measures.calc_matched_percent();
|
||||
|
||||
@@ -2,6 +2,12 @@ mod argp_version;
|
||||
mod cmd;
|
||||
mod util;
|
||||
|
||||
// musl's allocator is very slow, so use mimalloc when targeting musl.
|
||||
// Otherwise, use the system allocator to avoid extra code size.
|
||||
#[cfg(target_env = "musl")]
|
||||
#[global_allocator]
|
||||
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, str::FromStr};
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
|
||||
@@ -27,6 +27,9 @@ arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
|
||||
bindings = ["serde_json", "prost", "pbjson"]
|
||||
wasm = ["bindings", "console_error_panic_hook", "console_log"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["all"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
byteorder = "1.5"
|
||||
@@ -57,7 +60,7 @@ gimli = { version = "0.31", default-features = false, features = ["read-all"], o
|
||||
|
||||
# ppc
|
||||
cwdemangle = { version = "1.0", optional = true }
|
||||
cwextab = { version = "0.2", optional = true }
|
||||
cwextab = { version = "0.3", optional = true }
|
||||
ppc750cl = { version = "0.3", optional = true }
|
||||
|
||||
# mips
|
||||
|
||||
Binary file not shown.
@@ -32,6 +32,10 @@ message Measures {
|
||||
uint64 complete_data = 13;
|
||||
// Completed (or "linked") data percent
|
||||
float complete_data_percent = 14;
|
||||
// Total number of units
|
||||
uint32 total_units = 15;
|
||||
// Completed (or "linked") units
|
||||
uint32 complete_units = 16;
|
||||
}
|
||||
|
||||
// Project progress report
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::{borrow::Cow, collections::BTreeMap};
|
||||
use std::{borrow::Cow, collections::BTreeMap, ffi::CStr};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use byteorder::ByteOrder;
|
||||
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
|
||||
|
||||
use crate::{
|
||||
diff::DiffObjConfig,
|
||||
obj::{ObjIns, ObjReloc, ObjSection},
|
||||
util::ReallySigned,
|
||||
};
|
||||
|
||||
#[cfg(feature = "arm")]
|
||||
@@ -17,6 +19,97 @@ pub mod ppc;
|
||||
#[cfg(feature = "x86")]
|
||||
pub mod x86;
|
||||
|
||||
/// Represents the type of data associated with an instruction
|
||||
pub enum DataType {
|
||||
Int8,
|
||||
Int16,
|
||||
Int32,
|
||||
Int64,
|
||||
Int128,
|
||||
Float,
|
||||
Double,
|
||||
Bytes,
|
||||
String,
|
||||
}
|
||||
|
||||
impl DataType {
|
||||
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
|
||||
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self {
|
||||
DataType::Int8 => {
|
||||
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
|
||||
if i < 0 {
|
||||
format!("Int8: {:#x} ({:#x})", i, ReallySigned(i))
|
||||
} else {
|
||||
format!("Int8: {:#x}", i)
|
||||
}
|
||||
}
|
||||
DataType::Int16 => {
|
||||
let i = Endian::read_i16(bytes);
|
||||
if i < 0 {
|
||||
format!("Int16: {:#x} ({:#x})", i, ReallySigned(i))
|
||||
} else {
|
||||
format!("Int16: {:#x}", i)
|
||||
}
|
||||
}
|
||||
DataType::Int32 => {
|
||||
let i = Endian::read_i32(bytes);
|
||||
if i < 0 {
|
||||
format!("Int32: {:#x} ({:#x})", i, ReallySigned(i))
|
||||
} else {
|
||||
format!("Int32: {:#x}", i)
|
||||
}
|
||||
}
|
||||
DataType::Int64 => {
|
||||
let i = Endian::read_i64(bytes);
|
||||
if i < 0 {
|
||||
format!("Int64: {:#x} ({:#x})", i, ReallySigned(i))
|
||||
} else {
|
||||
format!("Int64: {:#x}", i)
|
||||
}
|
||||
}
|
||||
DataType::Int128 => {
|
||||
let i = Endian::read_i128(bytes);
|
||||
if i < 0 {
|
||||
format!("Int128: {:#x} ({:#x})", i, ReallySigned(i))
|
||||
} else {
|
||||
format!("Int128: {:#x}", i)
|
||||
}
|
||||
}
|
||||
DataType::Float => {
|
||||
format!("Float: {}", Endian::read_f32(bytes))
|
||||
}
|
||||
DataType::Double => {
|
||||
format!("Double: {}", Endian::read_f64(bytes))
|
||||
}
|
||||
DataType::Bytes => {
|
||||
format!("Bytes: {:#?}", bytes)
|
||||
}
|
||||
DataType::String => {
|
||||
format!("String: {:?}", CStr::from_bytes_until_nul(bytes).ok()?)
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn required_len(&self) -> Option<usize> {
|
||||
match self {
|
||||
DataType::Int8 => Some(1),
|
||||
DataType::Int16 => Some(2),
|
||||
DataType::Int32 => Some(4),
|
||||
DataType::Int64 => Some(8),
|
||||
DataType::Int128 => Some(16),
|
||||
DataType::Float => Some(4),
|
||||
DataType::Double => Some(8),
|
||||
DataType::Bytes => None,
|
||||
DataType::String => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ObjArch: Send + Sync {
|
||||
fn process_code(
|
||||
&self,
|
||||
@@ -42,6 +135,12 @@ pub trait ObjArch: Send + Sync {
|
||||
|
||||
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
|
||||
|
||||
fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None }
|
||||
|
||||
fn display_data_type(&self, _ty: DataType, bytes: &[u8]) -> Option<String> {
|
||||
Some(format!("Bytes: {:#x?}", bytes))
|
||||
}
|
||||
|
||||
// Downcast methods
|
||||
#[cfg(feature = "ppc")]
|
||||
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None }
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use std::{borrow::Cow, collections::BTreeMap};
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use byteorder::BigEndian;
|
||||
use cwextab::{decode_extab, ExceptionTableData};
|
||||
use object::{
|
||||
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
|
||||
Symbol, SymbolKind,
|
||||
};
|
||||
use ppc750cl::{Argument, InsIter, GPR};
|
||||
use ppc750cl::{Argument, InsIter, Opcode, GPR};
|
||||
|
||||
use crate::{
|
||||
arch::{ObjArch, ProcessCodeResult},
|
||||
arch::{DataType, ObjArch, ProcessCodeResult},
|
||||
diff::DiffObjConfig,
|
||||
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, ObjSymbol},
|
||||
};
|
||||
@@ -186,6 +187,34 @@ impl ObjArch for ObjArchPpc {
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
|
||||
// Always shows the first string of the table. Not ideal, but it's really hard to find
|
||||
// the actual string being referenced.
|
||||
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) {
|
||||
return Some(DataType::String);
|
||||
}
|
||||
|
||||
match Opcode::from(instruction.op as u8) {
|
||||
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
|
||||
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
|
||||
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
|
||||
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
|
||||
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
|
||||
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
|
||||
|
||||
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
|
||||
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
|
||||
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
|
||||
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
|
||||
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
|
||||
ty.display_bytes::<BigEndian>(bytes)
|
||||
}
|
||||
|
||||
fn ppc(&self) -> Option<&ObjArchPpc> { Some(self) }
|
||||
}
|
||||
|
||||
@@ -303,9 +332,13 @@ fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, Excep
|
||||
continue;
|
||||
};
|
||||
let data = match decode_extab(extab_data) {
|
||||
Some(decoded_data) => decoded_data,
|
||||
None => {
|
||||
log::warn!("Exception table decoding failed for function {}", extab_func_name);
|
||||
Ok(decoded_data) => decoded_data,
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"Exception table decoding failed for function {}, reason: {}",
|
||||
extab_func_name,
|
||||
e.to_string()
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,9 +8,10 @@ use serde_json::error::Category;
|
||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
||||
|
||||
pub const REPORT_VERSION: u32 = 1;
|
||||
pub const REPORT_VERSION: u32 = 2;
|
||||
|
||||
impl Report {
|
||||
/// Attempts to parse the report as binary protobuf or JSON.
|
||||
pub fn parse(data: &[u8]) -> Result<Self> {
|
||||
if data.is_empty() {
|
||||
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
|
||||
@@ -25,6 +26,7 @@ impl Report {
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
|
||||
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
||||
match serde_json::from_slice::<Self>(bytes) {
|
||||
Ok(report) => Ok(report),
|
||||
@@ -43,16 +45,23 @@ impl Report {
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrates the report to the latest version.
|
||||
/// Fails if the report version is newer than supported.
|
||||
pub fn migrate(&mut self) -> Result<()> {
|
||||
if self.version == 0 {
|
||||
self.migrate_v0()?;
|
||||
}
|
||||
if self.version == 1 {
|
||||
self.migrate_v1()?;
|
||||
}
|
||||
if self.version != REPORT_VERSION {
|
||||
bail!("Unsupported report version: {}", self.version);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds `complete_code`, `complete_data`, `complete_code_percent`, and `complete_data_percent`
|
||||
/// to measures, and sets `progress_categories` in unit metadata.
|
||||
fn migrate_v0(&mut self) -> Result<()> {
|
||||
let Some(measures) = &mut self.measures else {
|
||||
bail!("Missing measures in report");
|
||||
@@ -61,15 +70,16 @@ impl Report {
|
||||
let Some(unit_measures) = &mut unit.measures else {
|
||||
bail!("Missing measures in report unit");
|
||||
};
|
||||
let Some(metadata) = &mut unit.metadata else {
|
||||
bail!("Missing metadata in report unit");
|
||||
let mut complete = false;
|
||||
if let Some(metadata) = &mut unit.metadata {
|
||||
if metadata.module_name.is_some() || metadata.module_id.is_some() {
|
||||
metadata.progress_categories = vec!["modules".to_string()];
|
||||
} else {
|
||||
metadata.progress_categories = vec!["dol".to_string()];
|
||||
}
|
||||
complete = metadata.complete.unwrap_or(false);
|
||||
};
|
||||
if metadata.module_name.is_some() || metadata.module_id.is_some() {
|
||||
metadata.progress_categories = vec!["modules".to_string()];
|
||||
} else {
|
||||
metadata.progress_categories = vec!["dol".to_string()];
|
||||
}
|
||||
if metadata.complete.unwrap_or(false) {
|
||||
if complete {
|
||||
unit_measures.complete_code = unit_measures.total_code;
|
||||
unit_measures.complete_data = unit_measures.total_data;
|
||||
unit_measures.complete_code_percent = 100.0;
|
||||
@@ -84,10 +94,42 @@ impl Report {
|
||||
measures.complete_data += unit_measures.complete_data;
|
||||
}
|
||||
measures.calc_matched_percent();
|
||||
self.calculate_progress_categories();
|
||||
self.version = 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds `total_units` and `complete_units` to measures.
|
||||
fn migrate_v1(&mut self) -> Result<()> {
|
||||
let Some(total_measures) = &mut self.measures else {
|
||||
bail!("Missing measures in report");
|
||||
};
|
||||
for unit in &mut self.units {
|
||||
let Some(measures) = &mut unit.measures else {
|
||||
bail!("Missing measures in report unit");
|
||||
};
|
||||
let complete = unit.metadata.as_ref().and_then(|m| m.complete).unwrap_or(false) as u32;
|
||||
let progress_categories =
|
||||
unit.metadata.as_ref().map(|m| m.progress_categories.as_slice()).unwrap_or(&[]);
|
||||
measures.total_units = 1;
|
||||
measures.complete_units = complete;
|
||||
total_measures.total_units += 1;
|
||||
total_measures.complete_units += complete;
|
||||
for id in progress_categories {
|
||||
if let Some(category) = self.categories.iter_mut().find(|c| &c.id == id) {
|
||||
let Some(measures) = &mut category.measures else {
|
||||
bail!("Missing measures in category");
|
||||
};
|
||||
measures.total_units += 1;
|
||||
measures.complete_units += complete;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.version = 2;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Calculate progress categories based on unit metadata.
|
||||
pub fn calculate_progress_categories(&mut self) {
|
||||
for unit in &self.units {
|
||||
let Some(metadata) = unit.metadata.as_ref() else {
|
||||
@@ -117,6 +159,72 @@ impl Report {
|
||||
measures.calc_matched_percent();
|
||||
}
|
||||
}
|
||||
|
||||
/// Split the report into multiple reports based on progress categories.
|
||||
/// Assumes progress categories are in the format `version`, `version.category`.
|
||||
/// This is a hack for projects that generate all versions in a single report.
|
||||
pub fn split(self) -> Vec<(String, Report)> {
|
||||
let mut reports = Vec::new();
|
||||
// Map units to Option to allow taking ownership
|
||||
let mut units = self.units.into_iter().map(Some).collect::<Vec<_>>();
|
||||
for category in &self.categories {
|
||||
if category.id.contains(".") {
|
||||
// Skip subcategories
|
||||
continue;
|
||||
}
|
||||
fn is_sub_category(id: &str, parent: &str, sep: char) -> bool {
|
||||
id.starts_with(parent)
|
||||
&& id.get(parent.len()..).map_or(false, |s| s.starts_with(sep))
|
||||
}
|
||||
let mut sub_categories = self
|
||||
.categories
|
||||
.iter()
|
||||
.filter(|c| is_sub_category(&c.id, &category.id, '.'))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
// Remove category prefix
|
||||
for sub_category in &mut sub_categories {
|
||||
sub_category.id = sub_category.id[category.id.len() + 1..].to_string();
|
||||
}
|
||||
let mut sub_units = units
|
||||
.iter_mut()
|
||||
.filter_map(|opt| {
|
||||
let unit = opt.as_mut()?;
|
||||
let metadata = unit.metadata.as_ref()?;
|
||||
if metadata.progress_categories.contains(&category.id) {
|
||||
opt.take()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for sub_unit in &mut sub_units {
|
||||
// Remove leading version/ from unit name
|
||||
if let Some(name) =
|
||||
sub_unit.name.strip_prefix(&category.id).and_then(|s| s.strip_prefix('/'))
|
||||
{
|
||||
sub_unit.name = name.to_string();
|
||||
}
|
||||
// Filter progress categories
|
||||
let Some(metadata) = sub_unit.metadata.as_mut() else {
|
||||
continue;
|
||||
};
|
||||
metadata.progress_categories = metadata
|
||||
.progress_categories
|
||||
.iter()
|
||||
.filter(|c| is_sub_category(c, &category.id, '.'))
|
||||
.map(|c| c[category.id.len() + 1..].to_string())
|
||||
.collect();
|
||||
}
|
||||
reports.push((category.id.clone(), Report {
|
||||
measures: category.measures,
|
||||
units: sub_units,
|
||||
version: self.version,
|
||||
categories: sub_categories,
|
||||
}));
|
||||
}
|
||||
reports
|
||||
}
|
||||
}
|
||||
|
||||
impl Measures {
|
||||
@@ -176,6 +284,8 @@ impl AddAssign for Measures {
|
||||
self.matched_functions += other.matched_functions;
|
||||
self.complete_code += other.complete_code;
|
||||
self.complete_data += other.complete_data;
|
||||
self.total_units += other.total_units;
|
||||
self.complete_units += other.complete_units;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
io::{BufReader, Read},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
@@ -124,6 +124,10 @@ impl ProjectObject {
|
||||
pub fn hidden(&self) -> bool {
|
||||
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn source_path(&self) -> Option<&String> {
|
||||
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
@@ -156,7 +160,7 @@ pub struct ProjectConfigInfo {
|
||||
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
||||
for filename in CONFIG_FILENAMES.iter() {
|
||||
let config_path = dir.join(filename);
|
||||
let Ok(mut file) = File::open(&config_path) else {
|
||||
let Ok(file) = File::open(&config_path) else {
|
||||
continue;
|
||||
};
|
||||
let metadata = file.metadata();
|
||||
@@ -165,9 +169,10 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
||||
continue;
|
||||
}
|
||||
let ts = FileTime::from_last_modification_time(&metadata);
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut result = match filename.contains("json") {
|
||||
true => read_json_config(&mut file),
|
||||
false => read_yml_config(&mut file),
|
||||
true => read_json_config(&mut reader),
|
||||
false => read_yml_config(&mut reader),
|
||||
};
|
||||
if let Ok(config) = &result {
|
||||
// Validate min_version if present
|
||||
|
||||
@@ -126,6 +126,7 @@ pub struct ObjSymbol {
|
||||
pub virtual_address: Option<u64>,
|
||||
/// Original index in object symbol table
|
||||
pub original_index: Option<usize>,
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct ObjInfo {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use std::{collections::HashSet, fs, io::Cursor, mem::size_of, path::Path};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
io::Cursor,
|
||||
mem::size_of,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use filetime::FileTime;
|
||||
@@ -78,6 +84,16 @@ fn to_obj_symbol(
|
||||
let virtual_address = split_meta
|
||||
.and_then(|m| m.virtual_addresses.as_ref())
|
||||
.and_then(|v| v.get(symbol.index().0).cloned());
|
||||
|
||||
let bytes = symbol
|
||||
.section_index()
|
||||
.and_then(|idx| obj_file.section_by_index(idx).ok())
|
||||
.and_then(|section| section.data().ok())
|
||||
.and_then(|data| {
|
||||
data.get(section_address as usize..(section_address + symbol.size()) as usize)
|
||||
})
|
||||
.unwrap_or(&[]);
|
||||
|
||||
Ok(ObjSymbol {
|
||||
name: name.to_string(),
|
||||
demangled_name,
|
||||
@@ -89,6 +105,7 @@ fn to_obj_symbol(
|
||||
addend,
|
||||
virtual_address,
|
||||
original_index: Some(symbol.index().0),
|
||||
bytes: bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -136,6 +153,7 @@ fn symbols_by_section(
|
||||
obj_file: &File<'_>,
|
||||
section: &ObjSection,
|
||||
split_meta: Option<&SplitMeta>,
|
||||
name_counts: &mut HashMap<String, u32>,
|
||||
) -> Result<Vec<ObjSymbol>> {
|
||||
let mut result = Vec::<ObjSymbol>::new();
|
||||
for symbol in obj_file.symbols() {
|
||||
@@ -168,8 +186,14 @@ fn symbols_by_section(
|
||||
}
|
||||
if result.is_empty() {
|
||||
// Dummy symbol for empty sections
|
||||
*name_counts.entry(section.name.clone()).or_insert(0) += 1;
|
||||
let current_count: u32 = *name_counts.get(§ion.name).unwrap();
|
||||
result.push(ObjSymbol {
|
||||
name: format!("[{}]", section.name),
|
||||
name: if current_count > 1 {
|
||||
format!("[{} ({})]", section.name, current_count)
|
||||
} else {
|
||||
format!("[{}]", section.name)
|
||||
},
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
section_address: 0,
|
||||
@@ -179,6 +203,7 @@ fn symbols_by_section(
|
||||
addend: 0,
|
||||
virtual_address: None,
|
||||
original_index: None,
|
||||
bytes: Vec::new(),
|
||||
});
|
||||
}
|
||||
Ok(result)
|
||||
@@ -239,6 +264,7 @@ fn find_section_symbol(
|
||||
addend: offset_addr as i64,
|
||||
virtual_address: None,
|
||||
original_index: None,
|
||||
bytes: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -521,6 +547,7 @@ fn update_combined_symbol(symbol: ObjSymbol, address_change: i64) -> Result<ObjS
|
||||
None
|
||||
},
|
||||
original_index: symbol.original_index,
|
||||
bytes: symbol.bytes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -621,9 +648,15 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
|
||||
let arch = new_arch(&obj_file)?;
|
||||
let split_meta = split_meta(&obj_file)?;
|
||||
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
|
||||
let mut name_counts: HashMap<String, u32> = HashMap::new();
|
||||
for section in &mut sections {
|
||||
section.symbols =
|
||||
symbols_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
|
||||
section.symbols = symbols_by_section(
|
||||
arch.as_ref(),
|
||||
&obj_file,
|
||||
section,
|
||||
split_meta.as_ref(),
|
||||
&mut name_counts,
|
||||
)?;
|
||||
section.relocations =
|
||||
relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
|
||||
}
|
||||
|
||||
+10
-10
@@ -29,10 +29,10 @@ bytes = "1.7"
|
||||
cfg-if = "1.0"
|
||||
const_format = "0.2"
|
||||
cwdemangle = "1.0"
|
||||
cwextab = "0.2"
|
||||
cwextab = "0.3.1"
|
||||
dirs = "5.0"
|
||||
egui = "0.28"
|
||||
egui_extras = "0.28"
|
||||
egui = "0.29"
|
||||
egui_extras = "0.29"
|
||||
filetime = "0.2"
|
||||
float-ord = "0.3"
|
||||
font-kit = "0.14"
|
||||
@@ -40,10 +40,11 @@ globset = { version = "0.4", features = ["serde1"] }
|
||||
log = "0.4"
|
||||
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2" }
|
||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||
open = "5.3"
|
||||
png = "0.17"
|
||||
pollster = "0.3"
|
||||
regex = "1.10"
|
||||
rfd = { version = "0.14" } #, default-features = false, features = ['xdg-portal']
|
||||
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
|
||||
rlwinmdec = "1.0"
|
||||
ron = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -55,7 +56,7 @@ time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
|
||||
# Keep version in sync with egui
|
||||
[dependencies.eframe]
|
||||
version = "0.28"
|
||||
version = "0.29"
|
||||
features = [
|
||||
"default_fonts",
|
||||
"persistence",
|
||||
@@ -66,7 +67,7 @@ default-features = false
|
||||
|
||||
# Keep version in sync with eframe
|
||||
[dependencies.wgpu]
|
||||
version = "0.20"
|
||||
version = "22.1"
|
||||
features = [
|
||||
"dx12",
|
||||
"metal",
|
||||
@@ -89,9 +90,6 @@ self_update = "0.41"
|
||||
path-slash = "0.2"
|
||||
winapi = "0.3"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
exec = "0.3"
|
||||
|
||||
@@ -106,4 +104,6 @@ tracing-wasm = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0"
|
||||
vergen-gitcl = { version = "1.0", features = ["build", "cargo"] }
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
tauri-winres = "0.1"
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use anyhow::Result;
|
||||
use vergen_gitcl::{BuildBuilder, CargoBuilder, Emitter, GitclBuilder};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
winres::WindowsResource::new().set_icon("assets/icon.ico").compile()?;
|
||||
let mut res = tauri_winres::WindowsResource::new();
|
||||
res.set_icon("assets/icon.ico");
|
||||
res.set_language(0x0409); // US English
|
||||
res.compile()?;
|
||||
}
|
||||
Emitter::default()
|
||||
.add_instructions(&BuildBuilder::all_build()?)?
|
||||
.add_instructions(&CargoBuilder::all_cargo()?)?
|
||||
.add_instructions(&GitclBuilder::all_git()?)?
|
||||
.emit()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+131
-111
@@ -7,6 +7,7 @@ use std::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use filetime::FileTime;
|
||||
@@ -39,7 +40,7 @@ use crate::{
|
||||
frame_history::FrameHistory,
|
||||
function_diff::function_diff_ui,
|
||||
graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
|
||||
jobs::jobs_ui,
|
||||
jobs::{jobs_menu_ui, jobs_window},
|
||||
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
|
||||
symbol_diff::{symbol_diff_ui, DiffViewState, View},
|
||||
},
|
||||
@@ -61,6 +62,7 @@ pub struct ViewState {
|
||||
pub show_arch_config: bool,
|
||||
pub show_debug: bool,
|
||||
pub show_graphics: bool,
|
||||
pub show_jobs: bool,
|
||||
}
|
||||
|
||||
/// The configuration for a single object file.
|
||||
@@ -72,6 +74,7 @@ pub struct ObjectConfig {
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
pub complete: Option<bool>,
|
||||
pub scratch: Option<ScratchConfig>,
|
||||
pub source_path: Option<String>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -82,6 +85,36 @@ fn default_watch_patterns() -> Vec<Glob> {
|
||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub config: AppConfig,
|
||||
pub objects: Vec<ProjectObject>,
|
||||
pub object_nodes: Vec<ProjectObjectNode>,
|
||||
pub watcher_change: bool,
|
||||
pub config_change: bool,
|
||||
pub obj_change: bool,
|
||||
pub queue_build: bool,
|
||||
pub queue_reload: bool,
|
||||
pub project_config_info: Option<ProjectConfigInfo>,
|
||||
pub last_mod_check: Instant,
|
||||
}
|
||||
|
||||
impl Default for AppState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
config: Default::default(),
|
||||
objects: vec![],
|
||||
object_nodes: vec![],
|
||||
watcher_change: false,
|
||||
config_change: false,
|
||||
obj_change: false,
|
||||
queue_build: false,
|
||||
queue_reload: false,
|
||||
project_config_info: None,
|
||||
last_mod_check: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub struct AppConfig {
|
||||
// TODO: https://github.com/ron-rs/ron/pull/455
|
||||
@@ -116,23 +149,6 @@ pub struct AppConfig {
|
||||
pub recent_projects: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub diff_obj_config: DiffObjConfig,
|
||||
|
||||
#[serde(skip)]
|
||||
pub objects: Vec<ProjectObject>,
|
||||
#[serde(skip)]
|
||||
pub object_nodes: Vec<ProjectObjectNode>,
|
||||
#[serde(skip)]
|
||||
pub watcher_change: bool,
|
||||
#[serde(skip)]
|
||||
pub config_change: bool,
|
||||
#[serde(skip)]
|
||||
pub obj_change: bool,
|
||||
#[serde(skip)]
|
||||
pub queue_build: bool,
|
||||
#[serde(skip)]
|
||||
pub queue_reload: bool,
|
||||
#[serde(skip)]
|
||||
pub project_config_info: Option<ProjectConfigInfo>,
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
@@ -153,30 +169,22 @@ impl Default for AppConfig {
|
||||
watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(),
|
||||
recent_projects: vec![],
|
||||
diff_obj_config: Default::default(),
|
||||
objects: vec![],
|
||||
object_nodes: vec![],
|
||||
watcher_change: false,
|
||||
config_change: false,
|
||||
obj_change: false,
|
||||
queue_build: false,
|
||||
queue_reload: false,
|
||||
project_config_info: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
impl AppState {
|
||||
pub fn set_project_dir(&mut self, path: PathBuf) {
|
||||
self.recent_projects.retain(|p| p != &path);
|
||||
if self.recent_projects.len() > 9 {
|
||||
self.recent_projects.truncate(9);
|
||||
self.config.recent_projects.retain(|p| p != &path);
|
||||
if self.config.recent_projects.len() > 9 {
|
||||
self.config.recent_projects.truncate(9);
|
||||
}
|
||||
self.recent_projects.insert(0, path.clone());
|
||||
self.project_dir = Some(path);
|
||||
self.target_obj_dir = None;
|
||||
self.base_obj_dir = None;
|
||||
self.selected_obj = None;
|
||||
self.build_target = false;
|
||||
self.config.recent_projects.insert(0, path.clone());
|
||||
self.config.project_dir = Some(path);
|
||||
self.config.target_obj_dir = None;
|
||||
self.config.base_obj_dir = None;
|
||||
self.config.selected_obj = None;
|
||||
self.config.build_target = false;
|
||||
self.objects.clear();
|
||||
self.object_nodes.clear();
|
||||
self.watcher_change = true;
|
||||
@@ -187,33 +195,33 @@ impl AppConfig {
|
||||
}
|
||||
|
||||
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
||||
self.target_obj_dir = Some(path);
|
||||
self.selected_obj = None;
|
||||
self.config.target_obj_dir = Some(path);
|
||||
self.config.selected_obj = None;
|
||||
self.obj_change = true;
|
||||
self.queue_build = false;
|
||||
}
|
||||
|
||||
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
||||
self.base_obj_dir = Some(path);
|
||||
self.selected_obj = None;
|
||||
self.config.base_obj_dir = Some(path);
|
||||
self.config.selected_obj = None;
|
||||
self.obj_change = true;
|
||||
self.queue_build = false;
|
||||
}
|
||||
|
||||
pub fn set_selected_obj(&mut self, object: ObjectConfig) {
|
||||
self.selected_obj = Some(object);
|
||||
self.config.selected_obj = Some(object);
|
||||
self.obj_change = true;
|
||||
self.queue_build = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub type AppConfigRef = Arc<RwLock<AppConfig>>;
|
||||
pub type AppStateRef = Arc<RwLock<AppState>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct App {
|
||||
appearance: Appearance,
|
||||
view_state: ViewState,
|
||||
config: AppConfigRef,
|
||||
state: AppStateRef,
|
||||
modified: Arc<AtomicBool>,
|
||||
watcher: Option<notify::RecommendedWatcher>,
|
||||
app_path: Option<PathBuf>,
|
||||
@@ -241,16 +249,17 @@ impl App {
|
||||
if let Some(appearance) = eframe::get_value::<Appearance>(storage, APPEARANCE_KEY) {
|
||||
app.appearance = appearance;
|
||||
}
|
||||
if let Some(mut config) = deserialize_config(storage) {
|
||||
if config.project_dir.is_some() {
|
||||
config.config_change = true;
|
||||
config.watcher_change = true;
|
||||
if let Some(config) = deserialize_config(storage) {
|
||||
let mut state = AppState { config, ..Default::default() };
|
||||
if state.config.project_dir.is_some() {
|
||||
state.config_change = true;
|
||||
state.watcher_change = true;
|
||||
}
|
||||
if config.selected_obj.is_some() {
|
||||
config.queue_build = true;
|
||||
if state.config.selected_obj.is_some() {
|
||||
state.queue_build = true;
|
||||
}
|
||||
app.view_state.config_state.queue_check_update = config.auto_update_check;
|
||||
app.config = Arc::new(RwLock::new(config));
|
||||
app.view_state.config_state.queue_check_update = state.config.auto_update_check;
|
||||
app.state = Arc::new(RwLock::new(state));
|
||||
}
|
||||
}
|
||||
app.appearance.init_fonts(&cc.egui_ctx);
|
||||
@@ -336,8 +345,8 @@ impl App {
|
||||
jobs.results.append(&mut results);
|
||||
jobs.clear_finished();
|
||||
|
||||
diff_state.pre_update(jobs, &self.config);
|
||||
config_state.pre_update(jobs, &self.config);
|
||||
diff_state.pre_update(jobs, &self.state);
|
||||
config_state.pre_update(jobs, &self.state);
|
||||
debug_assert!(jobs.results.is_empty());
|
||||
}
|
||||
|
||||
@@ -345,23 +354,23 @@ impl App {
|
||||
self.appearance.post_update(ctx);
|
||||
|
||||
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
|
||||
config_state.post_update(ctx, jobs, &self.config);
|
||||
diff_state.post_update(ctx, jobs, &self.config);
|
||||
config_state.post_update(ctx, jobs, &self.state);
|
||||
diff_state.post_update(ctx, jobs, &self.state);
|
||||
|
||||
let Ok(mut config) = self.config.write() else {
|
||||
let Ok(mut state) = self.state.write() else {
|
||||
return;
|
||||
};
|
||||
let config = &mut *config;
|
||||
let state = &mut *state;
|
||||
|
||||
if let Some(info) = &config.project_config_info {
|
||||
if let Some(info) = &state.project_config_info {
|
||||
if file_modified(&info.path, info.timestamp) {
|
||||
config.config_change = true;
|
||||
state.config_change = true;
|
||||
}
|
||||
}
|
||||
|
||||
if config.config_change {
|
||||
config.config_change = false;
|
||||
match load_project_config(config) {
|
||||
if state.config_change {
|
||||
state.config_change = false;
|
||||
match load_project_config(state) {
|
||||
Ok(()) => config_state.load_error = None,
|
||||
Err(e) => {
|
||||
log::error!("Failed to load project config: {e}");
|
||||
@@ -370,47 +379,50 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
if config.watcher_change {
|
||||
if state.watcher_change {
|
||||
drop(self.watcher.take());
|
||||
|
||||
if let Some(project_dir) = &config.project_dir {
|
||||
match build_globset(&config.watch_patterns).map_err(anyhow::Error::new).and_then(
|
||||
|globset| {
|
||||
if let Some(project_dir) = &state.config.project_dir {
|
||||
match build_globset(&state.config.watch_patterns)
|
||||
.map_err(anyhow::Error::new)
|
||||
.and_then(|globset| {
|
||||
create_watcher(ctx.clone(), self.modified.clone(), project_dir, globset)
|
||||
.map_err(anyhow::Error::new)
|
||||
},
|
||||
) {
|
||||
}) {
|
||||
Ok(watcher) => self.watcher = Some(watcher),
|
||||
Err(e) => log::error!("Failed to create watcher: {e}"),
|
||||
}
|
||||
config.watcher_change = false;
|
||||
state.watcher_change = false;
|
||||
}
|
||||
}
|
||||
|
||||
if config.obj_change {
|
||||
if state.obj_change {
|
||||
*diff_state = Default::default();
|
||||
if config.selected_obj.is_some() {
|
||||
config.queue_build = true;
|
||||
if state.config.selected_obj.is_some() {
|
||||
state.queue_build = true;
|
||||
}
|
||||
config.obj_change = false;
|
||||
state.obj_change = false;
|
||||
}
|
||||
|
||||
if self.modified.swap(false, Ordering::Relaxed) && config.rebuild_on_changes {
|
||||
config.queue_build = true;
|
||||
if self.modified.swap(false, Ordering::Relaxed) && state.config.rebuild_on_changes {
|
||||
state.queue_build = true;
|
||||
}
|
||||
|
||||
if let Some(result) = &diff_state.build {
|
||||
if let Some((obj, _)) = &result.first_obj {
|
||||
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
||||
if file_modified(path, timestamp) {
|
||||
config.queue_reload = true;
|
||||
if state.last_mod_check.elapsed().as_millis() >= 500 {
|
||||
state.last_mod_check = Instant::now();
|
||||
if let Some((obj, _)) = &result.first_obj {
|
||||
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
||||
if file_modified(path, timestamp) {
|
||||
state.queue_reload = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((obj, _)) = &result.second_obj {
|
||||
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
||||
if file_modified(path, timestamp) {
|
||||
config.queue_reload = true;
|
||||
if let Some((obj, _)) = &result.second_obj {
|
||||
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
||||
if file_modified(path, timestamp) {
|
||||
state.queue_reload = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,17 +430,20 @@ impl App {
|
||||
|
||||
// Don't clear `queue_build` if a build is running. A file may have been modified during
|
||||
// the build, so we'll start another build after the current one finishes.
|
||||
if config.queue_build && config.selected_obj.is_some() && !jobs.is_running(Job::ObjDiff) {
|
||||
jobs.push(start_build(ctx, ObjDiffConfig::from_config(config)));
|
||||
config.queue_build = false;
|
||||
config.queue_reload = false;
|
||||
} else if config.queue_reload && !jobs.is_running(Job::ObjDiff) {
|
||||
let mut diff_config = ObjDiffConfig::from_config(config);
|
||||
if state.queue_build
|
||||
&& state.config.selected_obj.is_some()
|
||||
&& !jobs.is_running(Job::ObjDiff)
|
||||
{
|
||||
jobs.push(start_build(ctx, ObjDiffConfig::from_config(&state.config)));
|
||||
state.queue_build = false;
|
||||
state.queue_reload = false;
|
||||
} else if state.queue_reload && !jobs.is_running(Job::ObjDiff) {
|
||||
let mut diff_config = ObjDiffConfig::from_config(&state.config);
|
||||
// Don't build, just reload the current files
|
||||
diff_config.build_base = false;
|
||||
diff_config.build_target = false;
|
||||
jobs.push(start_build(ctx, diff_config));
|
||||
config.queue_reload = false;
|
||||
state.queue_reload = false;
|
||||
}
|
||||
|
||||
if graphics_state.should_relaunch {
|
||||
@@ -453,7 +468,7 @@ impl eframe::App for App {
|
||||
|
||||
self.pre_update(ctx);
|
||||
|
||||
let Self { config, appearance, view_state, .. } = self;
|
||||
let Self { state, appearance, view_state, .. } = self;
|
||||
let ViewState {
|
||||
jobs,
|
||||
config_state,
|
||||
@@ -469,6 +484,7 @@ impl eframe::App for App {
|
||||
show_arch_config,
|
||||
show_debug,
|
||||
show_graphics,
|
||||
show_jobs,
|
||||
} = view_state;
|
||||
|
||||
frame_history.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
|
||||
@@ -485,8 +501,8 @@ impl eframe::App for App {
|
||||
*show_project_config = !*show_project_config;
|
||||
ui.close_menu();
|
||||
}
|
||||
let recent_projects = if let Ok(guard) = config.read() {
|
||||
guard.recent_projects.clone()
|
||||
let recent_projects = if let Ok(guard) = state.read() {
|
||||
guard.config.recent_projects.clone()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
@@ -495,12 +511,12 @@ impl eframe::App for App {
|
||||
} else {
|
||||
ui.menu_button("Recent Projects…", |ui| {
|
||||
if ui.button("Clear").clicked() {
|
||||
config.write().unwrap().recent_projects.clear();
|
||||
state.write().unwrap().config.recent_projects.clear();
|
||||
};
|
||||
ui.separator();
|
||||
for path in recent_projects {
|
||||
if ui.button(format!("{}", path.display())).clicked() {
|
||||
config.write().unwrap().set_project_dir(path);
|
||||
state.write().unwrap().set_project_dir(path);
|
||||
ui.close_menu();
|
||||
}
|
||||
}
|
||||
@@ -533,12 +549,12 @@ impl eframe::App for App {
|
||||
*show_arch_config = !*show_arch_config;
|
||||
ui.close_menu();
|
||||
}
|
||||
let mut config = config.write().unwrap();
|
||||
let mut state = state.write().unwrap();
|
||||
let response = ui
|
||||
.checkbox(&mut config.rebuild_on_changes, "Rebuild on changes")
|
||||
.checkbox(&mut state.config.rebuild_on_changes, "Rebuild on changes")
|
||||
.on_hover_text("Automatically re-run the build & diff when files change.");
|
||||
if response.changed() {
|
||||
config.watcher_change = true;
|
||||
state.watcher_change = true;
|
||||
};
|
||||
ui.add_enabled(
|
||||
!diff_state.symbol_state.disable_reverse_fn_order,
|
||||
@@ -554,7 +570,7 @@ impl eframe::App for App {
|
||||
);
|
||||
if ui
|
||||
.checkbox(
|
||||
&mut config.diff_obj_config.relax_reloc_diffs,
|
||||
&mut state.config.diff_obj_config.relax_reloc_diffs,
|
||||
"Relax relocation diffs",
|
||||
)
|
||||
.on_hover_text(
|
||||
@@ -562,28 +578,32 @@ impl eframe::App for App {
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
config.queue_reload = true;
|
||||
state.queue_reload = true;
|
||||
}
|
||||
if ui
|
||||
.checkbox(
|
||||
&mut config.diff_obj_config.space_between_args,
|
||||
&mut state.config.diff_obj_config.space_between_args,
|
||||
"Space between args",
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
config.queue_reload = true;
|
||||
state.queue_reload = true;
|
||||
}
|
||||
if ui
|
||||
.checkbox(
|
||||
&mut config.diff_obj_config.combine_data_sections,
|
||||
&mut state.config.diff_obj_config.combine_data_sections,
|
||||
"Combine data sections",
|
||||
)
|
||||
.on_hover_text("Combines data sections with equal names.")
|
||||
.changed()
|
||||
{
|
||||
config.queue_reload = true;
|
||||
state.queue_reload = true;
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
if jobs_menu_ui(ui, jobs, appearance) {
|
||||
*show_jobs = !*show_jobs;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -603,8 +623,7 @@ impl eframe::App for App {
|
||||
} else {
|
||||
egui::SidePanel::left("side_panel").show(ctx, |ui| {
|
||||
egui::ScrollArea::both().show(ui, |ui| {
|
||||
config_ui(ui, config, show_project_config, config_state, appearance);
|
||||
jobs_ui(ui, jobs, appearance);
|
||||
config_ui(ui, state, show_project_config, config_state, appearance);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -613,21 +632,22 @@ impl eframe::App for App {
|
||||
});
|
||||
}
|
||||
|
||||
project_window(ctx, config, show_project_config, config_state, appearance);
|
||||
project_window(ctx, state, show_project_config, config_state, appearance);
|
||||
appearance_window(ctx, show_appearance_config, appearance);
|
||||
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
||||
rlwinm_decode_window(ctx, show_rlwinm_decode, rlwinm_decode_state, appearance);
|
||||
arch_config_window(ctx, config, show_arch_config, appearance);
|
||||
arch_config_window(ctx, state, show_arch_config, appearance);
|
||||
debug_window(ctx, show_debug, frame_history, appearance);
|
||||
graphics_window(ctx, show_graphics, frame_history, graphics_state, appearance);
|
||||
jobs_window(ctx, show_jobs, jobs, appearance);
|
||||
|
||||
self.post_update(ctx);
|
||||
}
|
||||
|
||||
/// Called by the frame work to save state before shutdown.
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
if let Ok(config) = self.config.read() {
|
||||
eframe::set_value(storage, CONFIG_KEY, &*config);
|
||||
if let Ok(state) = self.state.read() {
|
||||
eframe::set_value(storage, CONFIG_KEY, &state.config);
|
||||
}
|
||||
eframe::set_value(storage, APPEARANCE_KEY, &self.appearance);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user