Adventure Game Studio

Community => Adventure Related Talk & Chat => Topic started by: Monsieur OUXX on Tue 20/11/2018 15:16:50

Title: Dumping "Cruise for a corpse" graphics
Post by: Monsieur OUXX on Tue 20/11/2018 15:16:50
I've installed the sources of scummvm and I'm running it in step-by-step debug because I had this crazy idea that I'd love to dump all graphics of Cruise for a Corpse into some sprite sheets.

There are several reasons :
1) I think the graphics are gorgeous (in an Amiga 64-colors way) and I think it's a pity that they're not used as golden-age pixel art tribute as often and as easily as Lucasarts graphics.
2) I realized that this game could be much much more fun :
     - if there were some proper music and sound effects (the current music is painful because chiptune doesn't fit 1930's French opera/jazzy music well)
     - if the interface was slightly pimped up (less pixel hunt and more hinting)

The fact that the story isn't really branched (no big choices to make) makes it easy to revert-engineer.

I'd love to make it available as a template for AGS and then see what happens.

EDIT: I was daydreaming and I'm wondering who currently owns the rights to this? It was Delphine software --> Virgin --> ??
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: morganw on Tue 20/11/2018 15:45:18
Quote from: Monsieur OUXX on Tue 20/11/2018 15:16:50
I've installed the sources of scummvm and I'm running it in step-by-step debug because I had this crazy idea that I'd love to dump all graphics of Cruise for a Corpse into some sprite sheets.
As far as I know, the majority of things that moved in-game were vectors, so I don't think there will be equivalent bitmaps to dump.

Quote from: Monsieur OUXX on Tue 20/11/2018 15:16:50
1) I think the graphics are gorgeous (in an Amiga 64-colors way)
32 colours, without HAM.

Quote from: Monsieur OUXX on Tue 20/11/2018 15:16:50
- if there were some proper music and sound effects (the current music is painful because chiptune doesn't fit 1930's French opera/jazzy music well)
I had the Amiga version, and remember the music being okay (and very effective for the intro sequence), but playing the game was pretty much silent.

Quote from: Monsieur OUXX on Tue 20/11/2018 15:16:50
- if the interface was slightly pimped up (less pixel hunt and more hinting)
...I never finished it, and always seemed to get stuck at the same point. You could 'talk' to the wooden mermaid to get a code, which I think would give you a hint from the copy protection wheel. It is on the bucket list to try it again one day...
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: Monsieur OUXX on Tue 20/11/2018 17:14:24
Quote from: morganw on Tue 20/11/2018 15:45:18
Quote from: Monsieur OUXX on Tue 20/11/2018 15:16:50
I've installed the sources of scummvm and I'm running it in step-by-step debug because I had this crazy idea that I'd love to dump all graphics of Cruise for a Corpse into some sprite sheets.
As far as I know, the majority of things that moved in-game were vectors, so I don't think there will be equivalent bitmaps to dump.
Well they're stored as vectors -- but on-screen they're ultimately rendered as pixels. So I want to save the whole thing as sets of sprites, in good old PNG files.

Quote from: morganw on Tue 20/11/2018 15:45:18
Quote from: Monsieur OUXX on Tue 20/11/2018 15:16:50
1) I think the graphics are gorgeous (in an Amiga 64-colors way)
32 colours, without HAM.
Yes, sorry. I never had an Amiga but I can spot when there ar emore than 16 colors but less than 256.

Quote from: morganw on Tue 20/11/2018 15:45:18
Quote from: Monsieur OUXX on Tue 20/11/2018 15:16:50
- if there were some proper music and sound effects (the current music is painful because chiptune doesn't fit 1930's French opera/jazzy music well)
I had the Amiga version, and remember the music being okay (and very effective for the intro sequence), but playing the game was pretty much silent.
The intro music is cool. But then, later in the game, when you visit some rooms there are some extremely invasive chiptune melodies. They make you want to flee the room immediately.

Quote from: morganw on Tue 20/11/2018 15:45:18
Quote from: Monsieur OUXX on Tue 20/11/2018 15:16:50
- if the interface was slightly pimped up (less pixel hunt and more hinting)
...I never finished it, and always seemed to get stuck at the same point. You could 'talk' to the wooden mermaid to get a code, which I think would give you a hint from the copy protection wheel. It is on the bucket list to try it again one day...
The wooden mermaid thing was just an easter egg (if it ever existed). The terrible thing with that game is the complete absence of hinting which forced you to patrol the entire ship again and again in the hope of spotting a new object having appeared somewhere.
Apart from this gameplay aspect, the story itself was pretty cool (whodunit)
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: LimpingFish on Tue 20/11/2018 19:58:57
Quote from: Monsieur OUXX on Tue 20/11/2018 17:14:24
The intro music is cool. But then, later in the game, when you visit some rooms there are some extremely invasive chiptune melodies. They make you want to flee the room immediately.

