code:
#version 110
#extension GL_ARB_shader_texture_lod : enable

//include an additional space here so that we can programmatically search for and disable this preprocessor definition easily
 #define POLYMOST1_EXTENDED

//s_texture points to an indexed color texture
uniform sampler2D s_texture;
//s_palswap is the palette swap texture where u is the color index and v is the shade
uniform sampler2D s_palswap;
//s_palette is the base palette texture where u is the color index
uniform sampler2D s_palette;

#ifdef POLYMOST1_EXTENDED
uniform sampler2D s_detail;
uniform sampler2D s_glow;
#endif

//u_texturePosSize is the texture position & size packaged into a single vec4 as {pos.x, pos.y, size.x, size.y}
uniform vec4 u_texturePosSize;
uniform vec2 u_halfTexelSize;
uniform vec2 u_palswapPos;
uniform vec2 u_palswapSize;

uniform vec2 u_clamp;

uniform float u_shade;
uniform float u_numShades;
uniform float u_visFactor;
uniform float u_fogEnabled;

uniform float u_useColorOnly;
uniform float u_usePalette;
uniform float u_npotEmulation;
uniform float u_npotEmulationFactor;
uniform float u_npotEmulationXOffset;
uniform float u_brightness;
uniform float u_shadeInterpolate;

#ifdef POLYMOST1_EXTENDED
uniform float u_useDetailMapping;
uniform float u_useGlowMapping;
#endif

varying vec4 v_color;
varying float v_distance;

const float c_basepalScale = 255.0/256.0;
const float c_basepalOffset = 0.5/256.0;

const float c_zero = 0.0;
const float c_one = 1.0;
const float c_two = 2.0;
const vec4 c_vec4_one = vec4(c_one);
const float c_wrapThreshold = 0.9;

void main()
{
    float coordY = mix(gl_TexCoord[0].y,gl_TexCoord[0].x,u_usePalette);
    float coordX = mix(gl_TexCoord[0].x,gl_TexCoord[0].y,u_usePalette);
    float period = floor(coordY/u_npotEmulationFactor);
    coordX += u_npotEmulationXOffset*floor(mod(coordY,u_npotEmulationFactor));
    coordY = period+mod(coordY,u_npotEmulationFactor);
    vec2 newCoord = mix(gl_TexCoord[0].xy,mix(vec2(coordX,coordY),vec2(coordY,coordX),u_usePalette),u_npotEmulation);
#ifdef GL_ARB_shader_texture_lod
    vec2 texCoord = mix(fract(newCoord.xy), clamp(newCoord.xy, c_zero, c_one), u_clamp);
    texCoord = clamp(u_texturePosSize.zw*texCoord, u_halfTexelSize, u_texturePosSize.zw-u_halfTexelSize);
    vec4 color = texture2DGradARB(s_texture, u_texturePosSize.xy+texCoord, dFdx(gl_TexCoord[0].xy), dFdy(gl_TexCoord[0].xy));
#else
    vec2 transitionBlend = fwidth(floor(newCoord.xy));
    transitionBlend = fwidth(transitionBlend)+transitionBlend;
    vec2 texCoord = mix(mix(fract(newCoord.xy), abs(c_one-mod(newCoord.xy+c_one, c_two)), transitionBlend), clamp(newCoord.xy, c_zero, c_one), u_clamp);
    texCoord = clamp(u_texturePosSize.zw*texCoord, u_halfTexelSize, u_texturePosSize.zw-u_halfTexelSize);
    vec4 color = texture2D(s_texture, u_texturePosSize.xy+texCoord);
#endif

    float shade = clamp((u_shade+max(u_visFactor*v_distance-0.5*u_shadeInterpolate,c_zero)), c_zero, u_numShades-c_one);
    float shadeFrac = mod(shade, c_one);
    float colorIndex = texture2D(s_palswap, u_palswapPos+u_palswapSize*vec2(color.r, floor(shade)/u_numShades)).r;
    colorIndex = c_basepalOffset + c_basepalScale*colorIndex;
    vec4 palettedColor = texture2D(s_palette, vec2(colorIndex, c_zero));
    colorIndex = texture2D(s_palswap, u_palswapPos+u_palswapSize*vec2(color.r, (floor(shade)+c_one)/u_numShades)).r;
    colorIndex = c_basepalOffset + c_basepalScale*colorIndex;
    vec4 palettedColorNext = texture2D(s_palette, vec2(colorIndex, c_zero));
    palettedColor.rgb = mix(palettedColor.rgb, palettedColorNext.rgb, shadeFrac*u_shadeInterpolate);
    float fullbright = mix(u_usePalette*palettedColor.a, c_zero, u_useColorOnly);
    palettedColor.a = c_one-floor(color.r);
    color = mix(color, palettedColor, u_usePalette);

#ifdef POLYMOST1_EXTENDED
    vec4 detailColor = texture2D(s_detail, gl_TexCoord[3].xy);
    detailColor = mix(c_vec4_one, 2.0*detailColor, u_useDetailMapping*detailColor.a);
    color.rgb *= detailColor.rgb;
#endif

    color = mix(color, c_vec4_one, u_useColorOnly);

    // DEBUG
    //color = texture2D(s_palswap, gl_TexCoord[0].xy);
    //color = texture2D(s_palette, gl_TexCoord[0].xy);
    //color = texture2D(s_texture, gl_TexCoord[0].yx);

    color.rgb = mix(v_color.rgb*color.rgb, color.rgb, fullbright);

    fullbright = max(c_one-u_fogEnabled, fullbright);
    float fogFactor = clamp((gl_Fog.end-gl_FogFragCoord)*gl_Fog.scale, fullbright, c_one);
    //float fogFactor = clamp(gl_FogFragCoord, fullbright, c_one);
    color.rgb = mix(gl_Fog.color.rgb, color.rgb, fogFactor);

#ifdef POLYMOST1_EXTENDED
    coordY = mix(gl_TexCoord[4].y,gl_TexCoord[4].x,u_usePalette);
    coordX = mix(gl_TexCoord[4].x,gl_TexCoord[4].y,u_usePalette);
    period = floor(coordY/u_npotEmulationFactor);
    coordX += u_npotEmulationXOffset*floor(mod(coordY,u_npotEmulationFactor));
    coordY = period+mod(coordY,u_npotEmulationFactor);
    newCoord = mix(gl_TexCoord[4].xy,mix(vec2(coordX,coordY),vec2(coordY,coordX),u_usePalette),u_npotEmulation);
    vec4 glowColor = texture2D(s_glow, newCoord);
    color.rgb = mix(color.rgb, glowColor.rgb, u_useGlowMapping*glowColor.a*(c_one-u_useColorOnly));
#endif

    color.a *= v_color.a;

    color.rgb = pow(color.rgb, vec3(u_brightness));

    gl_FragData[0] = color;
}


code:
#version 110

varying vec4 v_color;
varying float v_distance;

//u_texturePosSize is the texture position & size packaged into a single vec4 as {pos.x, pos.y, size.x, size.y}
uniform vec4 u_texturePosSize;
uniform float u_usePalette;
uniform mat4 u_rotMatrix;

const float c_zero = 0.0;
const float c_one  = 1.0;

void main()
{
   vec4 vertex = u_rotMatrix * gl_Vertex;
   vec4 eyeCoordPosition = gl_ModelViewMatrix * vertex;
   gl_Position = gl_ModelViewProjectionMatrix * vertex;

   eyeCoordPosition.xyz /= eyeCoordPosition.w;

   gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
   gl_TexCoord[0] = mix(gl_TexCoord[0].xyzw, gl_TexCoord[0].yxzw, u_usePalette);

   gl_TexCoord[3] = gl_TextureMatrix[3] * gl_MultiTexCoord3;
   gl_TexCoord[4] = gl_TextureMatrix[4] * gl_MultiTexCoord4;

   gl_FogFragCoord = abs(eyeCoordPosition.z);
   //gl_FogFragCoord = clamp((gl_Fog.end-abs(eyeCoordPosition.z))*gl_Fog.scale, c_zero, c_one);

   v_color = gl_Color;
   v_distance = gl_Vertex.z;
}

repo_name: orochi/nblood

license: mit


code:
// Function-wrapped Watcom pragmas
// by Jonathon Fowler (jf@jonof.id.au)
//
// These functions represent some of the more longer-winded pragmas
// from the original pragmas.h wrapped into functions for easier
// use since many jumps and whatnot make it harder to write macro-
// inline versions. I'll eventually convert these to macro-inline
// equivalents.		--Jonathon

#include "compat.h"
#include "pragmas.h"

libdivide::libdivide_s64_t divtable64[DIVTABLESIZE];
libdivide::libdivide_s32_t divtable32[DIVTABLESIZE];
libdivide::libdivide_s64_branchfree_t bfdivtable64[DIVTABLESIZE];
libdivide::libdivide_s32_branchfree_t bfdivtable32[DIVTABLESIZE];

decltype(divideu32) *divideu32_noinline = divideu32;
decltype(divideu64) *divideu64_noinline = divideu64;
decltype(tabledivide32) *tabledivide32_noinline = tabledivide32;
decltype(tabledivide64) *tabledivide64_noinline = tabledivide64;

