Sign in

Caralynx

@sudo.caralynx.com
268 followers 89 following 704 posts

Reverse engineering, Japanese toy hacking, constantly distracted. Is ordinarily a tufty cat. Icon by @notaventura.bsky.social, banner by @hackmonthegoat.bsky.social‬ Teardowns: @teardowns.caralynx.com | Ko-fi: ko-fi.com/caralynx

PostsRepliesMedia
Caralynx @sudo.caralynx.com · 26/07/2026
By @hackmonthegoat.bsky.social
Lupaladinmon on the beach, propped up on his elbow, topless
010
Caralynx @sudo.caralynx.com · 26/07/2026
By @mundienaskd.bsky.social
Lupaladinmon holding his sword in front of him, making a growling face
120
Caralynx @sudo.caralynx.com · 26/07/2026
By @ridgeone1.bsky.social
Lupaladinmon hovering and holding his sword, ready to swing
14121
Caralynx @sudo.caralynx.com · 26/07/2026
It is time for more Lupaladinmon By @minkytaro.bsky.social
Lupaladinmon looking smug
110
Caralynx @sudo.caralynx.com · 28/05/2026
By @hackmonthegoat.bsky.social
Lupaladinmon ready to strike with his sword
110
Caralynx @sudo.caralynx.com · 28/05/2026
He is sooo round! By @heavymetalpapi.bsky.social
Lupaladinmon in a very round and cute form, leaning forward slightly as if he's flying
100
Caralynx @sudo.caralynx.com · 28/05/2026
Another chibi by @ridgeone1.bsky.social
Slightly more shaded chibi of Lupaladinmon, with him looking slightly upwards and with his sword tucked behind his back
100
Caralynx @sudo.caralynx.com · 28/05/2026
Chibi bsky.app/profile/nota...
Flat shaded chibi of Lupaladinmon
100
Caralynx @sudo.caralynx.com · 28/05/2026
A thread of Lupaladinmon. Currently obsessed with him. Designed by @dwily.bsky.social
Lupaladinmon, front side. A large anthropomorphic wolf clad in white and blue armor with gold accents, with wide blue wings and holding a large sword.Lupaladinmon, back side
1112
Caralynx @sudo.caralynx.com · 09/05/2026
Here's my red panda
Unnamed red panda stepping on and holding on to a stick of bamboo
120
Caralynx @sudo.caralynx.com · 18/04/2026
My sticker came a bit wrinkled at the bottom. Not sure whether it's intentional, but it adds a fantastic level of detail, as if steam was wafting across the ground.
Onsen Fox sticker held at an angle so the light can reflect off the wrinkles on the bottom half of the sticker.
010
Caralynx @sudo.caralynx.com · 09/04/2026
Here's the important part. For some reason this is not mentioned in any of their social media threads, causing people to wonder why "people on those platforms deserve access to information" but not people on Twitter. Don't know why they chose to omit it; I see it as gross miscommunication.
Screenshot of article linked in quoted post:

After almost twenty years on the platform, EFF is logging off of X. This isn’t a decision we made lightly, but it might be overdue. The math hasn’t worked out for a while now.

The Numbers Aren’t Working Out

