[Scummvm-git-logs] scummvm master -> 656a35ee87c6e06fc1a61dff9eb6b1119706466c

athrxx athrxx at scummvm.org
Fri Jan 22 00:20:33 UTC 2021


This automated email contains information about 10 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
d2b2d509ef AGOS: (ELVIRA/PC98/Jp) - add detection entry
bb8c70f865 AGOS: (ELVIRA/PC98/Jp) - fix startup
574133086d AGOS: (ELVIRA/PC98/Jp) - add Japanese font drawing
8d17584247 AGOS: (ELVIRA/PC98/Jp) - more dirty rect handling
512ae9cfe3 AGOS: (ELVIRA/PC98/Jp) - add static msg strings + some fixes
56d897a100 AGOS: (ELVIRA/PC98/Jp) - more fixes
a3fd19265f AGOS: (ELVIRA/PC98/Jp) - add sound driver
77b316ef58 AGOS: (ELVIRA) - block mouse wheel during save/load/pause dialog
8969fa9c09 AGOS: (ELVIRA/PC98/Jp) - fix game restart
656a35ee87 NEWS: mention Elvira PC-98 support


Commit: d2b2d509ef5a860c741ff64cf78945b85cffc078
    https://github.com/scummvm/scummvm/commit/d2b2d509ef5a860c741ff64cf78945b85cffc078
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:47:58+01:00

Commit Message:
AGOS: (ELVIRA/PC98/Jp) - add detection entry

Changed paths:
    engines/agos/detection_tables.h


diff --git a/engines/agos/detection_tables.h b/engines/agos/detection_tables.h
index 17a3e7cebc..8e4a6ebf9c 100644
--- a/engines/agos/detection_tables.h
+++ b/engines/agos/detection_tables.h
@@ -385,6 +385,28 @@ static const AGOSGameDescription gameDescriptions[] = {
 		GF_OLD_BUNDLE
 	},
 