decltype(divideu32_branchfree) *divideu32_branchfree_noinline = divideu32_branchfree;
decltype(divideu64_branchfree) *divideu64_branchfree_noinline = divideu64_branchfree;
decltype(tabledivide32_branchfree) *tabledivide32_branchfree_noinline = tabledivide32_branchfree;
decltype(tabledivide64_branchfree) *tabledivide64_branchfree_noinline = tabledivide64_branchfree;

int64_t lastd_s64;
int64_t lastd_s64_b;

int32_t lastd_s32;
int32_t lastd_s32_b;

void initdivtables(void)
{
    using namespace libdivide;

    for (int i = 1; i < DIVTABLESIZE; ++i)
    {
        divtable64[i] = libdivide_s64_gen(i);
        divtable32[i] = libdivide_s32_gen(i);

        bfdivtable64[i] = libdivide_s64_branchfree_gen(i);
        bfdivtable32[i] = libdivide_s32_branchfree_gen(i);
    }
}

#if defined(__GNUC__) && defined(__i386__) && !defined(NOASM)	// NOASM

//
// GCC Inline Assembler version
//

#define ASM __asm__ __volatile__

#define pragmas_have_clearbufbyte

void clearbufbyte(void *D, int32_t c, int32_t a)
{
    ASM(
        "cmpl $4, %%ecx\n\t"
        "jae 1f\n\t"
        "testb $1, %%cl\n\t"
        "jz 0f\n\t"			// jz preskip
        "stosb\n\t"
        "0:\n\t"			// preskip:
        "shrl $1, %%ecx\n\t"
        "rep\n\t"
        "stosw\n\t"
        "jmp 5f\n\t"			// jmp endit
        "1:\n\t"			// intcopy:
        "testl $1, %%edi\n\t"
        "jz 2f\n\t"			// jz skip1
        "stosb\n\t"
        "decl %%ecx\n\t"
        "2:\n\t"			// skip1:
        "testl $2, %%edi\n\t"
        "jz 3f\n\t"			// jz skip2
        "stosw\n\t"
        "subl $2, %%ecx\n\t"
        "3:\n\t"			// skip2:
        "movl %%ecx, %%ebx\n\t"
        "shrl $2, %%ecx\n\t"
        "rep\n\t"
        "stosl\n\t"
        "testb $2, %%bl\n\t"
        "jz 4f\n\t"			// jz skip3
        "stosw\n\t"
        "4:\n\t"			// skip3:
        "testb $1, %%bl\n\t"
        "jz 5f\n\t"			// jz endit
        "stosb\n\t"
        "5:"				// endit
    : "+D"(D), "+c"(c), "+a"(a) :
            : "ebx", "memory", "cc"
        );
}

#define pragmas_have_copybufbyte

void copybufbyte(const void *S, void *D, int32_t c)
{
    ASM(
        "cmpl $4, %%ecx\n\t"		// cmp ecx, 4
        "jae 1f\n\t"
        "testb $1, %%cl\n\t"		// test cl, 1
        "jz 0f\n\t"
        "movsb\n\t"
        "0:\n\t"			// preskip:
        "shrl $1, %%ecx\n\t"		// shr ecx, 1
        "rep\n\t"
        "movsw\n\t"
        "jmp 5f\n\t"
        "1:\n\t"			// intcopy:
        "testl $1, %%edi\n\t"		// test edi, 1
        "jz 2f\n\t"
        "movsb\n\t"
        "decl %%ecx\n\t"
        "2:\n\t"			// skip1:
        "testl $2, %%edi\n\t"		// test edi, 2
        "jz 3f\n\t"
        "movsw\n\t"
        "subl $2, %%ecx\n\t"		// sub ecx, 2
        "3:\n\t"			// skip2:
        "movl %%ecx, %%ebx\n\t"		// mov ebx, ecx
        "shrl $2, %%ecx\n\t"		// shr ecx ,2
        "rep\n\t"
        "movsl\n\t"
        "testb $2, %%bl\n\t"		// test bl, 2
        "jz 4f\n\t"
        "movsw\n\t"
        "4:\n\t"			// skip3:
        "testb $1, %%bl\n\t"		// test bl, 1
        "jz 5f\n\t"
        "movsb\n\t"
        "5:"				// endit:
    : "+c"(c), "+S"(S), "+D"(D) :
            : "ebx", "memory", "cc"
        );
}

#define pragmas_have_copybufreverse

void copybufreverse(const void *S, void *D, int32_t c)
{
    ASM(
        "shrl $1, %%ecx\n\t"
        "jnc 0f\n\t"		// jnc skipit1
        "movb (%%esi), %%al\n\t"
        "decl %%esi\n\t"
        "movb %%al, (%%edi)\n\t"
        "incl %%edi\n\t"
        "0:\n\t"		// skipit1:
        "shrl $1, %%ecx\n\t"
        "jnc 1f\n\t"		// jnc skipit2
        "movw -1(%%esi), %%ax\n\t"
        "subl $2, %%esi\n\t"
        "rorw $8, %%ax\n\t"
        "movw %%ax, (%%edi)\n\t"
        "addl $2, %%edi\n\t"
        "1:\n\t"		// skipit2
        "testl %%ecx, %%ecx\n\t"
        "jz 3f\n\t"		// jz endloop
        "2:\n\t"		// begloop
        "movl -3(%%esi), %%eax\n\t"
        "subl $4, %%esi\n\t"
        "bswapl %%eax\n\t"
        "movl %%eax, (%%edi)\n\t"
        "addl $4, %%edi\n\t"
        "decl %%ecx\n\t"
        "jnz 2b\n\t"		// jnz begloop
        "3:"
    : "+S"(S), "+D"(D), "+c"(c) :
            : "eax", "memory", "cc"
        );
}

#elif defined(_MSC_VER) && !defined(NOASM)		// __GNUC__ && __i386__

//
// Microsoft C Inline Assembler version
//

#elif defined(__GNUC__) && defined(GEKKO)

#define pragmas_have_clearbufbyte

void clearbufbyte(void *d, int32_t c, int32_t a)
{
    if (a==0) {
        uint8_t *dd = (uint8_t*)d;
        int32_t align = (32 - (int32_t)d) & 31;

        if (align && c >= align) {
            uint32_t izero = 0;
            double fzero = 0;
            c -= align;

            if (align&1) {
                *dd = izero;
                dd += 1;
            }
            if (align&2) {
                *(uint16_t*)dd = izero;
                dd += 2;
            }
            if (align&4) {
                *(uint32_t*)dd = izero;
                dd += 4;
            }
            if (align&8) {
                *(double*)dd = fzero;
                dd += 8;
            }
            if (align&16) {
                *(double*)dd = fzero;
                *(double*)(dd+8) = fzero;
                dd += 16;
            }
        }
        align = c >> 5;
        while (align) {
            __asm__ (
                " dcbz  0, %0\n"
                " addi %0, %0, 32\n"
                : "+r"(dd)
                :
                : "memory"
                );
            align--;
        }
        if ((c &= 31)) {
            while (c--) {
                *dd++ = 0;
            }
        }
        return;
    }
    __asm__ __volatile__(
        " add    %1, %1, %2\n"
        " neg.   %2, %2\n"
        " beq 2f\n"
        "1:\n"
        " stbx   %0, %1, %2\n"
        " addic. %2, %2, 1\n"
        " rotrwi %0, %0, 8\n"
        " bne 1b\n"
        "2:\n"
        : "+r"(a), "+b"(d), "+r"(c)
        :
        : "cc", "xer", "memory"
        );
}

#endif

//
// Generic C version
//

#ifndef pragmas_have_qinterpolatedown16
void qinterpolatedown16(intptr_t bufptr, int32_t num, int32_t val, int32_t add)
{
    auto lptr = (int32_t *)bufptr;
    for (size_t i = 0, i_end = num; i < i_end; ++i)
    {
        lptr[i] = val>>16;
        val += add;
    }
}

void qinterpolatedown16short(intptr_t bufptr, int32_t num, int32_t val, int32_t add)
{
    auto sptr = (int16_t *)bufptr;
    for (size_t i = 0, i_end = num; i < i_end; ++i)
    {
        sptr[i] = val>>16;
        val += add;
    }
}
#endif

#ifndef pragmas_have_clearbuf
void clearbuf(void *d, int32_t c, int32_t a)
{
    auto p = (int32_t *)d;

#if 0
    if (a == 0)
    {
        clearbufbyte(d, c<<2, 0);
        return;
    }
#endif

    while (c--)
        *p++ = a;
}
#endif

#ifndef pragmas_have_copybuf
void copybuf(const void *s, void *d, int32_t c)
{
    auto p = (const int32_t *) s;
    auto q = (int32_t *) d;

    while (c--)
        *q++ = *p++;
}
#endif

#ifndef pragmas_have_swaps
void swapbuf4(void *a, void *b, int32_t c)
{
    auto p = (int32_t *) a;
    auto q = (int32_t *) b;

    while ((c--) > 0)
    {
        int x = *q, y = *p;
        *(q++) = y;
        *(p++) = x;
    }
}
#endif