We posted to Twitter (now known as X) five to ten times a day in 2018. Those tweets garnered somewhere between 50 and 100 million impressions per month. By 2024, our 2,500 X posts generated around 2 million impressions each month. Last year, our 1,500 posts earned roughly 13 million impressions for the entire year. To put it bluntly, an X post today receives less than 3% of the views a single tweet delivered seven years ago.
030
Caralynx @sudo.caralynx.com · 11/02/2026
So... Renode. Most of the peripheral types seem to be modelled decently, and then you start looking at the ADCs. 10 implementations, 8 different interfaces. Somehow they did not specify a contract for ADCs, so everyone's just doing whatever they like. What is even going on over there?
A table comparing the different ways to feed samples into Renode ADC peripheral implementations
100
Caralynx @sudo.caralynx.com · 26/01/2026
For the curious, here's what's inside. The main processor is an Epson S1C33E07F00C100 with 8MB of parallel NOR flash on the side.
Top side of the ICDmini V2's PCBBottom side of the ICDmini V2 PCB, along with bottom half of its shell
010
Caralynx @sudo.caralynx.com · 25/01/2026
Watch my 39C3 talk on modding the Vital Bracelet series YouTube: www.youtube.com/watch?v=ry2Q... Slides available at cfp.cccv.de/39c3/talk/GM... CCC media site: media.ccc.de/v/39c3-be-mo...
Title slide for the presentation titled "BE Modded: Exploring and hacking the Vital Bracelet ecosystem". Sudo is shown to the right wearing and looking at a Vital Bracelet.
262
Caralynx @sudo.caralynx.com · 18/01/2026
Paw sleeves 🎨: @coille.bsky.social
Sudo in a sort of upright lounging position, showing his paws and winking. He is wearing arm and leg sleeves with the same blue coloring as his usual fur coloring in the same places.
130
Caralynx @sudo.caralynx.com · 13/01/2026
I got mine!
One mini print and four stickers
000
Caralynx @sudo.caralynx.com · 12/01/2026
If you see this, post an archer 🎨: @ponah.bsky.social
Tiger wolf pulling a bow with his feet
06417
Caralynx @sudo.caralynx.com · 12/01/2026
I put Garurumon on my Tamagotchi Paradise. (This is a downloadable item created from scratch with import of the animation frames from Digimon Color's firmware.)
0103
Caralynx @sudo.caralynx.com · 11/01/2026
Brute forcing my way through ImageSharp to write indexed PNGs using a specific palette and indexes instead of having it requantize RGB values. Instantiate quantizer and pass it to PngEncoder, then encode a dummy image of the correct dimensions to write out desired palette and indexes.
Screenshot of C# code, 1/3

        #region Quantizer

        class LiteralQuantizer : IQuantizer
        {
            public QuantizerOptions Options { get; } = new();
            public List<int>? Indexes { get; set; }
            public Rgba32[]? Palette { get; set; }

            public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration) where TPixel : unmanaged, IPixel<TPixel>
            {
                return CreatePixelSpecificQuantizer<TPixel>(configuration, Options);
            }

            public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration, QuantizerOptions options) where TPixel : unmanaged, IPixel<TPixel>
            {
                if (Indexes == null) throw new InvalidOperationException("Indexes not set.");
                if (Palette == null) throw new InvalidOperationException("Palette not set.");

                if (typeof(TPixel) == typeof(Rgba32))
                {
                    return (IQuantizer<TPixel>)new LiteralQuantizerRgba32(configuration, options, Indexes, Palette);
                }

                throw new NotSupportedException("Non-Rgba32 pixel formats not supported.");
            }
        }

