BfSteg4LK

Bf Steganography 4 Lost Kingdom or BfSteg4LK is a Binaryfuck>>>StegFuck derivative that encodes binary code in an image using Steganography. Created by User:Oshaboy in 2021. Bfsteg4lk is like StegFuck but without the curse words and is specifically meant to run Jon Ripley's Lost Kingdom and allow one to emulate (by extension of the interpreter) the save game functionality which is lost when translating the Lost Kingdom BFe game into StegFuck.

I don't know if this even makes enough of a new esolang to fit the wiki[, so instead its going into my portal folder. ~miu.]

Overview

StegFuck images are created by encoding the Binaryfuck program in the image file starting from the top left corner, going right then restarting at the next line from the left. Each channel in each pixel has the least significant 3 bits changed to the binaryfuck command. Then the 32bit length of the program is encoded from the bottom right going left and (if the image is thin enough) up in little endian. This results in a similar looking image that encodes the entire program. This is called Steganography.

Unlike other Image based esolangs like Piet and 4BOD which are visually distinctive. StegFuck images look to the uninitiated just like regular images. Resulting in obfuscation by default. Steganography (and by extension StegFuck) works best on noisy detailed pictures with few flat and dark colors, such as photographs. Note that StegFuck programs will not be preserved after lossy compression (such as JPEG compression) or reduction in color space. Images may be compressed automatically when uploaded to some websites and services. So StegFuck programs will be filtered away.


StegFuck Hello World
Hello World
StegFuck Cat Program
Cat Program
StegFuck Lost Kingdom
Lost Kingdom by Jon Ripley
StegFuck Nope Interpreter
The interpreter of the esolang "Nope."
StegFuck brainfuck interpreter
Brainfuck interpreter
StegFuck TernLSB Polyglot Truth Machine
StegFuck XKCD Random Number
XKCD Random Number program

Implementations

oshaboy's python implementation (modified for save states):

import sys, math
from PIL import Image

code = []
IP = 0
tape = [0 for _ in range(65536)]
pointer = 32768
loop_stack = []
input_buffer = ""
skip = 0
program_size = 0
input_file_lines = None  # list of lines from file, if provided
input_line_index = 0     # pointer into input_file_lines


def inc():
    global pointer, IP
    tape[pointer] = (tape[pointer] + 1) % 256
    IP += 1

def dec():
    global pointer, IP
    tape[pointer] = (tape[pointer] - 1) % 256
    IP += 1

def pointer_left():
    global pointer, tape, IP
    pointer += 1
    if pointer >= len(tape):
        tape.extend([0 for _ in range(65536)])
    IP += 1

def pointer_right():
    global pointer, tape, IP
    pointer -= 1
    if pointer < 0:
        tape = [0 for _ in range(65536)] + tape
        pointer += 65536
    IP += 1

def output_char():
    global IP
    print(chr(tape[pointer]), end="", flush=True)
    IP += 1

def input_char():
    global IP, input_buffer, input_file_lines, input_line_index

    if input_buffer == "":
        # First try scripted input file
        if input_file_lines is not None and input_line_index < len(input_file_lines):
            line = input_file_lines[input_line_index]
            input_line_index += 1
            input_buffer = line + "\n"
        else:
            # Fallback to interactive
            try:
                input_buffer = input() + "\n"
            except EOFError:
                input_buffer = "\n"

    tape[pointer] = ord(input_buffer[0])
    input_buffer = input_buffer[1:]
    IP += 1

def loop():
    global IP, skip
    if tape[pointer] == 0:
        skip += 1
    else:
        loop_stack.append(IP + 1)
    IP += 1

def end():
    global IP
    if tape[pointer] == 0:
        IP += 1
        loop_stack.pop()
    else:
        IP = loop_stack[-1]


instructions = [inc, dec, pointer_right, pointer_left, output_char, input_char, loop, end]
brainfuck_commands = ["+", "-", ">", "<", ".", ",", "[", "]"]

