Tcl script to make MSX-viewable screenshots

Page 2/2
1 |

By Louthrax

Paragon (1264)

Louthrax's picture

29-08-2016, 15:17

Finally, here's the C source code of MSX2MIG.EXE.

I wouldn't mind some code review on that if you guys have some time (Nyyrikki?). In fact I do not have so much experience with MSX 1 VDP Eek!

I tested it on a few games and it seems to work (but that does definitively not cover all cases), and I changed a good amount of things compared to Nyyrikki's original Tcl script:

  • Original VDP addresses are used (no need to re-order or pad areas as the registers are also saved in MIG files). Maybe this could also improve things in game having weird VRAM layouts ?
  • Sprites are not dumped on VDP_9938 or VDP_9958 if sprites are disabled.
  • Name table size is computed according to the screen height (24 or 27 lines).
  • Sprite tables always have the same size. I think they had different sizes in Nyyrikki's script for "fill-purposes" (even if that's not always explicitely commented in the code?).
  • Blink timer and colors for screen 0 / 80 columns are handled (animated screenshots if viewed on MSX :)).

The parts of the code I'd be happy to have feedback are functions vDumpVRAM and vDumpRegisters. There's just a missing encode.c file that performs the BitBuster compression (not dumped here to keep a reasonable post size).

#include "stdio.h"
#include "encode.h"

#define COMPRESSION_BLOCK_SIZE  16384

typedef enum { VDP_9918 = 0, VDP_9938 = 1, VDP_9958 = 2 } tdMIGVDPType;

typedef enum
{
    ID_REGISTERS_CHUNK  = 0,
    ID_PALETTE_CHUNK    = 1,
    ID_VRAM_CHUNK       = 2,
    ID_TERMINATOR       = 0xFF
} tdMIGChunkType;

typedef enum
{
    MODE_1,         /* Screen 1 */
    MODE_TEXT40,    /* Screen 0 (WIDTH 40) */
    MODE_3,         /* Screen 3 */
    MODE_2,         /* Screen 2 */
    MODE_4,         /* Screen 4 */
    MODE_TEXT80,    /* Screen 0 (WIDTH 80) */
    MODE_5,         /* Screen 5 */
    MODE_6,         /* Screen 6 */
    MODE_7,         /* Screen 7 */
    MODE_8,         /* Screen 8 */
    MODE_11,        /* Screen 11 */
    MODE_12,        /* Screen 12 */
} tdScreenMode;