Screenshot of C# code, 2/3

        class LiteralQuantizerRgba32 : IQuantizer<Rgba32>
        {
            List<int> indexes;
            Rgba32[] palette;

            public Configuration Configuration { get; }

            public QuantizerOptions Options { get; }

            public ReadOnlyMemory<Rgba32> Palette => palette;

            public LiteralQuantizerRgba32(Configuration configuration, QuantizerOptions options, List<int> indexes, Rgba32[] palette)
            {
                Configuration = configuration;
                Options = options;
                this.indexes = indexes;
                this.palette = palette;
            }

            public void AddPaletteColors(Buffer2DRegion<Rgba32> pixelRegion)
            {
            }

            public void Dispose()
            {
            }

            public byte GetQuantizedColor(Rgba32 color, out Rgba32 match)
            {
                throw new NotImplementedException();
            }

Screenshot of C# code, 3/3

            public IndexedImageFrame<Rgba32> QuantizeFrame(ImageFrame<Rgba32> source, Rectangle bounds)
            {
                if (source.Width * source.Height != indexes.Count) throw new ArgumentException("Image dimension mismatch.", nameof(source));
                Rectangle intersect = Rectangle.Intersect(source.Bounds(), bounds);
                IndexedImageFrame<Rgba32> indexedFrame = new(Configuration, intersect.Width, intersect.Height, Palette);

                for (int y = 0; y < intersect.Height; ++y)
                {
                    var row = indexedFrame.GetWritablePixelRowSpanUnsafe(y);
                    int sourceIndex = source.Width * (bounds.Top + y) + bounds.Left;
                    for (int x = 0; x < intersect.Width; ++x)
                    {
                        row[x] = (byte)indexes[sourceIndex++];
                    }
                }

                return indexedFrame;
            }
        }

        #endregion

        static void SaveIndexedPng(PngEncoder encoder, int width, int height, List<int> indexes, Rgba32[] palette, string outPath)
        {
            var quantizer = (LiteralQuantizer)encoder.Quantizer;
            quantizer.Indexes = indexes;
            quantizer.Palette = palette;
            using (Image<Rgba32> dummyImage = new(width, height))
            using (FileStream fs = File.Create(outPath))
            {
                encoder.Encode(dummyImage, fs);
            }
        }
000
Caralynx @sudo.caralynx.com · 29/12/2025
I am going to be talking about the Vital Bracelet series at #39c3 today, at 12:50 CET Fahrplan: fahrplan.events.ccc.de/congress/202... Stream: streaming.media.ccc.de/39c3/fuse
Title slide of the talk BE Modded
260
Caralynx @sudo.caralynx.com · 20/12/2025
It snowy outside 🎨: @ponah.bsky.social #furryart Seriously though, I had to shovel half a foot of snow in the past couple of days, and the forecast says it's going to go below -33°C today.
A caralynx (Sudo, in feral style) bearing gray spotted fur walking through a snow-laden forest away from the viewer with his rear prominent, and looking back.
0263
Caralynx @sudo.caralynx.com · 03/11/2025
Looks like I'm going to #39c3 this year
Top of the 39C3 Pretalx confirmation page for a talk titled "BE Modded: Exploring and hacking the Vital Bracelet ecosystem"
000
Caralynx @sudo.caralynx.com · 29/10/2025
I received a virtual prepaid debit card as a part of a promo, and selected to convert it to plastic. In the meantime, I spent it, and apparently they issued the card after that, because they printed a value of $0.00 on the card. Funniest thing I've seen all day.
A prepaid MasterCard attached to the letter it came on. The card has "0.00" printed in the top right corner (partially obscured by an activation prompt sticker), corresponding to the "0.00" printed near the top of the letter.
030
Caralynx @sudo.caralynx.com · 26/10/2025
I tried to turn on my PowerEdge R630 that has been sitting around turned off for like half a year, and it is no longer turning on. Getting a ton of errors about voltage levels and CPU thermal trip when trying to turn it on. I think it might be a system board issue. Anyone got any ideas?
Screenshot of iDRAC showing errors, including "The system board 3.3V PG voltage is outside of range.", "CPU 2 has a thermal trip (over-temperature) event.", "The system board VCCIO PG voltage is outside of range."
320
Caralynx @sudo.caralynx.com · 26/09/2025
Here's the code for generating that hash. Way too much SHA256 and SHA512 IMO, they could probably have made this simpler. Or used an HMAC.
Snippet of C#

string HashStringSHA256(string input)
{
    return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input))).ToLower();
}

string HashStringSHA512(string input)
{
    return Convert.ToHexString(SHA512.HashData(Encoding.UTF8.GetBytes(input))).ToLower();
}

