def replace_at_index(string: str, new_char: str, index: int, no_fail: bool = False):
    """
    Replace a string's character at the specified index.

    :param string: The original string.
    :param new_char: The new character(s).
    :param index: The index whose character you want to replace.
    :param no_fail: If True, silently fail and return original string.
    :return: A new string with the change applied.
    """
    n = len(string)
    if index not in range(n):
        if not no_fail:
            raise ValueError(f"Index out-of-bounds. Must be 0 < index ≤ {len(string)}.")
        else:
            return string

    max_index = n - 1

    new_string = string[:index] + new_char
    if index < max_index:
        new_string += string[index + 1:]

    return new_string