#ifndef pragmas_have_clearbufbyte
void clearbufbyte(void *D, int32_t c, int32_t a)
{
    // Cringe City
    constexpr int32_t m[4] = { 0xffl, 0xff00l, 0xff0000l, (int32_t)0xff000000l };
    int   z = 0;
    auto p = (char *)D;

    while ((c--) > 0)
    {
        *(p++) = (uint8_t)((a & m[z])>>(z<<3));
        z=(z+1)&3;
    }
}
#endif

#ifndef pragmas_have_copybufbyte
void copybufbyte(const void *s, void *d, int32_t c)
{
    auto src = (const char *)s;
    auto dst = (char *)d;

    while (c--)
        *dst++ = *src++;
}
#endif

// copybufreverse() is a special case: use the assembly version for GCC on x86
// *and* x86_64, and the C version otherwise.
// XXX: we don't honor NOASM in the x86_64 case.

#if defined(__GNUC__) && defined(__x86_64__)
// NOTE: Almost CODEDUP from x86 GCC assembly version, except that
// - %%esi -> %%rsi
// - %%edi -> %%rdi
// - (dec,inc,sub,add)l suffix removed where necessary
void copybufreverse(const void *S, void *D, int32_t c)
{
    __asm__ __volatile__(
        "shrl $1, %%ecx\n\t"
        "jnc 0f\n\t"		// jnc skipit1
        "movb (%%rsi), %%al\n\t"
        "dec %%rsi\n\t"
        "movb %%al, (%%rdi)\n\t"
        "inc %%rdi\n\t"
        "0:\n\t"		// skipit1:
        "shrl $1, %%ecx\n\t"
        "jnc 1f\n\t"		// jnc skipit2
        "movw -1(%%rsi), %%ax\n\t"
        "sub $2, %%rsi\n\t"
        "rorw $8, %%ax\n\t"
        "movw %%ax, (%%rdi)\n\t"
        "add $2, %%rdi\n\t"
        "1:\n\t"		// skipit2
        "testl %%ecx, %%ecx\n\t"
        "jz 3f\n\t"		// jz endloop
        "2:\n\t"		// begloop
        "movl -3(%%rsi), %%eax\n\t"
        "sub $4, %%rsi\n\t"
        "bswapl %%eax\n\t"
        "movl %%eax, (%%rdi)\n\t"
        "add $4, %%rdi\n\t"
        "decl %%ecx\n\t"
        "jnz 2b\n\t"		// jnz begloop
        "3:"
    : "+S"(S), "+D"(D), "+c"(c) :
            : "eax", "memory", "cc"
        );
}
#elif !defined pragmas_have_copybufreverse
void copybufreverse(const void *s, void *d, int32_t c)
{
    auto src = (const char *)s;
    auto dst = (char *)d;

    while (c--)
        *dst++ = *src--;
}
#endif


code:
#include "windows_inc.h"

#include "compat.h"
#include "rawinput.h"
#include "winlayer.h"
#include "scancodes.h"
#include "build.h"

static BOOL rawinput_started = 0;
static uint8_t KeyboardState[256] = {0}; // VKeys

extern bool g_mouseEnabled, g_mouseGrabbed;
extern void keySetState(int32_t key, int32_t state);

//#define MASK_DOWN (1<<(i<<1))
//#define MASK_UP (MASK_DOWN<<1)
#ifndef GET_RAWINPUT_CODE_WPARAM
#define GET_RAWINPUT_CODE_WPARAM(wParam)    ((wParam) & 0xff)
#endif

static inline void RI_ProcessMouse(const RAWMOUSE *rmouse)
{
    int32_t i, mask;
    int8_t MWheel = 0;

    if (!g_mouseGrabbed || !appactive)
        return;

    g_mousePos.x += rmouse->lLastX;
    g_mousePos.y += rmouse->lLastY;

    if (rmouse->usFlags & MOUSE_MOVE_ABSOLUTE)
    {
        // untested... maybe devices like wacom tablets set this flag?
        POINT pos = { xdim>>1, ydim>>1 };

        ClientToScreen((HWND)win_gethwnd(), &pos);

        g_mousePos.x -= pos.x;
        g_mousePos.y -= pos.y;
    }

    for (i = 0, mask = (1<<0); mask <= (1<<8); i++, mask<<=2)
    {
        // usButtonFlags:
        //  1<<0: left down   -> 1 / 1<<0
        //  1<<2: middle down -> 2 / 1<<1
        //  1<<4: right down  -> 3 / 1<<2
        //  1<<6: x1 down     -> 4 / 1<<3
        //                    ----------- mwheel here
        //  1<<8: x2 down     -> 7 / 1<<6

        if (mask == 1<<8)
            i = 6;

        if (rmouse->usButtonFlags & mask) // button down
        {
            if (g_mouseCallback)
                g_mouseCallback(i+1, 1);
            g_mouseBits |= 1<<i;
        }
        else if (rmouse->usButtonFlags & (mask<<1)) // button up
        {
            if (g_mouseCallback)
                g_mouseCallback(i+1, 0);
            g_mouseBits &= ~(1<<i);
        }
    }

    MWheel = (rmouse->usButtonFlags & RI_MOUSE_WHEEL) ? rmouse->usButtonData : 0;

    if (MWheel > 0)   	// wheel up
    {
        g_mouseBits |= 16;
        if (g_mouseCallback) g_mouseCallback(5, 1);
    }
    else if (MWheel < 0)  	// wheel down
    {
        g_mouseBits |= 32;
        if (g_mouseCallback) g_mouseCallback(6, 1);
    }
}

static inline void RI_ProcessKeyboard(const RAWKEYBOARD *rkbd)
{
    uint8_t key = rkbd->MakeCode, VKey = rkbd->VKey;

    // for some reason rkbd->MakeCode is wrong for these
    // even though rkbd->VKey is right...

    switch (VKey)
    {
    case VK_SHIFT:
        if (rkbd->Flags & RI_KEY_E0)
            VKey = VK_RSHIFT, key = sc_RightShift;
        break;
    case VK_CONTROL:
        if (rkbd->Flags & RI_KEY_E0)
            VKey = VK_RCONTROL, key = sc_RightControl;
        break;
    case VK_MENU:
        if (rkbd->Flags & RI_KEY_E0)
            VKey = VK_RMENU, key = sc_RightAlt;
        break;
    case VK_UP:
    case VK_NUMPAD8:
        if (rkbd->Flags & RI_KEY_E0)
            VKey = VK_UP, key = sc_UpArrow;
        else
            VKey = VK_NUMPAD8, key = sc_kpad_8;
        break;
    case VK_DOWN:
    case VK_NUMPAD2:
        if (rkbd->Flags & RI_KEY_E0)
            VKey = VK_DOWN, key = sc_DownArrow;
        else
            VKey = VK_NUMPAD2, key = sc_kpad_2;
        break;
    case VK_LEFT:
    case VK_NUMPAD4:
        if (rkbd->Flags & RI_KEY_E0)
            VKey = VK_LEFT, key = sc_LeftArrow;
        else
            VKey = VK_NUMPAD4, key = sc_kpad_4;
        break;
    case VK_RIGHT:
    case VK_NUMPAD6:
        if (rkbd->Flags & RI_KEY_E0)
            VKey = VK_RIGHT, key = sc_RightArrow;
        else
            VKey = VK_NUMPAD6, key = sc_kpad_6;
        break;
    case VK_DIVIDE:
#if 0
        if (rkbd->Flags & RI_KEY_E0)
            key = sc_Slash;
        else
#endif
        key = sc_kpad_Slash;
        break;
    case VK_INSERT:
        key = sc_Insert;
        break;
    case VK_HOME:
        key = sc_Home;
        break;
    case VK_DELETE:
        key = sc_Delete;
        break;
    case VK_END:
        key = sc_End;
        break;
    case VK_PRIOR:
        key = sc_PgUp;
        break;
    case VK_NEXT:
        key = sc_PgDn;
        break;
    case VK_RETURN:
        if (rkbd->Flags & RI_KEY_E0)
            key = sc_kpad_Enter;
        break;
    case VK_PAUSE:
        KeyboardState[VKey] = 1 - (rkbd->Flags & RI_KEY_BREAK);
        if (rkbd->Flags & RI_KEY_BREAK)
            return;

        keySetState(sc_Pause, 1);

        if (keypresscallback)
            keypresscallback(sc_Pause, 1);
    case 0xFF:
        return;
    }

    KeyboardState[VKey] = 1 - (rkbd->Flags & RI_KEY_BREAK);

    if (OSD_HandleScanCode(key, KeyboardState[VKey] > 0))
    {
        keySetState(key, KeyboardState[VKey] != 0);

        if (keypresscallback)
            keypresscallback(key, KeyboardState[VKey] != 0);
    }

    if (rkbd->Flags & RI_KEY_BREAK) return;
    if (((g_keyAsciiEnd+1)&(KEYFIFOSIZ-1)) == g_keyAsciiPos) return;
    if ((g_keyAsciiEnd - g_keyAsciiPos) > 0) return;

    {
        uint8_t buf[2];

        if (ToAscii(VKey, key, &KeyboardState[0], (LPWORD)&buf[0], 0) != 1) return;
        if ((OSD_OSDKey() < 128) && (Btolower(g_keyAsciiTable[OSD_OSDKey()]) == Btolower(buf[0]))) return;
        if (OSD_HandleChar(buf[0]) == 0) return;

        g_keyAsciiFIFO[g_keyAsciiEnd] = buf[0];
        g_keyAsciiEnd = ((g_keyAsciiEnd+1)&(KEYFIFOSIZ-1));
    }
}