string CalculateRequestHash(Dictionary<string, string> parameters)
{
    List<string> hashes = new();
    foreach (var p in parameters.OrderBy(p => p.Key))
    {
        hashes.Add($"{HashStringSHA256(p.Key)}_{HashStringSHA512(p.Value)}");
    }

    return HashStringSHA512(HashStringSHA256(string.Join(',', hashes)));
}
000
Caralynx @sudo.caralynx.com · 26/09/2025
Finally, we get into the actual scrambling logic. First is a generator function that returns the X/Y coordinate (in terms of tiles, not pixels) of the tiles to swap. It passes the array [0, 1, ..., numTiles ** 2] to the generate scramble map function, and generates which tile to copy in dest order.
Snippet of JavaScript

function* scrambleIterate(numTiles, scrambleSeed) {
    yield* generateScrambleMap([...Array(numTiles ** 2)].map((element, index) => index), scrambleSeed).map( (element, index) => ({
        source: {
            x: element % numTiles,
            y: Math.floor(element / numTiles)
        },
        dest: {
            x: index % numTiles,
            y: Math.floor(index / numTiles)
        }
    }))
}

function generateScrambleMap(tileIndexes, scrambleSeed) {
    const rng = xorshift32(scrambleSeed);
    // tileIndexes is [0, 1, 2, ..., numTiles ** 2 - 1]
    return tileIndexes.map(element => [rng.next().value, element])
        .sort( (a, b) => +(a[0] > b[0]) - +(b[0] > a[0])) // ascending order
        .map(r => r[1])
}

function* xorshift32(scrambleSeed) {
    const arr = Uint32Array.of(scrambleSeed);
    while (true) {
        arr[0] ^= arr[0] << 13,
        arr[0] ^= arr[0] >>> 17,
        arr[0] ^= arr[0] << 5,
        yield arr[0]
    }
}
100
Caralynx @sudo.caralynx.com · 26/09/2025
The tile size is calculated by first finding the LCM of the number of tiles horizontally and vertically (defined as 4 by the caller) and 8. Not sure why it needs to be a multiple of 8, but that's what they specified.
Snippet of JavaScript

function getTileSize(imgWidth, imgHeight, numTiles) {
    if (imgWidth < numTiles || imgHeight < numTiles)
        return null;
    const minTileSize = lcm(numTiles, minTileMultipleSize); // minTileMultipleSize = 8
    // Make width and height multiple of min tile size
    if (imgWidth > minTileSize && imgHeight > minTileSize) {
        imgWidth = Math.floor(imgWidth / minTileSize) * minTileSize;
        imgHeight = Math.floor(imgHeight / minTileSize) * minTileSize;
    }
    return {
        width: Math.floor(imgWidth / numTiles),
        height: Math.floor(imgHeight / numTiles)
    }
}

function lcm(a, b) {
    const gcd = (a, b) => a ? gcd(b % a, a) : b;
    return a * b / gcd(a, b);
}
100
Caralynx @sudo.caralynx.com · 26/09/2025
Let's look at the unscramble function. It draws the scrambled image, gets the tile size and checks whether it and the scramble seed (supplied by the web API) is valid, clears the scrambled region (we'll see why it draws the image first later), then draws each tile according to a generator.
Snippet of JavaScript

function unscramble(canvasContext, scrambledImg, numTiles, scrambleSeed) {
    canvasContext.drawImage(scrambledImg, 0, 0);
    const tileSize = getTileSize(scrambledImg.width, scrambledImg.height, numTiles);
    if (tileSize && scrambleSeedValid(scrambleSeed)) {
        canvasContext.clearRect(0, 0, tileSize.width * numTiles, tileSize.height * numTiles);
        for (const tile of scrambleIterate(numTiles, scrambleSeed ?? 1)) {
            canvasContext.drawImage(scrambledImg, tile.source.x * tileSize.width, tile.source.y * tileSize.height, tileSize.width, tileSize.height,
                tile.dest.x * tileSize.width, tile.dest.y * tileSize.height, tileSize.width, tileSize.height);
        }
    }
}

