Tech Arts is kind of a dinosaur in the bishoujo game business with several brands under it. Quite a lot of games were made with a tool internally called SceneMaker and are shipped with a runtime called ScenePlayer (the executable is usually named splayer.exe). Someone already made a plug-in for the graphics viewer "Susie" but I thought it might be worth writing down the format for future reference.

Here is a most likely incomplete list of games, directly taken from the plug-in page:

ブランド ソフト
Ripe 雪のにおいと風のいろ
Private Emotion
創美研究所 ぱちもそ
風輪の詩
神父のオシゴト
姫裸
メイビーソフト エスケイプPlus
SixtyNine
-スガタ-
えらぶる~えらぶ+らぶ×ダブルで
MBS TRUTH 天使の罠
灰被り姫の憂鬱
失格医師
失楽の神女

I only played 灰被り姫の憂鬱 from these games but I really like the graphics style because it looks a little bit like oil-painting.

So, to quote 金田一くん from 金田一少年の事件簿, 謎は全て解けた. It turned out that the format is actually not really a new format but the PMPs are simple BMPs compressed with deflate in the fastest mode and bytewise xor'ed with 0x21. This algorithm is btw. also used on the PMW audio files and the PMX scripting files. While the audio files are then simple WAV files that can be played back without further processing, the scripting files seem to be concatenated into archives with file allocation tables.

Knowing this, a small decoder in C could be for example (compiled binary for win32):

#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <math.h>
#include <zlib.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("Usage: splayer_decode infile outfile\n");
        return 1;
    }

    // Open image and read into inbuf
    FILE *infile = fopen(argv[1], "rb");
    fseek(infile, 0, SEEK_END);
    unsigned long  insize = ftell(infile);
    fseek(infile, 0, SEEK_SET);
    unsigned char inbuf[insize+sizeof(uintptr_t)];
    if (fread(&inbuf, 1, insize, infile) != insize) {
        printf("Error reading file %s\n", argv[1]);
        fclose(infile);
        return 1;
    }
    fclose(infile);

    // xor inbuf bytewise with 0x21, process multiple bytes at once
    int div = ceil((float)insize/sizeof(uintptr_t));
    uintptr_t magic;
    memset(&magic, 0x21, sizeof(uintptr_t));

    for (int i=0; i<div; i++) {
        ((uintptr_t*)inbuf)[i] = ((uintptr_t*)inbuf)[i]^magic;
    }

    // Initialize data structure for decompression
    z_stream zstream;
    zstream.zalloc = Z_NULL;
    zstream.zfree = Z_NULL;
    zstream.avail_in = insize;
    zstream.next_in = inbuf;
    inflateInit(&zstream);

    // Decode the buffer and write the uncompressed data into the target file
    FILE *outfile = fopen(argv[2], "wb");
    unsigned char outbuf[4096];
    do {
        zstream.next_out = outbuf;
        zstream.avail_out = 4096;
        zstream.total_out = 0;

        if (inflate(&zstream, Z_SYNC_FLUSH) < 0) {
            printf("Error decoding %s\n", argv[1]);
            fclose(outfile);
            return 1;
        }

        fwrite(outbuf, 1, zstream.total_out, outfile);
    } while (zstream.avail_in > 0);
    fclose(outfile);

    return 0;
}


No comments yet.

Leave a Comment

I respect your privacy
I don't run any trackers on this site.

Please use the share-buttons or leave comments so I know what might be worth writing about.

Thank you.
Contact