1use std::io;
2
3use crate::address::{AddressSpace, AddressValue};
4use crate::db::{Equivalent, Error, Function};
5use serde::{Deserialize, Serialize};
6
7pub trait Environment {
8 fn load_file_bytes(
9 &self,
10 file: &str,
11 offset: usize,
12 size: AddressValue,
13 ) -> Result<Vec<u8>, io::Error>;
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub enum Command {
18 SetLabel {
19 space: AddressSpace,
20 offset: AddressValue,
21 label: Option<String>,
22 },
23 SetEquivalent {
24 space: AddressSpace,
25 offset: AddressValue,
26 equivalent: Equivalent,
27 },
28 AutoDisassemble {
29 space: AddressSpace,
30 start: AddressValue,
31 },
32 ClearEquivalents {
33 space: AddressSpace,
34 offset: AddressValue,
35 size: AddressValue,
36 },
37 SetComment {
38 space: AddressSpace,
39 offset: AddressValue,
40 comment: Option<String>,
41 },
42 MapBytes {
43 space: AddressSpace,
44 offset: AddressValue,
45 file: String,
46 file_offset: usize,
47 size: AddressValue,
48 },
49 ClearBytes {
50 space: AddressSpace,
51 offset: AddressValue,
52 size: AddressValue,
53 },
54 SetConstantBytes {
55 space: AddressSpace,
56 offset: AddressValue,
57 size: AddressValue,
58 value: u8,
59 },
60 SetFunction {
61 space: AddressSpace,
62 offset: AddressValue,
63 function: Option<Function>,
64 },
65}
66
67impl Command {
68 pub fn set_label(space: AddressSpace, offset: AddressValue, label: impl Into<String>) -> Self {
69 Self::SetLabel {
70 space,
71 offset,
72 label: Some(label.into()),
73 }
74 }
75
76 pub fn clear_label(space: AddressSpace, offset: AddressValue) -> Self {
77 Self::SetLabel {
78 space,
79 offset,
80 label: None,
81 }
82 }
83
84 pub fn auto_disassemble(space: AddressSpace, start: AddressValue) -> Self {
85 Self::AutoDisassemble { space, start }
86 }
87
88 pub fn set_equivalent(
89 space: AddressSpace,
90 offset: AddressValue,
91 equivalent: Equivalent,
92 ) -> Self {
93 Self::SetEquivalent {
94 space,
95 offset,
96 equivalent,
97 }
98 }
99
100 pub fn clear_equivalents(
101 space: AddressSpace,
102 offset: AddressValue,
103 size: AddressValue,
104 ) -> Self {
105 Self::ClearEquivalents {
106 space,
107 offset,
108 size,
109 }
110 }
111
112 pub fn set_comment(
113 space: AddressSpace,
114 offset: AddressValue,
115 comment: impl Into<String>,
116 ) -> Self {
117 Self::SetComment {
118 space,
119 offset,
120 comment: Some(comment.into()),
121 }
122 }
123
124 pub fn clear_comment(space: AddressSpace, offset: AddressValue) -> Self {
125 Self::SetComment {
126 space,
127 offset,
128 comment: None,
129 }
130 }
131
132 pub fn map_bytes(
133 space: AddressSpace,
134 offset: AddressValue,
135 file: impl Into<String>,
136 file_offset: usize,
137 size: AddressValue,
138 ) -> Self {
139 Self::MapBytes {
140 space,
141 offset,
142 file: file.into(),
143 file_offset,
144 size,
145 }
146 }
147
148 pub fn clear_bytes(space: AddressSpace, offset: AddressValue, size: AddressValue) -> Self {
149 Self::ClearBytes {
150 space,
151 offset,
152 size,
153 }
154 }
155
156 pub fn set_constant_bytes(
157 space: AddressSpace,
158 offset: AddressValue,
159 size: AddressValue,
160 value: u8,
161 ) -> Self {
162 Self::SetConstantBytes {
163 space,
164 offset,
165 size,
166 value,
167 }
168 }
169
170 pub fn set_function(space: AddressSpace, offset: AddressValue, function: Function) -> Self {
171 Self::SetFunction {
172 space,
173 offset,
174 function: Some(function),
175 }
176 }
177
178 pub fn clear_function(space: AddressSpace, offset: AddressValue) -> Self {
179 Self::SetFunction {
180 space,
181 offset,
182 function: None,
183 }
184 }
185
186 pub fn apply(
187 self,
188 db: &mut crate::db::Db,
189 env: Option<&dyn Environment>,
190 ) -> Result<Vec<Command>, crate::db::Error> {
191 match self {
192 Command::SetLabel {
193 space,
194 offset,
195 label,
196 } => {
197 let region = db.region_mut(space);
198 let before = region.get_label(offset).map(str::to_owned);
199 match label.as_deref() {
200 Some(label) => region.set_label(offset, label),
201 None => region.clear_label(offset),
202 }
203 Ok(vec![Command::SetLabel {
204 space,
205 offset,
206 label: before,
207 }])
208 }
209 Command::SetEquivalent {
210 space,
211 offset,
212 equivalent,
213 } => {
214 let region = db.region_mut(space);
215 let span = region.equivalent_span(offset, &equivalent)?;
216 let before = region.snapshot_equivalents(offset, span);
217 region.set_equivalent(offset, equivalent)?;
218 let mut undo = vec![Command::ClearEquivalents {
219 space,
220 offset,
221 size: span,
222 }];
223 for (start, range) in before {
224 undo.push(Command::SetEquivalent {
225 space,
226 offset: start,
227 equivalent: range.equivalent,
228 });
229 }
230 Ok(undo)
231 }
232 Command::AutoDisassemble { space, start } => {
233 let region = db.region_mut(space);
234 let commands = region.auto_disassemble(start);
235 Ok(commands
236 .into_iter()
237 .map(|addr| Command::ClearEquivalents {
238 space,
239 offset: addr,
240 size: 1,
241 })
242 .collect())
243 }
244 Command::ClearEquivalents {
245 space,
246 offset,
247 size,
248 } => {
249 let region = db.region_mut(space);
250 let before = region.snapshot_equivalents(offset, size);
251 region.clear_equivalents(offset, size);
252 let mut undo = Vec::new();
253 for (start, range) in before {
254 undo.push(Command::SetEquivalent {
255 space,
256 offset: start,
257 equivalent: range.equivalent,
258 });
259 }
260 Ok(undo)
261 }
262 Command::SetComment {
263 space,
264 offset,
265 comment,
266 } => {
267 let region = db.region_mut(space);
268 let before = region.get_comment(offset).map(str::to_owned);
269 match comment.as_deref() {
270 Some(comment) => region.set_comment(offset, comment),
271 None => region.clear_comment(offset),
272 }
273 Ok(vec![Command::SetComment {
274 space,
275 offset,
276 comment: before,
277 }])
278 }
279 Command::MapBytes {
280 space,
281 offset,
282 file,
283 file_offset,
284 size,
285 } => {
286 let region = db.region_mut(space);
287 let Some(env) = env else {
288 return Err(Error::NoEnvironment);
289 };
290 let bytes = env
291 .load_file_bytes(&file, file_offset, size)
292 .map_err(Error::Io)?;
293 let size = bytes.len() as AddressValue;
294 let before = region.snapshot_byte_ranges(offset, size);
295 region.map_bytes(&file, file_offset, offset, &bytes);
296 Ok(undo_byte_ranges(space, offset, size, before))
297 }
298 Command::ClearBytes {
299 space,
300 offset,
301 size,
302 } => {
303 let region = db.region_mut(space);
304 let before = region.snapshot_byte_ranges(offset, size);
305 region.clear_bytes(offset, size);
306 Ok(undo_byte_ranges(space, offset, size, before))
307 }
308 Command::SetConstantBytes {
309 space,
310 offset,
311 size,
312 value,
313 } => {
314 let region = db.region_mut(space);
315 let before = region.snapshot_byte_ranges(offset, size);
316 region.set_constant(offset, size, value);
317 Ok(undo_byte_ranges(space, offset, size, before))
318 }
319 Command::SetFunction {
320 space,
321 offset,
322 function,
323 } => {
324 let region = db.region_mut(space);
325 let before = region.get_function(offset).cloned();
326 match function {
327 Some(function) => region.set_function(function),
328 None => region.clear_function(offset),
329 }
330 Ok(vec![Command::SetFunction {
331 space,
332 offset,
333 function: before,
334 }])
335 }
336 }
337 }
338}
339
340fn undo_byte_ranges(
341 space: AddressSpace,
342 offset: AddressValue,
343 size: AddressValue,
344 ranges: Vec<(AddressValue, crate::region::ByteRange)>,
345) -> Vec<Command> {
346 use crate::region::ByteRange;
347
348 let mut undo = vec![Command::ClearBytes {
349 space,
350 offset,
351 size,
352 }];
353 for (start, range) in ranges {
354 match range {
355 ByteRange::Mapped(file, file_offset, data) => {
356 undo.push(Command::MapBytes {
357 space,
358 offset: start,
359 file,
360 file_offset,
361 size: data.len() as AddressValue,
362 });
363 }
364 ByteRange::Constant(count, value) => {
365 undo.push(Command::SetConstantBytes {
366 space,
367 offset: start,
368 size: count as AddressValue,
369 value,
370 });
371 }
372 }
373 }
374 undo
375}