The Amiga version may have had a sample-based intro tune (https://www.youtube.com/watch?v=sTIKcYHRCWA), but the Atari ST version, which I owned, was a chiptune (https://www.youtube.com/watch?v=RcYUvavoQQA). I actually prefer the ST version, though. :)

Quote from: Monsieur OUXX on Tue 20/11/2018 15:16:50
EDIT: I was daydreaming and I'm wondering who currently owns the rights to this? It was Delphine software --> Virgin --> ??

I would hazard a guess at Ubisoft.
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: ManicMatt on Tue 20/11/2018 20:24:11
Yup, I too never finished this game. Didn't it have dead ends??

I loved the style and the idea of it, too.
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: morganw on Tue 20/11/2018 20:34:34
I also thought it had dead ends, although perhaps this was just to cover up my own failings in playing it. I always seemed to get stuck at the same point.
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: Monsieur OUXX on Wed 21/11/2018 13:27:48
So far I've succeeded to dump backgrounds and "normal" sprites (not polygonal sprites), and I'm on the verge of having the palette.
I've made a reader using AGS because I couldn't be arsed to program it with something too heavy with C++ in it ;)
My issue is that I dump things as they get loaded, so it means I can't dump the whole game and I'm dumping many things several times.
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: Monsieur OUXX on Wed 21/11/2018 15:00:40
OK so here is what I did :