function scrambleSeedValid(n) {
    if (n === undefined) return false;
    return n === Math.max(Math.min(n, scrambleSeedMax), scrambleSeedMin); // scrambleSeedMax = 2 ** 32 - 1, scrambleSeedMin = 1
}
100
Caralynx @sudo.caralynx.com · 26/09/2025
The page sets up a Vue component for displaying a canvas of the unscrambled image. That then creates a class that generates said canvas, and exposes functions for loading the scrambled image and drawing to the unscrambled canvas. Here's the code for drawing the canvas.
Snippet of JavaScript

function drawImage(targetWidth) {
    const unscrambledCanvasCtx = unscrambledCanvas.value?.getContext("2d");
    let scrambledCanvas, scrambledCanvasCtx;
    if ("OffscreenCanvas" in window) {
        scrambledCanvas = new OffscreenCanvas(0,0);
        scrambledCanvasCtx = scrambledCanvas.getContext("2d", { alpha: false });
    } else {
        scrambledCanvas = document.createElement("canvas");
        scrambledCanvasCtx = scrambledCanvas.getContext("2d", { alpha: false });
    }
    if (!unscrambledCanvas.value || !unscrambledCanvasCtx || !scrambledImg.value || !scrambledCanvas || !scrambledCanvasCtx)
        return false;

    const scaleFactor = targetWidth * window.devicePixelRatio < scrambledImg.value.width * _e ? _e : 1; // _e = 0.5
    if (scaleFactor === cachedScaleFactor.value)
        return false;

    cachedScaleFactor.value = scaleFactor;
    scrambledCanvas.width = scrambledImg.value.width;
    scrambledCanvas.height = scrambledImg.value.height;
    unscramble(scrambledCanvasCtx, scrambledImg.value, numTiles, scrambleSeed.value);
    unscrambledCanvas.value.width = scrambledCanvas.width * scaleFactor;
    unscrambledCanvas.value.height = scrambledCanvas.height * scaleFactor;
    unscrambledCanvasCtx.drawImage(scrambledCanvas, 0, 0, scrambledCanvas.width, scrambledCanvas.height, 0, 0, unscrambledCanvas.value.width, unscrambledCanvas.value.height);
    scrambledCanvas.width = 0;
    scrambledCanvas.height = 0;
    return true;
}
100
Caralynx @sudo.caralynx.com · 17/09/2025
Sudo has a winter coat too
Screenshot of Critter Cove with Sudo standing, in winter coat colors
030
Caralynx @sudo.caralynx.com · 17/09/2025
Converted the full color textures to masks
Screenshot of a folder of texture files, in both RGB masks and full color textures
110
Caralynx @sudo.caralynx.com · 11/09/2025
I made a mod for @crittercove.bsky.social for loading custom skins. The loading part is easy; next step is to figure out how to export the model and texture from the current body parts selection so people can have a reference to work from.
Sudo dressed in tropical clothing and standing on a beach on Critter CoveCritter Cove character creation screen, showing Sudo's custom skin from the frontCritter Cove character creation screen, showing Sudo's custom skin from the back
130
Caralynx @sudo.caralynx.com · 09/09/2025
Here's what the help command prints. You can see it trailing with a prompt, except that gets clobbered by the success message at the end of command processing.
Snippet of C code

