synchronized int putBytes(long offset, byte[] b, int off, int length)
			throws IOException, IndexOutOfBoundsException {

		checkValid();

		if (b == null) {
			throw new NullPointerException();
		}
		else if (off < 0 || length < 0 || length > b.length - off) {
			throw new IndexOutOfBoundsException();
		}
		else if (length == 0) {
			return 0;
		}

		// adjust size if asking length is more than we have
		length = (int) Math.min(length, size - offset);
		if (length == 0) {
			return 0;
		}

		// The max buffer size will be the size of the first buffer. (If more than
		// one buffer exists, then the first buffer will be the true max size.  If only one buffer,
		// then its actual size can be used as the max size and it won't matter.)
		int maxBufferSize = layeredBuffers[0].length();
		long fileBytesOffset = offset;
		int byteArrayOffset = off;
		int n = length;

		while (n > 0) {
			int dbBufferIndex = (int) (fileBytesOffset / maxBufferSize);
			int localOffset = (int) (fileBytesOffset % maxBufferSize);
			int writeLen = Math.min(maxBufferSize - localOffset, n);
			layeredBuffers[dbBufferIndex].put(localOffset, b, byteArrayOffset, writeLen);
			n -= writeLen;
			fileBytesOffset += writeLen;
			byteArrayOffset += writeLen;
		}
		return length;
	}
Changes the bytes at the given offset to the given values. Note, the 
original bytes can still be accessed via {@link #getOriginalBytes(long, byte[], int, int)}
If the bytes are changed more than once, only the original values are preserved.

@param offset the offset into the file bytes.
@param b a byte array with the new values to write.
@param off the offset into the byte array to get the bytes to write.
@param length the number of bytes to write.
@return the number of bytes written
@throws IndexOutOfBoundsException if invalid offset is specified
@throws IOException if the write to the database fails.
Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/FileBytes.java