#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>

#define ROL8(x, n) (((x) << ((n) % 8)) | ((x) >> (8 - ((n) % 8))))
#define ROR8(x, n) (((x) >> ((n) % 8)) | ((x) << (8 - ((n) % 8))))

static const uint8_t encrypted_flag1[] = {
    0xd7, 0x25, 0x7e, 0x9b, 0xa8, 0x2b, 0x5b, 0xa5, 0x56, 0x14,
    0xc2, 0xd9, 0xdb, 0x79, 0x23, 0x46, 0x3e, 0x15, 0x87, 0x17,
    0x38, 0xf2, 0x5b, 0x9f, 0x2c, 0x95, 0x98, 0xe5
};

static const uint8_t encrypted_flag2[] = {
    0x7b, 0x1f, 0x66, 0xfd, 0x20, 0xc3, 0x92, 0x7c, 0xb0, 0xad,
    0x8a, 0xbd, 0x91, 0x0a, 0x88, 0xde, 0x90, 0xbe, 0xce, 0xc1,
    0x48, 0x3a, 0xf9, 0x76, 0x40, 0xed, 0xba, 0xe9, 0xe9, 0x6a
};

__attribute__((noinline)) void print_flag1() {
    void *return_addr = __builtin_return_address(0);
    unsigned long ret = (unsigned long)return_addr;
    extern int main();
    unsigned long main_addr = (unsigned long)&main;
    if (ret < main_addr || ret > main_addr + 0x400) {
        printf(":(\n");
        return;
    }

    uint8_t key_base = 0x1F;
    printf("FLAG 1: ");
    for (size_t i = 0; i < sizeof(encrypted_flag1); i++) {
        uint8_t byte = encrypted_flag1[i];
        byte = byte ^ ((key_base * 3 + i * 3) % 256);
        byte = ROL8(byte, i % 8);
        byte = byte ^ ((key_base * 2 + i * 2) % 256);
        byte = ~byte;
        byte = ROR8(byte, (i + 3) % 8);
        byte = byte ^ ((key_base + i) % 256);
        printf("%c", byte);
    }
    printf("\n");
}

__attribute__((noinline)) void print_flag2() {
    void *return_addr = __builtin_return_address(0);
    unsigned long ret = (unsigned long)return_addr;
    extern int main();
    unsigned long main_addr = (unsigned long)&main;
    if (ret < main_addr || ret > main_addr + 0x400) {
        printf(":(\n");
        return;
    }

    uint8_t key_base = 0x3E;
    printf("FLAG 2: ");
    for (size_t i = 0; i < sizeof(encrypted_flag2); i++) {
        uint8_t byte = encrypted_flag2[i];
        byte = byte ^ ((key_base * 3 + i * 3) % 256);
        byte = ROL8(byte, i % 8);
        byte = byte ^ ((key_base * 2 + i * 2) % 256);
        byte = ~byte;
        byte = ROR8(byte, (i + 3) % 8);
        byte = byte ^ ((key_base + i) % 256);
        printf("%c", byte);
    }
    printf("\n");
}

int main() {
    setbuf(stdout, NULL);
    srand(time(NULL));
    uint8_t iv = rand() % 256;
    printf("IV: 0x%02X\n", iv);

    char input1[256];
    printf("Enter the code: ");
    if (fgets(input1, sizeof(input1), stdin) == NULL) {
        printf(":(\n");
        return 1;
    }

    size_t len1 = strlen(input1);
    if (len1 > 0 && input1[len1 - 1] == '\n') {
        input1[--len1] = '\0';
    }

    if (len1 != 8) {
        printf(":(\n");
        return 1;
    }

    // Part 1: CBC XOR with fixed ROL(x, 3)
    static const uint8_t target1[] = {101, 110, 100, 101, 114, 109, 97, 110}; // "enderman"

    uint8_t prev = iv;
    int ok1 = 1;
    for (int i = 0; i < 8; i++) {
        uint8_t out = ROL8((uint8_t)input1[i], 3) ^ prev;
        if (out != target1[i]) {
            ok1 = 0;
            break;
        }
        prev = out;
    }

    if (!ok1) {
        printf(":(\n");
        return 1;
    }

    print_flag1();
    printf("oh you beat me, but can you do it one more time?\n");

    char input2[256];
    printf("Enter the code: ");
    if (fgets(input2, sizeof(input2), stdin) == NULL) {
        printf(":(\n");
        return 1;
    }

    size_t len2 = strlen(input2);
    if (len2 > 0 && input2[len2 - 1] == '\n') {
        input2[--len2] = '\0';
    }

    if (len2 != 8) {
        printf(":(\n");
        return 1;
    }

    // Part 2: CBC XOR with variable ROL, amounts from input1
    // IV2 = XOR of all input1 bytes
    uint8_t iv2 = 0;
    for (int i = 0; i < 8; i++) {
        iv2 ^= (uint8_t)input1[i];
    }

    static const uint8_t target2[] = {114, 101, 100, 115, 116, 111, 110, 101}; // "redstone"

    prev = iv2;
    int ok2 = 1;
    for (int i = 0; i < 8; i++) {
        uint8_t rot = (uint8_t)input1[i] % 8;
        uint8_t out = ROL8((uint8_t)input2[i], rot) ^ prev;
        if (out != target2[i]) {
            ok2 = 0;
            break;
        }
        prev = out;
    }

    if (!ok2) {
        printf(":(\n");
        return 1;
    }

    print_flag2();
    return 0;
}
