[Scummvm-git-logs] scummvm master -> a665a1384fd22d76eb8c1c5f19c9770ee7579721
bluegr
noreply at scummvm.org
Thu Apr 23 00:52:03 UTC 2026
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
a665a1384f TINSEL: Add support for DW1 PSX Japanese font
Commit: a665a1384fd22d76eb8c1c5f19c9770ee7579721
https://github.com/scummvm/scummvm/commit/a665a1384fd22d76eb8c1c5f19c9770ee7579721
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2026-04-23T03:52:00+03:00
Commit Message:
TINSEL: Add support for DW1 PSX Japanese font
Changed paths:
A engines/tinsel/psx_japan_font.cpp
A engines/tinsel/psx_japan_font.h
engines/tinsel/graphics.cpp
engines/tinsel/handle.cpp
engines/tinsel/handle.h
engines/tinsel/module.mk
engines/tinsel/tinsel.cpp
engines/tinsel/tinsel.h
diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp
index 4991106979d..a4d0388c364 100644
--- a/engines/tinsel/graphics.cpp
+++ b/engines/tinsel/graphics.cpp
@@ -24,6 +24,7 @@
#include "tinsel/handle.h" // LockMem()
#include "tinsel/object.h"
#include "tinsel/palette.h"
+#include "tinsel/psx_japan_font.h"
#include "tinsel/scene.h"
#include "tinsel/tinsel.h"
@@ -1113,13 +1114,14 @@ void DrawObject(DRAWOBJECT *pObj) {
bool psxFourBitClut = false; // Used by Tinsel PSX, true if an image using a 4bit CLUT is rendered
bool psxSaturnRLEindex = false; // Used by Tinsel PSX/Saturn, true if an image is using PJCRLE compressed indexes
uint32 psxSkipBytes = 0; // Used by Tinsel PSX, number of bytes to skip before counting indexes for image tiles
+ bool psxJapanFontChar = (TinselV1PSXJapan && IsPsxJapanFontChar(pObj->hBits));
if ((pObj->width <= 0) || (pObj->height <= 0))
// Empty image, so return immediately
return;
// If writing constant data, don't bother locking the data pointer and reading src details
- if (((pObj->flags & DMA_CONST) == 0) || ((TinselVersion == 3) && ((pObj->flags & 0x05) == 0x05))) {
+ if (((pObj->flags & DMA_CONST) == 0 && !psxJapanFontChar) || ((TinselVersion == 3) && ((pObj->flags & 0x05) == 0x05))) {
if (TinselVersion >= 2) {
srcPtr = (byte *)_vm->_handle->LockMem(pObj->hBits);
pObj->charBase = nullptr;
@@ -1215,6 +1217,8 @@ void DrawObject(DRAWOBJECT *pObj) {
t3WrtNonZero(pObj, srcPtr, destPtr);
else if (TinselVersion >= 2)
t2WrtNonZero(pObj, srcPtr, destPtr, (typeId & DMA_CLIP) != 0, (typeId & DMA_FLIPH) != 0);
+ else if (psxJapanFontChar)
+ DrawPsxJapanFontChar(pObj, destPtr);
else if (TinselV1PSX || TinselV1Saturn)
psxSaturnDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41, psxFourBitClut, psxSkipBytes, psxMapperTable, true);
else if (TinselV1Mac)
diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp
index a6ae23bbd40..7ae45c4d915 100644
--- a/engines/tinsel/handle.cpp
+++ b/engines/tinsel/handle.cpp
@@ -34,6 +34,7 @@
#include "tinsel/handle.h"
#include "tinsel/heapmem.h" // heap memory manager
#include "tinsel/palette.h"
+#include "tinsel/psx_japan_font.h"
#include "tinsel/sched.h"
#include "tinsel/timers.h" // for DwGetCurrentTime()
#include "tinsel/tinsel.h"
@@ -329,7 +330,7 @@ void Handle::LoadFile(MEMHANDLE *pH) {
FONT *Handle::GetFont(SCNHANDLE offset) {
byte *data = LockMem(offset);
const bool isBE = TinselV1Mac || TinselV1Saturn;
- const uint32 characterCount = GetFontCharacterCount(offset);
+ const uint32 characterCount = GetFontCharacterCount(data);
const uint32 size = ((TinselVersion == 3) ? 12 * 4 : 11 * 4) + characterCount * 4; // FONT struct size
Common::MemoryReadStreamEndian *stream = new Common::MemoryReadStreamEndian(data, size, isBE);
@@ -347,35 +348,31 @@ FONT *Handle::GetFont(SCNHANDLE offset) {
font->fontInit.objY = stream->readSint32();
font->fontInit.objZ = stream->readSint32();
font->fontDef.resize(characterCount);
- for (uint32 i = 0; i < characterCount; i++)
- font->fontDef[i] = stream->readUint32();
+ if (!TinselV1PSXJapan) {
+ for (uint32 i = 0; i < characterCount; i++) {
+ font->fontDef[i] = stream->readUint32();
+ }
+ } else {
+ for (uint32 i = 0; i < characterCount; i++) {
+ font->fontDef[i] = GetPsxJapanFontCharHandle(i);
+ }
+ }
delete stream;
return font;
}
-uint32 Handle::GetFontCharacterCount(SCNHANDLE offset) const {
- // All fonts have 256 characters, unless this is a multibyte language
- if (!g_bMultiByte)
+uint32 Handle::GetFontCharacterCount(const byte *fontData) const {
+ // Multibyte fonts have variable numbers of characters, others have 256.
+ if (!g_bMultiByte) {
return 256;
-
- // For multibyte languages, different platforms have different characters.
- // There is only one font per font chunk, so we could use the font offset
- // to read the chunk header, determine the chunk size, and calculate the
- // character count. But since there are only three DW1 Japanese versions,
- // for now we'll just hard-code their known values.
- // TODO: Update this PSX number once we support its font format.
- // Japanese PSX stores glyphs in MULTIBYT.FNT and appears to use a
- // different font header that is larger than the normal header.
- if (TinselV1Mac)
- return 815;
- if (TinselV1PSX)
- return 667;
- if (TinselV1Saturn)
- return 662;
-
- error("unknown mbs platform");
+ } else {
+ // The first entry in the multibyte font character table is the
+ // maximum character index, instead of an image handle.
+ uint32 maxCharIndex = READ_32(fontData + 44);
+ return maxCharIndex + 1;
+ }
}
/**
@@ -414,6 +411,10 @@ PALETTE *Handle::GetPalette(SCNHANDLE offset) {
* @return IMAGE structure
*/
const IMAGE *Handle::GetImage(SCNHANDLE offset) {
+ if (TinselV1PSXJapan && IsPsxJapanFontChar(offset)) {
+ return GetPsxJapanFontCharImage(offset);
+ }
+
byte *data = LockMem(offset);
const bool isBE = TinselV1Mac || TinselV1Saturn;
const uint32 size = 16; // IMAGE struct size
diff --git a/engines/tinsel/handle.h b/engines/tinsel/handle.h
index f8c2b5269ea..e2c234a673b 100644
--- a/engines/tinsel/handle.h
+++ b/engines/tinsel/handle.h
@@ -50,7 +50,7 @@ public:
void SetupHandleTable();
FONT *GetFont(SCNHANDLE offset);
- uint32 GetFontCharacterCount(SCNHANDLE offset) const;
+ uint32 GetFontCharacterCount(const byte *fontData) const;
PALETTE *GetPalette(SCNHANDLE offset);
const IMAGE *GetImage(SCNHANDLE offset);
void SetImagePalette(SCNHANDLE offset, SCNHANDLE palHandle);
diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk
index 58f2b8db975..7493a0caa9e 100644
--- a/engines/tinsel/module.mk
+++ b/engines/tinsel/module.mk
@@ -36,6 +36,7 @@ MODULE_OBJS := \
play.o \
polygons.o \
psx_archive.o \
+ psx_japan_font.o \
saveload.o \
savescn.o \
scene.o \
diff --git a/engines/tinsel/psx_japan_font.cpp b/engines/tinsel/psx_japan_font.cpp
new file mode 100644
index 00000000000..1a37617c562
--- /dev/null
+++ b/engines/tinsel/psx_japan_font.cpp
@@ -0,0 +1,176 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/tinsel/psx_japan_font.h"
+
+#include "engines/tinsel/tinsel.h"
+
+namespace Tinsel {
+
+// DW1 PSX Japanese font routines.
+//
+// DW1 PSX Japan uses font differently than all other platforms and versions.
+// Instead of storing font glyphs as normal Tinsel images, the font glyphs are
+// stored in MULTIBYT.FNT as a single bitmap. The format is native to the PSX.
+// The Tinsel font resource defines all of the font glyphs as having no images.
+// Instead, the executable sent the bitmap glyphs directly to the PSX GPU.
+// The font glyphs were effectively drawn outside of the Tinsel graphics engine.
+//
+// MULTIBYT.FNT is an uncompressed 4 bpp bitmap with no header. The tricky part
+// is supporting this in our engine without the Tinsel image resources that
+// normally exist for each font glyph. We work around this limitation by using
+// pseudo handles. This allows us to detect attempts to use font glyphs without
+// significant changes to the engine. We effectively hook three operations:
+//
+// 1. Handle::GetFont() populates the character table with pseudo handles.
+// The upper bits contain a non-existent handle (0x1F0) that we use to
+// indicate a font glyph. The lower bits contain the character index.
+// 2. Handle::GetImage() calls GetPsxJapanFontCharImage() when it detects a font
+// pseudo handle. This creates an IMAGE structure with the PSX values.
+// 3. DrawObject() calls DrawPsxJapanFontChar() when it detects a font pseudo
+// handle. This draws the glyph from MULTIBYT.FNT using the character index
+// encoded in the pseudo handle.
+
+#define PSX_JAPAN_FONT_FILE_NAME "MULTIBYT.FNT"
+
+// Font pseudo handle mask. Encodes a non-existent handle index (0x1F0).
+#define PSX_JAPAN_FONT_CHAR_MASK 0xF8000000
+
+// Font glyphs are 18 x 17 pixels
+#define FONT_WIDTH 18
+#define FONT_HEIGHT 17
+
+// MULTIBYT.FNT is a 256 x 816 pixel bitmap containing a grid of glyphs.
+// Each glyph row contains 14 characters. The last 4 pixels are unused.
+#define FONT_ROW_WIDTH 256
+#define FONT_ROW_HEIGHT FONT_HEIGHT
+#define FONT_CHARS_IN_ROW 14
+
+// MULTIBYT.FNT contains three pixel values:
+// 0 Transparent
+// 1 Black
+// 2 White
+#define FONT_PIXEL_BLACK 1
+#define FONT_PIXEL_WHITE 2
+#define FONT_COLOR_BLACK 0
+#define FONT_COLOR_WHITE 255
+
+static Common::SeekableReadStream *g_MultiByteFont = nullptr;
+
+/**
+ * Open MULTIBYT.FNT during engine initialization.
+ * Only call this function for DW1 PSX Japan.
+ */
+void OpenPsxJapanFont() {
+ g_MultiByteFont = SearchMan.createReadStreamForMember(PSX_JAPAN_FONT_FILE_NAME);
+ if (g_MultiByteFont == nullptr) {
+ error("%s not found", PSX_JAPAN_FONT_FILE_NAME);
+ }
+}
+
+/**
+ * Close MULTIBYT.FNT (if open) during engine destruction.
+ */
+void ClosePsxJapanFont() {
+ delete g_MultiByteFont;
+ g_MultiByteFont = nullptr;
+}
+
+/**
+ * Returns a SCNHANDLE for a DW1 PSX Japan font character.
+ */
+SCNHANDLE GetPsxJapanFontCharHandle(uint32 charIndex) {
+ switch (charIndex) {
+ case 10: // newline
+ case 32: // space
+ return 0;
+ default:
+ return (PSX_JAPAN_FONT_CHAR_MASK | charIndex);
+ }
+}
+
+/**
+ * Returns true if a SCNHANDLE is for a DW1 PSX Japan font character.
+ */
+bool IsPsxJapanFontChar(SCNHANDLE offset) {
+ return ((offset & PSX_JAPAN_FONT_CHAR_MASK) == PSX_JAPAN_FONT_CHAR_MASK);
+}
+
+/**
+ * Creates an IMAGE object for a DW1 PSX Japan font character.
+ */
+const IMAGE *GetPsxJapanFontCharImage(SCNHANDLE offset) {
+ IMAGE *img = new IMAGE();
+ memset(img, 0, sizeof(IMAGE));
+
+ // All characters have the same width and height.
+ img->imgWidth = FONT_WIDTH;
+ img->imgHeight = FONT_HEIGHT;
+
+ // Set the bitmap handle to the same pseudo handle as the image so that we
+ // can detect it in DrawObject(). The DRAWOBJECT structure that is passed to
+ // DrawObject() only contains the bitmap handle, not the image handle.
+ img->hImgBits = offset;
+
+ return img;
+}
+
+/**
+ * Draws a DW1 PSX Japan font character from MULTIBYT.FNT.
+ */
+void DrawPsxJapanFontChar(DRAWOBJECT *pObj, uint8 *destP) {
+ // Extract character index from pseudo handle
+ const int charIndex = (pObj->hBits & 0x007fffffL);
+
+ // Calculate character's position in MULTIBYT.FNT
+ const int charRow = (charIndex / FONT_CHARS_IN_ROW) * (FONT_ROW_WIDTH / 2 * FONT_ROW_HEIGHT);
+ const int charCol = (charIndex % FONT_CHARS_IN_ROW) * (FONT_WIDTH / 2);
+ const int charUpperLeft = charRow + charCol;
+
+ // Draw character from top to bottom, left to right
+ const int height = pObj->height - pObj->botClip;
+ const int width = pObj->width - pObj->rightClip;
+ for (int y = pObj->topClip; y < height; y++) {
+ // Read character row (9 bytes containing 18 pixels)
+ const int charPos = charUpperLeft + (y * (FONT_ROW_WIDTH / 2));
+ uint8 charRowBytes[FONT_WIDTH / 2];
+ g_MultiByteFont->seek(charPos);
+ g_MultiByteFont->read(charRowBytes, sizeof(charRowBytes));
+
+ uint8 *rowDest = destP;
+ for (int x = pObj->leftClip; x < width; x++) {
+ // Bitmap format is 4 bits per pixel.
+ // Lower nibble: left pixel
+ // Upper nibble: right pixel
+ uint8 b = charRowBytes[x / 2];
+ uint8 pixel = (x & 1) ? (b >> 4) : (b & 0x0f);
+ if (pixel == FONT_PIXEL_BLACK) {
+ *rowDest = FONT_COLOR_BLACK;
+ } else if (pixel == FONT_PIXEL_WHITE) {
+ *rowDest = FONT_COLOR_WHITE;
+ }
+ rowDest++;
+ }
+ destP += SCREEN_WIDTH;
+ }
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/psx_japan_font.h b/engines/tinsel/psx_japan_font.h
new file mode 100644
index 00000000000..86dbb7d1d27
--- /dev/null
+++ b/engines/tinsel/psx_japan_font.h
@@ -0,0 +1,41 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef TINSEL_PSX_JAPAN_FONT_H
+#define TINSEL_PSX_JAPAN_FONT_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+struct IMAGE;
+struct DRAWOBJECT;
+
+void OpenPsxJapanFont();
+void ClosePsxJapanFont();
+SCNHANDLE GetPsxJapanFontCharHandle(uint32 charIndex);
+bool IsPsxJapanFontChar(SCNHANDLE offset);
+const IMAGE *GetPsxJapanFontCharImage(SCNHANDLE offset);
+void DrawPsxJapanFontChar(DRAWOBJECT *pObj, uint8 *destP);
+
+} // End of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
index dfbe008c0db..77626df11da 100644
--- a/engines/tinsel/tinsel.cpp
+++ b/engines/tinsel/tinsel.cpp
@@ -58,6 +58,7 @@
#include "tinsel/pid.h"
#include "tinsel/polygons.h"
#include "tinsel/psx_archive.h"
+#include "tinsel/psx_japan_font.h"
#include "tinsel/savescn.h"
#include "tinsel/scn.h"
#include "tinsel/sound.h"
@@ -992,6 +993,8 @@ TinselEngine::~TinselEngine() {
ResetVarsTinlib(); // tinlib.cpp
ResetVarsTinsel(); // tinsel.cpp
+ ClosePsxJapanFont();
+
CoroScheduler.destroy();
}
@@ -1108,6 +1111,11 @@ Common::Error TinselEngine::run() {
// Actors, globals and inventory icons
LoadBasicChunks();
+ // External font
+ if (TinselV1PSXJapan) {
+ OpenPsxJapanFont();
+ }
+
// Continuous game processes
CreateConstProcesses();
diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h
index 85aacbf8338..7e0a3d76c95 100644
--- a/engines/tinsel/tinsel.h
+++ b/engines/tinsel/tinsel.h
@@ -132,6 +132,7 @@ typedef bool (*KEYFPTR)(const Common::KeyState &, const Common::CustomEventType
#define TinselV1PSX (TinselVersion == 1 && _vm->getPlatform() == Common::kPlatformPSX)
#define TinselV1Mac (TinselVersion == 1 && _vm->getPlatform() == Common::kPlatformMacintosh)
#define TinselV1Saturn (TinselVersion == 1 && _vm->getPlatform() == Common::kPlatformSaturn)
+#define TinselV1PSXJapan (TinselVersion == 1 && _vm->getPlatform() == Common::kPlatformPSX && _vm->getLanguage() == Common::JA_JPN)
#define READ_16(v) (TinselV1Mac || TinselV1Saturn ? READ_BE_UINT16(v) : READ_LE_UINT16(v))
#define READ_32(v) (TinselV1Mac || TinselV1Saturn ? READ_BE_UINT32(v) : READ_LE_UINT32(v))
More information about the Scummvm-git-logs
mailing list