unsigned char g_acVDPRegisters[0x40];
unsigned char g_acVRAM[128 * 1024];
unsigned short int g_asiPalette[16];
tdScreenMode g_eScreenMode;
CharArray *g_poMIGData;
tdMIGVDPType g_eMIGVDPType;

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void vError(const char *, const char *, int, const char *, ...)
{
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void vComputeScreenMode()
{
    /*~~~~~*/
    int iVal;
    /*~~~~~*/

    iVal =
        ((g_acVDPRegisters[0] & 14) << 1) |
        ((g_acVDPRegisters[1] & 8) >> 2) |
        ((g_acVDPRegisters[1] & 16) >> 4);

    switch(iVal)
    {
    case 0b00000000:    g_eScreenMode = MODE_1; break;
    case 0b00000001:    g_eScreenMode = MODE_TEXT40; break;
    case 0b00000010:    g_eScreenMode = MODE_3; break;
    case 0b00000100:    g_eScreenMode = MODE_2; break;
    case 0b00001000:    g_eScreenMode = MODE_4; break;
    case 0b00001001:    g_eScreenMode = MODE_TEXT80; break;
    case 0b00001100:    g_eScreenMode = MODE_5; break;
    case 0b00010000:    g_eScreenMode = MODE_6; break;
    case 0b00010100:    g_eScreenMode = MODE_7; break;
    case 0b00011100:    g_eScreenMode = MODE_8; break;
    default:            exit(1);
    }

    if(((g_eScreenMode == 8) || (g_eScreenMode == 7)) && (g_acVDPRegisters[25] & 8))
    {
        g_eScreenMode = g_acVDPRegisters[25] & 16 ? MODE_11 : MODE_12;
    }
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void vAddVRAMChunk(int _iVRAMAddress, int _iVRAMSize)
{
    g_poMIGData->vAdd(ID_VRAM_CHUNK);
    g_poMIGData->vAdd((unsigned char *) &_iVRAMAddress, 3);
    g_poMIGData->vAdd((unsigned char *) &_iVRAMSize, 3);
    g_poMIGData->vAdd(g_acVRAM + _iVRAMAddress, _iVRAMSize);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void vDumpRegisters()
{
    /*~~~~~~~~~~~~~~~~*/
    int iIndex;
    int iRegistersCount;
    /*~~~~~~~~~~~~~~~~*/

    g_poMIGData->vAdd(ID_REGISTERS_CHUNK);

    iRegistersCount = 8;

    if(g_eMIGVDPType > VDP_9918)
    {
        iRegistersCount++;
        if(g_eScreenMode == MODE_TEXT80)
        {
            iRegistersCount += 2;
        }
    }
    g_poMIGData->vAdd(iRegistersCount);

    for(iIndex = 0; iIndex < 8; iIndex++)
    {
        g_poMIGData->vAdd(iIndex);
        g_poMIGData->vAdd(g_acVDPRegisters[iIndex]);
        g_poMIGData->vAdd(0b11111111);
    }

    if(g_eMIGVDPType > VDP_9918)
    {
        /* Register 9 (192/212 lines, interlace flags...) */
        g_poMIGData->vAdd(9);
        g_poMIGData->vAdd(g_acVDPRegisters[9]);

        /* Ignore 50Hz/60Hz flag */
        g_poMIGData->vAdd(0b11111110);

        if(g_eScreenMode == MODE_TEXT80)
        {
            /* Register 12, text and background color blink */
            g_poMIGData->vAdd(12);
            g_poMIGData->vAdd(g_acVDPRegisters[12]);
            g_poMIGData->vAdd(0b11111111);

            /* Register 13, blink period register */
            g_poMIGData->vAdd(13);
            g_poMIGData->vAdd(g_acVDPRegisters[13]);
            g_poMIGData->vAdd(0b11111111);
        }
    }
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void vDumpPalette()
{
    g_poMIGData->vAdd(ID_PALETTE_CHUNK);
    g_poMIGData->vAdd(0);
    g_poMIGData->vAdd(16);
    g_poMIGData->vAdd((unsigned char *) g_asiPalette, 32);
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
void vDumpVRAM()
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    int     name_base = g_acVDPRegisters[2] * 0x400;
    int     name_base_80 = (g_acVDPRegisters[2] & 252) * 0x400;
    int     name_base_bitmap = (g_acVDPRegisters[2] & 96) * 0x400;
    int     color_base = g_acVDPRegisters[3] * 0x40 + g_acVDPRegisters[10] * 0x4000;
    int     color_base_2 = (g_acVDPRegisters[3] & 128) * 0x40 + g_acVDPRegisters[10] * 0x4000;
    int     pattern_base = g_acVDPRegisters[4] * 0x800;
    int     pattern_base_2 = (g_acVDPRegisters[4] & 60) * 0x800;
    int     spr_att_base = g_acVDPRegisters[5] * 0x80 + g_acVDPRegisters[11] * 0x8000;
    int     spr_att_base_2 = (g_acVDPRegisters[5] & 252) * 0x80 + g_acVDPRegisters[11] * 0x8000 - 0x200;
    int     spr_pat_base = g_acVDPRegisters[6] * 0x800;
    int     iRows;
    bool    bSpritesOn;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    /* Used to compute name_base size */
    iRows = (g_eMIGVDPType > VDP_9918) && (g_acVDPRegisters[9] & 0x80) ? 27 : 24;

    /* Do we need to dump sprites data? */
    bSpritesOn = (g_eMIGVDPType == VDP_9918) || !(g_acVDPRegisters[8] & 0x02);

    switch(g_eScreenMode)
    {
    case MODE_TEXT40:
        vAddVRAMChunk(pattern_base, 0x800);         /* BG Tiles */
        vAddVRAMChunk(name_base, 40 * iRows);       /* BG Map */
        bSpritesOn = false;
        break;

    case MODE_TEXT80:
        vAddVRAMChunk(pattern_base, 0x800);         /* BG Tiles */
        vAddVRAMChunk(name_base_80, 80 * iRows);    /* BG Map */
        vAddVRAMChunk(color_base, 10 * iRows);      /* Blink */
        bSpritesOn = false;
        break;

    case MODE_1:
        vAddVRAMChunk(pattern_base, 0x1800);        /* BG Tiles */
        vAddVRAMChunk(name_base, 32 * iRows);       /* BG Map */
        vAddVRAMChunk(color_base, 0x20);            /* BG Colors */
        break;

    case MODE_2:
        vAddVRAMChunk(pattern_base_2, 0x1800);      /* BG Tiles */
        vAddVRAMChunk(name_base, 32 * iRows);       /* BG Map */
        vAddVRAMChunk(color_base_2, 0x1800);        /* BG Colors */
        break;

    case MODE_3:
        vAddVRAMChunk(pattern_base, 0x800);         /* BG Tiles */
        vAddVRAMChunk(name_base, 32 * iRows);       /* BG Map */
        break;

    default:
        /* Better convert other modes using MIF in order to handle scanline effects */
        exit(1);
    }

    if(bSpritesOn)
    {
        vAddVRAMChunk(spr_att_base, 0x80);          /* OBJ Attributes */
        vAddVRAMChunk(spr_pat_base, 0x800);         /* OBJ Tiles */
    }
}

/*
 =======================================================================================================================
 =======================================================================================================================
 */
int main(int argc, char *argv[])
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    FILE            *poFile;
    unsigned char   *pcCurrent;
    int             iRemainingBytes;
    long            lTotalSizePosition;
    long            lDataSize;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    if((argc != 5) && (argc != 6)) exit(1);

    g_poMIGData = new CharArray();

    switch(*argv[1])
    {
    case '0':   g_eMIGVDPType = VDP_9918; break;
    case '1':   g_eMIGVDPType = VDP_9938; break;
    case '2':   g_eMIGVDPType = VDP_9958; break;
    default:    exit(1);
    }

    poFile = fopen(argv[3], "rb");
    if(!poFile) exit(1);
    fread(g_acVDPRegisters, 1, sizeof(g_acVDPRegisters), poFile);
    fclose(poFile);

    poFile = fopen(argv[4], "rb");
    if(!poFile) exit(1);
    fread(g_acVRAM, 1, sizeof(g_acVRAM), poFile);
    fclose(poFile);

    vComputeScreenMode();

    if((argc == 6) && (g_eMIGVDPType > VDP_9918))
    {
        poFile = fopen(argv[5], "rb");
        if(!poFile) exit(1);
        fread(g_asiPalette, 1, sizeof(g_asiPalette), poFile);
        fclose(poFile);

        vDumpPalette();
    }

    /* Disable display (will be re-enabled by MIGVIEW.COM when loading is done) */
    g_acVDPRegisters[1] &= 0b10111111;
    vDumpRegisters();

    vDumpVRAM();

    g_poMIGData->vAdd(ID_TERMINATOR);

    poFile = fopen(argv[2], "r+b");
    if(!poFile)
    {
        poFile = fopen(argv[2], "wb");
        if(!poFile) exit(1);
    }
    else
    {
        fseek(poFile, 0, SEEK_END);
    }

    fwrite("MSXMIG", 1, 6, poFile);
    lTotalSizePosition = ftell(poFile);
    fwrite(&lTotalSizePosition, 4, 1, poFile);
    fwrite(&g_eMIGVDPType, 1, 1, poFile);

    vEncode_Init(2048);

    iRemainingBytes = g_poMIGData->iGetSize();
    pcCurrent = g_poMIGData->poGetData();

    while(iRemainingBytes)
    {
        /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/
        int         iBytesToWrite;
        int         iCompressedSize;
        CharArray   *poCompressedData;
        /*~~~~~~~~~~~~~~~~~~~~~~~~~~*/

        iBytesToWrite = iRemainingBytes;
        if(iBytesToWrite > COMPRESSION_BLOCK_SIZE) iBytesToWrite = COMPRESSION_BLOCK_SIZE;
        poCompressedData = poEncode_Encode(pcCurrent, iBytesToWrite);

        fwrite(&iBytesToWrite, 2, 1, poFile);
        iCompressedSize = poCompressedData->iGetSize();
        fwrite(&iCompressedSize, 2, 1, poFile);

        fwrite(poCompressedData->poGetData(), iCompressedSize, 1, poFile);

        delete poCompressedData;

        pcCurrent += iBytesToWrite;
        iRemainingBytes -= iBytesToWrite;
    }

    fwrite(&iRemainingBytes, 2, 1, poFile);

    lDataSize = ftell(poFile) - lTotalSizePosition;
    fseek(poFile, lTotalSizePosition, SEEK_SET);
    fwrite(&lDataSize, 4, 1, poFile);
    fclose(poFile);

    vEncode_Release();
}
Page 2/2
1 |
My MSX profile