+	// Elvira 1 - Japanese PC-98
+	{
+		{
+			"elvira1",
+			0,
+			{
+				{ "gamepcj.dat",	GAME_BASEFILE,	"f170990deafbf9adee360021a6b5f375", -1},
+				{ "icon.dat",		GAME_ICONFILE,	"fda48c9da7f3e72d0313e2f5f760fc45", -1},
+				{ "tbllist.dat",	GAME_TBLFILE,	"319f6b227c7822a551f57d24e70f8149", -1},
+				AD_LISTEND
+			},
+			Common::JA_JPN,
+			Common::kPlatformPC98,
+			ADGF_NO_FLAGS,
+			GUIO1(GUIO_NOSPEECH)
+		},
+
+		GType_ELVIRA1,
+		GID_ELVIRA1,
+		GF_OLD_BUNDLE
+	},
+
 	// Elvira 2 - English Amiga Floppy
 	{
 		{


Commit: bb8c70f865eed0475db1d20e20c01a697dceaebd
    https://github.com/scummvm/scummvm/commit/bb8c70f865eed0475db1d20e20c01a697dceaebd
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:47:58+01:00

Commit Message:
AGOS: (ELVIRA/PC98/Jp) - fix startup

Add necessary file name and file format modifications so that the game at least loads up and runs up to the start scene without glitches.

Changed paths:
    engines/agos/agos.cpp
    engines/agos/agos.h
    engines/agos/debug.cpp
    engines/agos/drivers/accolade/adlib.cpp
    engines/agos/gfx.cpp
    engines/agos/midi.cpp
    engines/agos/midi.h
    engines/agos/res.cpp
    engines/agos/res_ami.cpp
    engines/agos/res_snd.cpp
    engines/agos/subroutine.cpp
    engines/agos/vga.cpp


diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 8c3dd46513..0bc3811b5c 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -497,6 +497,7 @@ AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
 	memset(_lettersToPrintBuf, 0, sizeof(_lettersToPrintBuf));
 
 	_planarBuf = 0;
+	_pak98Buf = 0;
 
 	_midiEnabled = false;
 
@@ -582,11 +583,9 @@ Common::Error AGOSEngine::init() {
 	if ((getGameType() == GType_SIMON2 && getPlatform() == Common::kPlatformWindows) ||
 		(getGameType() == GType_SIMON1 && getPlatform() == Common::kPlatformWindows) ||
 		((getFeatures() & GF_TALKIE) && getPlatform() == Common::kPlatformAcorn) ||
-		(getPlatform() == Common::kPlatformDOS)) {
+		(getPlatform() == Common::kPlatformDOS || getPlatform() == Common::kPlatformPC98)) {
 
-		bool isDemo = (getFeatures() & GF_DEMO) ? true : false;
-
-		int ret = _midi->open(getGameType(), isDemo);
+		int ret = _midi->open(getGameType(), getPlatform(), (getFeatures() & GF_DEMO));
 		if (ret)
 			warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret));
 
@@ -945,6 +944,7 @@ AGOSEngine::~AGOSEngine() {
 		_backBuf->free();
 	delete _backBuf;
 	free(_planarBuf);
+	delete[] _pak98Buf;
 	if (_scaleBuf)
 		_scaleBuf->free();
 	delete _scaleBuf;
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index f8ad420007..9da4b0cf91 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -551,6 +551,7 @@ protected:
 	byte *_planarBuf;
 	byte _videoBuf1[32000];
 	uint16 _videoWindows[128];
+	const byte *_pak98Buf;
 
 	uint8 _window3Flag;
 	uint8 _window4Flag;
@@ -806,6 +807,9 @@ protected:
 
 	uint loadTextFile_simon1(const char *filename, byte *dst);
 	Common::SeekableReadStream *openTablesFile_simon1(const char *filename);
+	Common::SeekableReadStream *openTablesFile_pak98(const char *filename);
+	Common::SeekableReadStream *createPak98FileStream(const char *filename);
+	void convertPC98Image(VC10_state &state);
 
 	uint loadTextFile_gme(const char *filename, byte *dst);
 	Common::SeekableReadStream *openTablesFile_gme(const char *filename);
@@ -1153,7 +1157,7 @@ protected:
 	void verticalScroll(VC10_state *state);
 
 	int vcReadVarOrWord();
-	uint vcReadNextWord();
+	uint vcReadNextWord(bool forceLERead = false);
 	uint vcReadNextByte();
 	uint vcReadVar(uint var);
 	void vcWriteVar(uint var, int16 value);
@@ -1206,7 +1210,7 @@ protected:
 	byte *getBackGround();
 	byte *getScaleBuf();
 
-	byte *convertImage(VC10_state *state, bool compressed);
+	byte *convertAmigaImage(VC10_state *state, bool compressed);
 
 	bool decrunchFile(byte *src, byte *dst, uint32 size);
 	void loadVGABeardFile(uint16 id);
diff --git a/engines/agos/debug.cpp b/engines/agos/debug.cpp
index 2d7e3a0a3c..3bb13d16e8 100644
--- a/engines/agos/debug.cpp
+++ b/engines/agos/debug.cpp
@@ -499,7 +499,7 @@ void AGOSEngine::dumpBitmap(const char *filename, const byte *offs, uint16 w, ui
 	state.width = w / 16;
 
 	if (getFeatures() & GF_PLANAR) {
-		state.srcPtr = convertImage(&state, (getGameType() == GType_PN || (flags & 0x80) != 0));
+		state.srcPtr = convertAmigaImage(&state, (getGameType() == GType_PN || (flags & 0x80) != 0));
 		flags &= ~0x80;
 	}
 
diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp
index 2e3ce82605..9d013f9722 100644
--- a/engines/agos/drivers/accolade/adlib.cpp
+++ b/engines/agos/drivers/accolade/adlib.cpp
@@ -294,10 +294,10 @@ void MidiDriver_Accolade_AdLib::noteOn(byte FMvoiceChannel, byte note, byte velo
 	int16 channelVolumeAdjust = _channels[FMvoiceChannel].volumeAdjust;
 	channelVolumeAdjust += adjustedVelocity;
 	channelVolumeAdjust = CLIP<int16>(channelVolumeAdjust, 0, 0x7F);
-	
+
 	// adjust velocity with the master volume
 	byte volumeAdjust = adjustedVelocity * ((float) (128 + _masterVolume) / 128);
-	
+
 	adjustedVelocity = volumeAdjust;
 
 	if (!_musicDrvMode) {
diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp
index 867842276a..8bdcfcfa6d 100644
--- a/engines/agos/gfx.cpp
+++ b/engines/agos/gfx.cpp
@@ -936,7 +936,7 @@ void AGOSEngine::drawImage(VC10_state *state) {
 
 	state->surf_addr += xoffs + yoffs * state->surf_pitch;
 
-	if (getGameType() == GType_ELVIRA1 && (state->flags & kDFNonTrans) && yoffs > 133)
+	if (getGameType() == GType_ELVIRA1 && getPlatform() != Common::kPlatformPC98 && (state->flags & kDFNonTrans) && yoffs > 133)
 		state->paletteMod = 16;
 
 	if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW)
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 34c7634d94..6f9d73c48a 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -87,17 +87,23 @@ MidiPlayer::~MidiPlayer() {
 	clearConstructs();
 }
 
-int MidiPlayer::open(int gameType, bool isDemo) {
+int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 	// Don't call open() twice!
 	assert(!_driver);
 
 	Common::String accoladeDriverFilename;
 	musicType = MT_INVALID;
+	int devFlags = MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32;
 
 	switch (gameType) {
 	case GType_ELVIRA1:
-		_musicMode = kMusicModeAccolade;
-		accoladeDriverFilename = "INSTR.DAT";
+		if (platform == Common::kPlatformPC98) {
+			_musicMode = kMusicModeDisabled;
+			devFlags = (devFlags & ~MDT_ADLIB) | MDT_PC98;
+		} else {
+			_musicMode = kMusicModeAccolade;
+			accoladeDriverFilename = "INSTR.DAT";
+		}
 		break;
 	case GType_ELVIRA2:
 	case GType_WW:
@@ -130,7 +136,7 @@ int MidiPlayer::open(int gameType, bool isDemo) {
 	int ret = 0;
 
 	if (_musicMode != kMusicModeDisabled) {
-		dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32);
+		dev = MidiDriver::detectDevice(devFlags);
 		musicType = MidiDriver::getMusicType(dev);
 
 		switch (musicType) {
@@ -163,7 +169,7 @@ int MidiPlayer::open(int gameType, bool isDemo) {
 		switch (musicType) {
 		case MT_ADLIB:
 			_driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename);
-			
+
 			break;
 		case MT_MT32:
 			_driver = MidiDriver_Accolade_MT32_create(accoladeDriverFilename);
@@ -577,7 +583,7 @@ static const int simon1_gmf_size[] = {
 	17256, 5103, 8794, 4884, 16
 };
 
-void MidiPlayer::loadSMF(Common::File *in, int song, bool sfx) {
+void MidiPlayer::loadSMF(Common::SeekableReadStream *in, int song, bool sfx) {
 	Common::StackLock lock(_mutex);
 
 	MusicInfo *p = sfx ? &_sfx : &_music;
@@ -666,7 +672,7 @@ void MidiPlayer::loadSMF(Common::File *in, int song, bool sfx) {
 	p->parser = parser; // That plugs the power cord into the wall
 }
 
-void MidiPlayer::loadMultipleSMF(Common::File *in, bool sfx) {
+void MidiPlayer::loadMultipleSMF(Common::SeekableReadStream *in, bool sfx) {
 	// This is a special case for Simon 2 Windows.
 	// Instead of having multiple sequences as
 	// separate tracks in a Type 2 file, simon2win
@@ -722,7 +728,7 @@ void MidiPlayer::loadMultipleSMF(Common::File *in, bool sfx) {
 	}
 }
 
-void MidiPlayer::loadXMIDI(Common::File *in, bool sfx) {
+void MidiPlayer::loadXMIDI(Common::SeekableReadStream *in, bool sfx) {
 	Common::StackLock lock(_mutex);
 	MusicInfo *p = sfx ? &_sfx : &_music;
 	clearConstructs(*p);
@@ -768,7 +774,7 @@ void MidiPlayer::loadXMIDI(Common::File *in, bool sfx) {
 	p->parser = parser; // That plugs the power cord into the wall
 }
 
-void MidiPlayer::loadS1D(Common::File *in, bool sfx) {
+void MidiPlayer::loadS1D(Common::SeekableReadStream *in, bool sfx) {
 	Common::StackLock lock(_mutex);
 	MusicInfo *p = sfx ? &_sfx : &_music;
 	clearConstructs(*p);
diff --git a/engines/agos/midi.h b/engines/agos/midi.h
index a75f09df0b..7b5cd790ee 100644
--- a/engines/agos/midi.h
+++ b/engines/agos/midi.h
@@ -96,10 +96,10 @@ public:
 	MidiPlayer();
 	~MidiPlayer() override;
 
-	void loadSMF(Common::File *in, int song, bool sfx = false);
-	void loadMultipleSMF(Common::File *in, bool sfx = false);
-	void loadXMIDI(Common::File *in, bool sfx = false);
-	void loadS1D(Common::File *in, bool sfx = false);
+	void loadSMF(Common::SeekableReadStream *in, int song, bool sfx = false);
+	void loadMultipleSMF(Common::SeekableReadStream *in, bool sfx = false);
+	void loadXMIDI(Common::SeekableReadStream *in, bool sfx = false);
+	void loadS1D(Common::SeekableReadStream *in, bool sfx = false);
 
 	bool hasNativeMT32() const { return _nativeMT32; }
 	void setLoop(bool loop);
@@ -115,7 +115,7 @@ public:
 	void setVolume(int musicVol, int sfxVol);
 
 public:
-	int open(int gameType, bool isDemo);
+	int open(int gameType, Common::Platform platform, bool isDemo);
 
 	// MidiDriver_BASE interface implementation
 	void send(uint32 b) override;
@@ -124,7 +124,7 @@ public:
 private:
 	kMusicMode _musicMode;
 	MusicType musicType;
-	
+
 private:
 	Common::SeekableReadStream *simon2SetupExtractFile(const Common::String &requestedFileName);
 };
diff --git a/engines/agos/res.cpp b/engines/agos/res.cpp
index 76d23905b7..4b2373e2e4 100644
--- a/engines/agos/res.cpp
+++ b/engines/agos/res.cpp
@@ -806,6 +806,91 @@ void AGOSEngine::loadVGABeardFile(uint16 id) {
 	}
 }
 
+void decodePak98(uint8 *dst, const uint8 *src, uint32 inSize) {
+	const uint8 *src2 = 0;
+	uint8 cmd = 0x80;
+
+	for (uint32 bytesLeft = inSize; bytesLeft; ) {
+		if (cmd == 0x80) {
+			src2 = src + 1;
+			--bytesLeft;
+		}
+
+		if (cmd & *src) {
+			*dst++ = *src2++;
+			--bytesLeft;
+		} else {
+			bytesLeft -= 2;
+			uint16 cmd2 = READ_LE_UINT16(src2);
+			src2 += 2;
+			uint8 cmd3 = cmd2 & 0x0F;
+			cmd2 >>= 4;
+
+			if (cmd2 == 0) {
+				uint16 count = cmd3 + 4;
+				--bytesLeft;
+				if (cmd3 == 0x0F) {
+					count = READ_LE_UINT16(src2);
+					src2 += 2;
+					bytesLeft -= 2;
+				} else if (cmd3 == 0x0E) {
+					count = 18 + (*src2++);
+					--bytesLeft;
+				}
+
+				uint8 destVal = *src2++;
+				while (count--)
+					*dst++ = destVal;
+
+			} else if (cmd2 == 1) {
+				uint16 count = cmd3 + 3;
+				if (cmd3 == 0x0F) {
+					count = READ_LE_UINT16(src2);
+					src2 += 2;
+					bytesLeft -= 2;
+				} else if (cmd3 == 0x0E) {
+					count = 17 + (*src2++);
+					--bytesLeft;
+				}
+
+				dst += count;
+
+			} else if (cmd2 == 2) {
+				uint16 count = cmd3 + 16;
+				if (cmd3 == 0x0F) {
+					count = READ_LE_UINT16(src2);
+					src2 += 2;
+					bytesLeft -= 2;
+				} else if (cmd3 == 0x0E) {
+					count = 30 + (*src2++);
+					--bytesLeft;
+				}
+
+				bytesLeft -= count;
+				while (count--)
+					*dst++ = *src2++;
+
+			} else {
+				uint16 count = cmd3 + 3;
+				if (cmd3 == 0x0F) {
+					count = 18 + (*src2++);
+					--bytesLeft;
+				}
+
+				uint8 *src3 = dst - cmd2;
+				while (count--)
+					*dst++ = *src3++;
+			}
+		}
+
+		cmd >>= 1;
+		if (cmd == 0) {
+			cmd = 0x80;
+			src = src2;
+		}
+	}
+}
+
 void AGOSEngine::loadVGAVideoFile(uint16 id, uint8 type, bool useError) {
 	Common::File in;
 	char filename[15];
@@ -851,7 +936,9 @@ void AGOSEngine::loadVGAVideoFile(uint16 id, uint8 type, bool useError) {
 				sprintf(filename, "%.3d%d.pkd", id, type);
 			}
 		} else {
-			if (getGameType() == GType_ELVIRA1 || getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
+			if (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformPC98) {
+				sprintf(filename, "%.2d.GR2", id);
+			} else if (getGameType() == GType_ELVIRA1 || getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
 				sprintf(filename, "%.2d%d.VGA", id, type);
 			} else if (getGameType() == GType_PN) {
 				sprintf(filename, "%c%d.out", id + 48, type);
@@ -901,6 +988,40 @@ void AGOSEngine::loadVGAVideoFile(uint16 id, uint8 type, bool useError) {
 			dst = allocBlock (dstSize + extraBuffer);
 			decrunchFile(srcBuffer, dst, srcSize);
 			free(srcBuffer);
+		} else if (getPlatform() == Common::kPlatformPC98) {
+			bool compressed = (in.readUint16LE() == 1);
+			srcSize = in.readUint32LE();
+			if (type == 1) {
+				if (compressed)
+					srcSize = in.readUint32LE() + 2;
+				in.seek(srcSize, SEEK_CUR);
+				compressed = (in.readUint16LE() == 1);
+				srcSize = in.readUint32LE();
+			}
+
+			if (compressed) {
+				dstSize = srcSize;
+				srcSize = in.readUint32LE();
+				uint16 fill = in.readUint16LE();
+
+				uint8 *srcBuffer = new uint8[srcSize];
+				if (in.read(srcBuffer, srcSize) != srcSize)
+					error("loadVGAVideoFile: Read failed");
+				dst = allocBlock(dstSize);
+
+				Common::fill<uint16*, uint16>((uint16*)dst, (uint16*)(dst + (dstSize & ~1)), fill);
+				if (dstSize & 1)
+					*(dst + dstSize - 1) = fill & 0xff;
+
+				decodePak98(dst, srcBuffer, srcSize);
+
+				delete[] srcBuffer;
+			} else {
+				dstSize = srcSize;
+				dst = allocBlock(dstSize + extraBuffer);
+				if (in.read(dst, dstSize) != dstSize)
+					error("loadVGAVideoFile: Read failed");
+			}
 		} else {
 			dst = allocBlock(dstSize + extraBuffer);
 			if (in.read(dst, dstSize) != dstSize)
@@ -924,4 +1045,80 @@ void AGOSEngine::loadVGAVideoFile(uint16 id, uint8 type, bool useError) {
 	}
 }
 
+Common::SeekableReadStream *AGOSEngine::createPak98FileStream(const char *filename) {
+	Common::File in;
+	if (!in.open(filename))
+		return 0;
+
+	/*uint16 cmpType = */in.readUint16LE();
+	uint32 outSize = in.readUint32LE();
+	uint32 inSize = in.readUint32LE();
+	uint16 fill = in.readUint16LE();
+
+	uint8 *tempBuffer = new uint8[inSize];
+	uint8 *decBuffer = new uint8[outSize];
+	Common::fill<uint16*, uint16>((uint16*)decBuffer, (uint16*)(decBuffer + (outSize & ~1)), fill);
+	if (outSize & 1)
+		*(decBuffer + outSize - 1) = fill & 0xff;
+
+	in.read(tempBuffer, inSize);
+	decodePak98(decBuffer, tempBuffer, inSize);
+	delete[] tempBuffer;
+
+	return new Common::MemoryReadStream(decBuffer, outSize, DisposeAfterUse::YES);
+}
+
+void AGOSEngine::convertPC98Image(VC10_state &state) {
+	if (state.flags & (kDFCompressedFlip | kDFCompressed)) {
+		const byte *src = state.srcPtr;
+		uint32 outSize = READ_LE_UINT32(src + 2);
+		assert(outSize == (state.width << 3) * state.height);
+		uint32 inSize = READ_LE_UINT32(src + 6);
+		uint16 fill = READ_LE_UINT16(src + 10);
+		delete[] _pak98Buf;
+		byte *decBuffer = new uint8[outSize];
+		Common::fill<uint16*, uint16>((uint16*)decBuffer, (uint16*)(decBuffer + (outSize & ~1)), fill);
+		if (outSize & 1)
+			*(decBuffer + outSize - 1) = fill & 0xff;
+
+		decodePak98(decBuffer, src + 12, inSize);
+		_pak98Buf = state.srcPtr = decBuffer;
+	}
+
+	// The PC-98 images are in a planar format, but slightly different from the Amiga format. It does
+	// not make much sense to set the GF_PLANAR flag, since the icons do not require the conversion.
+	delete[] _planarBuf;
+	uint16 planeLW = state.width << 1;
+	uint16 planePitch = planeLW * 3;
+	_planarBuf = new byte[(state.width << 3) * state.height];
+
+	const byte *src[4];
+	memset(src, 0, sizeof(src));
+	for (int i = 0; i < 4; ++i)
+		src[i] = state.srcPtr + i * planeLW;
+	byte *dst = _planarBuf;
+
+	for (int y = 0; y < state.height; ++y) {
+		for (int x = 0; x < planeLW; ++x) {
+			for (int i = 0; i <= 6; i += 2) {
+				byte col = 0;
+				for (int ii = 0; ii < 4; ++ii) {
+					col |= ((*src[ii] >> (7 - i)) & 1) << (ii + 4);
+					col |= ((*src[ii] >> (6 - i)) & 1) << ii;
+				}
+				*dst++ = col;
+			}
+			for (int ii = 0; ii < 4; ++ii)
+				++src[ii];
+		}
+		for (int ii = 0; ii < 4; ++ii)
+			src[ii] += planePitch;
+	}
+
+	state.srcPtr = _planarBuf;
+	if (state.flags & kDFCompressedFlip)
+		state.flags |= kDFFlip;
+	state.flags &= ~(kDFCompressedFlip | kDFCompressed);
+}
+
 } // End of namespace AGOS
diff --git a/engines/agos/res_ami.cpp b/engines/agos/res_ami.cpp
index 4b3d575142..1f05cf716d 100644
--- a/engines/agos/res_ami.cpp
+++ b/engines/agos/res_ami.cpp
@@ -140,7 +140,7 @@ static void convertCompressedImage(const byte *src, byte *dst, uint8 colorDepth,
 	}
 }
 
-byte *AGOSEngine::convertImage(VC10_state *state, bool compressed) {
+byte *AGOSEngine::convertAmigaImage(VC10_state *state, bool compressed) {
 	int length, i, j;
 
 	uint8 colorDepth = 4;
diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp
index 81d89cc4d3..059473695e 100644
--- a/engines/agos/res_snd.cpp
+++ b/engines/agos/res_snd.cpp
@@ -290,16 +290,22 @@ void AGOSEngine::playMusic(uint16 music, uint16 track) {
 	} else {
 		_midi->setLoop(true); // Must do this BEFORE loading music.
 
-		char filename[15];
-		Common::File f;
-		sprintf(filename, "MOD%d.MUS", music);
-		f.open(filename);
-		if (f.isOpen() == false)
-			error("playMusic: Can't load music from '%s'", filename);
+		Common::SeekableReadStream *str = 0;
+		if (getPlatform() == Common::kPlatformPC98) {
+			str = createPak98FileStream(Common::String::format("MOD%d.PAK", music).c_str());
+			if (!str)
+				error("playMusic: Can't load music from 'MOD%d.PAK'", music);
+		} else {
+			Common::File *file = new Common::File();
+			if (!file->open(Common::String::format("MOD%d.MUS", music)))
+				error("playMusic: Can't load music from 'MOD%d.MUS'", music);
+			str = file;
+		}
 
-		_midi->loadS1D(&f);
+		_midi->loadS1D(str);
 		_midi->startTrack(0);
 		_midi->startTrack(track);
+		delete str;
 	}
 }
 
diff --git a/engines/agos/subroutine.cpp b/engines/agos/subroutine.cpp
index eaa080f6bf..906fa856d5 100644
--- a/engines/agos/subroutine.cpp
+++ b/engines/agos/subroutine.cpp
@@ -23,6 +23,7 @@
 #include "common/debug-channels.h"
 #include "common/file.h"
 #include "common/textconsole.h"
+#include "common/memstream.h"
 
 #include "agos/agos.h"
 #include "agos/intern.h"
@@ -259,7 +260,9 @@ void AGOSEngine::endCutscene() {
 }
 
 Common::SeekableReadStream *AGOSEngine::openTablesFile(const char *filename) {
-	if (getFeatures() & GF_OLD_BUNDLE)
+	if (getPlatform() == Common::kPlatformPC98)
+		return openTablesFile_pak98(filename);
+	else if (getFeatures() & GF_OLD_BUNDLE)
 		return openTablesFile_simon1(filename);
 	else
 		return openTablesFile_gme(filename);
@@ -272,6 +275,13 @@ Common::SeekableReadStream *AGOSEngine::openTablesFile_simon1(const char *filena
 	return in;
 }
 
+Common::SeekableReadStream *AGOSEngine::openTablesFile_pak98(const char *filename) {
+	Common::SeekableReadStream *in = createPak98FileStream(filename);
+	if (!in)
+		error("openTablesFile_pak98: Can't open '%s'", filename);
+	return in;
+}
+
 Common::SeekableReadStream *AGOSEngine::openTablesFile_gme(const char *filename) {
 	uint res;
 	uint32 offs;
@@ -287,7 +297,6 @@ bool AGOSEngine::loadTablesIntoMem(uint16 subrId) {
 	byte *p;
 	uint16 min_num, max_num, file_num;
 	Common::SeekableReadStream *in;
-	char filename[30];
 
 	if (_tblList == NULL)
 		return 0;
@@ -306,9 +315,8 @@ bool AGOSEngine::loadTablesIntoMem(uint16 subrId) {
 			_tablesHeapCurPos = _tablesHeapCurPosOrg;
 			_stringIdLocalMin = 1;
 			_stringIdLocalMax = 0;
-
-			sprintf(filename, "TABLES%.2d", file_num);
-			in = openTablesFile(filename);
+			Common::String filename = Common::String::format("TABLES%.2d%s", file_num, getPlatform() == Common::kPlatformPC98 ? ".PAK" : "");
+			in = openTablesFile(filename.c_str());
 			readSubroutineBlock(in);
 			closeTablesFile(in);
 
diff --git a/engines/agos/vga.cpp b/engines/agos/vga.cpp
index 7926f7f4fe..65f3efcd0d 100644
--- a/engines/agos/vga.cpp
+++ b/engines/agos/vga.cpp
@@ -268,9 +268,11 @@ int AGOSEngine::vcReadVarOrWord() {
 	}
 }
 
-uint AGOSEngine::vcReadNextWord() {
+uint AGOSEngine::vcReadNextWord(bool forceLERead) {
 	uint a;
 	a = readUint16Wrapper(_vcPtr);
+	if (forceLERead)
+		a = FROM_BE_16(a);
 	_vcPtr += 2;
 	return a;
 }
@@ -635,13 +637,13 @@ void AGOSEngine::drawImage_init(int16 image, uint16 palette, int16 x, int16 y, u
 	state.flags = flags;
 
 	src = _curVgaFile2 + state.image * 8;
-	state.srcPtr = _curVgaFile2 + readUint32Wrapper(src);
+	state.srcPtr = _curVgaFile2 + (getPlatform() == Common::kPlatformPC98 ? READ_LE_UINT32(src) : readUint32Wrapper(src));
 	if (getGameType() == GType_FF || getGameType() == GType_PP) {
 		width = READ_LE_UINT16(src + 6);
 		height = READ_LE_UINT16(src + 4) & 0x7FFF;
 		flags = src[5];
 	} else {
-		width = READ_BE_UINT16(src + 6) / 16;
+		width = (getPlatform() == Common::kPlatformPC98 ? READ_LE_UINT16(src + 6) : READ_BE_UINT16(src + 6)) / 16;
 		height = src[5];
 		flags = src[4];
 	}
@@ -662,10 +664,9 @@ void AGOSEngine::drawImage_init(int16 image, uint16 palette, int16 x, int16 y, u
 
 	if (getFeatures() & GF_PLANAR) {
 		if (getGameType() == GType_PN) {
-			state.srcPtr = convertImage(&state, ((state.flags & (kDFCompressed | kDFCompressedFlip)) != 0));
-		}
-		else
-			state.srcPtr = convertImage(&state, ((flags & 0x80) != 0));
+			state.srcPtr = convertAmigaImage(&state, ((state.flags & (kDFCompressed | kDFCompressedFlip)) != 0));
+		} else
+			state.srcPtr = convertAmigaImage(&state, ((flags & 0x80) != 0));
 
 		// converted planar clip is already uncompressed
 		if (state.flags & kDFCompressedFlip) {
@@ -690,6 +691,9 @@ void AGOSEngine::drawImage_init(int16 image, uint16 palette, int16 x, int16 y, u
 		}
 	}
 
+	if (getPlatform() == Common::kPlatformPC98)
+		convertPC98Image(state);
+
 	uint maxWidth = (getGameType() == GType_FF || getGameType() == GType_PP) ? 640 : 20;
 	if ((getGameType() == GType_SIMON2 || getGameType() == GType_FF) && width > maxWidth) {
 		horizontalScroll(&state);


Commit: 574133086d42e64d59cd4840f7f7cd524ec6afd7
    https://github.com/scummvm/scummvm/commit/574133086d42e64d59cd4840f7f7cd524ec6afd7
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:47:58+01:00

Commit Message:
AGOS: (ELVIRA/PC98/Jp) - add Japanese font drawing

- Add dual layer graphics display to display the Japanese characters in their proper (double) resolution on top of the graphics. The original actually uses text mode.
- Adapt mouse handling to dual layer code.
- For consistency I got rid of all direct calls to _system->lockScreen() and _system->unlockScreen(), even in the sub engines that aren't affected. I find it easier to debug and to maintain this way.

Changed paths:
    engines/agos/agos.cpp
    engines/agos/agos.h
    engines/agos/animation.cpp
    engines/agos/charset-fontdata.cpp
    engines/agos/charset.cpp
    engines/agos/cursor.cpp
    engines/agos/draw.cpp
    engines/agos/event.cpp
    engines/agos/gfx.cpp
    engines/agos/icons.cpp
    engines/agos/menus.cpp
    engines/agos/script_e1.cpp
    engines/agos/verb.cpp
    engines/agos/vga.cpp
    engines/agos/vga_e2.cpp
    engines/agos/vga_pn.cpp
    engines/agos/vga_ww.cpp
    engines/agos/window.cpp


diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 0bc3811b5c..eb8a5f6cd0 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -37,6 +37,7 @@
 #include "backends/audiocd/audiocd.h"
 
 #include "graphics/surface.h"
+#include "graphics/sjis.h"
 
 #include "audio/mididrv.h"
 
@@ -140,7 +141,23 @@ AGOSEngine_Elvira2::AGOSEngine_Elvira2(OSystem *system, const AGOSGameDescriptio
 }
 
 AGOSEngine_Elvira1::AGOSEngine_Elvira1(OSystem *system, const AGOSGameDescription *gd)
-	: AGOSEngine(system, gd) {
+	: AGOSEngine(system, gd), _sjisCurChar(0), _sjisFont(0) {
+}
+
+AGOSEngine_Elvira1::~AGOSEngine_Elvira1() {
+	delete _sjisFont;
+}
+
+Common::Error AGOSEngine_Elvira1::init() {
+	Common::Error ret = AGOSEngine::init();
+	if (ret.getCode() == Common::kNoError && getPlatform() == Common::kPlatformPC98) {
+		_sjisFont = Graphics::FontSJIS::createFont(Common::kPlatformPC98);
+		if (_sjisFont)
+			_sjisFont->toggleFatPrint(true);
+		else
+			error("AGOSEngine_Elvira1::init(): Failed to load SJIS font.");
+	}
+	return ret;
 }
 
 AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
@@ -541,11 +558,12 @@ AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
 	_moveXMax = 0;
 	_moveYMax = 0;
 
+	_forceAscii = false;
+
 	_vc10BasePtrOld = 0;
 	memcpy (_hebrewCharWidths,
 		"\x5\x5\x4\x6\x5\x3\x4\x5\x6\x3\x5\x5\x4\x6\x5\x3\x4\x6\x5\x6\x6\x6\x5\x5\x5\x6\x5\x6\x6\x6\x6\x6", 32);
 
-
 	const Common::FSNode gameDataDir(ConfMan.get("path"));
 
 	// Add default file directories for Acorn version of
@@ -576,7 +594,15 @@ Common::Error AGOSEngine::init() {
 		_screenHeight = 200;
 	}
 
-	initGraphics(_screenWidth, _screenHeight);
+	_internalWidth = _screenWidth;
+	_internalHeight = _screenHeight;
+
+	if (getPlatform() == Common::kPlatformPC98) {
+		_internalWidth <<= 1;
+		_internalHeight <<= 1;
+	}
+
+	initGraphics(_internalWidth, _internalHeight);
 
 	_midi = new MidiPlayer();
 
@@ -601,11 +627,11 @@ Common::Error AGOSEngine::init() {
 	_backGroundBuf = new Graphics::Surface();
 	_backGroundBuf->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());
 
-	if (getGameType() == GType_FF || getGameType() == GType_PP) {
+	if (getGameType() == GType_FF || getGameType() == GType_PP || (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformPC98)) {
 		_backBuf = new Graphics::Surface();
 		_backBuf->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());
 		_scaleBuf = new Graphics::Surface();
-		_scaleBuf->create(_screenWidth, _screenHeight, Graphics::PixelFormat::createFormatCLUT8());
+		_scaleBuf->create(_internalWidth, _internalHeight, Graphics::PixelFormat::createFormatCLUT8());
 	}
 
 	if (getGameType() == GType_SIMON2) {
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index 9da4b0cf91..553131127e 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -58,6 +58,7 @@ class SeekableReadStream;
 
 namespace Graphics {
 struct Surface;
+class FontSJIS;
 }
 
 namespace AGOS {
@@ -202,7 +203,7 @@ protected:
 	friend class Debugger;
 
 	// Engine APIs
-	Common::Error init();
+	virtual Common::Error init();
 	virtual Common::Error go();
 	Common::Error run() override {
 		Common::Error err;
@@ -349,6 +350,7 @@ protected:
 	uint16 _scrollWidth, _scrollHeight;
 	const byte *_scrollImage;
 	byte _boxStarHeight;
+	bool _forceAscii;
 
 	SubroutineLine *_classLine;
 	int16 _classMask, _classMode1, _classMode2;
@@ -445,6 +447,7 @@ protected:
 	volatile uint16 _fastFadeInFlag;
 
 	uint16 _screenWidth, _screenHeight;
+	uint16 _internalWidth, _internalHeight;
 
 	uint16 _noOverWrite;
 	bool _rejectBlock;
@@ -1156,6 +1159,10 @@ protected:
 	void horizontalScroll(VC10_state *state);
 	void verticalScroll(VC10_state *state);
 
+	Graphics::Surface *getBackendSurface() const;
+	void updateBackendSurface(Common::Rect *area = 0) const;
+	virtual void clearHiResTextLayer() {}
+
 	int vcReadVarOrWord();
 	uint vcReadNextWord(bool forceLERead = false);
 	uint vcReadNextByte();
@@ -1552,7 +1559,8 @@ protected:
 class AGOSEngine_Elvira1 : public AGOSEngine {
 public:
 	AGOSEngine_Elvira1(OSystem *system, const AGOSGameDescription *gd);
-	//~AGOSEngine_Elvira1();
+	~AGOSEngine_Elvira1() override;
+	Common::Error init() override;
 
 	void setupGame() override;
 	void setupOpcodes() override;
@@ -1626,8 +1634,15 @@ protected:
 	const OpcodeEntryElvira1 *_opcodesElvira1;
 
 	void drawIcon(WindowBlock *window, uint icon, uint x, uint y) override;
+	void windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) override;
+	void addHiResTextDirtyRect(Common::Rect rect);
+	void clearHiResTextLayer() override;
 
 	Common::String genSaveName(int slot) const override;
+
+	Graphics::FontSJIS *_sjisFont;
+	Common::Array<Common::Rect> _sjisTextFields;
+	uint16 _sjisCurChar;
 };
 
 class AGOSEngine_Elvira2 : public AGOSEngine_Elvira1 {
diff --git a/engines/agos/animation.cpp b/engines/agos/animation.cpp
index a217536081..1d73a7dd2a 100644
--- a/engines/agos/animation.cpp
+++ b/engines/agos/animation.cpp
@@ -344,9 +344,9 @@ void MoviePlayerDXA::handleNextFrame() {
 }
 
 bool MoviePlayerDXA::processFrame() {
-	Graphics::Surface *screen = _vm->_system->lockScreen();
+	Graphics::Surface *screen = _vm->getBackendSurface();
 	copyFrameToBuffer((byte *)screen->getPixels(), (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, screen->pitch);
-	_vm->_system->unlockScreen();
+	_vm->updateBackendSurface();
 
 	uint32 soundTime = _mixer->getSoundElapsedTime(_bgSound);
 	uint32 nextFrameStartTime = ((Video::VideoDecoder::VideoTrack *)getTrack(0))->getNextFrameStartTime();
@@ -495,9 +495,9 @@ void MoviePlayerSMK::nextFrame() {
 }
 
 bool MoviePlayerSMK::processFrame() {
-	Graphics::Surface *screen = _vm->_system->lockScreen();
+	Graphics::Surface *screen = _vm->getBackendSurface();
 	copyFrameToBuffer((byte *)screen->getPixels(), (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, screen->pitch);
-	_vm->_system->unlockScreen();
+	_vm->updateBackendSurface();
 
 	uint32 waitTime = getTimeToNextFrame();
 
diff --git a/engines/agos/charset-fontdata.cpp b/engines/agos/charset-fontdata.cpp
index ae31814e43..a10172647c 100644
--- a/engines/agos/charset-fontdata.cpp
+++ b/engines/agos/charset-fontdata.cpp
@@ -28,6 +28,7 @@
 #include "agos/intern.h"
 
 #include "graphics/surface.h"
+#include "graphics/sjis.h"
 
 namespace AGOS {
 
@@ -2921,7 +2922,7 @@ void AGOSEngine::windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) {
 
 	_videoLockOut |= 0x8000;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 
 	if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) {
 		dst = (byte *)screen->getPixels();
@@ -3019,9 +3020,85 @@ void AGOSEngine::windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) {
 		dst += dstPitch;
 	} while (--h);
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	_videoLockOut &= ~0x8000;
 }
 
+void AGOSEngine_Elvira1::windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) {
+	if (_language != Common::JA_JPN || _forceAscii) {
+		AGOSEngine::windowDrawChar(window, x, y, chr);
+		return;
+	}
+
+	if (_sjisCurChar) {
+		_sjisCurChar |= (chr << 8);
+	} else {
+		_sjisCurChar = chr;
+		if ((chr >= 0x80 && chr < 0xA0) || chr >= 0xE0)
+			return;		
+	}
+
+	_videoLockOut |= 0x8000;
+
+	x = x & ~7;
+	y = (y + 4) & ~3;
+	// PC-98 uses text mode black (hardware colors, not related to the graphics mode palette
+	// colors) for font drawing, so I just set one of the unused black colors (color 33) here.
+	_sjisFont->drawChar(*_scaleBuf, _sjisCurChar, x << 1, y << 1, 33, 0);
+	Common::Rect dirtyRect(x, y, x + (_sjisFont->getCharWidth(_sjisCurChar) >> 1), y + (_sjisFont->getFontHeight() >> 1));
+	addHiResTextDirtyRect(dirtyRect);
+	updateBackendSurface(&dirtyRect);
+	_sjisCurChar = 0;
+
+	_videoLockOut &= ~0x8000;
+}
+
+void AGOSEngine_Elvira1::addHiResTextDirtyRect(Common::Rect rect) {
+	rect.left >>= 1;
+	rect.top <<= 1;
+	rect.right >>= 1;
+	rect.bottom <<= 1;
+
+	for (Common::Array<Common::Rect>::iterator i = _sjisTextFields.begin(); i != _sjisTextFields.end(); ++i) {
+		// Merge rects if it makes sense, but only once.
+		if (rect.left <= i->right && rect.right >= i->left && rect.top <= i->bottom && rect.bottom >= i->top) {
+			i->left = MIN<int16>(i->left, rect.left);
+			i->top = MIN<int16>(i->top, rect.top);
+			i->right = MAX<int16>(i->right, rect.right);
+			i->bottom = MAX<int16>(i->bottom, rect.bottom);
+			return;
+		}
+	}
+
+	_sjisTextFields.push_back(rect);
+}
+
+void AGOSEngine_Elvira1::clearHiResTextLayer() {
+	if (getPlatform() != Common::kPlatformPC98)
+		return;
+
+	void *p = _scaleBuf->getPixels();
+	assert(p);
+
+	if (_sjisTextFields.size() < 10) {
+		for (Common::Array<Common::Rect>::iterator i = _sjisTextFields.begin(); i != _sjisTextFields.end(); ++i) {
+			uint16 w = i->width();
+			uint16 ptch = _scaleBuf->pitch >> 2;
+			uint32 *dst = (uint32*)p + i->top * ptch + i->left;
+			for (uint32 *end = dst + i->height() * ptch; dst < end; dst += ptch)
+				Common::fill<uint32*, uint32>(dst, &dst[w], 0);
+			i->left <<= 1;
+			i->top >>= 1;
+			i->right <<= 1;
+			i->bottom >>= 1;
+			updateBackendSurface(i);
+		}
+	} else {
+		memset(p, 0, _scaleBuf->w * _scaleBuf->h);
+		updateBackendSurface();
+	}
+	_sjisTextFields.clear();
+}
+
 } // End of namespace AGOS
diff --git a/engines/agos/charset.cpp b/engines/agos/charset.cpp
index 4d83a4ef1c..c3e8e49d63 100644
--- a/engines/agos/charset.cpp
+++ b/engines/agos/charset.cpp
@@ -148,6 +148,11 @@ void AGOSEngine::justifyOutPut(byte chr) {
 		_printCharPixelCount = 0;
 		doOutput(&chr, 1);
 		clsCheck(_textWindow);
+	} else if (getLanguage() == Common::JA_JPN) {
+		// Japanese has no word wrapping
+		_lettersToPrintBuf[0] = chr;
+		_lettersToPrintBuf[1] = '\0';
+		doOutput(_lettersToPrintBuf, 1);
 	} else if (chr == 0 || chr == ' ' || chr == 10) {
 		bool fit;
 
@@ -215,6 +220,7 @@ void AGOSEngine_PN::windowPutChar(WindowBlock *window, byte c, byte b) {
 
 void AGOSEngine::windowPutChar(WindowBlock *window, byte c, byte b) {
 	byte width = 6;
+	byte textColumnWidth = 8;
 
 	if (c == 12) {
 		clearWindow(window);
@@ -256,8 +262,9 @@ void AGOSEngine::windowPutChar(WindowBlock *window, byte c, byte b) {
 			return;
 		}
 
-		// Ignore invalid characters
-		if (c - 32 > 98)
+		if (_language == Common::JA_JPN)
+			textColumnWidth = width = 4;
+		else if (c - 32 > 98) // Ignore invalid characters
 			return;
 
 		if (window->textLength == window->textMaxLength) {
@@ -278,16 +285,15 @@ void AGOSEngine::windowPutChar(WindowBlock *window, byte c, byte b) {
 			windowDrawChar(window, (window->width + window->x - window->textColumn) * 8, window->textRow * 8 + window->y, c);
 			window->textLength++;
 		} else {
-			windowDrawChar(window, (window->textColumn + window->x) * 8, window->textRow * 8 + window->y, c);
-
+			windowDrawChar(window, window->x * 8 + window->textColumn * textColumnWidth, window->textRow * 8 + window->y, c);
 			window->textLength++;
-			window->textColumnOffset += 6;
+			window->textColumnOffset += width;
 			if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) {
 				if (c == 'i' || c == 'l')
 					window->textColumnOffset -= 2;
 			}
-			if (window->textColumnOffset >= 8) {
-				window->textColumnOffset -= 8;
+			if (window->textColumnOffset >= textColumnWidth) {
+				window->textColumnOffset -= textColumnWidth;
 				window->textColumn++;
 			}
 		}
@@ -354,24 +360,37 @@ void AGOSEngine::windowScroll(WindowBlock *window) {
 	_videoLockOut |= 0x8000;
 
 	if (window->height != 1) {
-		Graphics::Surface *screen = _system->lockScreen();
+		Graphics::Surface *screen = getBackendSurface();
 
 		byte *src, *dst;
-		uint16 w, h;
+		uint16 w1, h1, w2, h2;
 
-		w = window->width * 8;
-		h = (window->height -1) * 8;
+		w1 = w2 = window->width * 8;
+		h1 = h2 = (window->height -1) * 8;
 
 		dst = (byte *)screen->getBasePtr(window->x * 8, window->y);
 		src = dst + 8 * screen->pitch;
 
 		do {
-			memcpy(dst, src, w);
+			memcpy(dst, src, w1);
 			src += screen->pitch;
 			dst += screen->pitch;
-		} while (--h);
+		} while (--h1);
+
+		if (getGameId() == GID_ELVIRA1 && getPlatform() == Common::kPlatformPC98) {
+			w1 = w2 << 1;
+			h1 = h2 << 1;
+			dst = (byte *)_scaleBuf->getBasePtr(window->x * 16, window->y * 2);
+			src = dst + 16 * screen->pitch;
+			do {
+				memcpy(dst, src, w1);
+				src += screen->pitch;
+				dst += screen->pitch;
+			} while (--h1);
+		}
 
-		_system->unlockScreen();
+		Common::Rect dirtyRect(window->x * 8, window->y, window->x * 8 + w2, window->y + h2);
+		updateBackendSurface(&dirtyRect);
 	}
 
 	colorBlock(window, window->x * 8, (window->height - 1) * 8 + window->y, window->width * 8, 8);
diff --git a/engines/agos/cursor.cpp b/engines/agos/cursor.cpp
index 19a38116cf..6a16b764b4 100644
--- a/engines/agos/cursor.cpp
+++ b/engines/agos/cursor.cpp
@@ -559,6 +559,10 @@ void AGOSEngine::handleMouseMoved() {
 			_needHitAreaRecalc++;
 		}
 	} else if (getGameType() == GType_ELVIRA1) {
+		if (getPlatform() == Common::kPlatformPC98) {
+			_mouse.x >>= 1;
+			_mouse.y >>= 1;
+		}
 		if (_mouseCursor != _variableArray[438]) {
 			_mouseCursor = _variableArray[438];
 			_needHitAreaRecalc++;
@@ -784,6 +788,12 @@ static const byte mouseCursorPalette[] = {
 void AGOSEngine::initMouse() {
 	_maxCursorWidth = 16;
 	_maxCursorHeight = 16;
+
+	if (getGameId() == GID_ELVIRA1 && getPlatform() == Common::kPlatformPC98) {
+		_maxCursorWidth <<= 1;
+		_maxCursorHeight <<= 1;
+	}
+
 	_mouseData = (byte *)calloc(_maxCursorWidth * _maxCursorHeight, 1);
 
 	memset(_mouseData, 0xFF, _maxCursorWidth * _maxCursorHeight);
@@ -864,7 +874,21 @@ void AGOSEngine::drawMousePointer() {
 			src += 2;
 		}
 
-		CursorMan.replaceCursor(_mouseData, 16, 16, 0, 0, 0xFF);
+		if (getGameId() == GID_ELVIRA1 && getPlatform() == Common::kPlatformPC98) {
+			// Simple 2x upscaling for the cursor in dual layer hi-res mode.
+			uint8 ptch = 16;
+			uint16 *dst1 = &((uint16*)_mouseData)[16 * 16 * 2 - 1];
+			uint16 *dst2 = dst1 - ptch;
+			for (const byte *src = &_mouseData[16 * 16 - 1]; src >= _mouseData; --src) {
+				*dst1-- = *dst2-- = (*src << 8) | *src;
+				if (!(ptch = (ptch - 1) % 16)) {
+					dst1 -= 16;
+					dst2 -= 16;
+				}
+			}
+		}
+
+		CursorMan.replaceCursor(_mouseData, _maxCursorWidth, _maxCursorHeight, 0, 0, 0xFF);
 	}
 }
 
diff --git a/engines/agos/draw.cpp b/engines/agos/draw.cpp
index f7ceeb70c1..a627bb54d8 100644
--- a/engines/agos/draw.cpp
+++ b/engines/agos/draw.cpp
@@ -536,7 +536,7 @@ void AGOSEngine::displayBoxStars() {
 		ha = _hitAreas;
 		count = ARRAYSIZE(_hitAreas);
 
-		Graphics::Surface *screen = _system->lockScreen();
+		Graphics::Surface *screen = getBackendSurface();
 
 		do {
 			if (ha->id != 0 && ha->flags & kBFBoxInUse && !(ha->flags & kBFBoxDead)) {
@@ -604,7 +604,7 @@ void AGOSEngine::displayBoxStars() {
 			}
 		} while (ha++, --count);
 
-		_system->unlockScreen();
+		updateBackendSurface();
 
 		delay(100);
 
@@ -724,7 +724,7 @@ void AGOSEngine::fillBackFromBackGround(uint16 height, uint16 width) {
 }
 
 void AGOSEngine::fillBackFromFront() {
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	byte *src = (byte *)screen->getPixels();
 	byte *dst = getBackBuf();
 
@@ -733,7 +733,7 @@ void AGOSEngine::fillBackFromFront() {
 		src += screen->pitch;
 		dst += _backBuf->pitch;
 	}
-	_system->unlockScreen();
+	updateBackendSurface();
 }
 
 void AGOSEngine::fillBackGroundFromBack() {
@@ -747,7 +747,7 @@ void AGOSEngine::fillBackGroundFromBack() {
 }
 
 void AGOSEngine::fillBackGroundFromFront() {
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	byte *src = (byte *)screen->getPixels();
 	byte *dst = getBackGround();
 
@@ -756,7 +756,7 @@ void AGOSEngine::fillBackGroundFromFront() {
 		src += screen->pitch;
 		dst += _backGroundBuf->pitch;
 	}
-	_system->unlockScreen();
+	updateBackendSurface();
 }
 
 void AGOSEngine::setMoveRect(uint16 x, uint16 y, uint16 width, uint16 height) {
@@ -782,7 +782,7 @@ void AGOSEngine::displayScreen() {
 		}
 	}
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	if (getGameType() == GType_PP || getGameType() == GType_FF) {
 		byte *src = getBackBuf();
 		byte *dst = (byte *)screen->getPixels();
@@ -841,7 +841,7 @@ void AGOSEngine::displayScreen() {
 		}
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	if (getGameType() == GType_FF && _scrollFlag) {
 		scrollScreen();
diff --git a/engines/agos/event.cpp b/engines/agos/event.cpp
index 3c16590aff..2708d0a681 100644
--- a/engines/agos/event.cpp
+++ b/engines/agos/event.cpp
@@ -365,7 +365,7 @@ static const byte _image4[32] = {
 void AGOSEngine::drawStuff(const byte *src, uint xoffs) {
 	const uint8 y = (getPlatform() == Common::kPlatformAtariST) ? 132 : 135;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	byte *dst = (byte *)screen->getBasePtr(xoffs, y);
 
 	for (uint h = 0; h < 6; h++) {
@@ -374,7 +374,7 @@ void AGOSEngine::drawStuff(const byte *src, uint xoffs) {
 		dst += screen->pitch;
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 }
 
 void AGOSEngine::playerDamageEvent(VgaTimerEntry * vte, uint dx) {
diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp
index 8bdcfcfa6d..07342277a5 100644
--- a/engines/agos/gfx.cpp
+++ b/engines/agos/gfx.cpp
@@ -640,7 +640,7 @@ void AGOSEngine_Simon1::drawImage(VC10_state *state) {
 	if (!drawImage_clip(state))
 		return;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 
 	if (getFeatures() & GF_32COLOR)
 		state->palette = 0xC0;
@@ -738,7 +738,7 @@ void AGOSEngine_Simon1::drawImage(VC10_state *state) {
 		drawVertImage(state);
 	}
 
-	 _system->unlockScreen();
+	 updateBackendSurface();
 }
 
 void AGOSEngine::drawBackGroundImage(VC10_state *state) {
@@ -857,7 +857,7 @@ void AGOSEngine::drawImage(VC10_state *state) {
 	if (!drawImage_clip(state))
 		return;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 
 	uint16 xoffs = 0, yoffs = 0;
 	if (getGameType() == GType_WW) {
@@ -951,7 +951,7 @@ void AGOSEngine::drawImage(VC10_state *state) {
 		drawVertImage(state);
 	}
 
-	 _system->unlockScreen();
+	 updateBackendSurface();
 }
 
 void AGOSEngine::horizontalScroll(VC10_state *state) {
@@ -1023,6 +1023,61 @@ void AGOSEngine::verticalScroll(VC10_state *state) {
 	}
 }
 
+Graphics::Surface *AGOSEngine::getBackendSurface() const {
+	return (getGameId() == GID_ELVIRA1 && getPlatform() == Common::kPlatformPC98) ? _backBuf : _system->lockScreen();
+}
+
+void AGOSEngine::updateBackendSurface(Common::Rect *area) const {
+	if (getGameId() == GID_ELVIRA1 && getPlatform() == Common::kPlatformPC98) {
+		int x = 0;
+		int y = 0;
+		int w = _screenWidth;
+		int h = _screenHeight;
+
+		if (area) {
+			x = area->left;
+			y = area->top;
+			w = area->width();
+			h = area->height();
+		}
+
+		Graphics::Surface *screen = _system->lockScreen();
+
+		int src0Pitch = _backBuf->pitch;
+		int src1Pitch = _scaleBuf->pitch;
+		int dst1Pitch = screen->pitch;
+		const byte *src00 = (byte*)_backBuf->getBasePtr(x, y);
+		const byte *src10 = (byte*)_scaleBuf->getBasePtr(x << 1, y << 1);
+		const byte *src11 = src10 + src1Pitch;
+		byte *dst10 = (byte*)screen->getBasePtr(x << 1, y << 1);
+		byte *dst11 = dst10 + dst1Pitch;
+		src0Pitch -= w;
+		src1Pitch += (src1Pitch - (w << 1));
+		dst1Pitch += (dst1Pitch - (w << 1));
+
+		while (h--) {
+			for (int i = 0; i < w; ++i) {
+				uint8 v0 = *src00++;
+				uint8 v1 = *src10++;
+				*dst10++ = v1 ? v1 : v0;
+				v1 = *src10++;
+				*dst10++ = v1 ? v1 : v0;
+				v1 = *src11++;
+				*dst11++ = v1 ? v1 : v0;
+				v1 = *src11++;
+				*dst11++ = v1 ? v1 : v0;
+			}
+			src00 += src0Pitch;
+			src10 += src1Pitch;
+			src11 += src1Pitch;
+			dst10 += dst1Pitch;
+			dst11 += dst1Pitch;
+		}
+	}
+
+	_system->unlockScreen();
+}
+
 void AGOSEngine::paletteFadeOut(byte *palPtr, uint num, uint size) {
 	byte *p = palPtr;
 
@@ -1372,7 +1427,7 @@ void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCas
 		uint width = _videoWindows[updateWindow * 4 + 2] * 16;
 		uint height = _videoWindows[updateWindow * 4 + 3];
 
-		Graphics::Surface *screen = _system->lockScreen();
+		Graphics::Surface *screen = getBackendSurface();
 		byte *dst = (byte *)_backGroundBuf->getBasePtr(xoffs, yoffs);
 		byte *src = 0;
 		uint srcWidth = 0;
@@ -1389,7 +1444,7 @@ void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCas
 				src = (byte *)screen->getBasePtr(xoffs, yoffs);
 				srcWidth = screen->pitch;
 			} else {
-				_system->unlockScreen();
+				updateBackendSurface();
 				_videoLockOut &= ~0x20;
 				return;
 			}
@@ -1404,7 +1459,7 @@ void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCas
 				src = (byte *)screen->getBasePtr(xoffs, yoffs);
 				srcWidth = screen->pitch;
 			} else {
-				_system->unlockScreen();
+				updateBackendSurface();
 				_videoLockOut &= ~0x20;
 				return;
 			}
@@ -1416,7 +1471,7 @@ void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCas
 				src = (byte *)screen->getBasePtr(xoffs, yoffs);
 				srcWidth = screen->pitch;
 			} else {
-				_system->unlockScreen();
+				updateBackendSurface();
 				_videoLockOut &= ~0x20;
 				return;
 			}
@@ -1428,7 +1483,7 @@ void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCas
 				src = (byte *)screen->getBasePtr(xoffs, yoffs);
 				srcWidth = screen->pitch;
 			} else {
-				_system->unlockScreen();
+				updateBackendSurface();
 				_videoLockOut &= ~0x20;
 				return;
 			}
@@ -1474,7 +1529,7 @@ void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCas
 			}
 		}
 
-		_system->unlockScreen();
+		updateBackendSurface();
 	}
 
 	_videoLockOut &= ~0x20;
@@ -1485,7 +1540,7 @@ void AGOSEngine::drawEdging() {
 	byte *dst;
 	uint8 color = (getPlatform() == Common::kPlatformDOS) ? 7 : 15;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 
 	dst = (byte *)screen->getBasePtr(0, 136);
 	uint8 len = 52;
@@ -1499,7 +1554,7 @@ void AGOSEngine::drawEdging() {
 	dst = (byte *)screen->getBasePtr(0, 187);
 	memset(dst, color, _screenWidth);
 
-	_system->unlockScreen();
+	updateBackendSurface();
 }
 
 } // End of namespace AGOS
diff --git a/engines/agos/icons.cpp b/engines/agos/icons.cpp
index 364fbf5f15..3bbec55f3e 100644
--- a/engines/agos/icons.cpp
+++ b/engines/agos/icons.cpp
@@ -202,7 +202,7 @@ void AGOSEngine_Simon2::drawIcon(WindowBlock *window, uint icon, uint x, uint y)
 
 	_videoLockOut |= 0x8000;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	dst = (byte *)screen->getPixels();
 
 	dst += 110;
@@ -217,7 +217,7 @@ void AGOSEngine_Simon2::drawIcon(WindowBlock *window, uint icon, uint x, uint y)
 	src += READ_LE_UINT16(src + icon * 4 + 2);
 	decompressIcon(dst, src, 20, 10, 208, screen->pitch);
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	_videoLockOut &= ~0x8000;
 }
@@ -228,7 +228,7 @@ void AGOSEngine_Simon1::drawIcon(WindowBlock *window, uint icon, uint x, uint y)
 
 	_videoLockOut |= 0x8000;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	dst = (byte *)screen->getPixels();
 
 	dst += (x + window->x) * 8;
@@ -245,7 +245,7 @@ void AGOSEngine_Simon1::drawIcon(WindowBlock *window, uint icon, uint x, uint y)
 		decompressIcon(dst, src, 24, 12, 224, screen->pitch);
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	_videoLockOut &= ~0x8000;
 }
@@ -256,7 +256,7 @@ void AGOSEngine_Waxworks::drawIcon(WindowBlock *window, uint icon, uint x, uint
 
 	_videoLockOut |= 0x8000;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	dst = (byte *)screen->getPixels();
 
 	dst += (x + window->x) * 8;
@@ -273,7 +273,7 @@ void AGOSEngine_Waxworks::drawIcon(WindowBlock *window, uint icon, uint x, uint
 		decompressIcon(dst, src, 24, 10, color, screen->pitch);
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	_videoLockOut &= ~0x8000;
 }
@@ -284,7 +284,7 @@ void AGOSEngine_Elvira2::drawIcon(WindowBlock *window, uint icon, uint x, uint y
 
 	_videoLockOut |= 0x8000;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	dst = (byte *)screen->getPixels();
 
 	dst += (x + window->x) * 8;
@@ -301,7 +301,7 @@ void AGOSEngine_Elvira2::drawIcon(WindowBlock *window, uint icon, uint x, uint y
 		decompressIcon(dst, src, 24, 12, color, screen->pitch);
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	_videoLockOut &= ~0x8000;
 }
@@ -312,7 +312,7 @@ void AGOSEngine_Elvira1::drawIcon(WindowBlock *window, uint icon, uint x, uint y
 
 	_videoLockOut |= 0x8000;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	dst = (byte *)screen->getPixels();
 
 	dst += (x + window->x) * 8;
@@ -328,7 +328,7 @@ void AGOSEngine_Elvira1::drawIcon(WindowBlock *window, uint icon, uint x, uint y
 		decompressIconPlanar(dst, src, 24, 12, 16, screen->pitch, false);
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	_videoLockOut &= ~0x8000;
 }
@@ -339,7 +339,7 @@ void AGOSEngine::drawIcon(WindowBlock *window, uint icon, uint x, uint y) {
 
 	_videoLockOut |= 0x8000;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	dst = (byte *)screen->getBasePtr(x * 8, y);
 	src = _iconFilePtr + icon * 146;
 
@@ -365,7 +365,7 @@ void AGOSEngine::drawIcon(WindowBlock *window, uint icon, uint x, uint y) {
 		}
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	_videoLockOut &= ~0x8000;
 }
@@ -951,7 +951,7 @@ void AGOSEngine::drawArrow(uint16 x, uint16 y, int8 dir) {
 		src = _arrowImage;
 	}
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	byte *dst = (byte *)screen->getBasePtr(x * 8, y);
 
 	for (h = 0; h < 19; h++) {
@@ -964,7 +964,7 @@ void AGOSEngine::drawArrow(uint16 x, uint16 y, int8 dir) {
 		dst+= screen->pitch;
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 }
 
 void AGOSEngine_Simon1::removeArrows(WindowBlock *window, uint num) {
@@ -1042,7 +1042,7 @@ static const byte hitBarData[12 * 7] = {
 
 // Personal Nightmare specific
 void AGOSEngine_PN::drawIconHitBar() {
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	byte *dst = (byte *)screen->getBasePtr(6 * 8, 3);
 	const byte *src = hitBarData;
 	uint8 color = (getPlatform() == Common::kPlatformDOS) ? 7 : 15;
@@ -1061,7 +1061,7 @@ void AGOSEngine_PN::drawIconHitBar() {
 		dst += screen->pitch;
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 }
 
 void AGOSEngine_PN::iconPage() {
diff --git a/engines/agos/menus.cpp b/engines/agos/menus.cpp
index 77a37cb601..7de21be146 100644
--- a/engines/agos/menus.cpp
+++ b/engines/agos/menus.cpp
@@ -163,7 +163,7 @@ void AGOSEngine::unlightMenuStrip() {
 
 	mouseOff();
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	src = (byte *)screen->getBasePtr(272, 8);
 	w = 48;
 	h = 82;
@@ -179,7 +179,7 @@ void AGOSEngine::unlightMenuStrip() {
 	for (i = 120; i != 130; i++)
 		disableBox(i);
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	mouseOn();
 }
@@ -191,7 +191,7 @@ void AGOSEngine::lightMenuBox(uint hitarea) {
 
 	mouseOff();
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	src = (byte *)screen->getBasePtr(ha->x, ha->y);
 	w = ha->width;
 	h = ha->height;
@@ -204,7 +204,7 @@ void AGOSEngine::lightMenuBox(uint hitarea) {
 		src += screen->pitch;
 	} while (--h);
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	mouseOn();
 }
diff --git a/engines/agos/script_e1.cpp b/engines/agos/script_e1.cpp
index 38b29d475e..85f92bcf7a 100644
--- a/engines/agos/script_e1.cpp
+++ b/engines/agos/script_e1.cpp
@@ -1166,6 +1166,7 @@ void AGOSEngine::printStats() {
 	window->flags = 1;
 
 	mouseOff();
+	_forceAscii = true;
 
 	// Strength
 	val = _variableArray[0];
@@ -1215,6 +1216,7 @@ void AGOSEngine::printStats() {
 		val = 99;
 	writeChar(window, 36, 133, 4, val);
 
+	_forceAscii = false;
 	mouseOn();
 }
 
diff --git a/engines/agos/verb.cpp b/engines/agos/verb.cpp
index fb3878381f..7f1ff7592c 100644
--- a/engines/agos/verb.cpp
+++ b/engines/agos/verb.cpp
@@ -985,7 +985,7 @@ void AGOSEngine::invertBox(HitArea *ha, byte a, byte b, byte c, byte d) {
 
 	_videoLockOut |= 0x8000;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	src = (byte *)screen->getBasePtr(ha->x, ha->y);
 
 	// WORKAROUND: Hitareas for saved game names aren't adjusted for scrolling locations
@@ -1041,7 +1041,7 @@ void AGOSEngine::invertBox(HitArea *ha, byte a, byte b, byte c, byte d) {
 		src += screen->pitch;
 	} while (--h);
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	_videoLockOut &= ~0x8000;
 }
diff --git a/engines/agos/vga.cpp b/engines/agos/vga.cpp
index 65f3efcd0d..c3790de402 100644
--- a/engines/agos/vga.cpp
+++ b/engines/agos/vga.cpp
@@ -1182,7 +1182,7 @@ void AGOSEngine::vc31_setWindow() {
 
 void AGOSEngine::vc32_saveScreen() {
 	if (getGameType() == GType_PN) {
-		Graphics::Surface *screen = _system->lockScreen();
+		Graphics::Surface *screen = getBackendSurface();
 		byte *dst = getBackGround();
 		byte *src = (byte *)screen->getPixels();
 		for (int i = 0; i < _screenHeight; i++) {
@@ -1190,7 +1190,7 @@ void AGOSEngine::vc32_saveScreen() {
 			dst += _backGroundBuf->pitch;
 			src += screen->pitch;
 		}
-		_system->unlockScreen();
+		updateBackendSurface();
 	} else {
 		uint16 xoffs = _videoWindows[4 * 4 + 0] * 16;
 		uint16 yoffs = _videoWindows[4 * 4 + 1];
@@ -1251,13 +1251,14 @@ void AGOSEngine::clearVideoWindow(uint16 num, uint16 color) {
 	}
 
 	if (getGameType() == GType_ELVIRA1 && num == 3) {
-		Graphics::Surface *screen = _system->lockScreen();
+		Graphics::Surface *screen = getBackendSurface();
 		byte *dst = (byte *)screen->getPixels();
 		for (int i = 0; i < _screenHeight; i++) {
 			memset(dst, color, _screenWidth);
 			dst += screen->pitch;
 		}
-		 _system->unlockScreen();
+		clearHiResTextLayer();
+		updateBackendSurface();
 	} else {
 		const uint16 *vlut = &_videoWindows[num * 4];
 		uint16 xoffs = (vlut[0] - _videoWindows[16]) * 16;
diff --git a/engines/agos/vga_e2.cpp b/engines/agos/vga_e2.cpp
index a26f189c43..f4ecd11a05 100644
--- a/engines/agos/vga_e2.cpp
+++ b/engines/agos/vga_e2.cpp
@@ -89,7 +89,7 @@ void AGOSEngine::vc45_setWindowPalette() {
 			dst += width * 2;
 		}
 	} else {
-		Graphics::Surface *screen = _system->lockScreen();
+		Graphics::Surface *screen = getBackendSurface();
 		byte *dst = (byte *)screen->getBasePtr(vlut[0] * 16, vlut[1]);
 
 		if (getGameType() == GType_ELVIRA2 && num == 7) {
@@ -107,7 +107,7 @@ void AGOSEngine::vc45_setWindowPalette() {
 			dst += screen->pitch;
 		}
 
-		_system->unlockScreen();
+		updateBackendSurface();
 	}
 }
 
@@ -223,7 +223,7 @@ void AGOSEngine::vc53_dissolveIn() {
 
 	uint16 count = dissolveCheck * 2;
 	while (count--) {
-		Graphics::Surface *screen = _system->lockScreen();
+		Graphics::Surface *screen = getBackendSurface();
 		byte *dstPtr = (byte *)screen->getBasePtr(x, y);
 
 		yoffs = _rnd.getRandomNumber(dissolveY);
@@ -264,7 +264,7 @@ void AGOSEngine::vc53_dissolveIn() {
 		*dst &= color;
 		*dst |= *src & 0xF;
 
-		_system->unlockScreen();
+		updateBackendSurface();
 
 		dissolveCount--;
 		if (!dissolveCount) {
@@ -296,7 +296,7 @@ void AGOSEngine::vc54_dissolveOut() {
 
 	uint16 count = dissolveCheck * 2;
 	while (count--) {
-		Graphics::Surface *screen = _system->lockScreen();
+		Graphics::Surface *screen = getBackendSurface();
 		byte *dstPtr = (byte *)screen->getBasePtr(x, y);
 		color |= dstPtr[0] & 0xF0;
 
@@ -318,7 +318,7 @@ void AGOSEngine::vc54_dissolveOut() {
 		dst += xoffs;
 		*dst = color;
 
-		_system->unlockScreen();
+		updateBackendSurface();
 
 		dissolveCount--;
 		if (!dissolveCount) {
@@ -378,7 +378,7 @@ void AGOSEngine::fullFade() {
 }
 
 void AGOSEngine::vc56_fullScreen() {
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	byte *dst = (byte *)screen->getPixels();
 	byte *src = _curVgaFile2 + 800;
 
@@ -387,7 +387,7 @@ void AGOSEngine::vc56_fullScreen() {
 		src += 320;
 		dst += screen->pitch;
 	}
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	fullFade();
 }
diff --git a/engines/agos/vga_pn.cpp b/engines/agos/vga_pn.cpp
index 306c41c71c..629fd2882b 100644
--- a/engines/agos/vga_pn.cpp
+++ b/engines/agos/vga_pn.cpp
@@ -154,7 +154,7 @@ void AGOSEngine::vc48_specialEffect() {
 
 	if (getPlatform() == Common::kPlatformDOS) {
 		if (num == 1) {
-			Graphics::Surface *screen = _system->lockScreen();
+			Graphics::Surface *screen = getBackendSurface();
 			byte *dst = (byte *)screen->getPixels();
 
 			for (uint h = 0; h < _screenHeight; h++) {
@@ -164,7 +164,7 @@ void AGOSEngine::vc48_specialEffect() {
 				}
 				dst += screen->pitch;
 			}
-			_system->unlockScreen();
+			updateBackendSurface();
 		} else if (num == 2) {
 			const char *str = "There are gurgling noises from the sink.";
 			for (; *str; str++)
@@ -204,13 +204,13 @@ void AGOSEngine_PN::clearVideoWindow(uint16 num, uint16 color) {
 	uint16 xoffs = vlut[0] * 16;
 	uint16 yoffs = vlut[1];
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	byte *dst = (byte *)screen->getBasePtr(xoffs, yoffs);
 	for (uint h = 0; h < vlut[3]; h++) {
 		memset(dst, color, vlut[2] * 16);
 		dst += screen->pitch;
 	}
-	 _system->unlockScreen();
+	 updateBackendSurface();
 }
 
 } // End of namespace AGOS
diff --git a/engines/agos/vga_ww.cpp b/engines/agos/vga_ww.cpp
index 5bf8f84551..ceffe77101 100644
--- a/engines/agos/vga_ww.cpp
+++ b/engines/agos/vga_ww.cpp
@@ -142,7 +142,7 @@ void AGOSEngine::vc61() {
 	byte *src, *dst, *dstPtr;
 	uint h, tmp;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	dstPtr = (byte *)screen->getPixels();
 
 	if (a == 6) {
@@ -175,7 +175,7 @@ void AGOSEngine::vc61() {
 		}
 
 		if (a != 6) {
-			_system->unlockScreen();
+			updateBackendSurface();
 			return;
 		}
 
@@ -189,7 +189,7 @@ void AGOSEngine::vc61() {
 		dst += screen->pitch;
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	if (a == 6)
 		fullFade();
diff --git a/engines/agos/window.cpp b/engines/agos/window.cpp
index de0b768d02..8be23ef249 100644
--- a/engines/agos/window.cpp
+++ b/engines/agos/window.cpp
@@ -65,11 +65,13 @@ WindowBlock *AGOSEngine::openWindow(uint x, uint y, uint w, uint h, uint flags,
 	window->textRow = 0;
 	window->scrollY = 0;
 
-	// Characters are 6 pixels
+	// Characters are 6 pixels (except Japanese: when downscaled, 1-byte characters are 4 pixels, 2-byte characters are 8 pixels)
 	if (getGameType() == GType_ELVIRA2)
 		window->textMaxLength = (window->width * 8 - 4) / 6;
 	else if (getGameType() == GType_PN)
 		window->textMaxLength = window->width * 8 / 6 + 1;
+	else if (getGameType() == GType_ELVIRA1 && getPlatform() == Common::kPlatformPC98)
+		window->textMaxLength = window->width << 1;
 	else
 		window->textMaxLength = window->width * 8 / 6;
 
@@ -107,6 +109,7 @@ void AGOSEngine::closeWindow(uint a) {
 }
 
 void AGOSEngine::clearWindow(WindowBlock *window) {
+	clearHiResTextLayer();
 	if (window->flags & 0x10)
 		restoreWindow(window);
 	else
@@ -169,7 +172,7 @@ void AGOSEngine::colorWindow(WindowBlock *window) {
 void AGOSEngine::colorBlock(WindowBlock *window, uint16 x, uint16 y, uint16 w, uint16 h) {
 	_videoLockOut |= 0x8000;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	byte *dst = (byte *)screen->getBasePtr(x, y);
 
 	uint8 color = window->fillColor;
@@ -181,7 +184,7 @@ void AGOSEngine::colorBlock(WindowBlock *window, uint16 x, uint16 y, uint16 w, u
 		dst += screen->pitch;
 	} while (--h);
 
-	_system->unlockScreen();
+	updateBackendSurface();
 
 	_videoLockOut &= ~0x8000;
 }
@@ -231,7 +234,7 @@ void AGOSEngine::restoreBlock(uint16 x, uint16 y, uint16 w, uint16 h) {
 	byte *dst, *src;
 	uint i;
 
-	Graphics::Surface *screen = _system->lockScreen();
+	Graphics::Surface *screen = getBackendSurface();
 	dst = (byte *)screen->getPixels();
 	src = getBackGround();
 
@@ -250,7 +253,7 @@ void AGOSEngine::restoreBlock(uint16 x, uint16 y, uint16 w, uint16 h) {
 		src += _backGroundBuf->pitch;
 	}
 
-	_system->unlockScreen();
+	updateBackendSurface();
 }
 
 void AGOSEngine::setTextColor(uint color) {


Commit: 8d17584247e9c0820ac84e3bf290722b697a3aac
    https://github.com/scummvm/scummvm/commit/8d17584247e9c0820ac84e3bf290722b697a3aac
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:47:58+01:00

Commit Message:
AGOS: (ELVIRA/PC98/Jp) - more dirty rect handling

Make more use of dirty rect handling (applies to the PC-98 version only, the other versions don't need that).

Changed paths:
    engines/agos/charset-fontdata.cpp
    engines/agos/event.cpp
    engines/agos/gfx.cpp
    engines/agos/icons.cpp
    engines/agos/menus.cpp
    engines/agos/verb.cpp
    engines/agos/window.cpp


diff --git a/engines/agos/charset-fontdata.cpp b/engines/agos/charset-fontdata.cpp
index a10172647c..664b6bb964 100644
--- a/engines/agos/charset-fontdata.cpp
+++ b/engines/agos/charset-fontdata.cpp
@@ -3020,7 +3020,8 @@ void AGOSEngine::windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) {
 		dst += dstPitch;
 	} while (--h);
 
-	updateBackendSurface();
+	Common::Rect dirtyRect(x, y, x + 6, y + 8);
+	updateBackendSurface(&dirtyRect);
 
 	_videoLockOut &= ~0x8000;
 }
diff --git a/engines/agos/event.cpp b/engines/agos/event.cpp
index 2708d0a681..4a8f52a44d 100644
--- a/engines/agos/event.cpp
+++ b/engines/agos/event.cpp
@@ -374,7 +374,8 @@ void AGOSEngine::drawStuff(const byte *src, uint xoffs) {
 		dst += screen->pitch;
 	}
 
-	updateBackendSurface();
+	Common::Rect dirtyRect(xoffs, y, xoffs + 4, y + 6);
+	updateBackendSurface(&dirtyRect);
 }
 
 void AGOSEngine::playerDamageEvent(VgaTimerEntry * vte, uint dx) {
diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp
index 07342277a5..a7e8b0c15e 100644
--- a/engines/agos/gfx.cpp
+++ b/engines/agos/gfx.cpp
@@ -738,7 +738,7 @@ void AGOSEngine_Simon1::drawImage(VC10_state *state) {
 		drawVertImage(state);
 	}
 
-	 updateBackendSurface();
+	updateBackendSurface();
 }
 
 void AGOSEngine::drawBackGroundImage(VC10_state *state) {
@@ -859,7 +859,7 @@ void AGOSEngine::drawImage(VC10_state *state) {
 
 	Graphics::Surface *screen = getBackendSurface();
 
-	uint16 xoffs = 0, yoffs = 0;
+	uint16 xoffs = 0, yoffs = 0, xmax = 0, ymax = 0;
 	if (getGameType() == GType_WW) {
 		if (_windowNum == 4 || (_windowNum >= 10 && _windowNum <= 27)) {
 			state->surf_addr = (byte *)_window4BackScn->getPixels();
@@ -868,8 +868,8 @@ void AGOSEngine::drawImage(VC10_state *state) {
 			xoffs = ((vlut[0] - _videoWindows[16]) * 2 + state->x) * 8;
 			yoffs = (vlut[1] - _videoWindows[17] + state->y);
 
-			uint xmax = (xoffs + state->draw_width * 2);
-			uint ymax = (yoffs + state->draw_height);
+			xmax = (xoffs + state->draw_width * 2);
+			ymax = (yoffs + state->draw_height);
 			setMoveRect(xoffs, yoffs, xmax, ymax);
 
 			_window4Flag = 1;
@@ -879,6 +879,8 @@ void AGOSEngine::drawImage(VC10_state *state) {
 
 			xoffs = (vlut[0] * 2 + state->x) * 8;
 			yoffs = vlut[1] + state->y;
+			xmax = (xoffs + state->draw_width * 2);
+			ymax = (yoffs + state->draw_height);
 		}
 	} else if (getGameType() == GType_ELVIRA2) {
 		if (_windowNum == 4 || _windowNum >= 10) {
@@ -888,8 +890,8 @@ void AGOSEngine::drawImage(VC10_state *state) {
 			xoffs = ((vlut[0] - _videoWindows[16]) * 2 + state->x) * 8;
 			yoffs = (vlut[1] - _videoWindows[17] + state->y);
 
-			uint xmax = (xoffs + state->draw_width * 2);
-			uint ymax = (yoffs + state->draw_height);
+			xmax = (xoffs + state->draw_width * 2);
+			ymax = (yoffs + state->draw_height);
 			setMoveRect(xoffs, yoffs, xmax, ymax);
 
 			_window4Flag = 1;
@@ -899,6 +901,8 @@ void AGOSEngine::drawImage(VC10_state *state) {
 
 			xoffs = (vlut[0] * 2 + state->x) * 8;
 			yoffs = vlut[1] + state->y;
+			xmax = (xoffs + state->draw_width * 2);
+			ymax = (yoffs + state->draw_height);
 		}
 	} else if (getGameType() == GType_ELVIRA1) {
 		if (_windowNum == 6) {
@@ -907,12 +911,16 @@ void AGOSEngine::drawImage(VC10_state *state) {
 
 			xoffs = state->x * 8;
 			yoffs = state->y;
+			xmax = xoffs + vlut[2] * 16;
+			ymax = yoffs + vlut[3];
 		} else if (_windowNum == 2 || _windowNum == 3) {
 			state->surf_addr = (byte *)screen->getPixels();
 			state->surf_pitch = screen->pitch;
 
 			xoffs = (vlut[0] * 2 + state->x) * 8;
 			yoffs = vlut[1] + state->y;
+			xmax = xoffs + state->draw_width * 2;
+			ymax = yoffs + state->draw_height;
 		} else {
 			state->surf_addr = (byte *)_window4BackScn->getPixels();
 			state->surf_pitch = _videoWindows[18] * 16;
@@ -920,8 +928,8 @@ void AGOSEngine::drawImage(VC10_state *state) {
 			xoffs = ((vlut[0] - _videoWindows[16]) * 2 + state->x) * 8;
 			yoffs = (vlut[1] - _videoWindows[17] + state->y);
 
-			uint xmax = (xoffs + state->draw_width * 2);
-			uint ymax = (yoffs + state->draw_height);
+			xmax = (xoffs + state->draw_width * 2);
+			ymax = (yoffs + state->draw_height);
 			setMoveRect(xoffs, yoffs, xmax, ymax);
 
 			_window4Flag = 1;
@@ -951,7 +959,9 @@ void AGOSEngine::drawImage(VC10_state *state) {
 		drawVertImage(state);
 	}
 
-	 updateBackendSurface();
+
+	Common::Rect dirtyRect(xoffs, yoffs, xmax, ymax);
+	updateBackendSurface(&dirtyRect);
 }
 
 void AGOSEngine::horizontalScroll(VC10_state *state) {
@@ -1527,9 +1537,15 @@ void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCas
 					dst[w] += 0x10;
 				dst += screen->pitch;
 			}
+
+			if (getPlatform() == Common::kPlatformPC98) {
+				Common::Rect dirtyRect(0, 133, _screenWidth, _screenHeight);
+				updateBackendSurface(&dirtyRect);
+			}
 		}
 
-		updateBackendSurface();
+		Common::Rect dirtyRect(xoffs, yoffs, xoffs + width, yoffs + _boxStarHeight);
+		updateBackendSurface(&dirtyRect);
 	}
 
 	_videoLockOut &= ~0x20;
diff --git a/engines/agos/icons.cpp b/engines/agos/icons.cpp
index 3bbec55f3e..3aa25d4249 100644
--- a/engines/agos/icons.cpp
+++ b/engines/agos/icons.cpp
@@ -315,8 +315,9 @@ void AGOSEngine_Elvira1::drawIcon(WindowBlock *window, uint icon, uint x, uint y
 	Graphics::Surface *screen = getBackendSurface();
 	dst = (byte *)screen->getPixels();
 
-	dst += (x + window->x) * 8;
-	dst += (y * 8 + window->y) * screen->pitch;
+	x = (x + window->x) * 8;
+	y = (y * 8 + window->y);
+	dst += y * screen->pitch + x;
 
 	if (getFeatures() & GF_PLANAR) {
 		src = _iconFilePtr;
@@ -328,7 +329,8 @@ void AGOSEngine_Elvira1::drawIcon(WindowBlock *window, uint icon, uint x, uint y
 		decompressIconPlanar(dst, src, 24, 12, 16, screen->pitch, false);
 	}
 
-	updateBackendSurface();
+	Common::Rect dirtyRect(x, y, x + 24, y + 12);
+	updateBackendSurface(&dirtyRect);
 
 	_videoLockOut &= ~0x8000;
 }
@@ -964,7 +966,8 @@ void AGOSEngine::drawArrow(uint16 x, uint16 y, int8 dir) {
 		dst+= screen->pitch;
 	}
 
-	updateBackendSurface();
+	Common::Rect dirtyRect(x * 8, y, x * 8 + 16, y + 19);
+	updateBackendSurface(&dirtyRect);
 }
 
 void AGOSEngine_Simon1::removeArrows(WindowBlock *window, uint num) {
diff --git a/engines/agos/menus.cpp b/engines/agos/menus.cpp
index 7de21be146..7617c11681 100644
--- a/engines/agos/menus.cpp
+++ b/engines/agos/menus.cpp
@@ -179,7 +179,8 @@ void AGOSEngine::unlightMenuStrip() {
 	for (i = 120; i != 130; i++)
 		disableBox(i);
 
-	updateBackendSurface();
+	Common::Rect dirtyRect(272, 8, 320, 90);
+	updateBackendSurface(&dirtyRect);
 
 	mouseOn();
 }
@@ -204,7 +205,8 @@ void AGOSEngine::lightMenuBox(uint hitarea) {
 		src += screen->pitch;
 	} while (--h);
 
-	updateBackendSurface();
+	Common::Rect dirtyRect(ha->x, ha->y, ha->x + w, ha->y + ha->height);
+	updateBackendSurface(&dirtyRect);
 
 	mouseOn();
 }
diff --git a/engines/agos/verb.cpp b/engines/agos/verb.cpp
index 7f1ff7592c..75c40d9899 100644
--- a/engines/agos/verb.cpp
+++ b/engines/agos/verb.cpp
@@ -1041,7 +1041,8 @@ void AGOSEngine::invertBox(HitArea *ha, byte a, byte b, byte c, byte d) {
 		src += screen->pitch;
 	} while (--h);
 
-	updateBackendSurface();
+	Common::Rect dirtyRect(ha->x, ha->y, ha->x + w, ha->y + ha->height);
+	updateBackendSurface(&dirtyRect);
 
 	_videoLockOut &= ~0x8000;
 }
diff --git a/engines/agos/window.cpp b/engines/agos/window.cpp
index 8be23ef249..70775e9942 100644
--- a/engines/agos/window.cpp
+++ b/engines/agos/window.cpp
@@ -178,13 +178,15 @@ void AGOSEngine::colorBlock(WindowBlock *window, uint16 x, uint16 y, uint16 w, u
 	uint8 color = window->fillColor;
 	if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW)
 		color += dst[0] & 0xF0;
+	uint16 h2 = h;
 
 	do {
 		memset(dst, color, w);
 		dst += screen->pitch;
 	} while (--h);
 
-	updateBackendSurface();
+	Common::Rect dirtyRect(x, y, x + w, y + h2);
+	updateBackendSurface(&dirtyRect);
 
 	_videoLockOut &= ~0x8000;
 }
@@ -242,6 +244,7 @@ void AGOSEngine::restoreBlock(uint16 x, uint16 y, uint16 w, uint16 h) {
 	src += y * _backGroundBuf->pitch;
 
 	uint8 paletteMod = 0;
+	uint16 h2 = 2;
 	if (getGameType() == GType_ELVIRA1 && !(getFeatures() & GF_DEMO) && y >= 133)
 		paletteMod = 16;
 
@@ -253,7 +256,8 @@ void AGOSEngine::restoreBlock(uint16 x, uint16 y, uint16 w, uint16 h) {
 		src += _backGroundBuf->pitch;
 	}
 
-	updateBackendSurface();
+	Common::Rect dirtyRect(x, y, x + w, y + h2);
+	updateBackendSurface(&dirtyRect);
 }
 
 void AGOSEngine::setTextColor(uint color) {


Commit: 512ae9cfe3c7ffd11dfa8619da6d93006f400e8a
    https://github.com/scummvm/scummvm/commit/512ae9cfe3c7ffd11dfa8619da6d93006f400e8a
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:47:58+01:00

Commit Message:
AGOS: (ELVIRA/PC98/Jp) - add static msg strings + some fixes

(this concerns mostly the save/load/pause texts)

Changed paths:
    engines/agos/charset-fontdata.cpp
    engines/agos/charset.cpp
    engines/agos/saveload.cpp
    engines/agos/script_e1.cpp
    engines/agos/window.cpp


diff --git a/engines/agos/charset-fontdata.cpp b/engines/agos/charset-fontdata.cpp
index 664b6bb964..74988fd1fd 100644
--- a/engines/agos/charset-fontdata.cpp
+++ b/engines/agos/charset-fontdata.cpp
@@ -3020,7 +3020,7 @@ void AGOSEngine::windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) {
 		dst += dstPitch;
 	} while (--h);
 
-	Common::Rect dirtyRect(x, y, x + 6, y + 8);
+	Common::Rect dirtyRect(x + window->textColumnOffset, y, x + window->textColumnOffset + 6, y + 8);
 	updateBackendSurface(&dirtyRect);
 
 	_videoLockOut &= ~0x8000;
diff --git a/engines/agos/charset.cpp b/engines/agos/charset.cpp
index c3e8e49d63..94efc8dd75 100644
--- a/engines/agos/charset.cpp
+++ b/engines/agos/charset.cpp
@@ -148,7 +148,7 @@ void AGOSEngine::justifyOutPut(byte chr) {
 		_printCharPixelCount = 0;
 		doOutput(&chr, 1);
 		clsCheck(_textWindow);
-	} else if (getLanguage() == Common::JA_JPN) {
+	} else if (getLanguage() == Common::JA_JPN && !_forceAscii) {
 		// Japanese has no word wrapping
 		_lettersToPrintBuf[0] = chr;
 		_lettersToPrintBuf[1] = '\0';
@@ -262,7 +262,7 @@ void AGOSEngine::windowPutChar(WindowBlock *window, byte c, byte b) {
 			return;
 		}
 
-		if (_language == Common::JA_JPN)
+		if (_language == Common::JA_JPN && !_forceAscii)
 			textColumnWidth = width = 4;
 		else if (c - 32 > 98) // Ignore invalid characters
 			return;
diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp
index 8f3320b48b..9faa473cfe 100644
--- a/engines/agos/saveload.cpp
+++ b/engines/agos/saveload.cpp
@@ -238,6 +238,11 @@ bool AGOSEngine::confirmOverWrite(WindowBlock *window) {
 		message2 = "   Ueberschreiben ?\r\r";
 		message3 = "     Ja        Nein";
 		break;
+	case Common::JA_JPN:
+		message1 = "\r   ""\x82\xbb\x82\xcc\x83""t""\x83""@""\x83""C""\x83\x8b\x82\xcd\x82\xb7\x82\xc5\x82\xc9\x91\xb6\x8d\xdd\x82\xb5\x82\xdc\x82\xb7""\r\r";			
+		message2 = "     ""\x8f\xe3\x8f\x91\x82\xab\x82\xb5\x82\xc4\x82\xe6\x82\xeb\x82\xb5\x82\xa2\x82\xc5\x82\xb7\x82\xa9\x81""H\r\r";
+		message3 = "       ""\x82\xcd\x82\xa2""           ""\x82\xa2\x82\xa2\x82\xa6";
+		break;
 	default:
 		message1 = "\r File already exists.\r\r";
 		message2 = "    Overwrite it ?\r\r";
@@ -310,17 +315,22 @@ restart:
 	case Common::DE_DEU:
 		message1 = "\rLege Spielstandsdiskette ein. Dateinamen eingeben:\r\r   ";
 		break;
+	case Common::JA_JPN:
+		message1 = "\r  ""\x83""t""\x83""@""\x83""C""\x83\x8b\x96\xbc\x82\xf0\x93\xfc\x97\xcd\x82\xb5\x82\xc4\x82\xad\x82\xbe\x82\xb3\x82\xa2\x81""F\r\r\r   ";
+		break;
 	default:
 		message1 = "\r Insert savegame data disk & enter filename:\r\r   ";
 		break;
 	}
 
+	clearHiResTextLayer();
 	for (; *message1; message1++)
 		windowPutChar(window, *message1);
 
 	memset(_saveBuf, 0, 10);
 	name = _saveBuf;
 	_saveGameNameLen = 0;
+	_forceAscii = true;
 
 	while (!shouldQuit()) {
 		windowPutChar(window, 128);
@@ -350,6 +360,8 @@ restart:
 		}
 	}
 
+	_forceAscii = false;
+
 	if (_saveGameNameLen != 0) {
 		int16 slot = matchSaveGame(name, numSaveGames);
 		if (!load) {
@@ -373,6 +385,7 @@ restart:
 		printStats();
 	}
 
+	clearHiResTextLayer();
 	restartAnimation();
 	_gameStoppedClock = getTime() - saveTime + _gameStoppedClock;
 }
@@ -942,6 +955,10 @@ void AGOSEngine::fileError(WindowBlock *window, bool saveError) {
 			message1 = "\r  Sicherung erfolglos.";
 			message2 = "\rVersuche eine andere     Diskette.";
 			break;
+		case Common::JA_JPN:
+			message1 = "\r       ""\x83""Z""\x81""[""\x83""u""\x82\xc9\x8e\xb8\x94""s""\x82\xb5\x82\xdc\x82\xb5\x82\xbd";
+			message2 = "\r   ""\x95\xca\x82\xcc\x83""f""\x83""B""\x83""X""\x83""N""\x82\xf0\x8e""g""\x97""p""\x82\xb5\x82\xc4\x82\xad\x82\xbe\x82\xb3\x82\xa2";
+			break;
 		default:
 			message1 = "\r       Save failed.";
 			message2 = "\r       Disk error.";
@@ -978,6 +995,10 @@ void AGOSEngine::fileError(WindowBlock *window, bool saveError) {
 			message1 = "\r    Laden erfolglos.";
 			message2 = "\r  Datei nicht gefunden.";
 			break;
+		case Common::JA_JPN:
+			message1 = "\r       ""\x83\x8d\x81""[""\x83""h""\x82\xc9\x8e\xb8\x94""s""\x82\xb5\x82\xdc\x82\xb5\x82\xbd";
+			message2 = "\r     ""\x83""t""\x83""@""\x83""C""\x83\x8b\x82\xaa\x8c\xa9\x82\xc2\x82\xa9\x82\xe8\x82\xdc\x82\xb9\x82\xf1";
+			break;
 		default:
 			message1 = "\r       Load failed.";
 			message2 = "\r     File not found.";
diff --git a/engines/agos/script_e1.cpp b/engines/agos/script_e1.cpp
index 85f92bcf7a..d86bfb1d3d 100644
--- a/engines/agos/script_e1.cpp
+++ b/engines/agos/script_e1.cpp
@@ -927,6 +927,10 @@ restart:
 		message1 = "   Juego en Pausa\r\r\r";
 		message2 = "Continuar      Salir";
 		break;
+	case Common::JA_JPN:
+		message1 = "             ""\x83""Q""\x81""[""\x83\x80\x92\x86\x92""f\r\r\r";
+		message2 = "        ""\x91\xb1\x82\xaf\x82\xe9""         ""\x82\xe2\x82\xdf\x82\xe9";
+		break;
 	default:
 		message1 = "     Game Paused\r\r\r";
 		message2 = " Continue      Quit";
@@ -958,6 +962,10 @@ restart:
 			message1 = "    Estas seguro ?\r\r\r";
 			message2 = "    Si          No";
 			break;
+		case Common::JA_JPN:
+			message1 = "           ""\x82\xe6\x82\xeb\x82\xb5\x82\xa2\x82\xc5\x82\xb7\x82\xa9\x81""H\r\r\r";
+			message2 = "         ""\x82\xcd\x82\xa2""          ""\x82\xa2\x82\xa2\x82\xa6";
+			break;
 		default:
 			message1 = "    Are you sure ?\r\r\r";
 			message2 = "     Yes       No";
@@ -978,6 +986,7 @@ restart:
 		}
 	}
 
+	clearHiResTextLayer();
 	restartAnimation();
 	_gameStoppedClock = getTime() - pauseTime + _gameStoppedClock;
 }
@@ -1154,6 +1163,7 @@ void AGOSEngine::printScroll() {
 
 	_windowNum = 3;
 	_curVgaFile2 = vpe->vgaFile2;
+	clearHiResTextLayer();
 	drawImage_init(9, 0, 10, 32, 0);
 
 	_curVgaFile2 = curVgaFile2Orig;
@@ -1166,7 +1176,6 @@ void AGOSEngine::printStats() {
 	window->flags = 1;
 
 	mouseOff();
-	_forceAscii = true;
 
 	// Strength
 	val = _variableArray[0];
@@ -1216,7 +1225,6 @@ void AGOSEngine::printStats() {
 		val = 99;
 	writeChar(window, 36, 133, 4, val);
 
-	_forceAscii = false;
 	mouseOn();
 }
 
diff --git a/engines/agos/window.cpp b/engines/agos/window.cpp
index 70775e9942..73d1523f20 100644
--- a/engines/agos/window.cpp
+++ b/engines/agos/window.cpp
@@ -293,9 +293,11 @@ void AGOSEngine::waitWindow(WindowBlock *window) {
 	window->textRow = window->height - 1;
 	window->textLength = 0;
 
+	_forceAscii = true;
 	message = "[ OK ]";
 	for (; *message; message++)
 		windowPutChar(window, *message);
+	_forceAscii = false;
 
 	ha = findEmptyHitArea();
 	ha->x = (window->width / 2 + window->x - 3) * 8;
@@ -328,6 +330,7 @@ void AGOSEngine::waitWindow(WindowBlock *window) {
 
 void AGOSEngine::writeChar(WindowBlock *window, int x, int y, int offs, int val) {
 	int chr;
+	_forceAscii = true;
 
 	// Clear background of first digit
 	window->textColumnOffset = offs;
@@ -358,6 +361,8 @@ void AGOSEngine::writeChar(WindowBlock *window, int x, int y, int offs, int val)
 		window->textColor = 15;
 		windowDrawChar(window, x * 8, y, chr);
 	}
+
+	_forceAscii = false;
 }
 
 } // End of namespace AGOS


Commit: 56d897a100aeded316e16dc93aee66548806c8cd
    https://github.com/scummvm/scummvm/commit/56d897a100aeded316e16dc93aee66548806c8cd
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:47:59+01:00

Commit Message:
AGOS: (ELVIRA/PC98/Jp) - more fixes

Just walking around the castle fixing bugs as I find them, e. g.:
- make decompression routine safer (this could write a couple of invalid byte in at least one case)
- also support compressed data header without actual data (required in the kitchen scene when killing the evil cook)
- fix some graphics/palette related glitches

Changed paths:
    engines/agos/agos.cpp
    engines/agos/agos.h
    engines/agos/charset-fontdata.cpp
    engines/agos/cursor.cpp
    engines/agos/draw.cpp
    engines/agos/gfx.cpp
    engines/agos/icons.cpp
    engines/agos/res.cpp
    engines/agos/saveload.cpp
    engines/agos/window.cpp


diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index eb8a5f6cd0..794db26fb3 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -515,6 +515,7 @@ AGOSEngine::AGOSEngine(OSystem *system, const AGOSGameDescription *gd)
 
 	_planarBuf = 0;
 	_pak98Buf = 0;
+	_paletteModNext = 16;
 
 	_midiEnabled = false;
 
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index 553131127e..e70b65762e 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -555,6 +555,7 @@ protected:
 	byte _videoBuf1[32000];
 	uint16 _videoWindows[128];
 	const byte *_pak98Buf;
+	byte _paletteModNext;
 
 	uint8 _window3Flag;
 	uint8 _window4Flag;
diff --git a/engines/agos/charset-fontdata.cpp b/engines/agos/charset-fontdata.cpp
index 74988fd1fd..5b30a76d4f 100644
--- a/engines/agos/charset-fontdata.cpp
+++ b/engines/agos/charset-fontdata.cpp
@@ -3037,7 +3037,7 @@ void AGOSEngine_Elvira1::windowDrawChar(WindowBlock *window, uint x, uint y, byt
 	} else {
 		_sjisCurChar = chr;
 		if ((chr >= 0x80 && chr < 0xA0) || chr >= 0xE0)
-			return;		
+			return;
 	}
 
 	_videoLockOut |= 0x8000;
diff --git a/engines/agos/cursor.cpp b/engines/agos/cursor.cpp
index 6a16b764b4..5e05f14075 100644
--- a/engines/agos/cursor.cpp
+++ b/engines/agos/cursor.cpp
@@ -879,8 +879,8 @@ void AGOSEngine::drawMousePointer() {
 			uint8 ptch = 16;
 			uint16 *dst1 = &((uint16*)_mouseData)[16 * 16 * 2 - 1];
 			uint16 *dst2 = dst1 - ptch;
-			for (const byte *src = &_mouseData[16 * 16 - 1]; src >= _mouseData; --src) {
-				*dst1-- = *dst2-- = (*src << 8) | *src;
+			for (const byte *pos = &_mouseData[16 * 16 - 1]; pos >= _mouseData; --pos) {
+				*dst1-- = *dst2-- = (*pos << 8) | *pos;
 				if (!(ptch = (ptch - 1) % 16)) {
 					dst1 -= 16;
 					dst2 -= 16;
diff --git a/engines/agos/draw.cpp b/engines/agos/draw.cpp
index a627bb54d8..53d5b9c0af 100644
--- a/engines/agos/draw.cpp
+++ b/engines/agos/draw.cpp
@@ -501,7 +501,7 @@ void AGOSEngine::saveBackGround(VgaSprite *vsp) {
 	animTable->x = x;
 	animTable->y = y;
 
-	animTable->width = READ_BE_UINT16(ptr + 6) / 16;
+	animTable->width = (getPlatform() == Common::kPlatformPC98 ? READ_LE_UINT16(ptr + 6) : READ_BE_UINT16(ptr + 6)) / 16;
 	if (vsp->flags & 0x40) {
 		animTable->width++;
 	}
diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp
index a7e8b0c15e..5cdbe718f6 100644
--- a/engines/agos/gfx.cpp
+++ b/engines/agos/gfx.cpp
@@ -944,8 +944,9 @@ void AGOSEngine::drawImage(VC10_state *state) {
 
 	state->surf_addr += xoffs + yoffs * state->surf_pitch;
 
-	if (getGameType() == GType_ELVIRA1 && getPlatform() != Common::kPlatformPC98 && (state->flags & kDFNonTrans) && yoffs > 133)
-		state->paletteMod = 16;
+	if (getGameType() == GType_ELVIRA1 && (state->flags & kDFNonTrans) && yoffs > 133)
+		state->paletteMod = _paletteModNext;
+	_paletteModNext = 16;
 
 	if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW)
 		state->palette = state->surf_addr[0] & 0xF0;
@@ -959,7 +960,6 @@ void AGOSEngine::drawImage(VC10_state *state) {
 		drawVertImage(state);
 	}
 
-
 	Common::Rect dirtyRect(xoffs, yoffs, xmax, ymax);
 	updateBackendSurface(&dirtyRect);
 }
diff --git a/engines/agos/icons.cpp b/engines/agos/icons.cpp
index 3aa25d4249..7664a4182b 100644
--- a/engines/agos/icons.cpp
+++ b/engines/agos/icons.cpp
@@ -329,7 +329,7 @@ void AGOSEngine_Elvira1::drawIcon(WindowBlock *window, uint icon, uint x, uint y
 		decompressIconPlanar(dst, src, 24, 12, 16, screen->pitch, false);
 	}
 
-	Common::Rect dirtyRect(x, y, x + 24, y + 12);
+	Common::Rect dirtyRect(x, y, x + 24, y + 24);
 	updateBackendSurface(&dirtyRect);
 
 	_videoLockOut &= ~0x8000;
diff --git a/engines/agos/res.cpp b/engines/agos/res.cpp
index 4b2373e2e4..048cb963e3 100644
--- a/engines/agos/res.cpp
+++ b/engines/agos/res.cpp
@@ -806,51 +806,61 @@ void AGOSEngine::loadVGABeardFile(uint16 id) {
 	}
 }
 
-void decodePak98(uint8 *dst, const uint8 *src, uint32 inSize) {
-	const uint8 *src2 = 0;
-	uint8 cmd = 0x80;
-
-	for (uint32 bytesLeft = inSize; bytesLeft; ) {
-		if (cmd == 0x80) {
-			src2 = src + 1;
-			--bytesLeft;
-		}
+uint8 safeReadByte(const uint8 *&src, const uint8 *end) {
+	if (src < end)
+		return (*src++);
+	error("decodePak98(): invalid data");
+	return 0;
+}
+
+uint16 safeReadWord(const uint8 *&src, const uint8 *end) {
+	uint8 lo = safeReadByte(src, end);
+	return (safeReadByte(src, end) << 8) | lo;
+}
+
+#define S_NEXTBYTE safeReadByte(src, endSrc)
+#define S_NEXTWORD safeReadWord(src, endSrc)
 
-		if (cmd & *src) {
-			*dst++ = *src2++;
-			--bytesLeft;
+void decodePak98(uint8 *dst, uint32 outSize, const uint8 *src, uint32 inSize) {
+	const uint8 *end = dst + outSize;
+	const uint8 *endSrc = src + inSize;
+	uint8 state = 0x80;
+	uint8 flg = S_NEXTBYTE;
+
+	for (uint32 srcBytesLeft = inSize - 1; srcBytesLeft; ) {
+		if (state & flg) {
+			if (dst < end)
+				*dst++ = S_NEXTBYTE;
+			--srcBytesLeft;
 		} else {
-			bytesLeft -= 2;
-			uint16 cmd2 = READ_LE_UINT16(src2);
-			src2 += 2;
+			srcBytesLeft -= 2;
+			uint16 cmd2 = S_NEXTWORD;
 			uint8 cmd3 = cmd2 & 0x0F;
 			cmd2 >>= 4;
 
 			if (cmd2 == 0) {
 				uint16 count = cmd3 + 4;
-				--bytesLeft;
+				--srcBytesLeft;
 				if (cmd3 == 0x0F) {
-					count = READ_LE_UINT16(src2);
-					src2 += 2;
-					bytesLeft -= 2;
+					count = S_NEXTWORD;
+					srcBytesLeft -= 2;
 				} else if (cmd3 == 0x0E) {
-					count = 18 + (*src2++);
-					--bytesLeft;
+					count = 18 + (S_NEXTBYTE);
+					--srcBytesLeft;
 				}
 
-				uint8 destVal = *src2++;
-				while (count--)
+				uint8 destVal = S_NEXTBYTE;
+				while (count-- && dst < end)
 					*dst++ = destVal;
 
 			} else if (cmd2 == 1) {
 				uint16 count = cmd3 + 3;
 				if (cmd3 == 0x0F) {
-					count = READ_LE_UINT16(src2);
-					src2 += 2;
-					bytesLeft -= 2;
+					count = S_NEXTWORD;
+					srcBytesLeft -= 2;
 				} else if (cmd3 == 0x0E) {
-					count = 17 + (*src2++);
-					--bytesLeft;
+					count = 17 + (S_NEXTBYTE);
+					--srcBytesLeft;
 				}
 
 				dst += count;
@@ -858,39 +868,43 @@ void decodePak98(uint8 *dst, const uint8 *src, uint32 inSize) {
 			} else if (cmd2 == 2) {
 				uint16 count = cmd3 + 16;
 				if (cmd3 == 0x0F) {
-					count = READ_LE_UINT16(src2);
-					src2 += 2;
-					bytesLeft -= 2;
+					count = S_NEXTWORD;
+					srcBytesLeft -= 2;
 				} else if (cmd3 == 0x0E) {
-					count = 30 + (*src2++);
-					--bytesLeft;
+					count = 30 + (S_NEXTBYTE);
+					--srcBytesLeft;
 				}
 
-				bytesLeft -= count;
-				while (count--)
-					*dst++ = *src2++;
+				srcBytesLeft -= count;
+				while (count-- && dst < end)
+					*dst++ = S_NEXTBYTE;
 
 			} else {
 				uint16 count = cmd3 + 3;
 				if (cmd3 == 0x0F) {
-					count = 18 + (*src2++);
-					--bytesLeft;
+					count = 18 + (S_NEXTBYTE);
+					--srcBytesLeft;
 				}
 
-				uint8 *src3 = dst - cmd2;
-				while (count--)
-					*dst++ = *src3++;
+				const uint8 *src2 = dst - cmd2;
+				while (count-- && dst < end)
+					*dst++ = *src2++;
 			}
 		}
 
-		cmd >>= 1;
-		if (cmd == 0) {
-			cmd = 0x80;
-			src = src2;
+		if (!(state >>= 1)) {
+			state = 0x80;
+			if (srcBytesLeft) {
+				flg = S_NEXTBYTE;
+				srcBytesLeft--;
+			}
 		}
 	}
 }
 
+#undef S_NEXTBYTE
+#undef S_NEXTWORD
+
 void AGOSEngine::loadVGAVideoFile(uint16 id, uint8 type, bool useError) {
 	Common::File in;
 	char filename[15];
@@ -1003,19 +1017,19 @@ void AGOSEngine::loadVGAVideoFile(uint16 id, uint8 type, bool useError) {
 				dstSize = srcSize;
 				srcSize = in.readUint32LE();
 				uint16 fill = in.readUint16LE();
-
-				uint8 *srcBuffer = new uint8[srcSize];
-				if (in.read(srcBuffer, srcSize) != srcSize)
-					error("loadVGAVideoFile: Read failed");
 				dst = allocBlock(dstSize);
 
-				Common::fill<uint16*, uint16>((uint16*)dst, (uint16*)(dst + (dstSize & ~1)), fill);
+				Common::fill<uint16*, uint16>((uint16*)dst, (uint16*)(dst + (dstSize & ~1)), TO_LE_16(fill));
 				if (dstSize & 1)
 					*(dst + dstSize - 1) = fill & 0xff;
 
-				decodePak98(dst, srcBuffer, srcSize);
-
-				delete[] srcBuffer;
+				if (srcSize) {
+					uint8 *srcBuffer = new uint8[srcSize];
+					if (in.read(srcBuffer, srcSize) != srcSize)
+						error("loadVGAVideoFile: Read failed");
+					decodePak98(dst, dstSize, srcBuffer, srcSize);
+					delete[] srcBuffer;
+				}
 			} else {
 				dstSize = srcSize;
 				dst = allocBlock(dstSize + extraBuffer);
@@ -1055,15 +1069,17 @@ Common::SeekableReadStream *AGOSEngine::createPak98FileStream(const char *filena
 	uint32 inSize = in.readUint32LE();
 	uint16 fill = in.readUint16LE();
 
-	uint8 *tempBuffer = new uint8[inSize];
-	uint8 *decBuffer = new uint8[outSize];
-	Common::fill<uint16*, uint16>((uint16*)decBuffer, (uint16*)(decBuffer + (outSize & ~1)), fill);
+	uint8 *decBuffer = (uint8*)malloc(outSize);
+	Common::fill<uint16*, uint16>((uint16*)decBuffer, (uint16*)(decBuffer + (outSize & ~1)), TO_LE_16(fill));
 	if (outSize & 1)
 		*(decBuffer + outSize - 1) = fill & 0xff;
 
-	in.read(tempBuffer, inSize);
-	decodePak98(decBuffer, tempBuffer, inSize);
-	delete[] tempBuffer;
+	if (inSize) {
+		uint8 *tempBuffer = new uint8[inSize];
+		in.read(tempBuffer, inSize);
+		decodePak98(decBuffer, outSize, tempBuffer, inSize);
+		delete[] tempBuffer;
+	}
 
 	return new Common::MemoryReadStream(decBuffer, outSize, DisposeAfterUse::YES);
 }
@@ -1072,25 +1088,27 @@ void AGOSEngine::convertPC98Image(VC10_state &state) {
 	if (state.flags & (kDFCompressedFlip | kDFCompressed)) {
 		const byte *src = state.srcPtr;
 		uint32 outSize = READ_LE_UINT32(src + 2);
-		assert(outSize == (state.width << 3) * state.height);
+		assert(outSize >= (uint32)((state.width << 3) * state.height));
 		uint32 inSize = READ_LE_UINT32(src + 6);
 		uint16 fill = READ_LE_UINT16(src + 10);
 		delete[] _pak98Buf;
 		byte *decBuffer = new uint8[outSize];
-		Common::fill<uint16*, uint16>((uint16*)decBuffer, (uint16*)(decBuffer + (outSize & ~1)), fill);
+		Common::fill<uint16*, uint16>((uint16*)decBuffer, (uint16*)(decBuffer + (outSize & ~1)), TO_LE_16(fill));
 		if (outSize & 1)
 			*(decBuffer + outSize - 1) = fill & 0xff;
-
-		decodePak98(decBuffer, src + 12, inSize);
+		if (inSize)
+			decodePak98(decBuffer, outSize, src + 12, inSize);
 		_pak98Buf = state.srcPtr = decBuffer;
+		_paletteModNext = 0;
 	}
 
 	// The PC-98 images are in a planar format, but slightly different from the Amiga format. It does
-	// not make much sense to set the GF_PLANAR flag, since the icons do not require the conversion.
-	delete[] _planarBuf;
+	// not make much sense to set the GF_PLANAR flag, since the Amiga code can't be used anyway.
+	free(_planarBuf);
 	uint16 planeLW = state.width << 1;
 	uint16 planePitch = planeLW * 3;
-	_planarBuf = new byte[(state.width << 3) * state.height];
+
+	_planarBuf = (byte*)malloc((state.width << 3) * state.height);
 
 	const byte *src[4];
 	memset(src, 0, sizeof(src));
diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp
index 9faa473cfe..356babdea3 100644
--- a/engines/agos/saveload.cpp
+++ b/engines/agos/saveload.cpp
@@ -239,7 +239,7 @@ bool AGOSEngine::confirmOverWrite(WindowBlock *window) {
 		message3 = "     Ja        Nein";
 		break;
 	case Common::JA_JPN:
-		message1 = "\r   ""\x82\xbb\x82\xcc\x83""t""\x83""@""\x83""C""\x83\x8b\x82\xcd\x82\xb7\x82\xc5\x82\xc9\x91\xb6\x8d\xdd\x82\xb5\x82\xdc\x82\xb7""\r\r";			
+		message1 = "\r   ""\x82\xbb\x82\xcc\x83""t""\x83""@""\x83""C""\x83\x8b\x82\xcd\x82\xb7\x82\xc5\x82\xc9\x91\xb6\x8d\xdd\x82\xb5\x82\xdc\x82\xb7""\r\r";
 		message2 = "     ""\x8f\xe3\x8f\x91\x82\xab\x82\xb5\x82\xc4\x82\xe6\x82\xeb\x82\xb5\x82\xa2\x82\xc5\x82\xb7\x82\xa9\x81""H\r\r";
 		message3 = "       ""\x82\xcd\x82\xa2""           ""\x82\xa2\x82\xa2\x82\xa6";
 		break;
diff --git a/engines/agos/window.cpp b/engines/agos/window.cpp
index 73d1523f20..91a5ef99f8 100644
--- a/engines/agos/window.cpp
+++ b/engines/agos/window.cpp
@@ -244,7 +244,7 @@ void AGOSEngine::restoreBlock(uint16 x, uint16 y, uint16 w, uint16 h) {
 	src += y * _backGroundBuf->pitch;
 
 	uint8 paletteMod = 0;
-	uint16 h2 = 2;
+	Common::Rect dirtyRect(x, y, x + w, h);
 	if (getGameType() == GType_ELVIRA1 && !(getFeatures() & GF_DEMO) && y >= 133)
 		paletteMod = 16;
 
@@ -256,7 +256,6 @@ void AGOSEngine::restoreBlock(uint16 x, uint16 y, uint16 w, uint16 h) {
 		src += _backGroundBuf->pitch;
 	}
 
-	Common::Rect dirtyRect(x, y, x + w, y + h2);
 	updateBackendSurface(&dirtyRect);
 }
 


Commit: a3fd19265fdc745400d47d5f956fb1a26b7f88ea
    https://github.com/scummvm/scummvm/commit/a3fd19265fdc745400d47d5f956fb1a26b7f88ea
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:47:59+01:00

Commit Message:
AGOS: (ELVIRA/PC98/Jp) - add sound driver

Added support for PC9801-26, Roland MT-32 and General Midi. I have left out the PC speaker part for now.

Changed paths:
  A engines/agos/drivers/accolade/pc98.cpp
    engines/agos/drivers/accolade/mididriver.h
    engines/agos/midi.cpp
    engines/agos/midi.h
    engines/agos/module.mk


diff --git a/engines/agos/drivers/accolade/mididriver.h b/engines/agos/drivers/accolade/mididriver.h
index 253fb6b736..8f02b0034c 100644
--- a/engines/agos/drivers/accolade/mididriver.h
+++ b/engines/agos/drivers/accolade/mididriver.h
@@ -38,6 +38,7 @@ extern void MidiDriver_Accolade_readDriver(Common::String filename, MusicType re
 
 extern MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename);
 extern MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename);
+extern MidiDriver *MidiDriverPC98_create(MidiDriver::DeviceHandle dev);
 
 } // End of namespace AGOS
 
diff --git a/engines/agos/drivers/accolade/pc98.cpp b/engines/agos/drivers/accolade/pc98.cpp
new file mode 100644
index 0000000000..d2f3a15d95
--- /dev/null
+++ b/engines/agos/drivers/accolade/pc98.cpp
@@ -0,0 +1,718 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/softsynth/fmtowns_pc98/pc98_audio.h"
+#include "audio/mididrv.h"
+#include "audio/mixer.h"
+#include "engines/engine.h"
+#include "common/func.h"
+
+namespace AGOS {
+
+class PC98CommonDriver : public MidiDriver {
+public:
+	enum PC98DriverProperties {
+		kPropMusicVolume = 0x10,
+		kPropSfxVolume = 0x20,
+		kPropPause = 0x30
+	};
+public:
+	PC98CommonDriver();
+	virtual ~PC98CommonDriver() override {};
+
+	bool isOpen() const override { return _isOpen; }
+	void send(uint32 b) override;
+	void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
+	uint32 property(int prop, uint32 param) override;
+	uint32 getBaseTempo() override { return _baseTempo; }
+	MidiChannel *allocateChannel() override { return 0; }
+	MidiChannel *getPercussionChannel() override { return 0; }
+
+protected:
+	void updateSounds();
+	void updateParser();
+	void reset();
+
+	uint32 _baseTempo;
+	bool _isOpen;
+
+	Audio::Mixer *_mixer;
+
+	const uint8 *_instrumentsRemap;
+	const int8 *_instrumentLevelAdjust;
+	const uint8 *_partsRemap;
+
+	uint8 _chanUse[16];
+	uint8 _ngDelay;
+	bool _allNotes;
+	bool _programLock;
+	bool _noFadeRemap;
+	bool _delayedProgramChange;
+
+private:
+	virtual void noteOn(uint8 part, uint8 note, uint8 velo) = 0;
+	virtual void noteOff(uint8 part, uint8 note) = 0;
+	virtual void programChange(uint8 part, uint8 prog) = 0;
+	virtual void processSounds() = 0;
+	virtual void setVolume(int musicVolume, int sfxVolume) = 0;
+	virtual void pause(bool paused) = 0;
+
+	class TimerCb {
+	public:
+		typedef void(*FuncType)(void*);
+		TimerCb(const FuncType func, void *arg) : _func(func), _arg(arg) {}
+		bool isValid() const { return _func && _arg; }
+		void operator()() const { (*_func)(_arg); }
+	private:
+		const FuncType _func;
+		void *_arg;
+	} *_timerCb;
+
+	uint32 _internalUpdateTimer;
+
+	uint16 _musicVolume;
+	uint16 _sfxVolume;
+	int8 _fadeVolumeAdjust;
+	uint8 _partPrograms[16];
+};
+
+class PC98FMDriver : public PC98CommonDriver, private PC98AudioPluginDriver {
+public:
+	PC98FMDriver();
+	~PC98FMDriver() override;
+
+	int open() override;
+	void close() override;
+
+private:
+	void noteOn(uint8 part, uint8 note, uint8 velo) override;
+	void noteOff(uint8 part, uint8 note) override;
+	void programChange(uint8 part, uint8 prog) override;
+	void processSounds() override;
+	void setVolume(int musicVolume, int sfxVolume) override;
+	void pause(bool paused) override {}
+
+	void loadInstrument(uint8 chan, uint8 prg);
+	void startNote(uint8 chan, uint8 note, uint8 velo);
+	void stopNote(uint8 chan, uint8 note);
+
+	void timerCallbackA() override {}
+	void timerCallbackB() override;
+
+	PC98AudioCore *_pc98a;
+
+	uint8 _chanAssign[3];
+	uint8 _chanNotes[3];
+	uint8 _partProgramsInternal[16];
+	uint8 _partNotes[16];
+
+	static const uint8 _instrumentsRemapFM[128];
+	static const uint8 _instrumentLevelAdjustFM[128];
+	static const uint8 _partsRemapFM[16];
+	static const uint8 _instrumentPatches[16][25];
+	static const uint8 _ngMapping[76];
+	static const uint8 _carrier[8];
+	static const uint16 _frequency[12];
+};
+
+class PC98MidiDriver : public PC98CommonDriver {
+public:
+	PC98MidiDriver(DeviceHandle dev);
+	~PC98MidiDriver() override;
+
+	int open() override;
+	void close() override;
+
+	static void timerCallback(void *obj);
+
+private:
+	void noteOn(uint8 part, uint8 note, uint8 velo) override;
+	void noteOff(uint8 part, uint8 note) override;
+	void programChange(uint8 part, uint8 prog) override;
+	void processSounds() override {}
+	void setVolume(int musicVolume, int sfxVolume) override;
+	void pause(bool paused) override;
+	void sendSysexWithCheckSum(uint8 *data);
+
+	MidiDriver *_drv;
+	DeviceHandle _dev;
+
+	uint8 _volSysex[9];
+	uint8 _partAssignSysexGS[9];
+	uint8 _partAssignSysexMT32[9];
+
+	static const uint8 _instrumentsRemapMT32[128];
+	static const uint8 _instrumentsRemapGM[128];
+	static const uint8 _partsRemapMidi[16];
+	static const uint8 _sysexMsg[3][9];
+};
+
+PC98CommonDriver::PC98CommonDriver() : _mixer(g_engine->_mixer), _baseTempo(0), _fadeVolumeAdjust(0), _allNotes(false), _programLock(false), _isOpen(false), _noFadeRemap(false), _delayedProgramChange(false), _ngDelay(0), _timerCb(0), _musicVolume(0xff), _sfxVolume(0xff), _internalUpdateTimer(0) {
+	memset(_partPrograms, 0, sizeof(_partPrograms));
+	memset(_chanUse, 0, sizeof(_chanUse));
+}
+
+void PC98CommonDriver::send(uint32 b) {
+	if (!_isOpen)
+		return;
+
+	byte para2 = (b >> 16) & 0xFF;
+	byte para1 = (b >> 8) & 0xFF;
+	byte ch = b & 0x0F;
+
+	switch (b & 0xF0) {
+	case 0x80:
+		noteOff(ch, para1);
+		break;
+	case 0x90:
+		if (para2) {
+			int16 velo = para2;
+			if (ch != 9)
+				velo = CLIP<int16>(velo + _instrumentLevelAdjust[_partPrograms[ch]], 0, 127);
+			velo = CLIP<int16>(velo + _fadeVolumeAdjust, 0, 127);
+			noteOn(ch, para1, velo);
+		} else {
+			noteOff(ch, para1);
+		}
+		break;
+	case 0xC0:
+		_partPrograms[ch] = para1;
+		programChange(ch, ch == 9 ? 0 : _instrumentsRemap[para1 & 0x07F]);
+		break;
+	default:
+		// 0xA0 and 0xB0 are parsing related and will be filtered and handled in MidiParser_S1D.
+		if (!((b & 0xF0) == 0xB0 && (para1 == 0x7b || para1 == 0x07)))
+			warning("PC98CommonDriver::send(): Unsupported Midi Message: 0x%02x 0x%02x 0x%02x", b & 0xFF, para1, para2);
+		break;
+	}
+}
+
+void PC98CommonDriver::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
+	delete _timerCb; 
+	_timerCb = (_isOpen && timerParam && timerProc) ? new TimerCb(timerProc, timerParam) : 0;
+}
+
+uint32 PC98CommonDriver::property(int prop, uint32 param) {
+	uint32 res = 0;
+	switch (prop) {
+	case kPropMusicVolume:
+	case kPropSfxVolume: {
+		uint16 &v = (prop == kPropMusicVolume) ? _musicVolume : _sfxVolume;
+		res = v;
+		if ((int32)param != -1)
+			v = param & 0x1ff;
+		setVolume(_musicVolume, _sfxVolume);
+		break;
+	}
+	case kPropPause:
+		pause(param);
+		break;
+	default:
+		break;
+	}
+	return res;
+}
+
+void PC98CommonDriver::updateSounds() {
+	if (!_isOpen)
+		return;
+
+	_internalUpdateTimer += _baseTempo;
+	if (_internalUpdateTimer >= 16667) {
+		_internalUpdateTimer -= 16667;
+
+		// I haven't implemented music fading in and out, since Elvira 1 (the
+		// only game for this sound driver) does not use the feature at all.
+		// The fade volume would have to be updated here...
+
+		for (int i = 0; i < 16; ++i)
+			_chanUse[i] = 0;
+
+		processSounds();
+	}
+}
+
+void PC98CommonDriver::updateParser() {
+	if (_isOpen && _timerCb && _timerCb->isValid())
+		(*_timerCb)();
+}
+
+void PC98CommonDriver::reset() {
+	memset(_partPrograms, 0, sizeof(_partPrograms));
+	memset(_chanUse, 0, sizeof(_chanUse));
+	_allNotes = false;
+	_programLock = false;
+	_noFadeRemap = false;
+	_delayedProgramChange = false;
+	_ngDelay = 0;
+}
+
+PC98FMDriver::PC98FMDriver() : PC98CommonDriver(), _pc98a(0) {
+	_baseTempo = 10080;
+	_instrumentsRemap = _instrumentsRemapFM;
+	_instrumentLevelAdjust = (const int8*)_instrumentLevelAdjustFM;
+	_partsRemap = _partsRemapFM;
+	memset(_partProgramsInternal, 0, sizeof(_partProgramsInternal));
+	memset(_partNotes, 0, sizeof(_partNotes));
+	memset(_chanAssign, 0, sizeof(_chanAssign));
+	memset(_chanNotes, 0, sizeof(_chanNotes));
+}
+
+PC98FMDriver::~PC98FMDriver() {
+	_mixer->stopAll();
+	close();
+}
+
+int PC98FMDriver::open() {
+	if (_isOpen)
+		return MERR_ALREADY_OPEN;
+
+	delete _pc98a;
+
+	_pc98a = new PC98AudioCore(g_engine->_mixer, this, kType26);
+	if (_pc98a && _pc98a->init()) {
+		_pc98a->writeReg(0, 0x06, 0x0a);
+		_pc98a->writeReg(0, 0x07, 0x9c);
+		for (int i = 8; i < 11; ++i)
+			_pc98a->writeReg(0, i, 0);
+		_pc98a->writeReg(0, 0x27, 0x3a);
+	} else {
+		return MERR_DEVICE_NOT_AVAILABLE;
+	}
+
+	memset(_partProgramsInternal, 0, sizeof(_partProgramsInternal));
+	memset(_partNotes, 0, sizeof(_partNotes));
+	memset(_chanAssign, 0, sizeof(_chanAssign));
+	memset(_chanNotes, 0, sizeof(_chanNotes));
+
+	reset();
+
+	_isOpen = true;
+
+	return 0;
+}
+
+void PC98FMDriver::close() {
+	setTimerCallback(0, 0);
+	_isOpen = false;
+	delete _pc98a;
+	_pc98a = 0;
+}
+
+void PC98FMDriver::noteOn(uint8 part, uint8 note, uint8 velo) {
+	if (_delayedProgramChange && part != 9) {
+		int ch = 0x80;
+		uint8 high = 0x80;
+		for (int i = 2; i >= 0; --i) {
+			if (_chanAssign[i] == 0x80) {
+				ch = i;
+				break;
+			}
+			if (part < _chanAssign[i] && high > _chanAssign[i]) {
+				ch = i;
+				high = _chanAssign[i];
+			}
+		}
+		if (ch == 0x80)
+			return;
+
+		loadInstrument(ch, _partProgramsInternal[part]);
+
+		_partNotes[ch] = note;
+		_chanAssign[ch] = part;
+		part = ch;
+	}
+	startNote(part, note, velo);
+}
+
+void PC98FMDriver::noteOff(uint8 part, uint8 note) {
+	if (_delayedProgramChange) {
+		if (part == 9) {
+			_pc98a->writeReg(0, 6, 0);
+			stopNote(part, note);
+		} else {
+			for (int i = 2; i >= 0; --i) {
+				if (_chanAssign[i] != part || (note != _partNotes[i] && !_allNotes))
+					continue;
+				_chanAssign[i] = 0x80;
+				_partNotes[i] = 0;
+				stopNote(i, note);
+			}
+		}
+	} else {
+		stopNote(part, note);
+	}
+}
+
+void PC98FMDriver::programChange(uint8 part, uint8 prog) {
+	if (!_delayedProgramChange)
+		loadInstrument(part, prog);
+	_partProgramsInternal[part] = prog;
+}
+
+void PC98FMDriver::processSounds() {
+	if (_ngDelay)
+		--_ngDelay;
+	if (!_ngDelay)
+		_pc98a->writeReg(0, 0x0a, 0);
+}
+
+void PC98FMDriver::setVolume(int musicVolume, int sfxVolume) {
+	_pc98a->setMusicVolume(musicVolume);
+	_pc98a->setSoundEffectVolume(sfxVolume);
+}
+
+void PC98FMDriver::loadInstrument(uint8 chan, uint8 prg) {
+	if (chan > 2)
+		return;
+
+	assert(prg < ARRAYSIZE(_instrumentPatches));
+	const uint8 *src = _instrumentPatches[prg];
+	_pc98a->writeReg(0, 0xB0 | chan, *src++);
+
+	for (uint8 reg = 0x30 | chan; reg < 0x40; reg += 4) {
+		_pc98a->writeReg(0, reg, *src++);
+		_pc98a->writeReg(0, reg + 0x10, *src++);
+		_pc98a->writeReg(0, reg + 0x20, *src++);
+		_pc98a->writeReg(0, reg + 0x30, (*src++) & 0x1F);
+		_pc98a->writeReg(0, reg + 0x40, (*src++) & 0x1F);
+		_pc98a->writeReg(0, reg + 0x50, *src++);
+	}
+}
+
+void PC98FMDriver::startNote(uint8 chan, uint8 note, uint8 velo) {
+	if (chan == 9) {
+		if (note >= sizeof(_ngMapping) || _ngMapping[note] == 0xff)
+			return;
+		_pc98a->writeReg(0, 0x06, _ngMapping[note]);
+		_pc98a->writeReg(0, 0x0a, 0x0a);
+		_ngDelay = 3;
+	}
+
+	if (chan > 2)
+		return;
+
+	if (_chanUse[chan] && note < _chanNotes[chan])
+		return;
+
+	_allNotes = true;
+	stopNote(chan, 0);
+	_allNotes = false;
+	_chanNotes[chan] = note;
+	_chanUse[chan]++;
+
+	const uint8 *instr = _instrumentPatches[_partProgramsInternal[chan]];
+	uint8 c = _carrier[*instr & 7];
+
+	instr += 2;
+	const uint8 *pos = instr;
+	uint8 instvl = 0x7F;
+	for (int i = 0; i < 4; ++i) {
+		if (((c >> i) & 1) && *pos < instvl)
+			instvl = *pos;
+		pos += 6;
+	}
+
+	pos = instr;
+	velo = 0x7f - (0x57 + (velo >> 2)) - instvl;
+	for (uint8 i = 0x40 | chan; i < 0x50; i += 4) {
+		if (c & 1)
+			_pc98a->writeReg(0, i, MIN<uint8>(*pos + velo, 0x7f));
+		pos += 6;
+		c >>= 1;
+	}
+
+	if (note > 18)
+		note -= 12;
+	uint16 frq = _frequency[note % 12];
+	uint8 bl = (note / 12) << 3;
+	_pc98a->writeReg(0, 0xa4 | chan, (frq >> 8) | bl);
+	_pc98a->writeReg(0, 0xa0 | chan, frq & 0xff);
+
+	_pc98a->writeReg(0, 0x28, 0xF0 | chan);
+}
+
+void PC98FMDriver::stopNote(uint8 chan, uint8 note) {
+	if (chan > 2)
+		return;
+
+	if (_allNotes || note == _chanNotes[chan])
+		_pc98a->writeReg(0, 0x28, chan);
+}
+
+void PC98FMDriver::timerCallbackB() {
+	updateSounds();
+	PC98AudioCore::MutexLock tempUnlock = _pc98a->stackUnlockMutex();
+	updateParser();
+}
+
+const uint8 PC98FMDriver::_instrumentsRemapFM[128] = {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,	0x00, 0x09, 0x0a, 0x0b, 0x0c, 0x01, 0x02, 0x0f,
+	0x0f, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,	0x18, 0x19, 0x19, 0x1b, 0x1c, 0x1d, 0x1e, 0x03,
+	0x04, 0x21, 0x22, 0x23, 0x05, 0x25, 0x06, 0x27,	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2e,
+	0x30, 0x31, 0x35, 0x07, 0x35, 0x35, 0x36, 0x37,	0x38, 0x08, 0x3a, 0x3b, 0x3c, 0x3e, 0x3e, 0x3f,
+	0x40, 0x41, 0x42, 0x44, 0x44, 0x45, 0x09, 0x47,	0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x0a, 0x51, 0x51,
+	0x51, 0x54, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,	0x58, 0x59, 0x5a, 0x5b, 0x5a, 0x5a, 0x5e, 0x5f,
+	0x60, 0x61, 0x67, 0x63, 0x0c, 0x65, 0x66, 0x67,	0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c,
+	0x0c, 0x0c, 0x0c, 0x0d, 0x0e, 0x0f, 0x76, 0x77,	0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
+};
+
+const uint8 PC98FMDriver::_instrumentLevelAdjustFM[128] = {
+	0x28, 0x0f, 0x28, 0x1d, 0x14, 0x14, 0x23, 0x19,	0x28, 0x13, 0x23, 0x23, 0x30, 0x23, 0x17, 0x16,
+	0x23, 0x14, 0x15, 0x13, 0x23, 0x23, 0x19, 0x19,	0x32, 0x19, 0x16, 0x23, 0x05, 0x0a, 0x05, 0x0a,
+	0x35, 0x28, 0x2d, 0x23, 0x19, 0x1c, 0x22, 0x23,	0x23, 0x1a, 0x2d, 0x23, 0x23, 0x23, 0x23, 0x1e,
+	0x32, 0x1e, 0x37, 0x23, 0x18, 0x2e, 0x2b, 0x32,	0x11, 0x14, 0x0f, 0x0f, 0x14, 0x14, 0x14, 0x14,
+	0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x0a, 0x28,	0x0d, 0x14, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
+	0x23, 0x0a, 0x19, 0x19, 0x14, 0x14, 0x12, 0x14,	0x2b, 0x2c, 0x34, 0x2e, 0x30, 0x31, 0x15, 0x29,
+	0x32, 0x23, 0x23, 0x23, 0x23, 0x0b, 0x23, 0x23,	0x23, 0x23, 0x23, 0x23, 0x14, 0x14, 0x1e, 0x23,
+	0x23, 0x23, 0x23, 0x23, 0x2c, 0x23, 0x23, 0x23,	0x23, 0x23, 0x23, 0x23, 0x00, 0x23, 0x23, 0x23
+};
+
+const uint8 PC98FMDriver::_partsRemapFM[16] = {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+};
+
+const uint8 PC98FMDriver::_instrumentPatches[16][25] = {
+	{ 0x3f, 0x06, 0x6b, 0x1f, 0x0e, 0x00, 0xff, 0x03, 0x0a, 0x1f, 0x02, 0x01, 0x0f, 0x03, 0x0a, 0x1f, 0x02, 0x01, 0x0f, 0x02, 0x00, 0x1f, 0x02, 0x01, 0x0f },
+	{ 0x3f, 0x38, 0x75, 0x1f, 0x81, 0x01, 0x0a, 0x01, 0x00, 0x14, 0x82, 0x01, 0x0a, 0x73, 0x00, 0x14, 0x82, 0x01, 0x0a, 0x62, 0x00, 0x14, 0x82, 0x01, 0x0a },
+	{ 0x07, 0x08, 0x3e, 0xdf, 0x15, 0x00, 0x0f, 0x03, 0x11, 0x5f, 0x1f, 0x00, 0x0a, 0x02, 0x25, 0x5d, 0x1f, 0x00, 0x0a, 0x02, 0x00, 0x92, 0x1f, 0x00, 0x0a },
+	{ 0x3d, 0x4a, 0x23, 0x1b, 0x11, 0x00, 0xfa, 0x41, 0x00, 0x14, 0x00, 0x00, 0x0a, 0x42, 0x00, 0x14, 0x00, 0x00, 0x0a, 0x40, 0x00, 0x14, 0x00, 0x00, 0x0a },
+	{ 0x3c, 0x0b, 0x2b, 0x5a, 0x02, 0x01, 0x35, 0x63, 0x2a, 0x55, 0x01, 0x00, 0x24, 0x03, 0x19, 0x5c, 0x09, 0x05, 0x44, 0x21, 0x00, 0x4d, 0x06, 0x00, 0x44 },
+	{ 0x3c, 0x01, 0x20, 0x52, 0x0c, 0x01, 0x5a, 0x22, 0x17, 0x4f, 0x0a, 0x01, 0x2a, 0x26, 0x05, 0x45, 0x0a, 0x00, 0x0f, 0x31, 0x00, 0x47, 0x02, 0x00, 0x0f },
+	{ 0x2c, 0x3a, 0x2d, 0x58, 0x0e, 0x00, 0xf7, 0x05, 0x39, 0x5a, 0x0e, 0x00, 0xf4, 0x02, 0x00, 0x58, 0x08, 0x00, 0xf4, 0x01, 0x05, 0x9a, 0x08, 0x00, 0xf4 },
+	{ 0x3c, 0x01, 0x20, 0x52, 0x0c, 0x01, 0x2a, 0x21, 0x17, 0x4f, 0x0a, 0x01, 0x5a, 0x11, 0x00, 0x12, 0x8a, 0x01, 0x3a, 0x61, 0x07, 0x14, 0x82, 0x01, 0x3a },
+	{ 0x00, 0x01, 0x7f, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x23, 0x5f, 0x05, 0x00, 0xf3, 0x71, 0x1b, 0x5f, 0x0c, 0x01, 0xf5, 0x01, 0x07, 0x5f, 0x0c, 0x00, 0xf5 },
+	{ 0x18, 0x37, 0x2c, 0x9e, 0x0d, 0x08, 0xb6, 0x31, 0x18, 0x1c, 0x04, 0x03, 0x36, 0x30, 0x22, 0xdc, 0x06, 0x0a, 0xb6, 0x32, 0x00, 0x9c, 0x01, 0x05, 0x26 },
+	{ 0x3b, 0x0a, 0x00, 0x1f, 0x00, 0x00, 0x0a, 0x02, 0x22, 0x18, 0x0e, 0x00, 0x0a, 0x60, 0x3a, 0x18, 0x0c, 0x00, 0xfa, 0x31, 0x00, 0x51, 0x0b, 0x00, 0x38 },
+	{ 0x2c, 0x34, 0x21, 0x58, 0x0e, 0x00, 0xf7, 0x76, 0x2f, 0x58, 0x14, 0x00, 0xfa, 0x30, 0x00, 0xd8, 0x84, 0x00, 0xf2, 0x72, 0x0b, 0x98, 0x8c, 0x00, 0xf6 },
+	{ 0x3b, 0x08, 0x06, 0x5f, 0x00, 0x00, 0x1a, 0x01, 0x19, 0x4e, 0x0e, 0x00, 0x0a, 0x61, 0x30, 0x58, 0x18, 0x00, 0x5a, 0x30, 0x00, 0x50, 0x0b, 0x00, 0x38 },
+	{ 0x3b, 0x0a, 0x0d, 0x16, 0x00, 0x00, 0x0a, 0x00, 0x0b, 0x1a, 0x16, 0x40, 0xfb, 0x0d, 0x13, 0x1a, 0x1a, 0xc0, 0xfa, 0x01, 0x00, 0x5e, 0x8e, 0x00, 0xf7 },
+	{ 0x3b, 0x0e, 0x0c, 0x1f, 0x00, 0x00, 0x05, 0x0a, 0x25, 0x1b, 0x1b, 0x80, 0xfa, 0x00, 0x31, 0x1f, 0x0a, 0xc0, 0xf5, 0x00, 0x00, 0x5c, 0x8e, 0x40, 0xf7 },
+	{ 0x32, 0x02, 0x15, 0x58, 0x14, 0x00, 0xfa, 0x31, 0x25, 0x5f, 0x0a, 0x40, 0xf4, 0x01, 0x17, 0x5a, 0x0c, 0x80, 0xf6, 0x01, 0x00, 0x9a, 0x8b, 0x00, 0xf5 }
+};
+
+const uint8 PC98FMDriver::_ngMapping[76] = {
+	0x18, 0x1c, 0x3c, 0x20, 0x3e, 0x24, 0x40, 0x28,	0x2c, 0x38, 0x30, 0x3c, 0x00, 0x00, 0x36, 0x00,	0x38, 0x00, 0x00,
+	0x00, 0x16, 0x11, 0x16, 0x16, 0x11, 0x16, 0x11, 0x16, 0x11, 0x0f, 0x32, 0x00, 0x00, 0x0d, 0x00, 0x10, 0x1f, 0x1f,
+	0x0a, 0x08,	0x0a, 0x19, 0x04, 0x19, 0x04, 0x14, 0x04, 0x14, 0x0f, 0x0c, 0x0f, 0x0c, 0xff, 0xff, 0x0d, 0xff,	0x12,
+	0xff, 0xff, 0xff, 0x0f, 0x19, 0x0f, 0x0f, 0x19, 0x0f, 0x19, 0x0f, 0x19, 0x14, 0x06, 0xff, 0xff, 0x0f, 0xff, 0x00
+};
+
+const uint8 PC98FMDriver::_carrier[8] = {
+	0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F
+};
+
+const uint16 PC98FMDriver::_frequency[12] = {
+	0x0267, 0x028d, 0x02b3, 0x02dd, 0x0308, 0x0337, 0x0368, 0x039c, 0x03d3, 0x040e, 0x044b, 0x048b
+};
+
+#define MIDIMSG32(s, p1, p2) (p2 << 16 | p1 << 8 | s)
+
+PC98MidiDriver::PC98MidiDriver(MidiDriver::DeviceHandle dev) : _dev(dev), _drv(0) {
+	_instrumentsRemap = (getMusicType(dev) == MT_MT32) ? _instrumentsRemapMT32 : (getMusicType(dev) == MT_GM ? _instrumentsRemapGM : 0);
+	int8 *tbl2 = new int8[128];
+	memset(tbl2, 0, 128);
+	_instrumentLevelAdjust = tbl2;
+	_partsRemap = _partsRemapMidi;
+	memcpy(_volSysex, _sysexMsg[0], 9);
+	memcpy(_partAssignSysexGS, _sysexMsg[1], 9);
+	memcpy(_partAssignSysexMT32, _sysexMsg[2], 9);
+}
+
+PC98MidiDriver::~PC98MidiDriver() {
+	close();
+	delete[] _instrumentLevelAdjust;
+}
+
+int PC98MidiDriver::open() {
+	if (_isOpen)
+		return MERR_ALREADY_OPEN;
+
+	delete _drv;
+
+	_drv = MidiDriver::createMidi(_dev);
+	if (!_drv || !_instrumentsRemap)
+		return MERR_DEVICE_NOT_AVAILABLE;
+
+	_baseTempo = _drv->getBaseTempo();
+	int res = _drv->open();
+
+	if (!res) {
+		_drv->setTimerCallback(this, &timerCallback);
+
+		for (byte i = 0xB1; i < 0xBA; ++i)
+			_drv->send(MIDIMSG32(i, 0x79, 0));
+
+		property(kPropMusicVolume, Audio::Mixer::kMaxChannelVolume);
+
+		if (getMusicType(_dev) == MT_MT32) {
+			_partAssignSysexGS[7] = 0x10;
+			for (uint8 i = 0x10; i < 0x20; ++i) {
+				_partAssignSysexGS[5] = i;
+				sendSysexWithCheckSum(_partAssignSysexGS);
+			}
+
+			for (uint8 i = 0x01; i < 0x0A; ++i) {
+				_partAssignSysexMT32[6] = 0x0C + i;
+				_partAssignSysexMT32[7] = i;
+				sendSysexWithCheckSum(_partAssignSysexMT32);
+			}
+
+		} else if (getMusicType(_dev) == MT_GM) {
+			_partAssignSysexGS[5] = 0x10;
+			_partAssignSysexGS[7] = 9;
+			sendSysexWithCheckSum(_partAssignSysexGS);
+			uint8 p = 0;
+			for (uint8 i = 0x11; i < 0x20; ++i) {
+				_partAssignSysexGS[5] = i;
+				_partAssignSysexGS[7] = p++;
+				if (p == 9)
+					p++;
+				sendSysexWithCheckSum(_partAssignSysexGS);
+			}
+
+			_partAssignSysexMT32[7] = 0x10;
+			for (uint8 i = 0x0D; i < 0x16; ++i) {
+				_partAssignSysexMT32[6] = i;
+				sendSysexWithCheckSum(_partAssignSysexMT32);
+			}
+
+			_drv->send(MIDIMSG32(0xB9, 0x07, 0x46));
+		}
+
+		reset();
+		_isOpen = true;
+	}
+
+	return res;
+}
+
+void PC98MidiDriver::close() {
+	setTimerCallback(0, 0);
+	_isOpen = false;
+	if (!_drv)
+		return;
+
+	_drv->setTimerCallback(0, 0);
+	_mixer->stopAll();
+	_drv->close();
+	delete _drv;
+	_drv = 0;
+}
+
+void PC98MidiDriver::timerCallback(void *obj) {
+	PC98MidiDriver *drv = static_cast<PC98MidiDriver*>(obj);
+	drv->updateSounds();
+	drv->updateParser();
+}
+
+void PC98MidiDriver::noteOn(uint8 part, uint8 note, uint8 velo) {
+	_drv->send(MIDIMSG32(0x90 | _partsRemap[part & 0x0F], note, velo));
+}
+
+void PC98MidiDriver::noteOff(uint8 part, uint8 note) {
+	if (_allNotes)
+		_drv->send(MIDIMSG32(0xB0 | _partsRemap[part & 0x0F], 0x7B, 0));
+	else
+		_drv->send(MIDIMSG32(0x80 | _partsRemap[part & 0x0F], note, 0));
+}
+
+void PC98MidiDriver::programChange(uint8 part, uint8 prog) {
+	if (!_programLock)
+		_drv->send(MIDIMSG32(0xC0 | _partsRemap[part & 0x0F], prog, 0));
+}
+
+void PC98MidiDriver::setVolume(int musicVolume, int sfxVolume) {
+	if (!_isOpen)
+		return;
+
+	if (getMusicType(_dev) == MT_MT32) {
+		_volSysex[7] = musicVolume * 100 / Audio::Mixer::kMaxChannelVolume;
+		sendSysexWithCheckSum(_volSysex);
+	} else {
+		uint8 vol = musicVolume * 127 / Audio::Mixer::kMaxChannelVolume;
+		for (int i = 0; i < 16; ++i)
+			_drv->send(MIDIMSG32(0xB0 | _partsRemap[i], 0x07, vol));
+	}
+}
+
+void PC98MidiDriver::pause(bool paused) {
+	if (paused) {
+		_allNotes = true;
+		for (int i = 0; i < 16; ++i)
+			noteOff(i, 0);
+		_allNotes = false;
+	}
+}
+
+void PC98MidiDriver::sendSysexWithCheckSum(uint8 *data) {
+	uint8 chk = 0;
+	for (int i = 4; i < 8; ++i)
+		chk += data[i];
+	data[8] = 0x80 - (chk & 0x7f);
+	_drv->sysEx(data, 9);
+}
+
+const uint8 PC98MidiDriver::_instrumentsRemapMT32[128] = {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,	0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,	0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,	0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+	0x60, 0x61, 0x62, 0x63, 0x6e, 0x65, 0x66, 0x67,	0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,	0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
+};
+
+const uint8 PC98MidiDriver::_instrumentsRemapGM[128] = {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,	0x10, 0x09, 0x0a, 0x0b, 0x0c, 0x10, 0x10, 0x0f,
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x26,
+	0x58, 0x21, 0x22, 0x23, 0x61, 0x25, 0x0b, 0x27,	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+	0x30, 0x31, 0x32, 0x2d, 0x34, 0x35, 0x36, 0x37,	0x38, 0x2e, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x23, 0x47,	0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4c, 0x4e, 0x4f,
+	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,	0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+	0x60, 0x61, 0x62, 0x63, 0x4c, 0x65, 0x66, 0x67,	0x0c, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+	0x70, 0x71, 0x72, 0x75, 0x76, 0x74, 0x76, 0x77,	0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
+};
+
+const uint8 PC98MidiDriver::_partsRemapMidi[16] = {
+	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,	0x0f, 0x09, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f
+};
+
+const uint8 PC98MidiDriver::_sysexMsg[3][9] = {
+	{ 0x41, 0x10, 0x16, 0x12, 0x10, 0x00, 0x16, 0x64, 0x00 },
+	{ 0x41, 0x10, 0x42, 0x12, 0x40, 0x10, 0x02, 0x10, 0x00 },
+	{ 0x41, 0x10, 0x16, 0x12, 0x10, 0x00, 0x00, 0x00, 0x00 }
+};
+
+MidiDriver *MidiDriverPC98_create(MidiDriver::DeviceHandle dev) {
+	MusicType type = MidiDriver::getMusicType(dev);
+	if (type == MT_PC98)
+		return new PC98FMDriver();
+	else if (type == MT_GM || type == MT_MT32)
+		return new PC98MidiDriver(dev);
+	return 0;
+}
+
+#undef MIDIMSG32
+
+} // End of namespace AGOS
diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp
index 6f9d73c48a..2f9d2bb971 100644
--- a/engines/agos/midi.cpp
+++ b/engines/agos/midi.cpp
@@ -98,7 +98,7 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 	switch (gameType) {
 	case GType_ELVIRA1:
 		if (platform == Common::kPlatformPC98) {
-			_musicMode = kMusicModeDisabled;
+			_musicMode = kMusicModePC98;
 			devFlags = (devFlags & ~MDT_ADLIB) | MDT_PC98;
 		} else {
 			_musicMode = kMusicModeAccolade;
@@ -135,7 +135,14 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 	MidiDriver::DeviceHandle dev;
 	int ret = 0;
 
-	if (_musicMode != kMusicModeDisabled) {
+	if (_musicMode == kMusicModePC98) {
+		dev = MidiDriver::detectDevice(devFlags);
+		_driver = MidiDriverPC98_create(dev);
+		if (_driver && !_driver->open()) {
+			_driver->setTimerCallback(this, &onTimer);
+			return 0;
+		}
+	} else if (_musicMode != kMusicModeDisabled) {
 		dev = MidiDriver::detectDevice(devFlags);
 		musicType = MidiDriver::getMusicType(dev);
 
@@ -169,7 +176,6 @@ int MidiPlayer::open(int gameType, Common::Platform platform, bool isDemo) {
 		switch (musicType) {
 		case MT_ADLIB:
 			_driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename);
-
 			break;
 		case MT_MT32:
 			_driver = MidiDriver_Accolade_MT32_create(accoladeDriverFilename);
@@ -487,7 +493,10 @@ void MidiPlayer::pause(bool b) {
 	// if using the driver Accolade_AdLib call setVolume() to turn off\on the volume on all channels
 	if (musicType == MT_ADLIB && _musicMode == kMusicModeAccolade) {
 		static_cast <MidiDriver_Accolade_AdLib*> (_driver)->setVolume(_paused ? 0 : 128);
+	} else if (_musicMode == kMusicModePC98) {
+		_driver->property(0x30, _paused ? 1 : 0);
 	}
+
 	for (int i = 0; i < 16; ++i) {
 		if (_music.channel[i])
 			_music.channel[i]->volume(_paused ? 0 : (_music.volume[i] * _musicVolume / 255));
@@ -506,6 +515,11 @@ void MidiPlayer::setVolume(int musicVol, int sfxVol) {
 	_musicVolume = musicVol;
 	_sfxVolume   = sfxVol;
 
+	if (_musicMode == kMusicModePC98) {
+		_driver->property(0x10, _musicVolume);
+		_driver->property(0x20, _sfxVolume);
+	}
+
 	// Now tell all the channels this.
 	Common::StackLock lock(_mutex);
 	if (_driver && !_paused) {
diff --git a/engines/agos/midi.h b/engines/agos/midi.h
index 7b5cd790ee..68404cb6bd 100644
--- a/engines/agos/midi.h
+++ b/engines/agos/midi.h
@@ -37,7 +37,8 @@ enum kMusicMode {
 	kMusicModeDisabled = 0,
 	kMusicModeAccolade = 1,
 	kMusicModeMilesAudio = 2,
-	kMusicModeSimon1 = 3
+	kMusicModeSimon1 = 3,
+	kMusicModePC98 = 4
 };
 
 struct MusicInfo {
diff --git a/engines/agos/module.mk b/engines/agos/module.mk
index 919f819c1c..f128e7917b 100644
--- a/engines/agos/module.mk
+++ b/engines/agos/module.mk
@@ -3,6 +3,7 @@ MODULE := engines/agos
 MODULE_OBJS := \
 	drivers/accolade/adlib.o \
 	drivers/accolade/driverfile.o \
+	drivers/accolade/pc98.o \
 	drivers/accolade/mt32.o \
 	drivers/simon1/adlib.o \
 	agos.o \


Commit: 77b316ef58b782166c441222ea04492e453b22a7
    https://github.com/scummvm/scummvm/commit/77b316ef58b782166c441222ea04492e453b22a7
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:47:59+01:00

Commit Message:
AGOS: (ELVIRA) - block mouse wheel during save/load/pause dialog

I assume that this happens accidently, since the mouse wheel was never supported by the original. Currently, you can still scroll the inventory using the mouse wheel when a save/load/pause dialog is open. It causes a text glitch in the PC-98 version. Instead of just fixing that glitch I've decided to fix the underlying bug (limited to Elvira 1 though; no idea what the behavior is in the other games).

Changed paths:
    engines/agos/input.cpp


diff --git a/engines/agos/input.cpp b/engines/agos/input.cpp
index 3b57369f1e..4a10f151f9 100644
--- a/engines/agos/input.cpp
+++ b/engines/agos/input.cpp
@@ -514,14 +514,14 @@ void AGOSEngine_Elvira2::handleMouseWheelDown() {
 
 void AGOSEngine::handleMouseWheelUp() {
 	HitArea *ha = findBox(0x7FFB);
-	if (ha != NULL && (ha->flags & kBFBoxInUse)) {
+	if (ha != NULL && (ha->flags & kBFBoxInUse) && !(getGameId() == GID_ELVIRA1 && _windowNum == 3)) {
 		inventoryUp(ha->window);
 	}
 }
 
 void AGOSEngine::handleMouseWheelDown() {
 	HitArea *ha = findBox(0x7FFC);
-	if (ha != NULL && (ha->flags & kBFBoxInUse)) {
+	if (ha != NULL && (ha->flags & kBFBoxInUse) && !(getGameId() == GID_ELVIRA1 && _windowNum == 3)) {
 		inventoryDown(ha->window);
 	}
 }


Commit: 8969fa9c090081b3a9795a11b802e9a7905cfcb3
    https://github.com/scummvm/scummvm/commit/8969fa9c090081b3a9795a11b802e9a7905cfcb3
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:47:59+01:00

Commit Message:
AGOS: (ELVIRA/PC98/Jp) - fix game restart

Like most of the other data in the PC-98 version the restart save file also requires decompression.

Changed paths:
    engines/agos/saveload.cpp


diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp
index 356babdea3..e797b14ebb 100644
--- a/engines/agos/saveload.cpp
+++ b/engines/agos/saveload.cpp
@@ -1047,12 +1047,16 @@ bool AGOSEngine::loadGame(const Common::String &filename, bool restartMode) {
 
 	if (restartMode) {
 		// Load restart state
-		Common::File *file = new Common::File();
-		if (!file->open(filename)) {
-			delete file;
-			file = nullptr;
+		if (getPlatform() == Common::kPlatformPC98 && !filename.compareToIgnoreCase("start")) {
+			f = createPak98FileStream("START.PAK");
+		} else {
+			Common::File *file = new Common::File();
+			if (!file->open(filename)) {
+				delete file;
+				file = nullptr;
+			}
+			f = file;
 		}
-		f = file;
 	} else {
 		f = _saveFileMan->openForLoading(filename);
 	}


Commit: 656a35ee87c6e06fc1a61dff9eb6b1119706466c
    https://github.com/scummvm/scummvm/commit/656a35ee87c6e06fc1a61dff9eb6b1119706466c
Author: athrxx (athrxx at scummvm.org)
Date: 2021-01-22T00:51:05+01:00

Commit Message:
NEWS: mention Elvira PC-98 support

Changed paths:
    NEWS.md


diff --git a/NEWS.md b/NEWS.md
index 58e3ad1c52..5cf40b893d 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -21,6 +21,9 @@ For a more comprehensive changelog of the latest experimental code, see:
    - Switched ScummVM GUI output to UTF-32.
    - Updated the Roland MT-32 emulation code to the Munt project's mt32emu 2.4.2.
 
+ AGOS:
+   - Added support for the Japanese PC-98 version of Elvira 1.
+
  Dreamweb:
    - Rendering fixes for Russian fan translation.
 




More information about the Scummvm-git-logs mailing list