void uart_console_cmd_help(void)
{
    UART_WriteMultiBytes(UART_PORT_0, "\r\n"
        "Console Mode: \r\n"
        " 'R' - Reset CPU.  \r\n"
        " 'w' <addr> <value> - write memory \r\n"
        " 'r' <addr> <length> - read memory \r\n"
        " 'e' <type> <address> <number> - erase flash \r\n"
        " 'xw' <addr> - write by xmodem \r\n"
        " 'xr' <addr> <size>- read by xmodem \r\n"
        " 'sw' <data> - write flash status reg. \r\n"
        " 'sr' <address> - read flash status reg. \r\n"
        " 'id' - Read Flash ID \r\n"
        " 'j' <address> - jump and execute. \r\n"
        " 'q' - Back to ISP scan mode \r\n"
        " CMD: ", 414);
}
100
Caralynx @sudo.caralynx.com · 09/09/2025
For some reason if the command is one character, it checks the character directly, but if it's two, it uses memcmp(). I'm not sure if the compiler optimized the one-character the case, but using memcmp() is pretty wasteful when you could just check two characters directly.
Snippet of C code

    // 08004db4
    UART_WriteByte(UART_PORT_0, '\r');
    UART_WriteByte(UART_PORT_0, '\n');

    if (arg_offsets[0] == 1) {
        switch (uart_buffer[0]) {
            case '?':
                // 08004e30
                uart_console_cmd_help();
                goto cmd_done;
            case 'R':
                // 08004e0e
                UART_WriteMultiBytes(UART_PORT_0, "CMD Done.\r\n", 10);
                NVIC_SystemReset();
                // no return
            case 'w':
                // 08004e00
                *(uint32_t *)args_parsed[1] = args_parsed[2];
                goto cmd_done;
            case 'r':
                // 08004e4c
                uart_console_cmd_read_memory(args_parsed[1], args_parsed[2]);
                goto cmd_done;
            case 'e':
                // 08004e74
                uart_console_cmd_spifc_erase(args_parsed[1], args_parsed[2], args_parsed[3]);
                goto cmd_done;
            case 'j':
                // 08004f18
                Program_ForceJump(args_parsed[1]);
                goto cmd_done;
            case 'q':
                break;
        }
Snippet of C code

    } else if (arg_offsets[0] == 2) {
        if (!memcmp(uart_buffer, "xw", 2)) {
            if (arg_offsets[1] != 0 && uart_console_cmd_xwrite(args_parsed[1]) == 0) {
                goto cmd_done;
            }
        } else if (!memcmp(uart_buffer, "xr", 2)) {
            if (arg_offsets[2] != 0 && uart_console_cmd_xread(args_parsed[1], args_parsed[2]) == 0) {
                goto cmd_done;
            }
        } else if (!memcmp(uart_buffer, "sw", 2)) {
            if (arg_offsets[1] != 0) {
                uart_console_cmd_spifc_write_sr(args_parsed[1]);
                goto cmd_done;
            }
        } else if (!memcmp(uart_buffer, "sr", 2)) {
            uart_console_cmd_spifc_read_sr(&args_parsed[1]);
            goto cmd_done;
        } else if (!memcmp(uart_buffer, "id", 2)) {
            uart_console_cmd_spifc_read_id();
            goto cmd_done;
        } else if (!memcmp(uart_buffer, "dd", 2)) {
            args_parsed[1] -= '0';
            SN_GPIO0->DRVCTRL_b.DRV15 = args_parsed[1];
            SN_GPIO1->DRVCTRL_b.DRV00 = args_parsed[1];
            SN_GPIO1->DRVCTRL_b.DRV01 = args_parsed[1];
            SN_GPIO1->DRVCTRL_b.DRV02 = args_parsed[1];
            SN_GPIO1->DRVCTRL_b.DRV03 = args_parsed[1];
            SN_GPIO1->DRVCTRL_b.DRV04 = args_parsed[1];
            goto cmd_done;
        }
    }

    UART_WriteMultiBytes(UART_PORT_0, "\r\nErr\r\n", 8);
    return 1;

cmd_done:
    uart_buffer_index = 0;
    UART_WriteMultiBytes(UART_PORT_0, "\r\nCMD Done\r\n", 13);
    return 0;
}
100
Caralynx @sudo.caralynx.com · 09/09/2025
The number length is capped to 8 characters, which is fine, but it'll be truncated counting from the left, which isn't exactly how numbers usually work. Here's hex2int(), which doesn't seem to check whether you enter letters above F.
Snippet of C code