// keyboard is always captured regardless of what we tell this function
BOOL RI_CaptureInput(BOOL grab, HWND target)
{
    RAWINPUTDEVICE raw[2];

    raw[0].usUsagePage = 0x01;
    raw[0].usUsage = 0x02;
    raw[0].dwFlags = grab ? (RIDEV_NOLEGACY | RIDEV_CAPTUREMOUSE) : 0;
    raw[0].hwndTarget = grab ? target : NULL;

    raw[1].usUsagePage = 0x01;
    raw[1].usUsage = 0x06;
    raw[1].dwFlags = 0;
    raw[1].hwndTarget = target;

    g_mouseGrabbed = grab;

    return (RegisterRawInputDevices(raw, 2, sizeof(raw[0])) == FALSE);
}

void RI_ProcessMessage(MSG *msg)
{
    if (GET_RAWINPUT_CODE_WPARAM(msg->wParam) == RIM_INPUT)
    {
        UINT dwSize = sizeof(RAWINPUT);
        RAWINPUT raw;

        GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, &raw, &dwSize, sizeof(RAWINPUTHEADER));

        if (raw.header.dwType == RIM_TYPEKEYBOARD)
            RI_ProcessKeyboard(&raw.data.keyboard);
        else if (raw.header.dwType == RIM_TYPEMOUSE)
            RI_ProcessMouse(&raw.data.mouse);
    }

    DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
}

void RI_PollDevices(BOOL loop)
{
    int32_t i;
    MSG msg;

    if (!rawinput_started)
    {
        if (RI_CaptureInput(1, (HWND)win_gethwnd()))
            return;
        rawinput_started = 1;
    }

    if (inputchecked)
    {
        if (g_mouseCallback)
        {
            if (g_mouseBits & 16)
                g_mouseCallback(5, 0);
            if (g_mouseBits & 32)
                g_mouseCallback(6, 0);
        }

        OSD_HandleWheel();
        g_mouseBits &= ~(16|32);
    }

    // snapshot the whole keyboard state so we can translate key presses into ascii later
    for (i = 0; i < 256; i++)
        KeyboardState[i] = GetAsyncKeyState(i) >> 8;

    while (loop && PeekMessage(&msg, 0, WM_INPUT, WM_INPUT, PM_REMOVE | PM_QS_INPUT))
        RI_ProcessMessage(&msg);

    if (g_mouseGrabbed && appactive)
    {
        // center the cursor in the window
        POINT pt = { xdim>>1, ydim>>1 };

        ClientToScreen((HWND)win_gethwnd(), &pt);
        SetCursorPos(pt.x, pt.y);
    }
}

void mouseInit(void)
{
    if (!g_mouseEnabled)
        mouseGrabInput(g_mouseEnabled = g_mouseLockedToWindow);
}

void mouseUninit(void)
{
    if (!g_mouseEnabled) return;
    mouseGrabInput(g_mouseEnabled = 0);
}

void mouseGrabInput(bool grab)
{
    static POINT pos;
    static int32_t d = 0;

    if (!g_mouseEnabled) return;

    if (!g_mouseGrabbed || !d)
    {
        GetCursorPos(&pos);
        d++;
    }

    ShowCursor(grab == 0);
    RI_CaptureInput(grab, (HWND)win_gethwnd());
    SetCursorPos(pos.x, pos.y);
}

void mouseLockToWindow(char a)
{
    UNREFERENCED_PARAMETER(a);
}


code:
// This file is recompiled unconditionally so the timestamp gets updated, even for a partial recompile.
#include "rev.h"

#ifdef __cplusplus
extern "C" {
#endif
const char* s_buildRev = REVSTR;
const char* s_buildTimestamp = __DATE__ " " __TIME__;
#ifdef __cplusplus
}
#endif


code:
#include "compat.h"
#include "build.h"
#include "editor.h"

#include "pngwrite.h"

#include "vfs.h"
#include "communityapi.h"

#include "screenshot.h"

char screenshot_dir[BMAX_PATH] = "screenshots";

//
// screencapture
//

buildvfs_FILE OutputFileCounter::opennextfile(char *fn, char *zeros)
{
    do      // JBF 2004022: So we don't overwrite existing screenshots
    {
        if (count > 9999) return nullptr;

        zeros[0] = ((count/1000)%10)+'0';
        zeros[1] = ((count/100)%10)+'0';
        zeros[2] = ((count/10)%10)+'0';
        zeros[3] = (count%10)+'0';
#ifdef USE_PHYSFS
        buildvfs_FILE file;
        if ((file = buildvfs_fopen_read(fn)) == nullptr) break;
        buildvfs_fclose(file);
#else
        struct Bstat st;
        if (Bstat(fn, &st) == -1) break;
#endif
        count++;
    } while (1);

    return buildvfs_fopen_write(fn);
}

buildvfs_FILE OutputFileCounter::opennextfile_withext(char *fn, const char *ext)
{
    char *dot = strrchr(fn, '.');
    strcpy(dot+1, ext);
    return opennextfile(fn, dot-4);
}

static OutputFileCounter capturecounter;

static void screencapture_end(char *fn, buildvfs_FILE * filptr)
{
#ifdef VWSCREENSHOT
    communityapiSendScreenshot(fn);
#endif
    buildvfs_fclose(*filptr);
    OSD_Printf("Saved screenshot to %s\n", fn);
    Xfree(fn);
    capturecounter.count++;
}

# ifdef USE_OPENGL
#  define HICOLOR (videoGetRenderMode() >= REND_POLYMOST && in3dmode())
# else
#  define HICOLOR 0
# endif

static char* getScreenshotPath(const char* filename)
{
    char fn[BMAX_PATH];

    if (screenshot_dir[0] != 0)
    {
        struct Bstat st;
        if (!Bstat(screenshot_dir, &st) && ((st.st_mode & S_IFMT) == S_IFDIR))
            Bsnprintf(fn, sizeof(fn), "%s/%s", screenshot_dir, filename);
        else if (buildvfs_mkdir(screenshot_dir, S_IRWXU) == 0)
        {
#ifndef NDEBUG
            OSD_Printf("Directory \"%s\" created successfully!\n", screenshot_dir);
#endif
            Bsnprintf(fn, sizeof(fn), "%s/%s", screenshot_dir, filename);
        }
        else
        {
            OSD_Printf("Failed to create directory \"%s\", using root.\n", screenshot_dir);
            Bsnprintf(fn, sizeof(fn), "%s", filename);
        }
    }
    else
        Bsnprintf(fn, sizeof(fn), "%s", filename);

    char* ret = Xstrdup(fn);

    return ret;
}

int videoCaptureScreen(const char *filename, char inverseit)
{
    char* fn = getScreenshotPath(filename);
    buildvfs_FILE fp = capturecounter.opennextfile_withext(fn, "png");

    if (fp == nullptr)
    {
        Xfree(fn);
        return -1;
    }

    uint8_t * const imgBuf = (uint8_t *) Xmalloc(xdim * ydim * (HICOLOR ? 3 : 1));

    videoBeginDrawing(); //{{{

#ifdef USE_OPENGL
    if (HICOLOR)
    {
        glReadPixels(0, 0, xdim, ydim, GL_RGB, GL_UNSIGNED_BYTE, imgBuf);
        int const bytesPerLine = xdim * 3;

        if (inverseit)
        {
            for (int i=0, j = ydim * bytesPerLine; i<j; i+=3)
                swapchar(&imgBuf[i], &imgBuf[i+2]);
        }

        // flip rows
        uint8_t* rowBuf = (uint8_t *) Xmalloc(bytesPerLine);

        for (int i = 0, numRows = ydim >> 1; i < numRows; ++i)
        {
            Bmemcpy(rowBuf, imgBuf + i * bytesPerLine, bytesPerLine);
            Bmemcpy(imgBuf + i * bytesPerLine, imgBuf + (ydim - i - 1) * bytesPerLine, bytesPerLine);
            Bmemcpy(imgBuf + (ydim - i - 1) * bytesPerLine, rowBuf, bytesPerLine);
        }

        Xfree(rowBuf);
    }
    else
#endif
    {
        struct {
            uint8_t r, g, b;
        } palette[256];

        if (inverseit)
        {
            for (bssize_t i = 0; i < 256; ++i)
            {
                palette[i].r = 255 - curpalettefaded[i].r;
                palette[i].g = 255 - curpalettefaded[i].g;
                palette[i].b = 255 - curpalettefaded[i].b;
            }
        }
        else
        {
            for (bssize_t i = 0; i < 256; ++i)
                Bmemcpy(&palette[i], &curpalettefaded[i], sizeof(palette[0]));
        }

        png_set_pal((uint8_t *)palette, 256);

        for (int i = 0; i < ydim; ++i)
            Bmemcpy(imgBuf + i * xdim, (uint8_t *)frameplace + ylookup[i], xdim);
    }

    videoEndDrawing(); //}}}

    png_set_text("Software", osd->version.buf);
    png_write(fp, xdim, ydim, HICOLOR ? PNG_TRUECOLOR : PNG_INDEXED, imgBuf);
    Xfree(imgBuf);
    screencapture_end(fn, &fp);

    return 0;
}

