@Override
	public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) {
		FileAttributes result = new FileAttributes();

		// If the specified file isn't the payload file or the cartFile object isn't defined, bail
		if (!fsIndexHelper.isPayloadFile(file) || cartFile == null) {
			return result;
		}

		// Set of keys (lower-case) that are handled manually and should be skipped when
		// adding remaining keys to the file attributes.
		Set<String> skipKeys = new HashSet<>(Set.of("name"));

		// Set to track all attributes that have been added. Used during bulk addition
		// to add with _# suffixes.
		Set<String> addedAttributes = new HashSet<>();

		if (cartFile.getDataSize() >= 0) {
			result.add(FileAttributeType.SIZE_ATTR, cartFile.getDataSize());
		}
		skipKeys.add("length");

		result.add(FileAttributeType.COMPRESSED_SIZE_ATTR, cartFile.getPackedSize());
		result.add(FileAttributeType.IS_ENCRYPTED_ATTR, true);

		// Won't create the CaRT file object if we don't have a valid key
		result.add(FileAttributeType.HAS_GOOD_PASSWORD_ATTR, true);

		// Keep warning as the first custom attribute to display it first
		// in that section of the file information
		result.add("WARNING", """
				CaRT format is often used to neuter and share malicious files.
				Please use caution if exporting original binary.""");

		// Display the ARC4 key, in hex, that is being used
		result.add("ARC4 Key",
			new String(HashUtilities.hexDump(cartFile.getDecryptor().getARC4Key())));

		// Display the stored hashes next
		for (String hashName : CartV1Constants.EXPECTED_HASHES.keySet()) {
			byte[] footerHashValue = cartFile.getFooterHash(hashName);
			skipKeys.add(hashName.toLowerCase());

			if (footerHashValue != null) {
				result.add("Reported " + hashName,
					new String(HashUtilities.hexDump(footerHashValue)));
			}
			else {
				result.add("Reported " + hashName, "Missing");
			}
		}

		// Generate set of keys to protect that we don't want the metadata to be able
		// to overwrite.
		// Warn the user if any of these exist because they may indicate an attempt to
		// mess with the shown information.
		// Also, set up the list of added attributes so far so that we can track and
		// add _# for non-protected.
		Set<String> protectedKeys = new HashSet<>();
		for (FileAttribute<?> attribute : result.getAttributes()) {
			protectedKeys.add(attribute.getAttributeDisplayName().toLowerCase());
			addedAttributes.add(attribute.getAttributeDisplayName());
		}

		// Before processing final metadata for inclusion in file attributes check if
		// CaRT's header/footer merging is obscuring any attempted overwrite of
		// header data with footer data
		Set<String> warnKeys = new HashSet<>();
		for (Entry<String, JsonElement> entry : cartFile.getHeader()
				.optionalHeaderData()
				.entrySet()) {
			if (CartV1Constants.FOOTER_ONLY_KEYS.contains(entry.getKey().toLowerCase())) {
				warnKeys.add(entry.getKey());
			}
		}

		if (!warnKeys.isEmpty()) {
			result.add("SECURITY WARNING",
				"CaRT file metadata may be attempting to overwrite protected file data: " +
					StringEscapeUtils.escapeHtml4(String.join(", ", warnKeys)) + ".");
		}

		// Construct object to pretty print JSON elements to be shown in the display
		Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();

		// Clear any key warnings to prepare to collect a new set
		warnKeys.clear();

		// Walk all the optional header JsonElements, then the optional footer
		// JsonElements adding each to the file's attributes. Skip any that are
		// in the list of keys handled manually.
		for (Entry<String, JsonElement> entry : cartFile.getMetadata().entrySet()) {
			if (skipKeys.contains(entry.getKey().toLowerCase())) {
				continue;
			}

			// Key not being skipped, check if it is protected, if so record then skip
			if (protectedKeys.contains(entry.getKey().toLowerCase())) {
				warnKeys.add(entry.getKey());
				continue;
			}

			String value = "<Unknown>";

			try {
				value = gson.toJson(entry.getValue());
			}
			catch (IllegalStateException e) {
				value = "Invalid JSON String";
			}

			String key = entry.getKey();
			int suffix_counter = 0;

			while (addedAttributes.contains(key)) {
				suffix_counter++;
				// If more than 100 of the same key are found, stop trying to add them
				if (suffix_counter > 100) {
					suffix_counter = -1;
					break;
				}

				key = entry.getKey() + "_" + suffix_counter;
			}

			if (suffix_counter != -1) {
				result.add(key, value);
				addedAttributes.add(key);
			}
		}

		// If any protected keys were skipped, notify the user
		if (!warnKeys.isEmpty()) {
			result.add("SECURITY WARNING",
				"CaRT file metadata may be attempting to overwrite protected file data: " +
					StringEscapeUtils.escapeHtml4(String.join(", ", warnKeys)) +
					". Skipped those keys.");
		}

		return result;
	}
Helper function to create byte provider for CaRT payload content that is
decompressed and decrypted.

@param payloadFSRL The payload {@link FSRL} of the file system.
@param monitor The task monitor for this system handling
@return A {@link ByteProvider} for the payload content
@throws CancelledException If the user cancels via the monitor
@throws IOException If the file fails to read or CaRT fails
Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/cart/CartFileSystem.java