uint32_t hex2int(char *str, int len)
{
    uint32_t value = 0;

    for (int i = 0; i < len; ++i) {
        char ch = str[i];
        if (ch >= 'a') {
            ch = ch - 'a' + 10;
        } else if (ch >= 'A') {
            ch = ch - 'A' + 10;
        } else {
            ch = ch - '0';
        }

        value |= ch << (len - 1 - i) * 4;
    }

    return value;
}
100
Caralynx @sudo.caralynx.com · 09/09/2025
Now let's look at argument handling. Nifty code, though still buggy. The code splits arguments by spaces, and records the index of each space. It's a pretty naive approach, though. If multiple spaces are entered in a row, they count as blank arguments.
Snippet of C code

    for (int i = 0; i < uart_buffer_index; ++i) {
        if (uart_buffer[i] == ' ') {
            arg_offsets[num_args++] = i;
        }
    }

    arg_offsets[num_args] = uart_buffer_index - 1;
    uint32_t args_parsed[5] = {0};

    for (unsigned int i = 0; i < num_args + 1; ++i) {
        uint8_t parse_len;
        if (i == 0) {
            parse_len = arg_offsets[i];
        } else {
            parse_len = arg_offsets[i] - arg_offsets[i - 1] - 1;
        }
        if (parse_len > 8) parse_len = 8;
        
        args_parsed[i] = hex2int(&uart_buffer[arg_offsets[i - 1] + 1], parse_len);
    }
100
Caralynx @sudo.caralynx.com · 09/09/2025
Here's the start of the function. The buffer is 4KB. No idea why it's so large, because none of the commands use that much space. First thing to note in the code is it immediately accessing a negative array index. For some reason whoever that wrote the console code is allergic to bounds checking.
Snippet of C code