int videoCaptureScreenTGA(const char *filename, char inverseit)
{
    int32_t i;
    char head[18] = { 0,1,1,0,0,0,1,24,0,0,0,0,0/*wlo*/,0/*whi*/,0/*hlo*/,0/*hhi*/,8,0 };
    //char palette[4*256];
    char* fn = getScreenshotPath(filename);

    buildvfs_FILE fil = capturecounter.opennextfile_withext(fn, "tga");
    if (fil == nullptr)
    {
        Xfree(fn);
        return -1;
    }

#ifdef USE_OPENGL
    if (HICOLOR)
    {
        head[1] = 0;    // no colourmap
        head[2] = 2;    // uncompressed truecolour
        head[3] = 0;    // (low) first colourmap index
        head[4] = 0;    // (high) first colourmap index
        head[5] = 0;    // (low) number colourmap entries
        head[6] = 0;    // (high) number colourmap entries
        head[7] = 0;    // colourmap entry size
        head[16] = 24;  // 24 bits per pixel
    }
#endif

    head[12] = xdim & 0xff;
    head[13] = (xdim >> 8) & 0xff;
    head[14] = ydim & 0xff;
    head[15] = (ydim >> 8) & 0xff;

    buildvfs_fwrite(head, 18, 1, fil);

    // palette first
#ifdef USE_OPENGL
    if (!HICOLOR)
#endif
    {
        if (inverseit)
        {
            for (i=0; i<256; i++)
            {
                buildvfs_fputc(255 - curpalettefaded[i].b, fil);
                buildvfs_fputc(255 - curpalettefaded[i].g, fil);
                buildvfs_fputc(255 - curpalettefaded[i].r, fil);
            }
        }
        else
        {
            for (i=0; i<256; i++)
            {
                buildvfs_fputc(curpalettefaded[i].b, fil);
                buildvfs_fputc(curpalettefaded[i].g, fil);
                buildvfs_fputc(curpalettefaded[i].r, fil);
            }
        }
    }

    videoBeginDrawing(); //{{{

# ifdef USE_OPENGL
    if (HICOLOR)
    {
        // 24bit
        int const size = xdim * ydim * 3;
        uint8_t *inversebuf = (uint8_t *) Xmalloc(size);

        glReadPixels(0, 0, xdim, ydim, GL_RGB, GL_UNSIGNED_BYTE, inversebuf);

        for (i = 0; i < size; i += 3)
            swapchar(&inversebuf[i], &inversebuf[i + 2]);

        buildvfs_fwrite(inversebuf, xdim*ydim, 3, fil);
        Xfree(inversebuf);
    }
    else
# endif
    {
        char * const ptr = (char *) frameplace;

        for (i = ydim-1; i >= 0; i--)
            buildvfs_fwrite(ptr + i * bytesperline, xdim, 1, fil);
    }

    videoEndDrawing();   //}}}

    screencapture_end(fn, &fil);

    return 0;
}
#undef HICOLOR


code:
/*
 * screentext.cpp
 *
 * Copyright © 2020, Evan Ramos. All rights reserved.
 */

#include "screentext.h"
#include "build.h"

static inline void SetIfGreater(int32_t *variable, int32_t potentialValue)
{
    if (potentialValue > *variable)
        *variable = potentialValue;
}

using glyph_t = ScreenTextGlyph_t;

// get the string length until the next newline
static inline int32_t GetStringLineLength(glyph_t const * const start, glyph_t const * const end)
{
    glyph_t const * text = start;

    while (text < end && !screentextGlyphIsNewline(*text))
        ++text;

    return text - start;
}

static inline int32_t GetStringNumLines(glyph_t const * text, glyph_t const * const end)
{
    int32_t count = 1;

    while (text < end)
    {
        if (screentextGlyphIsNewline(*text))
            ++count;
        ++text;
    }

    return count;
}

// qstrdim
vec2_t screentextGetSize(ScreenTextSize_t const & data)
{
    if (data.text == NULL)
        return {};

    // optimization: justification in both directions
    if ((data.f & TEXT_XJUSTIFY) && (data.f & TEXT_YJUSTIFY))
        return data.between;

    glyph_t const * text = data.text;
    glyph_t const * const end = data.text + data.len;

    vec2_t size{}; // eventually the return value
    vec2_t pos{}; // holds the coordinate position as we draw each character tile of the string
    vec2_t extent{}; // holds the x-width of each character and the greatest y-height of each line
    vec2_t offset{}; // temporary; holds the last movement made in both directions

    // handle zooming where applicable
    int32_t xspace = mulscale16(data.empty.x, data.zoom);
    int32_t yline = mulscale16(data.empty.y, data.zoom);
    int32_t xbetween = mulscale16(data.between.x, data.zoom);
    int32_t ybetween = mulscale16(data.between.y, data.zoom);
    int32_t constwidth = mulscale16(data.constwidth, data.zoom);
    // size/width/height/spacing/offset values should be multiplied or scaled by zoom (since 100% is 65536, the same as 1<<16)

    glyph_t glyph;
    int constwidthactive = 0;

    // loop through the string
    while (text < end && (glyph = *text))
    {
        // reset this here because we haven't printed anything yet this loop
        extent.x = 0;

        // reset this here because the act of printing something on this line means that we include the margin above in the total size
        offset.y = 0;

        // handle each character itself in the context of screen drawing
        if (screentextGlyphIsControlCode(glyph))
        {
            if (screentextGlyphIsSpace(glyph))
            {
                // width
                extent.x = xspace;

                // prepare the height
                SetIfGreater(&extent.y, yline);
            }
            else if (screentextGlyphIsTab(glyph))
            {
                // width
                extent.x = xspace << 2; // * 4

                // prepare the height
                SetIfGreater(&extent.y, yline);
            }
            else if (screentextGlyphIsNewline(glyph))
            {
                extent.x = 0;

                // save the position
                if (!(data.f & TEXT_XOFFSETZERO)) // we want the entire offset to count as the character width
                    pos.x -= offset.x;
                SetIfGreater(&size.x, pos.x);

                // reset the position
                pos.x = 0;

                // prepare the height
                SetIfGreater(&extent.y, yline);

                // move down the line height
                if (!(data.f & TEXT_YOFFSETZERO))
                    pos.y += extent.y;

                // reset the current height
                extent.y = 0;

                // line spacing
                offset.y = (data.f & TEXT_YJUSTIFY) ? 0 : ybetween; // ternary to prevent overflow
                pos.y += offset.y;
            }
            else if (screentextGlyphIsPalChange(glyph))
            {
                ++text;
                continue;
            }
            else if (screentextGlyphIsConstWidth(glyph))
            {
                constwidthactive = 1;
                ++text;
                continue;
            }
        }
        else
        {
            uint16_t const tile = screentextGlyphGetTile(glyph);
            vec2_16_t const siz = tilesiz[tile];

            // width
            extent.x = constwidthactive ? constwidth : siz.x * data.zoom;

            // height
            SetIfGreater(&extent.y, (siz.y * data.zoom));
        }

        // incrementing the coordinate counters
        offset.x = 0;

        // advance the x coordinate
        if (!(data.f & TEXT_XOFFSETZERO) || constwidthactive)
            offset.x += extent.x;

        // account for text spacing
        if (!constwidthactive
            && !screentextGlyphIsNewline(glyph)
            && !(data.f & TEXT_XJUSTIFY)) // to prevent overflow
            offset.x += xbetween;

        pos.x += offset.x;

        // save some trouble with calculation in case the line breaks
        if (!(data.f & TEXT_XOFFSETZERO) || constwidthactive)
            offset.x -= extent.x;

        // iterate to the next character in the string
        ++text;

        // reset at end of loop
        constwidthactive = 0;
    }

    // calculate final size
    if (!(data.f & TEXT_XOFFSETZERO))
        pos.x -= offset.x;

    if (!(data.f & TEXT_YOFFSETZERO))
    {
        pos.y -= offset.y;
        pos.y += extent.y;
    }
    else
        pos.y += ybetween;

    SetIfGreater(&size.x, pos.x);
    SetIfGreater(&size.y, pos.y);

    // justification where only one of the two directions is set, so we have to iterate
    if (data.f & TEXT_XJUSTIFY)
        size.x = xbetween;
    if (data.f & TEXT_YJUSTIFY)
        size.y = ybetween;

    return size;
}