(modified code from ScummVM, in engine "CRUISE")
(everything between //TEST and //~TEST is my custom code)
File "maindraw.cpp"
Code (ags) Select

//TEST
int exportCount = 0;
//~TEST

void drawSprite(int width, int height, cellStruct *currentObjPtr, const uint8 *dataIn, int ys, int xs, uint8 *output, const uint8 *dataBuf) {
int x = 0;
int y = 0;

// Flag the given area as having been changed
Common::Point ps = Common::Point(MAX(MIN(xs, 320), 0), MAX(MIN(ys, 200), 0));
Common::Point pe = Common::Point(MAX(MIN(xs + width, 320), 0), MAX(MIN(ys + height, 200), 0));
if ((ps.x != pe.x) && (ps.y != pe.y))
// At least part of sprite is on-screen
gfxModuleData_addDirtyRect(Common::Rect(ps.x, ps.y, pe.x, pe.y));

cellStruct* plWork = currentObjPtr;
int workBufferSize = height * (width / 8);

unsigned char* workBuf = (unsigned char*)MemAlloc(workBufferSize);
memcpy(workBuf, dataBuf, workBufferSize);

int numPasses = 0;

while (plWork) {
if (plWork->type == OBJ_TYPE_BGMASK && plWork->freeze == 0) {
objectParamsQuery params;

getMultipleObjectParam(plWork->overlay, plWork->idx, &params);

int maskX = params.X;
int maskY = params.Y;
int maskFrame = params.fileIdx;

if (filesDatabase[maskFrame].subData.resourceType == OBJ_TYPE_BGMASK && filesDatabase[maskFrame].subData.ptrMask) {
drawMask(workBuf, width / 8, height, filesDatabase[maskFrame].subData.ptrMask, filesDatabase[maskFrame].width / 8, filesDatabase[maskFrame].height, maskX - xs, maskY - ys, numPasses++);
} else
if (filesDatabase[maskFrame].subData.resourceType == OBJ_TYPE_SPRITE && filesDatabase[maskFrame].subData.ptrMask) {
drawMask(workBuf, width / 8, height, filesDatabase[maskFrame].subData.ptrMask, filesDatabase[maskFrame].width / 8, filesDatabase[maskFrame].height, maskX - xs, maskY - ys, numPasses++);
}

}

plWork = plWork->next;
}


//TEST
Common::DumpFile fout;
if (exportCount < 2000) {
char nameBuffer[256];
//fileEntry *buffer;

sprintf(nameBuffer, "./export3/export%d.dmp", exportCount);


//fout.open(nameBuffer, Common::File::kFileWriteMode);
fout.open(nameBuffer);
if (fout.isOpen()) {
fout.writeUint32LE(width);
fout.writeUint32LE(height);

//dump palette
for (int i = 0; i < 256 * 3; i++) {
fout.writeUint32LE(palScreen[0][i]);
}

}
}

// fout.write(uncompBuffer, buffer[j].extSize);


bool drawn = false;

//~TEST


for (y = 0; y < height; y++) {
for (x = 0; x < (width); x++) {
drawn = false;
uint8 color = *dataIn++;

if ((x + xs) >= 0 && (x + xs) < 320 && (y + ys) >= 0 && (y + ys) < 200) {
if (testMask(x, y, workBuf, width / 8)) {
output[320 * (y + ys) + x + xs] = color;
//TEST
drawn = true;

if (fout.isOpen()) {
fout.writeUint32LE(color);
}
//~TEST
}
}

                        //TEST
if (!drawn && fout.isOpen()) {
fout.writeUint32LE(0);
}
                        //~TEST
}
}
//TEST
//if (fout.isOpen())
fout.close();
exportCount++;
//~TEST
MemFree(workBuf);
}





In file background.cpp
Code (ags) Select



//TEST
int exportCount2 = 100000;
//~TEST

int loadBackground(const char *name, int idx) {
uint8 *ptr;
uint8 *ptr2;
uint8 *ptrToFree;

debug(1, "Loading BG: %s", name);

if (!backgroundScreens[idx]) {
backgroundScreens[idx] = (uint8 *)mallocAndZero(320 * 200);
}

if (!backgroundScreens[idx]) {
backgroundTable[idx].name[0] = 0;
return (-2);
}

backgroundChanged[idx] = true;

ptrToFree = gfxModuleData.pPage10;
if (loadFileSub1(&ptrToFree, name, NULL) < 0) {
if (ptrToFree != gfxModuleData.pPage10)
MemFree(ptrToFree);

return (-18);
}

if (lastFileSize == 32078 || lastFileSize == 32080 || lastFileSize == 32034) {
colorMode = 0;
} else {
colorMode = 1;
}

ptr = ptrToFree;
ptr2 = ptrToFree;

if (!strcmp(name, "LOGO.PI1")) {
oldSpeedGame = speedGame;
flagSpeed = 1;
speedGame = 1;
} else if (flagSpeed) {
speedGame = oldSpeedGame;
flagSpeed = 0;
}

if (!strcmp((char *)ptr, "PAL")) {
memcpy(palScreen[idx], ptr + 4, 256*3);
gfxModuleData_setPal256(palScreen[idx]);
} else {
int mode = ptr2[1];
ptr2 += 2;
// read palette
switch (mode) {
case 0:
case 4: { // color on 3 bit
uint16 oldPalette[32];

memcpy(oldPalette, ptr2, 0x20);
ptr2 += 0x20;
flipGen(oldPalette, 0x20);

for (unsigned long int i = 0; i < 32; i++) {
gfxModuleData_convertOldPalColor(oldPalette[i], &palScreen[idx][i*3]);
}

// duplicate the palette
for (unsigned long int i = 1; i < 8; i++) {
memcpy(&palScreen[idx][32*i*3], &palScreen[idx][0], 32*3);
}

break;
}
case 5: { // color on 4 bit
for (unsigned long int i = 0; i < 32; i++) {
uint8* inPtr = ptr2 + i * 2;
uint8* outPtr = palScreen[idx] + i * 3;

outPtr[2] = ((inPtr[1]) & 0x0F) * 17;
outPtr[1] = (((inPtr[1]) & 0xF0) >> 4) * 17;
outPtr[0] = ((inPtr[0]) & 0x0F) * 17;
}
ptr2 += 2 * 32;

// duplicate the palette
for (unsigned long int i = 1; i < 8; i++) {
memcpy(&palScreen[idx][32*i*3], &palScreen[idx][0], 32*3);
}

break;
}
case 8:
memcpy(palScreen[idx], ptr2, 256*3);
ptr2 += 256 * 3;
break;

default:
assert(0);
}

gfxModuleData_setPal256(palScreen[idx]);

// read image data
gfxModuleData_gfxClearFrameBuffer(backgroundScreens[idx]);

switch (mode) {
case 0:
case 4:
convertGfxFromMode4(ptr2, 320, 200, backgroundScreens[idx]);
ptr2 += 32000;
break;
case 5:
convertGfxFromMode5(ptr2, 320, 200, backgroundScreens[idx]);
break;
case 8:
memcpy(backgroundScreens[idx], ptr2, 320 * 200);
ptr2 += 320 * 200;
break;
}

loadMEN(&ptr2);
loadCVT(&ptr2);
}

MemFree(ptrToFree);

// NOTE: the following is really meant to compare pointers and not the actual
// strings. See r48092 and r48094.
if (name != backgroundTable[idx].name) {
if (strlen(name) >= sizeof(backgroundTable[idx].name))
warning("background name length exceeded allowable maximum");

Common::strlcpy(backgroundTable[idx].name, name, sizeof(backgroundTable[idx].name));
}

//TEST
Common::DumpFile fout;
if (exportCount2 - 100000 < 10) {
char nameBuffer[256];
fileEntry *buffer;

sprintf(nameBuffer, "./export4/export%d.dmp", exportCount2);


//fout.open(nameBuffer, Common::File::kFileWriteMode);
fout.open(nameBuffer);
if (fout.isOpen()) {
fout.writeUint32LE(320);
fout.writeUint32LE(200);

//dump palette
for (int i = 0; i < 256 * 3; i++) {
fout.writeUint32LE(palScreen[idx][i]);
}

for (int y = 0; y < 200; y++) {
for (int x = 0; x < (320); x++) {

fout.writeUint32LE(backgroundScreens[idx][320 * y + x]);
}
}

//if (fout.isOpen())
fout.close();
exportCount2++;

}
}

// fout.write(uncompBuffer, buffer[j].extSize);




//~TEST


return (0);
}



This generates files "export0.dmp, export1.dmp, export2.dmp, ..." for sprites and files "export100000.dmp, export100001.dmp, export100002.dmp, ... for backgrounds.
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: Monsieur OUXX on Wed 21/11/2018 15:07:42
>> Download viewer << (https://www.dropbox.com/s/2pbfk7iv0uss3hj/CruiseDumpReader.zip?dl=0)

It's made with AGS :D

How to use :

Screenshot:
(https://www.dropbox.com/s/xizmy590tecfn8n/2018-11-21_16h13_01.png?raw=1)
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: Monsieur OUXX on Wed 21/11/2018 18:40:24
BOOM there you go ;-)

https://imgur.com/gallery/62ty1iU

[imgzoom]https://i.imgur.com/kfiPHaC.png[/imgzoom][imgzoom]https://i.imgur.com/n0eaYgC.png[/imgzoom][imgzoom]https://i.imgur.com/RotAdqG.png[/imgzoom]
[imgzoom]https://i.imgur.com/90Xyuk3.png[/imgzoom][imgzoom]https://i.imgur.com/7pwf2wQ.png[/imgzoom][imgzoom]https://i.imgur.com/JSUsoua.png[/imgzoom]
[imgzoom]https://i.imgur.com/EMPEDWG.png[/imgzoom][imgzoom]https://i.imgur.com/4R2BBb3.png[/imgzoom][imgzoom]https://i.imgur.com/smyh5BD.png[/imgzoom]
[imgzoom]https://i.imgur.com/24pLfwy.png[/imgzoom][imgzoom]https://i.imgur.com/SDcAes4.png[/imgzoom]
etc.
(125 images)
https://imgur.com/gallery/62ty1iU

Title: Re: Dumping "Cruise for a corpse" graphics
Post by: selmiak on Wed 21/11/2018 20:45:59
dem ded?
(https://i.imgur.com/ahMNkLZ.png)
(https://i.imgur.com/CwAc15C.png)
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: Monsieur OUXX on Wed 21/11/2018 21:47:58
Quote from: selmiak on Wed 21/11/2018 20:45:59
dem ded?
Nope. But it's the raw background. Then there's animation on top for each of them. The missing parts are the animated parts.
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: ManicMatt on Wed 21/11/2018 23:26:57
interesting to see how many times they use the same door assets and some other things. Can't blame them, I do the same, saves time.
Title: Re: Dumping "Cruise for a corpse" graphics
Post by: Monsieur OUXX on Thu 22/11/2018 00:50:14
Spritesheet 1
(https://i.imgur.com/yif2fES.png)
Spritesheet 2
(https://i.imgur.com/c4T8SIK.png)
Spritesheet 3
(https://i.imgur.com/rU5wO2t.png)
Spritesheet 4
(https://i.imgur.com/sXEaZgv.png)




https://imgur.com/gallery/vBBUpls



That's all 1723 sprites
Unfortunately I screwed up the palette so i'll have to do it again, but this is a good start.




Title: Re: Dumping "Cruise for a corpse" graphics
Post by: Monsieur OUXX on Thu 22/11/2018 00:56:25
In retrospect, I realise that it's really heartbreaking how they didn't use their vector graphics engine to rotoscope the main character better than that. They had everything they needed for that, and yet they inserted this ugly polygonal guy into their gorgeous pixel art. Pity! the game could have looked like a fullscreen Prince of Persia. If they had followed this example (https://www.youtube.com/watch?v=c5kuYfTCGLg), the game would have looked like a proto FMV ...as early as 1991!