def encode(image_filename, code_string, dest_filename, bppness=32):
    code_length = len(code_string)
    channel_count = 4
    dest_image_mode = "RGBA"
    if bppness == 24:
        channel_count = 3
        dest_image_mode = "RGB"
    elif bppness != 32:
        print("I only support 24 and 32 bpp RGB(A) Images")
        sys.exit(1)

    source_image = Image.open(image_filename).convert(dest_image_mode)
    image_dump = source_image.load()

    x = 0
    y = 0
    number_of_comment_chars = 0
    print("Encoding Code")
    for i in range(0, code_length, channel_count):
        new_pixel = []
        j = 0
        while j < channel_count:
            if i + j + number_of_comment_chars >= code_length:
                amount_done = len(new_pixel)
                for k in range(channel_count - amount_done):
                    new_pixel.append(image_dump[(x, y)][k + amount_done])
                break
            Found = False
            for k in range(8):
                if brainfuck_commands[k] == code_string[i + j + number_of_comment_chars]:
                    new_pixel.append(k)
                    Found = True
                    break
            if not Found:
                number_of_comment_chars += 1
            else:
                new_pixel[j] |= image_dump[(x, y)][j] & 0xf8
                j += 1
        image_dump[(x, y)] = tuple(new_pixel)
        x += 1
        if x >= source_image.width:
            x = 0
            y += 1
        if (y >= source_image.height or
           (y == source_image.height - 1 and x >= source_image.width - math.ceil(32 / channel_count))):
            print("Original Image File too small for Brainfuck program")
            sys.exit(3)
    code_length -= number_of_comment_chars
    y = source_image.height - 1
    x = source_image.width - 1
    print("Encoding Length")
    for i in range(0, 16, channel_count):
        new_pixel = []
        for z in range(channel_count):
            new_pixel.append(image_dump[(x, y)][z] & 0xfc)
            new_pixel[z] |= (code_length & 3)
            code_length >>= 2
        image_dump[(x, y)] = tuple(new_pixel)
        x -= 1
        if x < 0:
            x = source_image.width - 1
            y -= 1
    print("Saving")
    source_image.save(dest_filename)
    print("Image saved as ", dest_filename)


def run(image_filename):
    global IP, skip, program_size, code
    source_image = Image.open(image_filename)
    source_image_dump = source_image.load()
    if source_image.mode == "RGB":
        bppness = 24
    elif source_image.mode == "RGBA":
        bppness = 32
    else:
        print("I only support 24 and 32 bpp RGB(A) Images")
        sys.exit(1)

    x = source_image.width - 1
    y = source_image.height - 1
    counter = 0
    program_size = 0
    while counter < 32:
        pixel = source_image_dump[(x, y)]
        for channel in pixel:
            program_size += (channel & 3) << counter
            counter += 2
            if counter >= 32:
                break
        x -= 1
        if x < 0:
            x = source_image.width
            y -= 1
    x = 0
    y = 0
    i = 0
    while i < program_size:
        pixel = source_image_dump[(x, y)]
        for channel in pixel:
            if i >= program_size:
                break
            code.append(channel & 7)
            i += 1
        x += 1
        if x >= source_image.width:
            x = 0
            y += 1
    while IP < program_size:
        if skip > 0:
            if code[IP] == 7:
                skip -= 1
            elif code[IP] == 6:
                skip += 1
            IP += 1
        else:
            instructions[code[IP]]()


# ---------- ENTRY ----------
if len(sys.argv) < 2:
    print("Usage:")
    print("  python StegFuck.py  [input.txt]")
    sys.exit(1)

image_file = sys.argv[1]
if len(sys.argv) >= 3:
    with open(sys.argv[2], "r", encoding="utf-8") as f:
        input_file_lines = [line.rstrip("\n\r") for line in f]

run(image_file)

See Also

  • Braincopter: A very similar idea
  • TernLSB: A very similar idea, but uses ternary LSB's instead of binary ones.