static inline void AddCoordsFromRotation(vec2_t *coords, const vec2_t *unitDirection, const int32_t magnitude)
{
    coords->x += mulscale14(magnitude, unitDirection->x);
    coords->y += mulscale14(magnitude, unitDirection->y);
}

// screentext
vec2_t screentextRender(ScreenText_t const & data)
{
    if (data.text == NULL)
        return {};

    glyph_t const * text = data.text;
    glyph_t const * const end = data.text + data.len;

    ScreenTextSize_t sizedata{data.size};
    sizedata.f &= ~(TEXT_XJUSTIFY|TEXT_YJUSTIFY);
    if (data.f & TEXT_XJUSTIFY)
        sizedata.between.x = 0;
    if (data.f & TEXT_YJUSTIFY)
        sizedata.between.y = 0;

    vec2_t size = screentextGetSize(sizedata); // eventually the return value, and we need it for alignment
    vec2_t origin{}; // where to start, depending on the alignment
    vec2_t pos{}; // holds the coordinate position as we draw each character tile of the string
    vec2_t extent{}; // holds the x-width of each character and the greatest y-height of each line
    const vec2_t Xdirection = { sintable[(data.blockangle+512)&2047], sintable[data.blockangle&2047], };
    const vec2_t Ydirection = { sintable[(data.blockangle+1024)&2047], sintable[(data.blockangle+512)&2047], };

    // handle zooming where applicable
    int32_t xspace = mulscale16(data.empty.x, data.zoom);
    int32_t yline = mulscale16(data.empty.y, data.zoom);
    int32_t xbetween = mulscale16(data.between.x, data.zoom);
    int32_t ybetween = mulscale16(data.between.y, data.zoom);
    int32_t constwidth = mulscale16(data.constwidth, data.zoom);
    // size/width/height/spacing/offset values should be multiplied or scaled by zoom (since 100% is 65536, the same as 1<<16)

    int32_t alpha = data.alpha, blendidx = 0, o = data.o;
    NEG_ALPHA_TO_BLEND(alpha, blendidx, o);
    uint8_t pal = data.pal;

    glyph_t glyph;
    int constwidthactive = 0;

    if ((data.f & TEXT_VARHEIGHT) && !(o & RS_TOPLEFT))
        origin.y = mulscale16(data.standardhalfheight, data.zoom);

    // near-CODEDUP "alignments"
    {
        int32_t lines = GetStringNumLines(text, end);

        if ((data.f & TEXT_XJUSTIFY) || (data.f & TEXT_XRIGHT) || (data.f & TEXT_XCENTER))
        {
            int32_t const length = GetStringLineLength(text, end);

            int32_t linewidth = size.x;

            if (lines != 1)
            {
                sizedata.text = text;
                sizedata.len = length;
                linewidth = screentextGetSize(sizedata).x;
            }

            if (data.f & TEXT_XJUSTIFY)
            {
                size.x = xbetween;

                xbetween = (length == 1) ? 0 : tabledivide32_noinline((xbetween - linewidth), (length - 1));

                linewidth = size.x;
            }

            if (data.f & TEXT_XRIGHT)
                origin.x = -(linewidth/data.zoom*data.zoom);
            else if (data.f & TEXT_XCENTER)
                origin.x = -(linewidth/2/data.zoom*data.zoom);
        }

        if (data.f & TEXT_YJUSTIFY)
        {
            const int32_t tempswap = ybetween;
            ybetween = (lines == 1) ? 0 : tabledivide32_noinline(ybetween - size.y, lines - 1);
            size.y = tempswap;
        }

        if (data.f & TEXT_YBOTTOM)
            origin.y += -(size.y/data.zoom*data.zoom);
        else if (data.f & TEXT_YCENTER)
            origin.y += -(size.y/2/data.zoom*data.zoom);
    }

    // loop through the string
    while (text < end && (glyph = *text))
    {
        // reset this here because we haven't printed anything yet this loop
        extent.x = 0;

        int32_t angle = data.blockangle + data.charangle;

        // handle each character itself in the context of screen drawing
        if (screentextGlyphIsControlCode(glyph))
        {
            if (screentextGlyphIsSpace(glyph))
            {
                // width
                extent.x = xspace;

                // prepare the height
                SetIfGreater(&extent.y, yline);
            }
            else if (screentextGlyphIsTab(glyph))
            {
                // width
                extent.x = xspace << 2; // * 4

                // prepare the height
                SetIfGreater(&extent.y, yline);
            }
            else if (screentextGlyphIsNewline(glyph))
            {
                extent.x = 0;

                // reset the position
                pos.x = 0;

                // prepare the height
                SetIfGreater(&extent.y, yline);

                // move down the line height
                if (!(data.f & TEXT_YOFFSETZERO))
                    pos.y += extent.y;

                // reset the current height
                extent.y = 0;

                // line spacing
                pos.y += ybetween;

                // near-CODEDUP "alignments"
                if ((data.f & TEXT_XJUSTIFY) || (data.f & TEXT_XRIGHT) || (data.f & TEXT_XCENTER))
                {
                    int32_t const length = GetStringLineLength(text+1, end);

                    sizedata.text = text+1;
                    sizedata.len = length;
                    int32_t linewidth = screentextGetSize(sizedata).x;

                    if (data.f & TEXT_XJUSTIFY)
                    {
                        xbetween = (length == 1) ? 0 : tabledivide32_noinline(xbetween - linewidth, length - 1);

                        linewidth = size.x;
                    }

                    if (data.f & TEXT_XRIGHT)
                        origin.x = -(linewidth/data.zoom*data.zoom);
                    else if (data.f & TEXT_XCENTER)
                        origin.x = -(linewidth/2/data.zoom*data.zoom);
                }
            }
            else if (screentextGlyphIsPalChange(glyph))
            {
                pal = screentextGlyphGetPalChange(glyph);
                ++text;
                continue;
            }
            else if (screentextGlyphIsConstWidth(glyph))
            {
                constwidthactive = 1;
                ++text;
                continue;
            }
        }
        else
        {
            uint16_t const tile = screentextGlyphGetTile(glyph);
            vec2_16_t const siz = tilesiz[tile];
            vec2_t location{data.pos};

            AddCoordsFromRotation(&location, &Xdirection, origin.x);
            AddCoordsFromRotation(&location, &Ydirection, origin.y);

            AddCoordsFromRotation(&location, &Xdirection, pos.x);
            AddCoordsFromRotation(&location, &Ydirection, pos.y);

            if ((data.f & TEXT_VARHEIGHT) && !(o & RS_TOPLEFT))
                AddCoordsFromRotation(&location, &Xdirection, (siz.x >> 1) * data.zoom);

            rotatesprite_(location.x, location.y, data.zoom, angle, tile, data.shade, pal, o, alpha, blendidx, data.b1.x, data.b1.y, data.b2.x, data.b2.y);

            // width
            extent.x = constwidthactive ? constwidth : siz.x * data.zoom;

            // height
            SetIfGreater(&extent.y, (siz.y * data.zoom));
        }

        // incrementing the coordinate counters
        int32_t xoffset = 0;

        // advance the x coordinate
        if (!(data.f & TEXT_XOFFSETZERO) || constwidthactive)
            xoffset += extent.x;

        // account for text spacing
        if (!constwidthactive
            && !screentextGlyphIsNewline(glyph))
            xoffset += xbetween;

        pos.x += xoffset;

        // iterate to the next character in the string
        ++text;

        // reset at end of loop
        constwidthactive = 0;
    }

    return size;
}

vec2_t screentextRenderShadow(ScreenText_t const & data, vec2_t shadowpos, int32_t shadowpal)
{
    ScreenText_t shadow{data};
    shadow.pos.x += mulscale16(shadowpos.x, data.zoom);
    shadow.pos.y += mulscale16(shadowpos.y, data.zoom);
    shadow.shade = 127;
    shadow.pal = shadowpal;
    screentextRender(shadow);

    return screentextRender(data);
}

#include <unordered_map>

using tilefont_map_t = std::unordered_map<uint32_t, uint16_t>;
static std::unordered_map<uint16_t, tilefont_map_t> tilefontList;

TileFontPtr_t tilefontGetPtr(uint16_t tilenum)
{
    tilefont_map_t & myTileFont = tilefontList[tilenum];
    return TileFontPtr_t{&myTileFont};
}

TileFontPtr_t tilefontFind(uint16_t tilenum)
{
    auto iter = tilefontList.find(tilenum);
    if (iter == tilefontList.end())
        return TileFontPtr_t{nullptr};

    tilefont_map_t & myTileFont = iter->second;
    return TileFontPtr_t{&myTileFont};
}

void tilefontDefineMapping(TileFontPtr_t tilefontPtr, uint32_t chr, uint16_t tilenum)
{
    auto & myTileFont = *(tilefont_map_t *)tilefontPtr.opaque;
    myTileFont[chr] = tilenum;
}

void tilefontMaybeDefineMapping(TileFontPtr_t tilefontPtr, uint32_t chr, uint16_t tilenum)
{
    auto & myTileFont = *(tilefont_map_t *)tilefontPtr.opaque;
    myTileFont.emplace(chr, tilenum);
}