int uart_console_run(void)
{
    int arg_offsets[5] = {0};
    int num_args = 0;
    uart_console_echo = 1;
    uart_buffer_index = 0;
    uart_buffer[0] = '\0';
    
    while (uart_buffer[uart_buffer_index - 1] != '\r') {
        if (uart_buffer[0] == '\e') {
            UART_WriteByte(UART_PORT_0, '\r');
            UART_WriteByte(UART_PORT_0, '\n');
            uart_buffer_index = 0;
            uart_buffer[0] = '\0';
        }
        
        uint32_t index = uart_buffer_index;
        if (uart_console_proc_index != index) {
            uart_console_proc_index = index;
            uint8_t ch = uart_buffer[uart_buffer_index - 1];
            if (ch == '\b') {
                if (uart_buffer_index < 2) {
                    uart_console_proc_index = 0;
                    uart_buffer_index = 0;
                } else {
                    uart_buffer_index -= 2;
                    uart_console_proc_index = index - 2;
                }
            }

            if (uart_console_echo && (ch >= ' ' || ch == '\b')) {
                UART_WriteByte(UART_PORT_0, ch);
            }
        }
    }
100
Caralynx @sudo.caralynx.com · 01/09/2025
Continued looking through SNC7330's bootrom, and here's a perplexing piece of code that I reconstructed. It's supposed to return the block (or page?) to read the load table from (basically a bad block table), but I'm pretty sure it doesn't actually work.
Screenshot of C code:

uint32_t boot_load_table_offset_from_bb(const uint32_t *tbl)
{
    if ((*tbl & 0xffff) == 0x4242) {
        uint16_t count = *tbl >> 16;
        for (int i = 0; i < count; ++i) {
            uint8_t entry = tbl[i];
            for (int j = 0; j < 32; j += 2) {
                if ((entry >> j & 0x3) == 1) {
                    return i * 4 + j;
                }
            }
        }
    }

    return 0;
}
110
Caralynx @sudo.caralynx.com · 01/09/2025
Managed to find the code in question in the SDK. `over_sampling` is a power of 2, so they could have easily done a shift with one less bit, checked that bit, and rounded up instead of doing any floating point operations.
Screenshot of C code:

int _Set_BaudRate(uart_port_t *pDev, uint32_t UartFreq, uint32_t BaudRate)
{
    #define UART_OVER_SAMPLE_8      1
    #define UART_OVER_SAMPLE_16     0
 
    uint32_t over_sampling;
    double dll;
    
    /* #. Computing Uart Baudrate settings ---------------------------------- */
    {
        double pre_scalar = UartFreq / BaudRate;

        if(pre_scalar > (16 * 0x0000FFFFUL))   /* Out of the maximum of prescaler */
        {
            return 1;
        }
        else if(pre_scalar > 0x0000FFFFUL)
        {
            over_sampling = 16;
            
        }
        else if(pre_scalar <= 0x0000FFFFUL)
        {
            over_sampling = 8;
        }
        
        dll = pre_scalar / over_sampling;

        if( (dll-(uint32_t)dll) >= 0.5 )            /* Rounding */
        {
            dll += 1;
        }
    }
    
    /* #. Fill in UART baud rate registers --------------------------------- */
    {
        pDev->FD_b.OVER8    = ((over_sampling == 8 )? UART_OVER_SAMPLE_8 : 
                                                      UART_OVER_SAMPLE_16);
        pDev->DLL  = (uint32_t)dll & 0xFF;
        pDev->DLM  = ((uint32_t)dll >>8) & 0xFF;
    }
    
    return 0;
}
000
Caralynx @sudo.caralynx.com · 16/08/2025
What I've been working on recently: figuring out how to create custom items for Tamagotchi Paradise Download icon by @ponah.bsky.social, dot sprite by @rayjt9.bsky.social
3131
Caralynx @sudo.caralynx.com · 01/08/2025
Stupid code found in the runtime for a course produced with Articulate Rise 360. This is not minimized. What's wrong with `arr.indexOf(val) !== -1`?
JavaScript code snippet with the following text:

function arrayHas(val, arr) {
  return !!(~(arr.indexOf(val)))
}
100
Caralynx @sudo.caralynx.com · 26/07/2025
No cheating, post your lock screen!
Lock screen of my phone, showing Sudo entangled in vines in the background
010
Caralynx @sudo.caralynx.com · 04/07/2025
Congrats to 1k!
000
Caralynx @sudo.caralynx.com · 22/06/2025
I played Critter Cove Demo (@crittercove.bsky.social) yesterday, and the character customization is pretty good. Was able to make a fairly good approximation of Sudo (colors adjusted manually in the save file). Wish it was possible to import custom textures for exact markings.
Screenshot of Critter Cove where an in-game created version of Sudo stands looking towards the camera.
000
Caralynx @sudo.caralynx.com · 23/05/2025
Who TF returns an exception instead of throwing it?
Screenshot of function WavDotNet.Core.WavWrite<T>.InitialiseFromFile()
100
Caralynx @sudo.caralynx.com · 11/05/2025
Definitely not suspicious at all
Screenshot of a Steam account stealer site's landing pageScreenshot of the Steam account stealer site showing the credential collection page in a fake Google Chrome window. Note the browser in use is Arc.
110
Caralynx @sudo.caralynx.com · 07/05/2025
Here's the actual dongle with HDR. Notice how the only appreciable change is the end of the title. It's not even 4K (the 4K version is more expensive and unavailable).
The HDR variant listing. Note the same title except for "2K-1P-HDR" at the end.
100
Caralynx @sudo.caralynx.com · 07/05/2025
Open the listing, and here's what the title says. 4K and HDR, peachy right? Wrong.
Item listing of the plug when you come from the search results. Note "2K-1P" at the end of the title.
100
Caralynx @sudo.caralynx.com · 07/05/2025
If you search for "EDID dongle HDR" on Amazon, the first option is this one. It's also the cheapest one by far.
Search listing of the HDMI plug
100