@Nonnull
	private static StringDecodingResult decodingResult(@Nonnull byte[] data, @Nonnull Charset encoding, int bytesToChars) {
		CharsetDecoder decoder = encoding.newDecoder()
				.onMalformedInput(CodingErrorAction.REPORT)
				.onUnmappableCharacter(CodingErrorAction.REPORT);
		ByteBuffer buffer = ByteBuffer.wrap(data);
		int totalChars = 0;
		int totalTextChars = 0;
		int length = data.length;
		int bufferSize = Math.min(length, 4096);
		char[] charArray = new char[bufferSize];
		CharBuffer charBuf = CharBuffer.wrap(charArray);
		StringBuilder output = new StringBuilder(data.length / bytesToChars);
		while (true) {
			try {
				// Exit when no remaining chars to decode
				if (!buffer.hasRemaining())
					break;

				// Decode next chunk into buffer
				CoderResult result = decoder.decode(buffer, charBuf, true);
				if (result.isMalformed() || result.isError() || result.isUnmappable())
					return failedDecoding(data);
				if (result.isUnderflow())
					decoder.flush(charBuf);

				// Check each character
				int textChars = 0;
				int arrayEnd = charBuf.position();
				for (int i = 0; i < arrayEnd; i++) {
					char c = charArray[i];
					int type = Character.getType(c);
					boolean isTextChar = switch (type) {
						case Character.CONTROL -> (c == '\n' || c == '\r');
						case Character.FORMAT -> EscapeUtil.isWhitespaceChar(c);
						case Character.PRIVATE_USE, Character.SURROGATE, Character.UNASSIGNED -> false;
						default -> true;
					};
					if (isTextChar) textChars++;
				}
				totalTextChars += textChars;
				output.append(charArray, 0, arrayEnd);
				totalChars += arrayEnd;

				// If ay any point we see more than half of the temporary buffer full of non-text characters
				// we're going to assume the rest of the content is also going to be garbage.
				if (((double) textChars / arrayEnd) <= 0.5)
					return failedDecoding(data);

				// If we overflowed in our result, we still have more to decode with this
				// current input buffer, so we should clear the output buffer and continue as-is.
				if (result.isOverflow()) {
					charBuf.flip();
					continue;
				}

				// Ensure buffer position increments to next place, but does not exceed the wrapped array's length.
				buffer.position(Math.min(length, totalChars));

				// Reset the char-buffer contents.
				charBuf.flip();

				// If we underflowed in our result we are most likely done.
				if (result.isUnderflow())
					break;
			} catch (Exception ex) {
				return failedDecoding(data);
			}
		}

		// This isn't great but works well enough for now.1
		// Basically, if most of the content is text we'll call it text even if there is some that isn't totally valid.
		if (((double) totalTextChars / totalChars) > 0.9)
			return new StringDecodingResult(data, encoding, output.toString());
		return failedDecoding(data);
	}
@param data
		Some data to decode.
@param encoding
		Encoding to decode with.
@param bytesToChars
		Expected number of bytes to make up a single {@code char} in the decoded output.
		Strictly used to reduce calls to {@link StringBuilder#ensureCapacity(int)}.

@return String decoding result. Check {@link StringDecodingResult#couldDecode()} to determine if successful.
recaf-core/src/main/java/software/coley/recaf/util/StringUtil.java