1- // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors.
1+ // SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors.
22// SPDX-License-Identifier: CC-BY-NC-ND-4.0
33
44#include " memory_scanner.h"
55#include " bus.h"
66#include " cpu_core.h"
77#include " cpu_core_private.h"
88
9+ #include " common/error.h"
10+ #include " common/file_system.h"
911#include " common/log.h"
12+ #include " common/path.h"
13+ #include " common/ryml_helpers.h"
1014
1115#include " fmt/format.h"
1216
@@ -340,6 +344,7 @@ bool MemoryWatchList::AddEntry(std::string description, u32 address, MemoryAcces
340344 entry.freeze = freeze;
341345
342346 m_entries.push_back (std::move (entry));
347+ m_entries_changed = true ;
343348 return true ;
344349}
345350
@@ -357,6 +362,7 @@ void MemoryWatchList::RemoveEntry(u32 index)
357362 return ;
358363
359364 m_entries.erase (m_entries.begin () + index);
365+ m_entries_changed = true ;
360366}
361367
362368bool MemoryWatchList::RemoveEntryByAddress (u32 address)
@@ -366,6 +372,7 @@ bool MemoryWatchList::RemoveEntryByAddress(u32 address)
366372 if (it->address == address)
367373 {
368374 m_entries.erase (it);
375+ m_entries_changed = true ;
369376 return true ;
370377 }
371378 }
@@ -389,6 +396,7 @@ void MemoryWatchList::SetEntryFreeze(u32 index, bool freeze)
389396
390397 Entry& entry = m_entries[index];
391398 entry.freeze = freeze;
399+ m_entries_changed = true ;
392400}
393401
394402void MemoryWatchList::SetEntryValue (u32 index, u32 value)
@@ -427,6 +435,12 @@ void MemoryWatchList::UpdateValues()
427435 UpdateEntryValue (&entry);
428436}
429437
438+ void MemoryWatchList::ClearEntries ()
439+ {
440+ m_entries.clear ();
441+ m_entries_changed = false ;
442+ }
443+
430444void MemoryWatchList::SetEntryValue (Entry* entry, u32 value)
431445{
432446 switch (entry->size )
@@ -482,3 +496,91 @@ void MemoryWatchList::UpdateEntryValue(Entry* entry)
482496 if (entry->freeze && entry->changed )
483497 SetEntryValue (entry, old_value);
484498}
499+
500+ bool MemoryWatchList::LoadFromFile (const char * path, Error* error)
501+ {
502+ std::optional<std::string> yaml_data = FileSystem::ReadFileToString (path, error);
503+ if (!yaml_data.has_value ())
504+ {
505+ Error::AddPrefixFmt (error, " Failed to read {}: " , Path::GetFileName (path));
506+ return false ;
507+ }
508+
509+ m_entries.clear ();
510+ m_entries_changed = false ;
511+
512+ const ryml::Tree yaml =
513+ ryml::parse_in_place (to_csubstr (path), c4::substr (reinterpret_cast <char *>(yaml_data->data ()), yaml_data->size ()));
514+ const ryml::ConstNodeRef root = yaml.rootref ();
515+
516+ m_entries.reserve (root.num_children ());
517+ for (const ryml::ConstNodeRef& child : root.cchildren ())
518+ {
519+ Entry entry;
520+ std::string_view address;
521+ std::string_view size;
522+ std::optional<u32 > parsed_address;
523+ if (!GetStringFromObject (child, " description" , &entry.description ) ||
524+ !GetStringFromObject (child, " address" , &address) || !GetStringFromObject (child, " size" , &size) ||
525+ !GetUIntFromObject (child, " isSigned" , &entry.is_signed ) || !GetUIntFromObject (child, " freeze" , &entry.freeze ) ||
526+ !(parsed_address = StringUtil::FromCharsWithOptionalBase<u32 >(address)).has_value () ||
527+ (size != " byte" && size != " halfword" && size != " word" ))
528+ {
529+ Error::SetStringView (error, " One or more required fields are missing in the memory watch entry." );
530+ m_entries.clear ();
531+ return false ;
532+ }
533+
534+ entry.address = parsed_address.value ();
535+ if (size == " byte" )
536+ entry.size = MemoryAccessSize::Byte;
537+ else if (size == " halfword" )
538+ entry.size = MemoryAccessSize::HalfWord;
539+ else // if (size == "word")
540+ entry.size = MemoryAccessSize::Word;
541+
542+ entry.changed = false ;
543+ UpdateEntryValue (&entry);
544+
545+ m_entries.push_back (std::move (entry));
546+ }
547+
548+ DEV_LOG (" Loaded {} entries from {}" , m_entries.size (), Path::GetFileName (path));
549+ return true ;
550+ }
551+
552+ bool MemoryWatchList::SaveToFile (const char * path, Error* error)
553+ {
554+ std::string buf;
555+ auto appender = std::back_inserter (buf);
556+
557+ for (const Entry& entry : m_entries)
558+ {
559+ fmt::format_to (appender, " - description: {}\n " , entry.description );
560+ fmt::format_to (appender, " address: 0x{:08x}\n " , entry.address );
561+ fmt::format_to (appender, " size: {}\n " ,
562+ (entry.size == MemoryAccessSize::Byte) ?
563+ " byte" :
564+ ((entry.size == MemoryAccessSize::HalfWord) ? " halfword" : " word" ));
565+ fmt::format_to (appender, " isSigned: {}\n " , entry.is_signed );
566+ fmt::format_to (appender, " freeze: {}\n " , entry.freeze );
567+ }
568+
569+ // avoid rewriting if unchanged
570+ std::optional<std::string> current_file = FileSystem::ReadFileToString (path);
571+ if (current_file.has_value () && current_file.value () == buf)
572+ {
573+ DEV_LOG (" Memory watch list unchanged, not saving to {}" , Path::GetFileName (path));
574+ m_entries_changed = false ;
575+ return true ;
576+ }
577+
578+ if (!FileSystem::WriteStringToFile (path, buf, error))
579+ {
580+ Error::AddPrefixFmt (error, " Failed to write {}: " , Path::GetFileName (path));
581+ return false ;
582+ }
583+
584+ m_entries_changed = false ;
585+ return true ;
586+ }
0 commit comments