uint16_t tilefontLookup(TileFontPtr_t tilefontPtr, uint32_t chr)
{
    auto & myTileFont = *(tilefont_map_t *)tilefontPtr.opaque;

    auto iter = myTileFont.find(chr);
    if (iter == myTileFont.end())
        return 0;

    return iter->second;
}

struct LocaleCharWrapper_t
{
    char const * str;

    LocaleCharWrapper_t deepCopy()
    {
        str = strdup(str);
        return *this;
    }

    void doFree()
    {
        free(const_cast<char *>(str));
        str = nullptr;
    }
};

/* 32 bit FNV-1a Hash */
struct LocaleCharWrapper_hash
{
    size_t operator()(const LocaleCharWrapper_t& s) const noexcept
    {
        uint32_t hash = 0x811c9dc5;
        uint32_t fnv_1_prime = 0x01000193;

        char const * content = s.str;
        int i = 0;
        while(content[i])
        {
            hash ^= content[i++];
            hash *= fnv_1_prime;
        }

        return hash;
    }
};

/* Required for comparing wrappers inside the unordered map. */
inline bool operator==(const LocaleCharWrapper_t a, const LocaleCharWrapper_t b)
{
    return Bstrcmp(a.str, b.str) == 0;
}

using locale_map_t = std::unordered_map<LocaleCharWrapper_t, LocaleCharWrapper_t, LocaleCharWrapper_hash>;
static std::unordered_map<LocaleCharWrapper_t, locale_map_t, LocaleCharWrapper_hash> localeList{{LocaleCharWrapper_t{"en"}.deepCopy(), {}}};
static locale_map_t * currentLocale;

LocalePtr_t localeGetPtr(char const * localeName)
{
    auto wrappedName = LocaleCharWrapper_t{localeName};
    auto iter = localeList.find(wrappedName);

    if (iter == localeList.end())
    {
        locale_map_t & myLocale = localeList[wrappedName.deepCopy()];
        return LocalePtr_t{&myLocale};
    }
    else
    {
        locale_map_t & myLocale = iter->second;
        return LocalePtr_t{&myLocale};
    }
}

/* Replace existing mapping */
void localeDefineMapping(LocalePtr_t localePtr, char const * key, char const * val)
{
    auto & myLocale = *(locale_map_t *)localePtr.opaque;
    auto wrappedKey = LocaleCharWrapper_t{key};
    auto wrappedVal = LocaleCharWrapper_t{val};
    auto iter = myLocale.find(wrappedKey);

    if (iter != myLocale.end())
    {
        iter->second.doFree();
        iter->second = wrappedVal.deepCopy();
    }
    else
    {
        myLocale[wrappedKey.deepCopy()] = wrappedVal.deepCopy();
    }
}

/* Don't replace existing mapping */
void localeMaybeDefineMapping(LocalePtr_t localePtr, char const * key, char const * val)
{
    auto & myLocale = *(locale_map_t *)localePtr.opaque;
    auto wrappedKey = LocaleCharWrapper_t{key};
    auto iter = myLocale.find(wrappedKey);

    if (iter != myLocale.end())
        return;

    auto wrappedVal = LocaleCharWrapper_t{val};
    myLocale[wrappedKey.deepCopy()] = wrappedVal.deepCopy();
}

void localeSetCurrent(char const * localeName)
{
    auto wrappedName = LocaleCharWrapper_t{localeName};
    auto iter = localeList.find(wrappedName);

    if (iter == localeList.end())
        return;

    locale_map_t & myLocale = iter->second;
    currentLocale = &myLocale;
}

const char * localeGetCurrent(void)
{
    if (currentLocale == nullptr)
        return nullptr;

    for (auto iter = localeList.begin(); iter != localeList.end(); ++iter)
    {
        if (currentLocale == &iter->second) return iter->first.str;
    }

    return nullptr;
}

const char ** localeGetKeys(int32_t& localeCount)
{
    if (currentLocale == nullptr)
    {
        localeCount = 0;
        return nullptr;
    }

    localeCount = localeList.size();
    const char ** localeKeyList = (const char **) Xmalloc(localeCount * sizeof(char*));

    int k = 0;
    for (auto iter = localeList.begin(); iter != localeList.end(); ++iter)
        localeKeyList[k++] = iter->first.str;

    return localeKeyList;
}

const char * localeLookup(char const * str)
{
    if (currentLocale == nullptr)
        return str;

    auto wrappedStr = LocaleCharWrapper_t{str};
    auto iter = currentLocale->find(wrappedStr);

    if (iter == currentLocale->end())
        return str;

    return iter->second.str;
}


code:
/*
 * File Tokeniser/Parser/Whatever
 * by Jonathon Fowler
 * Remixed completely by Ken Silverman
 * See the included license file "BUILDLIC.TXT" for license info.
 */

#include "compat.h"
#include "scriptfile.h"
#include "baselayer.h"
#include "compat.h"
#include "cache1d.h"

#include "vfs.h"

#define ISWS(x) ((x == ' ') || (x == '\t') || (x == '\r') || (x == '\n'))
static inline void skipoverws(scriptfile *sf) { if ((sf->textptr < sf->eof) && (!sf->textptr[0])) sf->textptr++; }
static inline void skipovertoken(scriptfile *sf) { while ((sf->textptr < sf->eof) && (sf->textptr[0])) sf->textptr++; }

char *scriptfile_gettoken(scriptfile *sf)
{
    if (scriptfile_eof(sf)) return NULL;

    char *start = sf->ltextptr = sf->textptr;
    skipovertoken(sf);
    return start;
}

static int scriptfile_eof_error(scriptfile *sf)
{
    if (scriptfile_eof(sf))
    {
        LOG_F(ERROR, "%s:%d: Unexpected EOF!", sf->filename, scriptfile_getlinum(sf, sf->textptr));
        return -1;
    }

    return 0;
}

int32_t scriptfile_getstring(scriptfile *sf, char **retst)
{
    (*retst) = scriptfile_gettoken(sf);
    if (*retst == NULL)
    {
        LOG_F(ERROR, "%s:%d: Unexpected EOF!",sf->filename,scriptfile_getlinum(sf,sf->textptr));
        return -2;
    }
    return 0;
}

int32_t scriptfile_getnumber(scriptfile *sf, int32_t *num)
{
    if (scriptfile_eof_error(sf)) return -1;

    while ((sf->textptr[0] == '0') && isdigit(sf->textptr[1]))
        sf->textptr++; //hack to treat octal numbers like decimal

    sf->ltextptr = sf->textptr;
    (*num) = strtol((const char *)sf->textptr,&sf->textptr,0);
    if (!ISWS(*sf->textptr) && *sf->textptr)
    {
        char *p = sf->textptr;
        skipovertoken(sf);
        LOG_F(ERROR, "%s:%d: Expected an integer, got '%s'",sf->filename,scriptfile_getlinum(sf,sf->ltextptr),p);
        return -2;
    }
    return 0;
}

static double parsedouble(char *ptr, char **end)
{
    int32_t beforedecimal = 1, negative = 0, dig;
    int32_t exposgn = 0, expo = 0;
    double num = 0.0, decpl = 0.1;
    char *p;

    p = ptr;
    if (*p == '-') negative = 1, p++;
    else if (*p == '+') p++;
    for (;; p++)
    {
        if (isdigit(*p))
        {
            dig = *p - '0';
            if (beforedecimal) num = num * 10.0 + dig;
            else if (exposgn) expo = expo*10 + dig;
            else
            {
                num += (double)dig * decpl;
                decpl /= 10.0;
            }
        }
        else if (*p == '.')
        {
            if (beforedecimal) beforedecimal = 0;
            else break;
        }
        else if ((*p == 'E') || (*p == 'e'))
        {
            exposgn = 1;
            if (p[1] == '-') { exposgn = -1; p++; }
            else if (p[1] == '+') p++;
        }
        else break;
    }

    if (end) *end = p;
    if (exposgn) num *= pow(10.0,(double)(expo*exposgn));
    return negative ? -num : num;
}

int32_t scriptfile_getdouble(scriptfile *sf, double *num)
{
    if (scriptfile_eof_error(sf))
        return -1;

    sf->ltextptr = sf->textptr;

    // On Linux, locale settings interfere with interpreting x.y format numbers
    //(*num) = strtod((const char *)sf->textptr,&sf->textptr);
    (*num) = parsedouble(sf->textptr, &sf->textptr);

    if (!ISWS(*sf->textptr) && *sf->textptr)
    {
        char *p = sf->textptr;
        skipovertoken(sf);
        LOG_F(ERROR, "%s:%d: Expected a float, got '%s'",sf->filename,scriptfile_getlinum(sf,sf->ltextptr),p);
        return -2;
    }
    return 0;
}

