Dumping "Cruise for a corpse" graphics

Started by Monsieur OUXX, Tue 20/11/2018 15:16:50

Previous topic - Next topic

Monsieur OUXX

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 --> ??
 

morganw

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...

Monsieur OUXX

#2
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)
 

LimpingFish

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, but the Atari ST version, which I owned, was a chiptune. 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.
Steam: LimpingFish
PSN: LFishRoller
XB: TheActualLimpingFish
Spotify: LimpingFish

ManicMatt

Yup, I too never finished this game. Didn't it have dead ends??

I loved the style and the idea of it, too.

morganw

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.

Monsieur OUXX

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.
 

Monsieur OUXX

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

//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



//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.
 

Monsieur OUXX

#8
>> Download viewer <<

It's made with AGS :D

How to use :

  • Type in any number in the textbox (e.g. "6") then press return..
  • after the sprite is displayed, press the left and right arrows to open the next or previous sprite. Pro-tip : If you started from sprite 0, then you should click "right" several times before you see anything interesting.
  • Type 100000 or above to see backgrounds.


Screenshot:

 

Monsieur OUXX

 

selmiak


Monsieur OUXX

#11
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.
 

ManicMatt

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.

Monsieur OUXX

Spritesheet 1

Spritesheet 2

Spritesheet 3

Spritesheet 4





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.




 

Monsieur OUXX

#14
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, the game would have looked like a proto FMV ...as early as 1991!
 

SMF spam blocked by CleanTalk