Files
ppsspp/Common/Data/Encoding/Shiftjis.h
mariobob a42ed5e312 Encoding: Report bad UTF-16 surrogates and Shift-JIS lead bytes as invalid
UTF16_Type::next() assumed every surrogate came in a valid pair, so a lone
high surrogate swallowed the unit after it, and one right before the
terminator stepped over it and kept reading. It now returns INVALID and
consumes only the bad unit, which sceCccDecodeUTF16() already checks for.

ShiftJIS::next() returned 0xA0 and 0xFD-0xFF as single-byte characters.
Like 0x80, which it already rejected, they encode nothing (the PSP's JIS
to UCS table in pspautotests/tests/ccc/jis2ucs.bin has no entry for any of
them), so they're INVALID too.

Only sceCcc uses these decoders.
2026-10-16 22:05:12 +02:00

154 lines
2.9 KiB
C++

#pragma once
#include <cstdint>
// Warning: decodes/encodes JIS, not Unicode.
// Use a table to map.
struct ShiftJIS {
static const uint32_t INVALID = (uint32_t) -1;
ShiftJIS(const char *c) : c_(c), index_(0) {}
uint32_t next() {
uint32_t j = (uint8_t)c_[index_++];
int row;
bool emojiAdjust = false;
switch (j >> 4) {
case 0x8:
if (j == 0x80) {
return INVALID;
}
// Intentional fall-through.
[[fallthrough]];
case 0x9:
case 0xE:
row = ((j & 0x3F) << 1) - 0x01;
break;
case 0xF:
emojiAdjust = true;
if (j < 0xF4) {
row = ((j & 0x7F) << 1) - 0x59;
} else if (j < 0xFD) {
row = ((j & 0x7F) << 1) - 0x1B;
} else {
return INVALID;
}
break;
case 0xA:
if (j == 0xA0) {
return INVALID;
}
return j;
// Anything else (i.e. <= 0x7x, 0xBx, 0xCx, and 0xDx) is JIS X 0201, return directly.
default:
return j;
}
// Okay, if we didn't return, it's time for the second byte (the cell.)
if (c_[index_] == 0) {
// Truncated sequence right at the end of the string - don't consume the
// terminator, or index_ would end up one past it (OOB on the next call).
return INVALID;
}
j = (uint8_t)c_[index_++];
// Not a valid second byte.
if (j < 0x40 || j == 0x7F || j >= 0xFD) {
return INVALID;
}
if (j >= 0x9F) {
// This range means the row was even.
++row;
j -= 0x7E;
} else {
if (j >= 0x80) {
j -= 0x20;
} else {
// Yuck. They wrapped around 0x7F, so we subtract one less.
j -= 0x20 - 1;
}
if (emojiAdjust) {
// These are shoved in where they'll fit.
if (row == 0x87) {
// First byte was 0xF0.
row = 0x81;
} else if (row == 0x8B) {
// First byte was 0xF2.
row = 0x85;
} else if (row == 0xCD) {
// First byte was 0xF4.
row = 0x8F;
}
}
}
// j is already the cell + 0x20.
return ((row + 0x20) << 8) | j;
}
bool end() const {
return c_[index_] == 0;
}
int length() const {
int len = 0;
for (ShiftJIS dec(c_); !dec.end(); dec.next())
++len;
return len;
}
int byteIndex() const {
return index_;
}
static int encode(char *dest, uint32_t j) {
int row = (j >> 8) - 0x20;
int offsetCell = j & 0xFF;
// JIS X 0201.
if ((j & ~0xFF) == 0) {
*dest = j;
return 1;
}
if (row < 0x3F) {
*dest++ = 0x80 + ((row + 1) >> 1);
} else if (row < 0x5F) {
// Reduce by 0x40 to account for the above range.
*dest++ = 0xE0 + ((row - 0x40 + 1) >> 1);
} else {
// Rows 0x5F-0x7F and 0x80+ are not encodable.
return 0;
}
if (row & 1) {
if (offsetCell < 0x60) {
// Subtract one to shift around 0x7F.
*dest++ = offsetCell + 0x20 - 1;
} else {
*dest++ = offsetCell + 0x20;
}
} else {
*dest++ = offsetCell + 0x7E;
}
return 2;
}
static int encodeUnits(uint32_t j) {
if ((j & ~0xFF) == 0) {
return 1;
}
return 2;
}
private:
const char *c_;
int index_;
};