int scriptfile_getsymbol(scriptfile *sf, int32_t *num)
{
    char *t = scriptfile_gettoken(sf);
    if (!t) return -1;

    char *  e;
    int32_t v = Bstrtol(t, &e, 10);

    if (*e)
    {
        // looks like a string, so find it in the symbol table
        if (scriptfile_getsymbolvalue(t, num)) return 0;
        LOG_F(ERROR, "%s:%d: Expected a symbol, got '%s'", sf->filename, scriptfile_getlinum(sf, sf->ltextptr), t);
        return -2;
    }

    *num = v;
    return 0;
}

int32_t scriptfile_getbraces(scriptfile *sf, char **braceend)
{
    if (scriptfile_eof_error(sf))
        return -1;

    if (sf->textptr[0] != '{')
    {
        LOG_F(ERROR, "%s:%d: Expected '{'",sf->filename,scriptfile_getlinum(sf,sf->textptr));
        return -1;
    }

    char *bracestart = ++sf->textptr;
    int   bracecnt   = 1;

    do
    {
        if (sf->textptr >= sf->eof)
            return 0;
        else if (sf->textptr[0] == '{')
            bracecnt++;
        else if (sf->textptr[0] == '}')
            if (!(--bracecnt))
                break;
        sf->textptr++;
    } while (1);

    (*braceend) = sf->textptr;
    sf->textptr = bracestart;
    return 0;
}

int32_t scriptfile_getlinum(const scriptfile *sf, const char *ptr)
{
    int32_t i, stp;
    intptr_t ind;

    //for(i=0;i<sf->linenum;i++) if (sf->lineoffs[i] >= ind) return i+1; //brute force algo

    ind = ((intptr_t)ptr) - ((intptr_t)sf->textbuf);

    for (stp=1; stp+stp<sf->linenum; stp+=stp) { } //stp = highest power of 2 less than sf->linenum
    for (i=0; stp; stp>>=1)
        if ((i+stp-1 < sf->linenum) && (sf->lineoffs[i+stp-1] < ind)) i += stp;
    return i+1; //i = index to highest lineoffs which is less than ind; convert to 1-based line numbers
}

void scriptfile_preparse(scriptfile *sf, char *tx, int32_t flen)
{
    int32_t i, cr, numcr, nflen, ws, cs, inquote;

    //Count number of lines
    numcr = 1;
    for (i=0; i<flen; i++)
    {
        //detect all 4 types of carriage return (\r, \n, \r\n, \n\r :)
        cr = 0;
        if (tx[i] == '\r')
        {
            i += (tx[i + 1] == '\n');
            cr = 1;
        }
        else if (tx[i] == '\n')
        {
            i += (tx[i + 1] == '\r');
            cr = 1;
        }
        if (cr)
        {
            numcr++;
            continue;
        }
    }

    sf->linenum = numcr;
    sf->lineoffs = (int32_t *)Xmalloc(sf->linenum*sizeof(int32_t));

    //Preprocess file for comments (// and /*...*/, and convert all whitespace to single spaces)
    nflen = 0; ws = 0; cs = 0; numcr = 0; inquote = 0;
    for (i=0; i<flen; i++)
    {
        //detect all 4 types of carriage return (\r, \n, \r\n, \n\r :)
        cr = 0;
        if (tx[i] == '\r')
        {
            i += (tx[i + 1] == '\n');
            cr = 1;
        }
        else if (tx[i] == '\n')
        {
            i += (tx[i + 1] == '\r');
            cr = 1;
        }
        if (cr)
        {
            //Remember line numbers by storing the byte index at the start of each line
            //Line numbers can be retrieved by doing a binary search on the byte index :)
            sf->lineoffs[numcr++] = nflen;
            if (cs == 1) cs = 0;
            ws = 1; continue; //strip CR/LF
        }

        if ((!inquote) && ((tx[i] == ' ') || (tx[i] == '\t')))
        {
            ws = 1;
            continue;
        }  // strip Space/Tab
        if ((tx[i] == '/') && (tx[i + 1] == '/') && (!cs))
            cs = 1;
        if ((tx[i] == '/') && (tx[i + 1] == '*') && (!cs))
        {
            ws = 1;
            cs = 2;
        }
        if ((tx[i] == '*') && (tx[i + 1] == '/') && (cs == 2))
        {
            cs = 0;
            i++;
            continue;
        }
        if (cs)
            continue;

        if (ws)
            tx[nflen++] = ws = 0;

        //quotes inside strings: \"
        if ((tx[i] == '\\') && (tx[i+1] == '\"')) { i++; tx[nflen++] = '\"'; continue; }
        if (tx[i] == '\"') { inquote ^= 1; continue; }
        tx[nflen++] = tx[i];
    }
    tx[nflen++] = 0; sf->lineoffs[numcr] = nflen;
    tx[nflen++] = 0;

#if 0
    //for debugging only:
    printf("pre-parsed file:flen=%d,nflen=%d\n",flen,nflen);
    for (i=0; i<nflen; i++) { if (tx[i] < 32) printf("_"); else printf("%c",tx[i]); }
    printf("[eof]\nnumlines=%d\n",sf->linenum);
    for (i=0; i<sf->linenum; i++) printf("line %d = byte %d\n",i,sf->lineoffs[i]);
#endif

    sf->textbuf = sf->textptr = tx;
    sf->textlength = nflen;
    sf->eof = &sf->textbuf[nflen-1];
}

scriptfile *scriptfile_fromfile(const char *fn)
{
    buildvfs_kfd fp = kopen4load(fn, 0);
    if (fp == buildvfs_kfd_invalid) return nullptr;

    uint32_t flen = kfilelength(fp);
    char *   tx   = (char *)Xmalloc(flen + 2);

    scriptfile *sf = (scriptfile *)Xmalloc(sizeof(scriptfile));

    kread(fp, tx, flen);
    kclose(fp);

    tx[flen] = tx[flen+1] = 0;

    scriptfile_preparse(sf,tx,flen);
    sf->filename = Xstrdup(fn);

    return sf;
}

scriptfile *scriptfile_fromstring(const char *string)
{
    if (!string) return nullptr;

    uint32_t flen = Bstrlen(string);
    char *   tx   = (char *)Xmalloc(flen + 2);

    scriptfile *sf = (scriptfile *)Xmalloc(sizeof(scriptfile));

    Bmemcpy(tx, string, flen);
    tx[flen] = tx[flen+1] = 0;

    scriptfile_preparse(sf,tx,flen);
    sf->filename = NULL;

    return sf;
}

void scriptfile_close(scriptfile *sf)
{
    if (!sf) return;
    Xfree(sf->lineoffs);
    Xfree(sf->textbuf);
    Xfree(sf->filename);
    Xfree(sf);
}

int scriptfile_eof(scriptfile *sf)
{
    skipoverws(sf);
    return !!(sf->textptr >= sf->eof);
}

#define SYMBTABSTARTSIZE 256
static int32_t symbtablength=0, symbtaballoclength=0;
static char *symbtab = NULL;

static char *getsymbtabspace(int32_t reqd)
{
    char *pos,*np;
    int32_t i;

    if (symbtablength + reqd > symbtaballoclength)
    {
        for (i=max(symbtaballoclength,SYMBTABSTARTSIZE); symbtablength+reqd>i; i<<=1) { }
        np = (char *)Xrealloc(symbtab, i);
        symbtab = np; symbtaballoclength = i;
    }

    pos = &symbtab[symbtablength];
    symbtablength += reqd;
    return pos;
}

int32_t scriptfile_getsymbolvalue(char const *name, int32_t *val)
{
    if (Bstrlen(name) > 2)
    {
        if (name[0] == '0' && tolower(name[1]) == 'x')  // hex constants
        {
            uint64_t x;
            sscanf(name + 2, "%" PRIx64 "", &x);

            if (EDUKE32_PREDICT_FALSE(x > UINT32_MAX))
                LOG_F(WARNING, "value 0x%016" PRIx64 " truncated to 32 bits.", x);

            *val = x;
            return 1;
        }
    }
    char *scanner = symbtab;

    if (!symbtab) return 0;
    while (scanner - symbtab < symbtablength)
    {
        if (!Bstrcasecmp(name, scanner))
        {
            *val = B_UNBUF32(scanner + strlen(scanner) + 1);
            return 1;
        }

        scanner += strlen(scanner) + 1 + sizeof(int32_t);
    }

    return 0;
}

int32_t scriptfile_addsymbolvalue(char const *name, int32_t val)
{
    char *sp;
    //	if (scriptfile_getsymbolvalue(name, &x)) return -1;   // already exists

    if (symbtab)
    {
        char *scanner = symbtab;
        while (scanner - symbtab < symbtablength)
        {
            if (!Bstrcasecmp(name, scanner))
            {
                B_BUF32(scanner + strlen(scanner) + 1, val);
                return 1;
            }

            scanner += strlen(scanner) + 1 + sizeof(int32_t);
        }
    }

    sp = getsymbtabspace(strlen(name) + 1 + sizeof(int32_t));
    if (!sp) return 0;
    strcpy(sp, name);
    sp += strlen(name)+1;
    B_BUF32(sp, val);
    return 1;   // added
}

void scriptfile_clearsymbols(void)
{
    DO_FREE_AND_NULL(symbtab);
    symbtablength = 0;
    symbtaballoclength = 0;
}