1use saturnus_core::{Machine, Model};
15use serde::Serialize;
16
17use anyhow::{Context, Result, bail, ensure};
18
19use crate::decompile::Settings;
20use crate::names::NameTable;
21use crate::object::{Base, Memory, Object, Reader};
22use crate::prolog::{ObjectType, Record, records};
23
24const MAX_LEVELS: usize = 100_000;
27const MAX_REGION: u32 = 1 << 20;
29
30impl Memory for Machine {
31 fn nibble(&self, addr: u32) -> Option<u8> {
32 (addr < MAX_REGION).then(|| self.peek(addr))
33 }
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct Layout {
41 pub home_ptr: u32,
43 pub home_end_ptr: u32,
45 pub context_ptr: u32,
47 pub stack_ptr: u32,
49 pub stack_end_ptr: u32,
51 pub system_flags: u32,
53 pub user_flags: u32,
55 pub flag_words: usize,
57 pub menu_ptr: u32,
61}
62
63impl Layout {
64 pub const HP48SX: Layout = Layout {
66 home_ptr: 0x70592,
67 home_end_ptr: 0x70597,
68 context_ptr: 0x7059C,
69 stack_ptr: 0x70579,
70 stack_end_ptr: 0x7057E,
71 system_flags: 0x706C5,
72 user_flags: 0x706D5,
73 flag_words: 1,
74 menu_ptr: 0x7061E,
75 };
76 pub const HP48GX: Layout = Layout {
78 home_ptr: 0x80711,
79 home_end_ptr: 0x80716,
80 context_ptr: 0x8071B,
81 stack_ptr: 0x806F8,
82 stack_end_ptr: 0x806FD,
83 system_flags: 0x80843,
84 user_flags: 0x80853,
85 flag_words: 1,
86 menu_ptr: 0x8079D,
87 };
88 pub const HP49G: Layout = Layout {
90 home_ptr: 0x80711,
91 home_end_ptr: 0x80716,
92 context_ptr: 0x8071B,
93 stack_ptr: 0x806F8,
94 stack_end_ptr: 0x806FD,
95 system_flags: 0x80F02,
96 user_flags: 0x80F22,
97 flag_words: 2,
98 menu_ptr: 0x807ED,
99 };
100
101 pub fn of(model: Model) -> Option<Layout> {
104 match model {
105 Model::Hp48sx => Some(Self::HP48SX),
106 Model::Hp48gx => Some(Self::HP48GX),
107 Model::Hp49g => Some(Self::HP49G),
108 Model::Hp38g
109 | Model::Hp39g
110 | Model::Hp40g
111 | Model::Hp42s
112 | Model::Hp27s
113 | Model::Hp17bii
114 | Model::Hp17b
115 | Model::Hp28s => None,
116 }
117 }
118}
119
120#[derive(Clone, Debug, PartialEq, Serialize)]
123pub struct Variable {
124 pub name: String,
126 #[serde(rename = "type")]
128 pub kind: String,
129 pub size: f64,
132 pub checksum: u16,
134 pub address: u32,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub variables: Option<Vec<Variable>>,
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
143pub struct Flags {
144 pub system: Vec<u64>,
146 pub user: Vec<u64>,
148}
149
150impl Flags {
151 pub fn get(&self, n: i32) -> Option<bool> {
154 let (words, i) = match n {
155 0 => return None,
156 n if n < 0 => (&self.system, n.unsigned_abs() - 1),
157 n => (&self.user, n.unsigned_abs() - 1),
158 };
159 let word = words.get(usize::try_from(i / 64).ok()?)?;
160 Some(word >> (i % 64) & 1 == 1)
161 }
162
163 pub fn base(&self) -> Base {
165 match (self.get(-11), self.get(-12)) {
166 (Some(true), Some(true)) => Base::Hex,
167 (Some(true), _) => Base::Oct,
168 (_, Some(true)) => Base::Bin,
169 _ => Base::Dec,
170 }
171 }
172
173 pub fn set(&self) -> Vec<i32> {
176 let mut out = Vec::new();
177 for (words, sign) in [(&self.system, -1i32), (&self.user, 1)] {
178 for (w, word) in words.iter().enumerate() {
179 for bit in 0..64 {
180 if word >> bit & 1 == 1 {
181 let n = i32::try_from(w * 64 + bit + 1).unwrap_or(i32::MAX);
182 out.push(sign * n);
183 }
184 }
185 }
186 }
187 out
188 }
189}
190
191impl Serialize for Flags {
192 fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
196 use serde::ser::SerializeStruct;
197 let hex = |w: &Vec<u64>| w.iter().map(|v| format!("{v:016X}")).collect::<Vec<_>>();
198 let mut st = s.serialize_struct("Flags", 3)?;
199 st.serialize_field("system", &hex(&self.system))?;
200 st.serialize_field("user", &hex(&self.user))?;
201 st.serialize_field("set", &self.set())?;
202 st.end()
203 }
204}
205
206pub fn crc(nibbles: &[u8]) -> u16 {
209 let mut c: u32 = 0;
210 for &n in nibbles {
211 c = (c >> 4) ^ (((c ^ u32::from(n)) & 0xF) * 0x1081);
212 }
213 (c & 0xFFFF) as u16
214}
215
216#[derive(Clone, Copy)]
218pub struct UserMemory<'a> {
219 mem: &'a dyn Memory,
220 layout: Layout,
221 names: Option<&'a NameTable>,
224}
225
226impl std::fmt::Debug for UserMemory<'_> {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 f.debug_struct("UserMemory")
229 .field("layout", &self.layout)
230 .field("names", &self.names.is_some())
231 .finish_non_exhaustive()
232 }
233}
234
235struct Home {
237 addr: u32,
239 nibbles: Vec<u8>,
241 offset_at: usize,
243}
244
245impl<'a> UserMemory<'a> {
246 pub fn new(mem: &'a dyn Memory, layout: Layout) -> Self {
248 Self {
249 mem,
250 layout,
251 names: None,
252 }
253 }
254
255 pub fn with_names(self, names: &'a NameTable) -> Self {
259 Self {
260 names: Some(names),
261 ..self
262 }
263 }
264
265 pub fn of(machine: &'a Machine) -> Result<Self> {
267 let model = machine.model();
268 let layout = Layout::of(model).with_context(|| {
269 let why = if model == Model::Hp28s {
270 "keeps its RPL memory in a layout this does not read"
271 } else if model.is_pioneer() {
272 "has no RPL user memory (no HOME directory, no RPL stack)"
273 } else {
274 "keeps aplets, not a HOME directory"
275 };
276 format!("the {} {why}: no memory view", model.name().to_uppercase())
277 })?;
278 Ok(Self::new(machine, layout))
279 }
280
281 pub fn layout(&self) -> Layout {
283 self.layout
284 }
285
286 fn nibble(&self, addr: u32) -> Result<u8> {
287 self.mem
288 .nibble(addr)
289 .with_context(|| format!("#{addr:05X} is not readable"))
290 }
291
292 fn pointer(&self, addr: u32) -> Result<u32> {
294 let mut v = 0;
295 for i in (0..5).rev() {
296 v = (v << 4) | u32::from(self.nibble(addr + i)?);
297 }
298 Ok(v)
299 }
300
301 fn region(&self, from: u32, to: u32) -> Result<Vec<u8>> {
303 ensure!(
304 from <= to && to - from <= MAX_REGION,
305 "region #{from:05X}-#{to:05X} is not plausible"
306 );
307 (from..to).map(|a| self.nibble(a)).collect()
308 }
309
310 fn home(&self) -> Result<Home> {
311 let addr = self.pointer(self.layout.home_ptr)?;
312 let end = self.pointer(self.layout.home_end_ptr)?;
313 let nibbles = self.region(addr, end).context("reading HOME")?;
314 let prolog = crate::prolog::read_field(&nibbles, 0, 5);
315 ensure!(
316 prolog == Some(ObjectType::Directory.prolog()),
317 "no directory at HOME (#{addr:05X}): the ROM has not set up memory yet"
318 );
319 let count = crate::prolog::read_field(&nibbles, 5, 3).context("HOME truncated")?;
322 let offset_at = 8 + 13 * usize::try_from(count)?;
323 Ok(Home {
324 addr,
325 nibbles,
326 offset_at,
327 })
328 }
329
330 fn variables(
334 &self,
335 n: &[u8],
336 base: u32,
337 offset_at: usize,
338 depth: usize,
339 ) -> Result<Vec<Variable>> {
340 let rs = records(n, offset_at, depth)?;
341 let mut out = Vec::with_capacity(rs.len());
342 for r in rs.iter().rev() {
343 if r.name_len == 0 {
345 continue;
346 }
347 out.push(self.variable(n, base, r, depth)?);
348 }
349 Ok(out)
350 }
351
352 fn variable(&self, n: &[u8], base: u32, r: &Record, depth: usize) -> Result<Variable> {
353 let name_bytes: Vec<u8> = (0..r.name_len)
354 .map(|i| {
355 crate::prolog::read_field(n, r.name_at + 2 + 2 * i, 2)
356 .map(|b| (b & 0xFF) as u8)
357 .context("name truncated")
358 })
359 .collect::<Result<_>>()?;
360 let object = &n[r.object..r.end()];
361 let address = base + u32::try_from(r.object)?;
362 let prolog = crate::prolog::read_field(object, 0, 5).context("object truncated")?;
363 let ty = ObjectType::from_prolog(prolog);
364 let (kind, variables) = match ty {
365 Some(ObjectType::Directory) => (
366 ty.map(ObjectType::name),
367 Some(self.variables(n, base, r.object + 8, depth + 1)?),
368 ),
369 Some(t) => (Some(t.name()), None),
370 None => (
373 self.pointer(prolog)
374 .ok()
375 .and_then(ObjectType::from_prolog)
376 .map(ObjectType::name),
377 None,
378 ),
379 };
380 Ok(Variable {
381 name: crate::charset::decode(&name_bytes),
382 kind: kind.unwrap_or("ROM Pointer").to_string(),
383 size: (r.end() - r.start) as f64 / 2.0,
384 checksum: crc(object),
385 address,
386 variables,
387 })
388 }
389
390 pub fn tree(&self) -> Result<Vec<Variable>> {
395 let home = self.home()?;
396 let vars = self.variables(&home.nibbles, home.addr, home.offset_at, 0)?;
397 let end = records(&home.nibbles, home.offset_at, 0)?
399 .last()
400 .map_or(home.offset_at + 5, Record::end);
401 ensure!(
402 end == home.nibbles.len(),
403 "HOME's variables end at #{:05X}, HOME at #{:05X}",
404 home.addr + u32::try_from(end)?,
405 home.addr + u32::try_from(home.nibbles.len())?
406 );
407 Ok(vars)
408 }
409
410 pub fn current_path(&self) -> Result<Vec<String>> {
412 let context = self.pointer(self.layout.context_ptr)?;
413 let home = self.pointer(self.layout.home_ptr)?;
414 let mut path = vec!["HOME".to_string()];
415 if context == home {
416 return Ok(path);
417 }
418 if find(&self.tree()?, context, &mut path) {
419 Ok(path)
420 } else {
421 bail!("the current directory (#{context:05X}) is not under HOME")
422 }
423 }
424
425 pub fn stack_addresses(&self) -> Result<Vec<u32>> {
427 let top = self.pointer(self.layout.stack_ptr)?;
428 let end = self.pointer(self.layout.stack_end_ptr)?;
429 let span = end
430 .checked_sub(top)
431 .filter(|s| s % 5 == 0 && *s >= 5)
432 .with_context(|| format!("stack pointers #{top:05X}-#{end:05X} are not plausible"))?;
433 let depth = usize::try_from(span / 5 - 1)?;
434 ensure!(depth <= MAX_LEVELS, "stack deeper than {MAX_LEVELS} levels");
435 ensure!(
436 self.pointer(end - 5)? == 0,
437 "no end marker at #{:05X}: the ROM is not idle",
438 end - 5
439 );
440 (0..depth)
441 .map(|i| {
442 let at = top + 5 * u32::try_from(i)?;
443 let p = self.pointer(at)?;
444 ensure!(p != 0, "stack level {} is 0 at #{at:05X}", i + 1);
445 Ok(p)
446 })
447 .collect()
448 }
449
450 pub fn stack(&self) -> Result<Vec<Object>> {
453 let flags = self.flags()?;
454 let base = flags.base();
455 let settings = self.settings(&flags);
456 let reader = self.reader(&settings);
459 self.stack_addresses()?
460 .into_iter()
461 .enumerate()
462 .map(|(i, addr)| {
463 let mut obj = reader
464 .decode_at(addr)
465 .with_context(|| format!("stack level {}", i + 1))?;
466 obj.set_base(base);
467 Ok(obj)
468 })
469 .collect()
470 }
471
472 fn settings(&self, flags: &Flags) -> Settings {
475 let model = if self.layout == Layout::HP49G {
476 Model::Hp49g
477 } else {
478 Model::Hp48gx
479 };
480 Settings::from_flags(flags, model)
481 }
482
483 fn reader(&self, settings: &Settings) -> Reader<'a> {
484 match self.names {
485 Some(names) => Reader::with_names(self.mem, names, *settings),
486 None => Reader::new(self.mem),
487 }
488 }
489
490 pub fn object_at(&self, addr: u32) -> Result<Object> {
493 let flags = self.flags()?;
494 let mut obj = self.reader(&self.settings(&flags)).decode_at(addr)?;
495 obj.set_base(flags.base());
496 Ok(obj)
497 }
498
499 pub fn stack_described(&self) -> Result<Vec<serde_json::Value>> {
504 let settings = self.settings(&self.flags()?);
505 let mut left = crate::decompile::MAX_DESCRIBED_TEXT;
506 self.stack()?
507 .iter()
508 .map(|o| crate::decompile::described_within(o, &settings, &mut left))
509 .collect()
510 }
511
512 pub fn stack_top(&self) -> Result<(usize, Option<serde_json::Value>)> {
516 let addresses = self.stack_addresses()?;
517 let top = match addresses.first() {
518 Some(&addr) => Some(self.object_described(addr).context("stack level 1")?),
519 None => None,
520 };
521 Ok((addresses.len(), top))
522 }
523
524 pub fn object_described(&self, addr: u32) -> Result<serde_json::Value> {
527 let settings = self.settings(&self.flags()?);
528 crate::decompile::described(&self.object_at(addr)?, &settings)
529 }
530
531 pub fn edit_text_at(&self, addr: u32) -> Result<String> {
536 let flags = self.flags()?;
537 let settings = self.settings(&flags).for_editing();
538 let mut obj = self.reader(&settings).decode_at(addr)?;
539 obj.set_base(flags.base());
540 crate::decompile::edit_text(&obj, &settings)
541 }
542
543 pub fn identity_at(&self, addr: u32) -> Result<(usize, u16)> {
547 let n: Vec<u8> = (addr..MAX_REGION)
548 .map(|a| self.nibble(a))
549 .collect::<Result<_>>()?;
550 let size = crate::prolog::object_size(&n, 0)?;
551 Ok((size, crc(&n[..size])))
552 }
553
554 fn words(&self, at: u32) -> Result<Vec<u64>> {
555 (0..self.layout.flag_words)
556 .map(|w| {
557 let start = at + 16 * u32::try_from(w)?;
558 let mut v = 0u64;
559 for i in (0..16).rev() {
560 v = (v << 4) | u64::from(self.nibble(start + i)?);
561 }
562 Ok(v)
563 })
564 .collect()
565 }
566
567 pub fn menu_pointer(&self) -> Result<u32> {
570 self.pointer(self.layout.menu_ptr)
571 }
572
573 pub fn flags(&self) -> Result<Flags> {
575 Ok(Flags {
576 system: self.words(self.layout.system_flags)?,
577 user: self.words(self.layout.user_flags)?,
578 })
579 }
580
581 pub fn change_counter(&self) -> Result<u64> {
589 let mut h = Fnv::default();
590 let l = self.layout;
591 for p in [
592 l.home_ptr,
593 l.home_end_ptr,
594 l.context_ptr,
595 l.stack_ptr,
596 l.stack_end_ptr,
597 ] {
598 h.word(u64::from(self.pointer(p)?));
599 }
600 let home = self.pointer(l.home_ptr)?;
601 let end = self.pointer(l.home_end_ptr)?;
602 for n in self.region(home, end)? {
603 h.byte(n);
604 }
605 let top = self.pointer(l.stack_ptr)?;
607 let stack_end = self
608 .pointer(l.stack_end_ptr)?
609 .min(top + 5 * MAX_LEVELS as u32);
610 for a in (top..stack_end).step_by(5) {
611 h.word(u64::from(self.pointer(a)?));
612 }
613 let flags = self.flags()?;
614 for w in flags.system.iter().chain(&flags.user) {
615 h.word(*w);
616 }
617 Ok(h.0)
618 }
619}
620
621fn find(vars: &[Variable], addr: u32, path: &mut Vec<String>) -> bool {
623 for v in vars {
624 let Some(children) = &v.variables else {
625 continue;
626 };
627 path.push(v.name.clone());
628 if v.address == addr || find(children, addr, path) {
629 return true;
630 }
631 path.pop();
632 }
633 false
634}
635
636struct Fnv(u64);
638
639impl Default for Fnv {
640 fn default() -> Self {
641 Fnv(0xCBF2_9CE4_8422_2325)
642 }
643}
644
645impl Fnv {
646 fn byte(&mut self, b: u8) {
647 self.0 = (self.0 ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01B3);
648 }
649
650 fn word(&mut self, w: u64) {
651 for b in w.to_le_bytes() {
652 self.byte(b);
653 }
654 }
655}
656
657pub fn memory_tree(machine: &Machine) -> Result<Vec<Variable>> {
659 UserMemory::of(machine)?.tree()
660}
661
662pub fn current_path(machine: &Machine) -> Result<Vec<String>> {
664 UserMemory::of(machine)?.current_path()
665}
666
667pub fn stack_objects(machine: &Machine) -> Result<Vec<Object>> {
669 UserMemory::of(machine)?.stack()
670}
671
672pub fn flags(machine: &Machine) -> Result<Flags> {
674 UserMemory::of(machine)?.flags()
675}
676
677pub fn change_counter(machine: &Machine) -> Result<u64> {
679 UserMemory::of(machine)?.change_counter()
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685
686 #[test]
689 fn models_without_a_memory_view() {
690 for model in [Model::Hp38g, Model::Hp39g, Model::Hp40g, Model::Hp42s] {
691 assert!(Layout::of(model).is_none(), "{}", model.name());
692 }
693 let m = Machine::new(Model::Hp42s, &vec![0u8; Model::Hp42s.rom_bytes()]).unwrap();
694 let e = UserMemory::of(&m).err().unwrap().to_string();
695 assert!(e.contains("the 42S has no RPL user memory"), "{e}");
696 let m = Machine::new(Model::Hp38g, &vec![0u8; Model::Hp38g.rom_bytes()]).unwrap();
697 let e = UserMemory::of(&m).err().unwrap().to_string();
698 assert!(e.contains("keeps aplets"), "{e}");
699 }
700 use crate::object::{MAX_DECODED_OBJECTS, Real, decode_at};
701
702 struct Ram(Vec<u8>);
704
705 impl Memory for Ram {
706 fn nibble(&self, addr: u32) -> Option<u8> {
707 let i = usize::try_from(addr.checked_sub(0x70000)?).ok()?;
708 self.0.get(i).copied()
709 }
710 }
711
712 impl Ram {
713 fn put(&mut self, addr: u32, hex: &str) {
714 let at = (addr - 0x70000) as usize;
715 for (i, c) in hex.chars().enumerate() {
716 self.0[at + i] = c.to_digit(16).unwrap() as u8;
717 }
718 }
719
720 fn put_ptr(&mut self, addr: u32, value: u32) {
721 let hex: String = (0..5)
722 .map(|i| format!("{:X}", value >> (4 * i) & 0xF))
723 .collect();
724 self.put(addr, &hex);
725 }
726 }
727
728 fn sx() -> Ram {
732 let mut r = Ram(vec![0; 0x10000]);
733 r.put(
734 0x7FE9B,
735 concat!(
736 "69A20200200746228D356007EFD2200000501000",
737 "00000069A20FF7C500000000C055375627B45697",
738 "37E2342534C0E4A20900006E57A2000805537562",
739 "7B45697378047A20B2130E10006014C61627D637",
740 "6047A20B2130080005094F40514255047A201932",
741 "24B2A24B2A24B2A23F2A29C2A2B2130630001085",
742 "10339201000000000000240B100020959520C2A2",
743 "09000084946100040449425144069A20FF7A0000",
744 "0000010A51033920000000000000005",
745 ),
746 );
747 let l = Layout::HP48SX;
748 r.put_ptr(l.home_ptr, 0x7FE9B);
749 r.put_ptr(l.home_end_ptr, 0x7FFFB);
750 r.put_ptr(l.context_ptr, 0x7FFCE);
751 r.put(0x71000, "C2A2090000142433920000000000000007");
753 r.put(0x71030, "E4A2051000A200000000000000");
754 r.put_ptr(0x7F000, 0x71000);
755 r.put_ptr(0x7F005, 0x7100E);
756 r.put_ptr(0x7F00A, 0x71030);
757 r.put_ptr(l.stack_ptr, 0x7F000);
758 r.put_ptr(l.stack_end_ptr, 0x7F014);
759 r.put(l.system_flags, "0FF0000008000000");
761 r.put(l.user_flags, "0400000000000000");
762 r
763 }
764
765 #[test]
766 fn tree_sizes_and_checksums_match_the_calculator() {
767 let ram = sx();
768 let m = UserMemory::new(&ram, Layout::HP48SX);
769 let tree = m.tree().unwrap();
770 let flat: Vec<(&str, &str, f64, u16)> = tree
772 .iter()
773 .map(|v| (v.name.as_str(), v.kind.as_str(), v.size, v.checksum))
774 .collect();
775 assert_eq!(
776 flat,
777 [
778 ("DIRA", "Directory", 31.0, 44093),
779 ("YY", "String", 13.5, 5889),
780 ("X", "Real Number", 16.0, 59472),
781 ("IOPAR", "List", 29.5, 8861),
782 ]
783 );
784 let dira = tree[0].variables.as_ref().unwrap();
785 assert_eq!(dira.len(), 1);
786 assert_eq!(
787 (dira[0].name.as_str(), dira[0].size, dira[0].checksum),
788 ("Z", 16.0, 23381)
789 );
790 assert_eq!(dira[0].address, 0x7FFE6);
791 assert_eq!(m.current_path().unwrap(), ["HOME", "DIRA"]);
792 assert_eq!(
793 decode_at(tree[2].address, &ram).unwrap(),
794 Object::Real {
795 value: Real::parse("42").unwrap()
796 }
797 );
798 }
799
800 #[test]
801 fn stack_and_flags() {
802 let ram = sx();
803 let m = UserMemory::new(&ram, Layout::HP48SX);
804 let flags = m.flags().unwrap();
805 assert_eq!(flags.get(-40), Some(true));
806 assert_eq!(flags.get(7), Some(true));
807 assert_eq!(flags.get(8), Some(false));
808 assert_eq!(flags.get(65), None);
809 assert_eq!(flags.base(), Base::Hex);
810 assert_eq!(flags.set(), [-5, -6, -7, -8, -9, -10, -11, -12, -40, 7]);
811 let json = serde_json::to_value(&flags).unwrap();
812 assert_eq!(json["system"][0], "0000008000000FF0");
813 assert_eq!(json["user"][0], "0000000000000040");
814 let stack = m.stack().unwrap();
815 assert_eq!(stack.len(), 3);
816 assert_eq!(stack[0], Object::String { value: "AB".into() });
817 assert_eq!(
818 stack[1],
819 Object::Real {
820 value: Real::parse("7").unwrap()
821 }
822 );
823 assert_eq!(
824 serde_json::to_value(&stack[2]).unwrap(),
825 serde_json::json!({"type": "binary", "value": 42, "base": "hex", "text": "# 2Ah"})
826 );
827 }
828
829 #[test]
830 fn change_counter_moves_with_the_tree_and_the_stack() {
831 let mut ram = sx();
832 let c0 = UserMemory::new(&ram, Layout::HP48SX)
833 .change_counter()
834 .unwrap();
835 assert_eq!(
836 UserMemory::new(&ram, Layout::HP48SX)
837 .change_counter()
838 .unwrap(),
839 c0
840 );
841 ram.put(0x7FF39 + 16, "3");
843 let c1 = UserMemory::new(&ram, Layout::HP48SX)
844 .change_counter()
845 .unwrap();
846 assert_ne!(c1, c0);
847 ram.put_ptr(Layout::HP48SX.stack_ptr, 0x7F005);
848 assert_ne!(
849 UserMemory::new(&ram, Layout::HP48SX)
850 .change_counter()
851 .unwrap(),
852 c1
853 );
854 }
855
856 fn shared_pointers(levels: u32) -> Ram {
859 let mut r = sx();
860 let base = 0x71000;
861 for i in 0..levels {
862 let a = base + 20 * i;
863 r.put(a, "47A20");
864 if i + 1 < levels {
865 r.put_ptr(a + 5, a + 20);
866 r.put_ptr(a + 10, a + 20);
867 r.put(a + 15, "B2130");
868 } else {
869 r.put(a + 5, "B2130");
870 }
871 }
872 r.put_ptr(0x7F000, base);
873 r.put_ptr(0x7F005, 0);
874 r.put_ptr(Layout::HP48SX.stack_ptr, 0x7F000);
875 r.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F00A);
876 r
877 }
878
879 #[test]
880 fn shared_pointers_hit_the_decode_budget() {
881 let ram = shared_pointers(40);
882 let m = UserMemory::new(&ram, Layout::HP48SX);
883 let calls: [&dyn Fn() -> Result<()>; 2] = [&|| decode_at(0x71000, &ram).map(drop), &|| {
884 m.stack().map(drop)
885 }];
886 for call in calls {
887 let e = call().unwrap_err();
890 assert!(format!("{e:#}").contains("(MAX_DECODED_"), "{e:#}");
891 }
892 let ram = shared_pointers(12);
894 let obj = decode_at(0x71000, &ram).unwrap();
895 fn count(o: &Object) -> usize {
896 match o {
897 Object::List { items } => 1 + items.iter().map(count).sum::<usize>(),
898 _ => 1,
899 }
900 }
901 assert_eq!(count(&obj), (1 << 12) - 1);
902 assert!(count(&obj) < MAX_DECODED_OBJECTS);
903 }
904
905 fn repeated(levels: u32, hex: &str) -> Ram {
907 let mut r = sx();
908 r.put(0x71000, hex);
909 for i in 0..levels {
910 r.put_ptr(0x7F000 + 5 * i, 0x71000);
911 }
912 r.put_ptr(0x7F000 + 5 * levels, 0);
913 r.put_ptr(Layout::HP48SX.stack_ptr, 0x7F000);
914 r.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F005 + 5 * levels);
915 r
916 }
917
918 fn string(chars: usize) -> String {
920 let mut len = String::new();
921 for i in 0..5 {
922 len.push_str(&format!("{:X}", (5 + 2 * chars) >> (4 * i) & 0xF));
923 }
924 format!("C2A20{len}{}", "14".repeat(chars))
925 }
926
927 #[test]
928 fn repeated_large_objects_hit_the_nibble_budget() {
929 let ram = repeated(500, &string(20_000));
932 let m = UserMemory::new(&ram, Layout::HP48SX);
933 for e in [m.stack().unwrap_err(), m.stack_described().unwrap_err()] {
934 assert!(format!("{e:#}").contains("MAX_CLONED_NIBBLES"), "{e:#}");
935 }
936 let ram = repeated(100, &string(20_000));
939 let stack = UserMemory::new(&ram, Layout::HP48SX).stack().unwrap();
940 assert_eq!(stack.len(), 100);
941 assert_eq!(
942 stack[99],
943 Object::String {
944 value: "A".repeat(20_000)
945 }
946 );
947 }
948
949 #[test]
950 fn nested_texts_hit_the_described_budget() {
951 let hex = format!(
954 "{}{}{}",
955 "47A20".repeat(60),
956 string(2000),
957 "B2130".repeat(60)
958 );
959 let ram = repeated(100, &hex);
960 let m = UserMemory::new(&ram, Layout::HP48SX);
961 assert_eq!(m.stack().unwrap().len(), 100);
962 let e = m.stack_described().unwrap_err();
963 assert!(format!("{e:#}").contains("MAX_DESCRIBED_TEXT"), "{e:#}");
964 let ram = repeated(1, &hex);
966 let level = &UserMemory::new(&ram, Layout::HP48SX)
967 .stack_described()
968 .unwrap()[0];
969 assert_eq!(
970 level["items"][0]["text"].as_str().map(str::len),
971 Some(2000 + 59 * 4 + 2)
972 );
973 }
974
975 #[test]
976 fn broken_pointers_are_errors() {
977 let mut ram = sx();
978 ram.put_ptr(Layout::HP48SX.home_end_ptr, 0x7FFF0);
979 assert!(UserMemory::new(&ram, Layout::HP48SX).tree().is_err());
980 let mut ram = sx();
981 ram.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F013);
982 assert!(UserMemory::new(&ram, Layout::HP48SX).stack().is_err());
983 ram.put_ptr(Layout::HP48SX.stack_end_ptr, 0x7F00F);
984 assert!(UserMemory::new(&ram, Layout::HP48SX).stack().is_err());
985 }
986}