1use std::io;
2
3use crate::address::{AddressRange, AddressSpace, AddressValue};
4use crate::db::{Equivalent, Error, Function, Note, NoteId};
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 SetNote {
66 space: AddressSpace,
67 range: AddressRange,
68 note: Note,
69 },
70 ClearNote {
71 id: NoteId,
72 },
73}
74
75impl Command {
76 pub fn set_label(space: AddressSpace, offset: AddressValue, label: impl Into<String>) -> Self {
77 Self::SetLabel {
78 space,
79 offset,
80 label: Some(label.into()),
81 }
82 }
83
84 pub fn clear_label(space: AddressSpace, offset: AddressValue) -> Self {
85 Self::SetLabel {
86 space,
87 offset,
88 label: None,
89 }
90 }
91
92 pub fn auto_disassemble(space: AddressSpace, start: AddressValue) -> Self {
93 Self::AutoDisassemble { space, start }
94 }
95
96 pub fn set_equivalent(
97 space: AddressSpace,
98 offset: AddressValue,
99 equivalent: Equivalent,
100 ) -> Self {
101 Self::SetEquivalent {
102 space,
103 offset,
104 equivalent,
105 }
106 }
107
108 pub fn clear_equivalents(
109 space: AddressSpace,
110 offset: AddressValue,
111 size: AddressValue,
112 ) -> Self {
113 Self::ClearEquivalents {
114 space,
115 offset,
116 size,
117 }
118 }
119
120 pub fn set_comment(
121 space: AddressSpace,
122 offset: AddressValue,
123 comment: impl Into<String>,
124 ) -> Self {
125 Self::SetComment {
126 space,
127 offset,
128 comment: Some(comment.into()),
129 }
130 }
131
132 pub fn clear_comment(space: AddressSpace, offset: AddressValue) -> Self {
133 Self::SetComment {
134 space,
135 offset,
136 comment: None,
137 }
138 }
139
140 pub fn map_bytes(
141 space: AddressSpace,
142 offset: AddressValue,
143 file: impl Into<String>,
144 file_offset: usize,
145 size: AddressValue,
146 ) -> Self {
147 Self::MapBytes {
148 space,
149 offset,
150 file: file.into(),
151 file_offset,
152 size,
153 }
154 }
155
156 pub fn clear_bytes(space: AddressSpace, offset: AddressValue, size: AddressValue) -> Self {
157 Self::ClearBytes {
158 space,
159 offset,
160 size,
161 }
162 }
163
164 pub fn set_constant_bytes(
165 space: AddressSpace,
166 offset: AddressValue,
167 size: AddressValue,
168 value: u8,
169 ) -> Self {
170 Self::SetConstantBytes {
171 space,
172 offset,
173 size,
174 value,
175 }
176 }
177
178 pub fn set_function(space: AddressSpace, offset: AddressValue, function: Function) -> Self {
179 Self::SetFunction {
180 space,
181 offset,
182 function: Some(function),
183 }
184 }
185
186 pub fn clear_function(space: AddressSpace, offset: AddressValue) -> Self {
187 Self::SetFunction {
188 space,
189 offset,
190 function: None,
191 }
192 }
193
194 pub fn apply(
195 self,
196 db: &mut crate::db::Db,
197 env: Option<&dyn Environment>,
198 ) -> Result<Vec<Command>, crate::db::Error> {
199 match self {
200 Command::SetLabel {
201 space,
202 offset,
203 label,
204 } => {
205 let region = db.region_mut(space);
206 let before = region.get_label(offset).map(str::to_owned);
207 match label.as_deref() {
208 Some(label) => region.set_label(offset, label),
209 None => region.clear_label(offset),
210 }
211 Ok(vec![Command::SetLabel {
212 space,
213 offset,
214 label: before,
215 }])
216 }
217 Command::SetEquivalent {
218 space,
219 offset,
220 equivalent,
221 } => {
222 let region = db.region_mut(space);
223 let span = region.equivalent_span(offset, &equivalent)?;
224 let before = region.snapshot_equivalents(offset, span);
225 region.set_equivalent(offset, equivalent)?;
226 let mut undo = vec![Command::ClearEquivalents {
227 space,
228 offset,
229 size: span,
230 }];
231 for (start, range) in before {
232 undo.push(Command::SetEquivalent {
233 space,
234 offset: start,
235 equivalent: range.equivalent,
236 });
237 }
238 Ok(undo)
239 }
240 Command::AutoDisassemble { space, start } => {
241 let region = db.region_mut(space);
242 let addresses = region.auto_disassemble(start).success;
243 Ok(addresses
244 .into_iter()
245 .map(|addr| Command::ClearEquivalents {
246 space,
247 offset: addr,
248 size: 1,
249 })
250 .collect())
251 }
252 Command::ClearEquivalents {
253 space,
254 offset,
255 size,
256 } => {
257 let region = db.region_mut(space);
258 let before = region.snapshot_equivalents(offset, size);
259 region.clear_equivalents(offset, size);
260 let mut undo = Vec::new();
261 for (start, range) in before {
262 undo.push(Command::SetEquivalent {
263 space,
264 offset: start,
265 equivalent: range.equivalent,
266 });
267 }
268 Ok(undo)
269 }
270 Command::SetComment {
271 space,
272 offset,
273 comment,
274 } => {
275 let region = db.region_mut(space);
276 let before = region.get_comment(offset).map(str::to_owned);
277 match comment.as_deref() {
278 Some(comment) => region.set_comment(offset, comment),
279 None => region.clear_comment(offset),
280 }
281 Ok(vec![Command::SetComment {
282 space,
283 offset,
284 comment: before,
285 }])
286 }
287 Command::MapBytes {
288 space,
289 offset,
290 file,
291 file_offset,
292 size,
293 } => {
294 let region = db.region_mut(space);
295 let Some(env) = env else {
296 return Err(Error::NoEnvironment);
297 };
298 let bytes = env
299 .load_file_bytes(&file, file_offset, size)
300 .map_err(Error::Io)?;
301 let size = bytes.len() as AddressValue;
302 let before = region.snapshot_byte_ranges(offset, size);
303 region.map_bytes(&file, file_offset, offset, &bytes);
304 Ok(undo_byte_ranges(space, offset, size, before))
305 }
306 Command::ClearBytes {
307 space,
308 offset,
309 size,
310 } => {
311 let region = db.region_mut(space);
312 let before = region.snapshot_byte_ranges(offset, size);
313 region.clear_bytes(offset, size);
314 Ok(undo_byte_ranges(space, offset, size, before))
315 }
316 Command::SetConstantBytes {
317 space,
318 offset,
319 size,
320 value,
321 } => {
322 let region = db.region_mut(space);
323 let before = region.snapshot_byte_ranges(offset, size);
324 region.set_constant(offset, size, value);
325 Ok(undo_byte_ranges(space, offset, size, before))
326 }
327 Command::SetFunction {
328 space,
329 offset,
330 function,
331 } => {
332 let region = db.region_mut(space);
333 let before = region.get_function(offset).cloned();
334 match function {
335 Some(function) => region.set_function(function),
336 None => region.clear_function(offset),
337 }
338 Ok(vec![Command::SetFunction {
339 space,
340 offset,
341 function: before,
342 }])
343 }
344 Command::SetNote { space, range, note } => {
345 let id = note.id.clone();
346 let before = db.notes.note_range(space, &id).and_then(|old_range| {
347 db.notes
348 .get(&id)
349 .cloned()
350 .map(|old_note| (old_range, old_note))
351 });
352 db.notes.set_address(space, range, note);
353 match before {
354 Some((old_range, old_note)) => Ok(vec![Command::SetNote {
355 space,
356 range: old_range,
357 note: old_note,
358 }]),
359 None => Ok(vec![Command::ClearNote { id }]),
360 }
361 }
362 Command::ClearNote { id } => {
363 let Some((space, range, note)) = db.notes.clear_address(&id) else {
364 return Ok(vec![]);
365 };
366 Ok(vec![Command::SetNote { space, range, note }])
367 }
368 }
369 }
370}
371
372fn undo_byte_ranges(
373 space: AddressSpace,
374 offset: AddressValue,
375 size: AddressValue,
376 ranges: Vec<(AddressValue, crate::region::ByteRange)>,
377) -> Vec<Command> {
378 use crate::region::ByteRange;
379
380 let mut undo = vec![Command::ClearBytes {
381 space,
382 offset,
383 size,
384 }];
385 for (start, range) in ranges {
386 match range {
387 ByteRange::Mapped(file, file_offset, data) => {
388 undo.push(Command::MapBytes {
389 space,
390 offset: start,
391 file,
392 file_offset,
393 size: data.len() as AddressValue,
394 });
395 }
396 ByteRange::Constant(count, value) => {
397 undo.push(Command::SetConstantBytes {
398 space,
399 offset: start,
400 size: count as AddressValue,
401 value,
402 });
403 }
404 }
405 }
406 undo
407}