Compare commits

...

25 Commits

Author SHA1 Message Date
Luke Street a5d9d8282e Update all dependencies 2024-10-03 22:00:43 -06:00
Amber Brault 3287a0f65c Bump cwextab again to 1.0.2 (#114)
* Bump cwextab

* Updated cwextab to not error on null actions

* Bump cwextab again
2024-10-03 01:12:37 -06:00
Amber Brault fab9c62dfb Bump cwextab (#113)
* Bump cwextab

* Updated cwextab to not error on null actions
2024-10-01 23:20:09 -06:00
Luke Street 08cd768260 Add total_units, complete_units to progress report 2024-09-30 21:41:57 -06:00
Luke Street 8acaaf528c Version v2.2.0 2024-09-29 12:26:41 -06:00
Luke Street 6e881a74e1 Remove armv7-unknown-linux-musleabi build 2024-09-29 11:57:13 -06:00
Luke Street cc1bc44e69 Use mimalloc when targeting musl 2024-09-29 11:52:04 -06:00
Luke Street c7b85518ab Rework jobs view & error handling improvements
Job status is now shown in the top menu bar,
with a new Jobs window that can be toggled.

Build and diff errors are now handled more
gracefully.

Fixes #40
2024-09-28 12:14:20 -06:00
Luke Street bb039a1445 Add "Open source file" option
Available when right-clicking an object in
the object list or when viewing an object

Resolves #99
2024-09-28 11:50:56 -06:00
Luke Street 8fc142d316 Debounce loaded object modification check
Before, this was running 2 fs::metadata
calls every frame. We don't need to do it
nearly that often, so now it only checks
once every 500ms.

This required refactoring AppConfig into
a separate AppState that holds transient
runtime state along with the loaded
AppConfig.
2024-09-28 10:55:22 -06:00
Luke Street b0123b3f83 Improve build log message when command doesn't exist
Before, it didn't include the actual command
that was attempted to run.
2024-09-28 10:55:09 -06:00
Luke Street 2ec17aee9b Improve config read/write performance
We were accidentally using unbuffered readers
and writers before, leading to long pauses on
the main thread on slow filesystems. (e.g.
FUSE or WSL)
2024-09-28 10:54:54 -06:00
Luke Street ec9731e1e5 Set app_id in eframe NativeOptions
Fixes missing WM_CLASS on Wayland
2024-09-28 10:53:58 -06:00
OndrikB a06382c27e Disambiguate dummy symbols (#107)
* Disambiguate dummy symbols

* Small formatting improvement

* Put HashMap logic into symbol creation
2024-09-27 00:33:36 -06:00
Luke Street e013638c5a clippy fixes 2024-09-27 00:30:30 -06:00
Luke Street 70ab82f1f7 gui: Highlight registers in columns separately
This matches the behavior of decomp.me and the
CLI.

Resolves #71
2024-09-27 00:27:36 -06:00
Luke Street c5896689cf Use ppc750cl Opcode::from 2024-09-27 00:12:21 -06:00
Luke Street 67719dd93e report: Exclude "hidden" functions
Fixes #111
2024-09-27 00:12:21 -06:00
Luke Street 258e141017 Upgrade all dependencies 2024-09-27 00:12:16 -06:00
Luke Street dbdda55065 Add Report::split
A hack for supporting games that build
all versions at once.
2024-09-26 23:47:03 -06:00
Steven Casper a43320af1f PPC: Guess reloc data type based on the instruction. (#108)
* Guess reloc data type based on the instruction.

Adds an entry to the reloc tooltip to show the inferred data type
and value.

* Fix clippy warning

* Match on Opcode rather than mnemonic string
2024-09-25 23:45:37 -06:00
Amber Brault 35bbd40f5d Actually update extab stuff (#110)
* Update cwextab

* Update

* Update ppc.rs

* Make fmt shut up
2024-09-24 09:16:14 -06:00
Amber Brault c1cb4b0b19 Update cwextab (#109) 2024-09-23 21:24:33 -06:00
Luke Street 2379853faa Remove unused imports 2024-09-10 23:29:22 -06:00
Luke Street 5e1aff180f Remove vergen / GIT_COMMIT_SHA handling 2024-09-10 23:22:40 -06:00
32 changed files with 1682 additions and 1178 deletions
-5
View File
@@ -110,11 +110,6 @@ jobs:
name: linux-aarch64 name: linux-aarch64
build: zigbuild build: zigbuild
features: default features: default
- platform: ubuntu-latest
target: armv7-unknown-linux-musleabi
name: linux-armv7l
build: zigbuild
features: default
- platform: windows-latest - platform: windows-latest
target: i686-pc-windows-msvc target: i686-pc-windows-msvc
name: windows-x86 name: windows-x86
Generated
+599 -714
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -13,7 +13,7 @@ strip = "debuginfo"
codegen-units = 1 codegen-units = 1
[workspace.package] [workspace.package]
version = "2.0.0" version = "2.2.2"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
+3 -1
View File
@@ -11,7 +11,6 @@ description = """
A local diffing tool for decompilation projects. A local diffing tool for decompilation projects.
""" """
publish = false publish = false
build = "build.rs"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
@@ -29,3 +28,6 @@ supports-color = "3.0"
time = { version = "0.3", features = ["formatting", "local-offset"] } time = { version = "0.3", features = ["formatting", "local-offset"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[target.'cfg(target_env = "musl")'.dependencies]
mimalloc = "0.1"
-9
View File
@@ -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");
}
+1 -2
View File
@@ -31,10 +31,9 @@ where T: FromArgs
Ok(v) => { Ok(v) => {
if v.version { if v.version {
println!( println!(
"{} {} {}", "{} {}",
command_name.first().unwrap_or(&""), command_name.first().unwrap_or(&""),
env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_VERSION"),
env!("GIT_COMMIT_SHA"),
); );
std::process::exit(0); std::process::exit(0);
} else { } else {
+3 -2
View File
@@ -199,7 +199,7 @@ fn report_object(
.unwrap_or_default(), .unwrap_or_default(),
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated), 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 sections = vec![];
let mut functions = vec![]; let mut functions = vec![];
@@ -237,7 +237,7 @@ fn report_object(
} }
for (symbol, symbol_diff) in section.symbols.iter().zip(&section_diff.symbols) { for (symbol, symbol_diff) in section.symbols.iter().zip(&section_diff.symbols) {
if symbol.size == 0 { if symbol.size == 0 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
continue; continue;
} }
if let Some(existing_functions) = &mut existing_functions { if let Some(existing_functions) = &mut existing_functions {
@@ -280,6 +280,7 @@ fn report_object(
if metadata.complete.unwrap_or(false) { if metadata.complete.unwrap_or(false) {
measures.complete_code = measures.total_code; measures.complete_code = measures.total_code;
measures.complete_data = measures.total_data; measures.complete_data = measures.total_data;
measures.complete_units = 1;
} }
measures.calc_fuzzy_match_percent(); measures.calc_fuzzy_match_percent();
measures.calc_matched_percent(); measures.calc_matched_percent();
+6
View File
@@ -2,6 +2,12 @@ mod argp_version;
mod cmd; mod cmd;
mod util; 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 std::{env, ffi::OsStr, fmt::Display, path::PathBuf, str::FromStr};
use anyhow::{Error, Result}; use anyhow::{Error, Result};
+4 -1
View File
@@ -27,6 +27,9 @@ arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
bindings = ["serde_json", "prost", "pbjson"] bindings = ["serde_json", "prost", "pbjson"]
wasm = ["bindings", "console_error_panic_hook", "console_log"] wasm = ["bindings", "console_error_panic_hook", "console_log"]
[package.metadata.docs.rs]
features = ["all"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
byteorder = "1.5" byteorder = "1.5"
@@ -57,7 +60,7 @@ gimli = { version = "0.31", default-features = false, features = ["read-all"], o
# ppc # ppc
cwdemangle = { version = "1.0", optional = true } cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "0.2", optional = true } cwextab = { version = "1.0.2", optional = true }
ppc750cl = { version = "0.3", optional = true } ppc750cl = { version = "0.3", optional = true }
# mips # mips
Binary file not shown.
+4
View File
@@ -32,6 +32,10 @@ message Measures {
uint64 complete_data = 13; uint64 complete_data = 13;
// Completed (or "linked") data percent // Completed (or "linked") data percent
float complete_data_percent = 14; float complete_data_percent = 14;
// Total number of units
uint32 total_units = 15;
// Completed (or "linked") units
uint32 complete_units = 16;
} }
// Project progress report // Project progress report
+100 -1
View File
@@ -1,11 +1,13 @@
use std::{borrow::Cow, collections::BTreeMap}; use std::{borrow::Cow, collections::BTreeMap, ffi::CStr};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use byteorder::ByteOrder;
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol}; use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
use crate::{ use crate::{
diff::DiffObjConfig, diff::DiffObjConfig,
obj::{ObjIns, ObjReloc, ObjSection}, obj::{ObjIns, ObjReloc, ObjSection},
util::ReallySigned,
}; };
#[cfg(feature = "arm")] #[cfg(feature = "arm")]
@@ -17,6 +19,97 @@ pub mod ppc;
#[cfg(feature = "x86")] #[cfg(feature = "x86")]
pub mod 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 { pub trait ObjArch: Send + Sync {
fn process_code( fn process_code(
&self, &self,
@@ -42,6 +135,12 @@ pub trait ObjArch: Send + Sync {
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() } 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 // Downcast methods
#[cfg(feature = "ppc")] #[cfg(feature = "ppc")]
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None } fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None }
+38 -5
View File
@@ -1,15 +1,16 @@
use std::{borrow::Cow, collections::BTreeMap}; use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{bail, ensure, Result}; use anyhow::{bail, ensure, Result};
use byteorder::BigEndian;
use cwextab::{decode_extab, ExceptionTableData}; use cwextab::{decode_extab, ExceptionTableData};
use object::{ use object::{
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget, elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
Symbol, SymbolKind, Symbol, SymbolKind,
}; };
use ppc750cl::{Argument, InsIter, GPR}; use ppc750cl::{Argument, InsIter, Opcode, GPR};
use crate::{ use crate::{
arch::{ObjArch, ProcessCodeResult}, arch::{DataType, ObjArch, ProcessCodeResult},
diff::DiffObjConfig, diff::DiffObjConfig,
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, ObjSymbol}, 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) } fn ppc(&self) -> Option<&ObjArchPpc> { Some(self) }
} }
@@ -303,9 +332,13 @@ fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, Excep
continue; continue;
}; };
let data = match decode_extab(extab_data) { let data = match decode_extab(extab_data) {
Some(decoded_data) => decoded_data, Ok(decoded_data) => decoded_data,
None => { Err(e) => {
log::warn!("Exception table decoding failed for function {}", extab_func_name); log::warn!(
"Exception table decoding failed for function {}, reason: {}",
extab_func_name,
e.to_string()
);
return Ok(None); return Ok(None);
} }
}; };
+119 -9
View File
@@ -8,9 +8,10 @@ use serde_json::error::Category;
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs")); include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs")); include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
pub const REPORT_VERSION: u32 = 1; pub const REPORT_VERSION: u32 = 2;
impl Report { impl Report {
/// Attempts to parse the report as binary protobuf or JSON.
pub fn parse(data: &[u8]) -> Result<Self> { pub fn parse(data: &[u8]) -> Result<Self> {
if data.is_empty() { if data.is_empty() {
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof)); bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
@@ -25,6 +26,7 @@ impl Report {
Ok(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> { fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
match serde_json::from_slice::<Self>(bytes) { match serde_json::from_slice::<Self>(bytes) {
Ok(report) => Ok(report), 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<()> { pub fn migrate(&mut self) -> Result<()> {
if self.version == 0 { if self.version == 0 {
self.migrate_v0()?; self.migrate_v0()?;
} }
if self.version == 1 {
self.migrate_v1()?;
}
if self.version != REPORT_VERSION { if self.version != REPORT_VERSION {
bail!("Unsupported report version: {}", self.version); bail!("Unsupported report version: {}", self.version);
} }
Ok(()) 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<()> { fn migrate_v0(&mut self) -> Result<()> {
let Some(measures) = &mut self.measures else { let Some(measures) = &mut self.measures else {
bail!("Missing measures in report"); bail!("Missing measures in report");
@@ -61,15 +70,16 @@ impl Report {
let Some(unit_measures) = &mut unit.measures else { let Some(unit_measures) = &mut unit.measures else {
bail!("Missing measures in report unit"); bail!("Missing measures in report unit");
}; };
let Some(metadata) = &mut unit.metadata else { let mut complete = false;
bail!("Missing metadata in report unit"); 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() { if complete {
metadata.progress_categories = vec!["modules".to_string()];
} else {
metadata.progress_categories = vec!["dol".to_string()];
}
if metadata.complete.unwrap_or(false) {
unit_measures.complete_code = unit_measures.total_code; unit_measures.complete_code = unit_measures.total_code;
unit_measures.complete_data = unit_measures.total_data; unit_measures.complete_data = unit_measures.total_data;
unit_measures.complete_code_percent = 100.0; unit_measures.complete_code_percent = 100.0;
@@ -84,10 +94,42 @@ impl Report {
measures.complete_data += unit_measures.complete_data; measures.complete_data += unit_measures.complete_data;
} }
measures.calc_matched_percent(); measures.calc_matched_percent();
self.calculate_progress_categories();
self.version = 1; self.version = 1;
Ok(()) 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) { pub fn calculate_progress_categories(&mut self) {
for unit in &self.units { for unit in &self.units {
let Some(metadata) = unit.metadata.as_ref() else { let Some(metadata) = unit.metadata.as_ref() else {
@@ -117,6 +159,72 @@ impl Report {
measures.calc_matched_percent(); 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 { impl Measures {
@@ -176,6 +284,8 @@ impl AddAssign for Measures {
self.matched_functions += other.matched_functions; self.matched_functions += other.matched_functions;
self.complete_code += other.complete_code; self.complete_code += other.complete_code;
self.complete_data += other.complete_data; self.complete_data += other.complete_data;
self.total_units += other.total_units;
self.complete_units += other.complete_units;
} }
} }
+9 -4
View File
@@ -1,6 +1,6 @@
use std::{ use std::{
fs::File, fs::File,
io::Read, io::{BufReader, Read},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@@ -124,6 +124,10 @@ impl ProjectObject {
pub fn hidden(&self) -> bool { pub fn hidden(&self) -> bool {
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false) 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)] #[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)> { pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
for filename in CONFIG_FILENAMES.iter() { for filename in CONFIG_FILENAMES.iter() {
let config_path = dir.join(filename); let config_path = dir.join(filename);
let Ok(mut file) = File::open(&config_path) else { let Ok(file) = File::open(&config_path) else {
continue; continue;
}; };
let metadata = file.metadata(); let metadata = file.metadata();
@@ -165,9 +169,10 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
continue; continue;
} }
let ts = FileTime::from_last_modification_time(&metadata); let ts = FileTime::from_last_modification_time(&metadata);
let mut reader = BufReader::new(file);
let mut result = match filename.contains("json") { let mut result = match filename.contains("json") {
true => read_json_config(&mut file), true => read_json_config(&mut reader),
false => read_yml_config(&mut file), false => read_yml_config(&mut reader),
}; };
if let Ok(config) = &result { if let Ok(config) = &result {
// Validate min_version if present // Validate min_version if present
+1
View File
@@ -126,6 +126,7 @@ pub struct ObjSymbol {
pub virtual_address: Option<u64>, pub virtual_address: Option<u64>,
/// Original index in object symbol table /// Original index in object symbol table
pub original_index: Option<usize>, pub original_index: Option<usize>,
pub bytes: Vec<u8>,
} }
pub struct ObjInfo { pub struct ObjInfo {
+37 -4
View File
@@ -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 anyhow::{anyhow, bail, ensure, Context, Result};
use filetime::FileTime; use filetime::FileTime;
@@ -78,6 +84,16 @@ fn to_obj_symbol(
let virtual_address = split_meta let virtual_address = split_meta
.and_then(|m| m.virtual_addresses.as_ref()) .and_then(|m| m.virtual_addresses.as_ref())
.and_then(|v| v.get(symbol.index().0).cloned()); .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 { Ok(ObjSymbol {
name: name.to_string(), name: name.to_string(),
demangled_name, demangled_name,
@@ -89,6 +105,7 @@ fn to_obj_symbol(
addend, addend,
virtual_address, virtual_address,
original_index: Some(symbol.index().0), original_index: Some(symbol.index().0),
bytes: bytes.to_vec(),
}) })
} }
@@ -136,6 +153,7 @@ fn symbols_by_section(
obj_file: &File<'_>, obj_file: &File<'_>,
section: &ObjSection, section: &ObjSection,
split_meta: Option<&SplitMeta>, split_meta: Option<&SplitMeta>,
name_counts: &mut HashMap<String, u32>,
) -> Result<Vec<ObjSymbol>> { ) -> Result<Vec<ObjSymbol>> {
let mut result = Vec::<ObjSymbol>::new(); let mut result = Vec::<ObjSymbol>::new();
for symbol in obj_file.symbols() { for symbol in obj_file.symbols() {
@@ -168,8 +186,14 @@ fn symbols_by_section(
} }
if result.is_empty() { if result.is_empty() {
// Dummy symbol for empty sections // Dummy symbol for empty sections
*name_counts.entry(section.name.clone()).or_insert(0) += 1;
let current_count: u32 = *name_counts.get(&section.name).unwrap();
result.push(ObjSymbol { result.push(ObjSymbol {
name: format!("[{}]", section.name), name: if current_count > 1 {
format!("[{} ({})]", section.name, current_count)
} else {
format!("[{}]", section.name)
},
demangled_name: None, demangled_name: None,
address: 0, address: 0,
section_address: 0, section_address: 0,
@@ -179,6 +203,7 @@ fn symbols_by_section(
addend: 0, addend: 0,
virtual_address: None, virtual_address: None,
original_index: None, original_index: None,
bytes: Vec::new(),
}); });
} }
Ok(result) Ok(result)
@@ -239,6 +264,7 @@ fn find_section_symbol(
addend: offset_addr as i64, addend: offset_addr as i64,
virtual_address: None, virtual_address: None,
original_index: None, original_index: None,
bytes: Vec::new(),
}) })
} }
@@ -521,6 +547,7 @@ fn update_combined_symbol(symbol: ObjSymbol, address_change: i64) -> Result<ObjS
None None
}, },
original_index: symbol.original_index, 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 arch = new_arch(&obj_file)?;
let split_meta = split_meta(&obj_file)?; let split_meta = split_meta(&obj_file)?;
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?; 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 { for section in &mut sections {
section.symbols = section.symbols = symbols_by_section(
symbols_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?; arch.as_ref(),
&obj_file,
section,
split_meta.as_ref(),
&mut name_counts,
)?;
section.relocations = section.relocations =
relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?; relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
} }
+12 -12
View File
@@ -29,10 +29,10 @@ bytes = "1.7"
cfg-if = "1.0" cfg-if = "1.0"
const_format = "0.2" const_format = "0.2"
cwdemangle = "1.0" cwdemangle = "1.0"
cwextab = "0.2" cwextab = "1.0.2"
dirs = "5.0" dirs = "5.0"
egui = "0.28" egui = "0.29"
egui_extras = "0.28" egui_extras = "0.29"
filetime = "0.2" filetime = "0.2"
float-ord = "0.3" float-ord = "0.3"
font-kit = "0.14" font-kit = "0.14"
@@ -40,22 +40,23 @@ globset = { version = "0.4", features = ["serde1"] }
log = "0.4" log = "0.4"
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2" } notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2" }
objdiff-core = { path = "../objdiff-core", features = ["all"] } objdiff-core = { path = "../objdiff-core", features = ["all"] }
open = "5.3"
png = "0.17" png = "0.17"
pollster = "0.3" pollster = "0.3"
regex = "1.10" regex = "1.11"
rfd = { version = "0.14" } #, default-features = false, features = ['xdg-portal'] rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
rlwinmdec = "1.0" rlwinmdec = "1.0"
ron = "0.8" ron = "0.8"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
shell-escape = "0.1" shell-escape = "0.1"
strum = { version = "0.26", features = ["derive"] } strum = { version = "0.26", features = ["derive"] }
tempfile = "3.12" tempfile = "3.13"
time = { version = "0.3", features = ["formatting", "local-offset"] } time = { version = "0.3", features = ["formatting", "local-offset"] }
# Keep version in sync with egui # Keep version in sync with egui
[dependencies.eframe] [dependencies.eframe]
version = "0.28" version = "0.29"
features = [ features = [
"default_fonts", "default_fonts",
"persistence", "persistence",
@@ -66,7 +67,7 @@ default-features = false
# Keep version in sync with eframe # Keep version in sync with eframe
[dependencies.wgpu] [dependencies.wgpu]
version = "0.20" version = "22.1"
features = [ features = [
"dx12", "dx12",
"metal", "metal",
@@ -89,9 +90,6 @@ self_update = "0.41"
path-slash = "0.2" path-slash = "0.2"
winapi = "0.3" winapi = "0.3"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
exec = "0.3" exec = "0.3"
@@ -106,4 +104,6 @@ tracing-wasm = "0.2"
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"
vergen-gitcl = { version = "1.0", features = ["build", "cargo"] }
[target.'cfg(windows)'.build-dependencies]
tauri-winres = "0.1"
+5 -7
View File
@@ -1,14 +1,12 @@
use anyhow::Result; use anyhow::Result;
use vergen_gitcl::{BuildBuilder, CargoBuilder, Emitter, GitclBuilder};
fn main() -> Result<()> { fn main() -> Result<()> {
#[cfg(windows)] #[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() Ok(())
.add_instructions(&BuildBuilder::all_build()?)?
.add_instructions(&CargoBuilder::all_cargo()?)?
.add_instructions(&GitclBuilder::all_git()?)?
.emit()
} }
+131 -111
View File
@@ -7,6 +7,7 @@ use std::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Mutex, RwLock, Arc, Mutex, RwLock,
}, },
time::Instant,
}; };
use filetime::FileTime; use filetime::FileTime;
@@ -39,7 +40,7 @@ use crate::{
frame_history::FrameHistory, frame_history::FrameHistory,
function_diff::function_diff_ui, function_diff::function_diff_ui,
graphics::{graphics_window, GraphicsConfig, GraphicsViewState}, graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
jobs::jobs_ui, jobs::{jobs_menu_ui, jobs_window},
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState}, rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
symbol_diff::{symbol_diff_ui, DiffViewState, View}, symbol_diff::{symbol_diff_ui, DiffViewState, View},
}, },
@@ -61,6 +62,7 @@ pub struct ViewState {
pub show_arch_config: bool, pub show_arch_config: bool,
pub show_debug: bool, pub show_debug: bool,
pub show_graphics: bool, pub show_graphics: bool,
pub show_jobs: bool,
} }
/// The configuration for a single object file. /// The configuration for a single object file.
@@ -72,6 +74,7 @@ pub struct ObjectConfig {
pub reverse_fn_order: Option<bool>, pub reverse_fn_order: Option<bool>,
pub complete: Option<bool>, pub complete: Option<bool>,
pub scratch: Option<ScratchConfig>, pub scratch: Option<ScratchConfig>,
pub source_path: Option<String>,
} }
#[inline] #[inline]
@@ -82,6 +85,36 @@ fn default_watch_patterns() -> Vec<Glob> {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect() 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)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct AppConfig { pub struct AppConfig {
// TODO: https://github.com/ron-rs/ron/pull/455 // TODO: https://github.com/ron-rs/ron/pull/455
@@ -116,23 +149,6 @@ pub struct AppConfig {
pub recent_projects: Vec<PathBuf>, pub recent_projects: Vec<PathBuf>,
#[serde(default)] #[serde(default)]
pub diff_obj_config: DiffObjConfig, 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 { 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(), watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(),
recent_projects: vec![], recent_projects: vec![],
diff_obj_config: Default::default(), 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) { pub fn set_project_dir(&mut self, path: PathBuf) {
self.recent_projects.retain(|p| p != &path); self.config.recent_projects.retain(|p| p != &path);
if self.recent_projects.len() > 9 { if self.config.recent_projects.len() > 9 {
self.recent_projects.truncate(9); self.config.recent_projects.truncate(9);
} }
self.recent_projects.insert(0, path.clone()); self.config.recent_projects.insert(0, path.clone());
self.project_dir = Some(path); self.config.project_dir = Some(path);
self.target_obj_dir = None; self.config.target_obj_dir = None;
self.base_obj_dir = None; self.config.base_obj_dir = None;
self.selected_obj = None; self.config.selected_obj = None;
self.build_target = false; self.config.build_target = false;
self.objects.clear(); self.objects.clear();
self.object_nodes.clear(); self.object_nodes.clear();
self.watcher_change = true; self.watcher_change = true;
@@ -187,33 +195,33 @@ impl AppConfig {
} }
pub fn set_target_obj_dir(&mut self, path: PathBuf) { pub fn set_target_obj_dir(&mut self, path: PathBuf) {
self.target_obj_dir = Some(path); self.config.target_obj_dir = Some(path);
self.selected_obj = None; self.config.selected_obj = None;
self.obj_change = true; self.obj_change = true;
self.queue_build = false; self.queue_build = false;
} }
pub fn set_base_obj_dir(&mut self, path: PathBuf) { pub fn set_base_obj_dir(&mut self, path: PathBuf) {
self.base_obj_dir = Some(path); self.config.base_obj_dir = Some(path);
self.selected_obj = None; self.config.selected_obj = None;
self.obj_change = true; self.obj_change = true;
self.queue_build = false; self.queue_build = false;
} }
pub fn set_selected_obj(&mut self, object: ObjectConfig) { 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.obj_change = true;
self.queue_build = false; self.queue_build = false;
} }
} }
pub type AppConfigRef = Arc<RwLock<AppConfig>>; pub type AppStateRef = Arc<RwLock<AppState>>;
#[derive(Default)] #[derive(Default)]
pub struct App { pub struct App {
appearance: Appearance, appearance: Appearance,
view_state: ViewState, view_state: ViewState,
config: AppConfigRef, state: AppStateRef,
modified: Arc<AtomicBool>, modified: Arc<AtomicBool>,
watcher: Option<notify::RecommendedWatcher>, watcher: Option<notify::RecommendedWatcher>,
app_path: Option<PathBuf>, app_path: Option<PathBuf>,
@@ -241,16 +249,17 @@ impl App {
if let Some(appearance) = eframe::get_value::<Appearance>(storage, APPEARANCE_KEY) { if let Some(appearance) = eframe::get_value::<Appearance>(storage, APPEARANCE_KEY) {
app.appearance = appearance; app.appearance = appearance;
} }
if let Some(mut config) = deserialize_config(storage) { if let Some(config) = deserialize_config(storage) {
if config.project_dir.is_some() { let mut state = AppState { config, ..Default::default() };
config.config_change = true; if state.config.project_dir.is_some() {
config.watcher_change = true; state.config_change = true;
state.watcher_change = true;
} }
if config.selected_obj.is_some() { if state.config.selected_obj.is_some() {
config.queue_build = true; state.queue_build = true;
} }
app.view_state.config_state.queue_check_update = config.auto_update_check; app.view_state.config_state.queue_check_update = state.config.auto_update_check;
app.config = Arc::new(RwLock::new(config)); app.state = Arc::new(RwLock::new(state));
} }
} }
app.appearance.init_fonts(&cc.egui_ctx); app.appearance.init_fonts(&cc.egui_ctx);
@@ -336,8 +345,8 @@ impl App {
jobs.results.append(&mut results); jobs.results.append(&mut results);
jobs.clear_finished(); jobs.clear_finished();
diff_state.pre_update(jobs, &self.config); diff_state.pre_update(jobs, &self.state);
config_state.pre_update(jobs, &self.config); config_state.pre_update(jobs, &self.state);
debug_assert!(jobs.results.is_empty()); debug_assert!(jobs.results.is_empty());
} }
@@ -345,23 +354,23 @@ impl App {
self.appearance.post_update(ctx); self.appearance.post_update(ctx);
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state; let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
config_state.post_update(ctx, jobs, &self.config); config_state.post_update(ctx, jobs, &self.state);
diff_state.post_update(ctx, jobs, &self.config); 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; 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) { if file_modified(&info.path, info.timestamp) {
config.config_change = true; state.config_change = true;
} }
} }
if config.config_change { if state.config_change {
config.config_change = false; state.config_change = false;
match load_project_config(config) { match load_project_config(state) {
Ok(()) => config_state.load_error = None, Ok(()) => config_state.load_error = None,
Err(e) => { Err(e) => {
log::error!("Failed to load project config: {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()); drop(self.watcher.take());
if let Some(project_dir) = &config.project_dir { if let Some(project_dir) = &state.config.project_dir {
match build_globset(&config.watch_patterns).map_err(anyhow::Error::new).and_then( match build_globset(&state.config.watch_patterns)
|globset| { .map_err(anyhow::Error::new)
.and_then(|globset| {
create_watcher(ctx.clone(), self.modified.clone(), project_dir, globset) create_watcher(ctx.clone(), self.modified.clone(), project_dir, globset)
.map_err(anyhow::Error::new) .map_err(anyhow::Error::new)
}, }) {
) {
Ok(watcher) => self.watcher = Some(watcher), Ok(watcher) => self.watcher = Some(watcher),
Err(e) => log::error!("Failed to create watcher: {e}"), 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(); *diff_state = Default::default();
if config.selected_obj.is_some() { if state.config.selected_obj.is_some() {
config.queue_build = true; state.queue_build = true;
} }
config.obj_change = false; state.obj_change = false;
} }
if self.modified.swap(false, Ordering::Relaxed) && config.rebuild_on_changes { if self.modified.swap(false, Ordering::Relaxed) && state.config.rebuild_on_changes {
config.queue_build = true; state.queue_build = true;
} }
if let Some(result) = &diff_state.build { if let Some(result) = &diff_state.build {
if let Some((obj, _)) = &result.first_obj { if state.last_mod_check.elapsed().as_millis() >= 500 {
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) { state.last_mod_check = Instant::now();
if file_modified(path, timestamp) { if let Some((obj, _)) = &result.first_obj {
config.queue_reload = true; 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((obj, _)) = &result.second_obj { if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) { if file_modified(path, timestamp) {
if file_modified(path, timestamp) { state.queue_reload = true;
config.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 // 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. // 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) { if state.queue_build
jobs.push(start_build(ctx, ObjDiffConfig::from_config(config))); && state.config.selected_obj.is_some()
config.queue_build = false; && !jobs.is_running(Job::ObjDiff)
config.queue_reload = false; {
} else if config.queue_reload && !jobs.is_running(Job::ObjDiff) { jobs.push(start_build(ctx, ObjDiffConfig::from_config(&state.config)));
let mut diff_config = ObjDiffConfig::from_config(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 // Don't build, just reload the current files
diff_config.build_base = false; diff_config.build_base = false;
diff_config.build_target = false; diff_config.build_target = false;
jobs.push(start_build(ctx, diff_config)); jobs.push(start_build(ctx, diff_config));
config.queue_reload = false; state.queue_reload = false;
} }
if graphics_state.should_relaunch { if graphics_state.should_relaunch {
@@ -453,7 +468,7 @@ impl eframe::App for App {
self.pre_update(ctx); self.pre_update(ctx);
let Self { config, appearance, view_state, .. } = self; let Self { state, appearance, view_state, .. } = self;
let ViewState { let ViewState {
jobs, jobs,
config_state, config_state,
@@ -469,6 +484,7 @@ impl eframe::App for App {
show_arch_config, show_arch_config,
show_debug, show_debug,
show_graphics, show_graphics,
show_jobs,
} = view_state; } = view_state;
frame_history.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage); 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; *show_project_config = !*show_project_config;
ui.close_menu(); ui.close_menu();
} }
let recent_projects = if let Ok(guard) = config.read() { let recent_projects = if let Ok(guard) = state.read() {
guard.recent_projects.clone() guard.config.recent_projects.clone()
} else { } else {
vec![] vec![]
}; };
@@ -495,12 +511,12 @@ impl eframe::App for App {
} else { } else {
ui.menu_button("Recent Projects…", |ui| { ui.menu_button("Recent Projects…", |ui| {
if ui.button("Clear").clicked() { if ui.button("Clear").clicked() {
config.write().unwrap().recent_projects.clear(); state.write().unwrap().config.recent_projects.clear();
}; };
ui.separator(); ui.separator();
for path in recent_projects { for path in recent_projects {
if ui.button(format!("{}", path.display())).clicked() { if ui.button(format!("{}", path.display())).clicked() {
config.write().unwrap().set_project_dir(path); state.write().unwrap().set_project_dir(path);
ui.close_menu(); ui.close_menu();
} }
} }
@@ -533,12 +549,12 @@ impl eframe::App for App {
*show_arch_config = !*show_arch_config; *show_arch_config = !*show_arch_config;
ui.close_menu(); ui.close_menu();
} }
let mut config = config.write().unwrap(); let mut state = state.write().unwrap();
let response = ui 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."); .on_hover_text("Automatically re-run the build & diff when files change.");
if response.changed() { if response.changed() {
config.watcher_change = true; state.watcher_change = true;
}; };
ui.add_enabled( ui.add_enabled(
!diff_state.symbol_state.disable_reverse_fn_order, !diff_state.symbol_state.disable_reverse_fn_order,
@@ -554,7 +570,7 @@ impl eframe::App for App {
); );
if ui if ui
.checkbox( .checkbox(
&mut config.diff_obj_config.relax_reloc_diffs, &mut state.config.diff_obj_config.relax_reloc_diffs,
"Relax relocation diffs", "Relax relocation diffs",
) )
.on_hover_text( .on_hover_text(
@@ -562,28 +578,32 @@ impl eframe::App for App {
) )
.changed() .changed()
{ {
config.queue_reload = true; state.queue_reload = true;
} }
if ui if ui
.checkbox( .checkbox(
&mut config.diff_obj_config.space_between_args, &mut state.config.diff_obj_config.space_between_args,
"Space between args", "Space between args",
) )
.changed() .changed()
{ {
config.queue_reload = true; state.queue_reload = true;
} }
if ui if ui
.checkbox( .checkbox(
&mut config.diff_obj_config.combine_data_sections, &mut state.config.diff_obj_config.combine_data_sections,
"Combine data sections", "Combine data sections",
) )
.on_hover_text("Combines data sections with equal names.") .on_hover_text("Combines data sections with equal names.")
.changed() .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 { } else {
egui::SidePanel::left("side_panel").show(ctx, |ui| { egui::SidePanel::left("side_panel").show(ctx, |ui| {
egui::ScrollArea::both().show(ui, |ui| { egui::ScrollArea::both().show(ui, |ui| {
config_ui(ui, config, show_project_config, config_state, appearance); config_ui(ui, state, show_project_config, config_state, appearance);
jobs_ui(ui, jobs, 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); appearance_window(ctx, show_appearance_config, appearance);
demangle_window(ctx, show_demangle, demangle_state, appearance); demangle_window(ctx, show_demangle, demangle_state, appearance);
rlwinm_decode_window(ctx, show_rlwinm_decode, rlwinm_decode_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); debug_window(ctx, show_debug, frame_history, appearance);
graphics_window(ctx, show_graphics, frame_history, graphics_state, appearance); graphics_window(ctx, show_graphics, frame_history, graphics_state, appearance);
jobs_window(ctx, show_jobs, jobs, appearance);
self.post_update(ctx); self.post_update(ctx);
} }
/// Called by the frame work to save state before shutdown. /// Called by the frame work to save state before shutdown.
fn save(&mut self, storage: &mut dyn eframe::Storage) { fn save(&mut self, storage: &mut dyn eframe::Storage) {
if let Ok(config) = self.config.read() { if let Ok(state) = self.state.read() {
eframe::set_value(storage, CONFIG_KEY, &*config); eframe::set_value(storage, CONFIG_KEY, &state.config);
} }
eframe::set_value(storage, APPEARANCE_KEY, &self.appearance); eframe::set_value(storage, APPEARANCE_KEY, &self.appearance);
} }

Some files were not shown because too many files have changed in this diff Show More