• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

Solução para tfs 1.5 aceitar acentuações sem precisa salvar como ansi

andredede

New Member
Joined
Jun 17, 2019
Messages
17
Reaction score
4
Estou trazendo uma solução para o tfs 1.5 aceitar os caracter com acentuação sem a necessidade de ter que ficar salvando como ansi ou western.

O client cip 8.6 e otc recebe a formatação latin1, o que eu fiz foi converter de utf8 para latin1 antes de enviar. assim fazendo funcionar as acentuações:

LUA:
static std::string utf8ToLatin1(std::string_view utf8String)
{
    std::string result;
    result.reserve(utf8String.size());
    
    for (size_t i = 0; i < utf8String.size(); ++i) {
        unsigned char c = static_cast<unsigned char>(utf8String[i]);
        
        // ASCII puro (0-127)
        if (c < 0x80) {
            result += c;
        }
        // Sequência UTF-8 de 2 bytes
        else if ((c & 0xE0) == 0xC0 && i + 1 < utf8String.size()) {
            unsigned char c2 = static_cast<unsigned char>(utf8String[++i]);
            unsigned int codepoint = ((c & 0x1F) << 6) | (c2 & 0x3F);
            
            // Codepoint está dentro do range Latin1 (0-255)
            if (codepoint <= 0xFF) {
                result += static_cast<char>(codepoint);
            } else {
                result += '?'; // Caractere não suportado em Latin1
            }
        }
        // Sequência UTF-8 de 3 bytes
        else if ((c & 0xF0) == 0xE0 && i + 2 < utf8String.size()) {
            // Pula os próximos 2 bytes e substitui por '?'
            i += 2;
            result += '?';
        }
        // Sequência UTF-8 de 4 bytes
        else if ((c & 0xF8) == 0xF0 && i + 3 < utf8String.size()) {
            // Pula os próximos 3 bytes e substitui por '?'
            i += 3;
            result += '?';
        }
        // Byte inválido
        else {
            result += '?';
        }
    }
    
    return result;
}

criei essa funçaõ dentro de networkmessage.cpp



e alterei a função de addstring:


LUA:
void NetworkMessage::addString(std::string_view value)
{
    std::string latin1String = utf8ToLatin1(value);
    size_t stringLen = latin1String.length();
    if (!canAdd(stringLen + 2) || stringLen > 8192) {
        return;
    }
    add<uint16_t>(stringLen);
    std::memcpy(buffer.data() + info.position, latin1String.data(), stringLen); // AQUI! Mudei de value.data() para latin1String.data()
    info.position += stringLen;
    info.length += stringLen;
}

só compilar e testar
 
Correção para a solução do andredede

A ideia está correta (converter UTF-8 → Latin-1 antes de enviar, pois o client 8.6/OTC recebe Latin-1), mas o código original tem 3 bugs:

Bug 1 (grave): destrói texto que já está em Latin-1. O client 8.6 envia o chat em Latin-1. Se um jogador escreve "ação", chegam os bytes 61 E7 E3 6F — o 0xE7 (ç) casa com o padrão de "sequência UTF-8 de 3 bytes", o código engole os 2 caracteres seguintes e o server reenvia "a?". Todo chat com acento quebra. O mesmo vale para arquivos antigos salvos em ANSI e conteúdo do banco de dados.

Bug 2: não valida os bytes de continuação (10xxxxxx). Um "Á" Latin-1 (0xC1) seguido de uma letra normal gera um codepoint lixo — engole o Á e corrompe a palavra.

Bug 3: sequências truncadas no fim da string caem em ramos errados e produzem ? múltiplos.

A correção: validar a sequência completa. Se é UTF-8 válido → converte. Se não é → o byte passa sem alteração (assume que já é Latin-1). Assim funciona com arquivos UTF-8, arquivos ANSI antigos e chat dos jogadores, tudo misturado, sem quebrar nada. Também adicionei um fast-path: strings ASCII puras (a grande maioria) não pagam o custo da conversão. Emojis/CJK/símbolos fora do Latin-1 viram ? limpo (o tamanho da mensagem é calculado depois da conversão, então o protocolo nunca corrompe).



C++:
namespace {

inline bool isUtf8Continuation(const std::string& s, size_t i)
{
    return (static_cast<unsigned char>(s[i]) & 0xC0) == 0x80;
}

// Converts valid UTF-8 to Latin-1 (ISO-8859-1), which is what the 8.6
// client (and OTC in 8.6 mode) understands. Bytes that do NOT form a
// valid UTF-8 sequence are copied through untouched: text that is
// already Latin-1 (player chat, files saved as ANSI, database content)
// passes unchanged. Codepoints outside Latin-1 (emojis, CJK, etc.) are
// replaced with '?'.
std::string utf8ToLatin1(const std::string& utf8String)
{
    std::string result;
    result.reserve(utf8String.size());

    const size_t size = utf8String.size();
    for (size_t i = 0; i < size; ++i) {
        unsigned char c = static_cast<unsigned char>(utf8String[i]);

        // Plain ASCII (0-127)
        if (c < 0x80) {
            result += static_cast<char>(c);
            continue;
        }

        // 2-byte UTF-8 sequence (U+0080 - U+07FF).
        // Valid lead bytes are C2-DF: C0/C1 would be overlong encodings,
        // and in Latin-1 they are A-grave/A-acute, so they fall through
        // to the passthrough below.
        if (c >= 0xC2 && c <= 0xDF && i + 1 < size && isUtf8Continuation(utf8String, i + 1)) {
            unsigned int codepoint = ((c & 0x1F) << 6) | (static_cast<unsigned char>(utf8String[i + 1]) & 0x3F);
            result += (codepoint <= 0xFF) ? static_cast<char>(codepoint) : '?';
            ++i;
            continue;
        }

        // 3-byte sequence (U+0800 - U+FFFF): outside Latin-1
        if ((c & 0xF0) == 0xE0 && i + 2 < size
            && isUtf8Continuation(utf8String, i + 1) && isUtf8Continuation(utf8String, i + 2)) {
            result += '?';
            i += 2;
            continue;
        }

        // 4-byte sequence (U+10000+, emojis): outside Latin-1
        if ((c & 0xF8) == 0xF0 && i + 3 < size
            && isUtf8Continuation(utf8String, i + 1) && isUtf8Continuation(utf8String, i + 2)
            && isUtf8Continuation(utf8String, i + 3)) {
            result += '?';
            i += 3;
            continue;
        }

        // Not valid UTF-8: assume it is already Latin-1, pass through
        result += static_cast<char>(c);
    }

    return result;
}

}

void NetworkMessage::addString(const std::string& value)
{
    // Fast path: pure ASCII needs no conversion
    bool hasHighByte = false;
    for (size_t i = 0; i < value.size(); ++i) {
        if (static_cast<unsigned char>(value[i]) >= 0x80) {
            hasHighByte = true;
            break;
        }
    }

    std::string converted;
    if (hasHighByte) {
        converted = utf8ToLatin1(value);
    }

    const std::string& out = hasHighByte ? converted : value;
    size_t stringLen = out.length();
    if (!canAdd(stringLen + 2) || stringLen > 8192) {
        return;
    }

    add<uint16_t>(stringLen);
    memcpy(buffer + info.position, out.c_str(), stringLen);
    info.position += stringLen;
    info.length += stringLen;
}

Tambien dejare algo mas para tfs 0.3.7-0.4 OTX 2.x.

C++:
namespace {

inline bool isUtf8Continuation(const std::string& s, size_t i)
{
    return (static_cast<unsigned char>(s[i]) & 0xC0) == 0x80;
}

// Convierte UTF-8 valido a Latin-1 (ISO-8859-1), que es lo que entiende
// el cliente 8.6/OTCv8. Los bytes que NO forman una secuencia UTF-8
// valida se copian tal cual: texto que ya viene en Latin-1 (chat de los
// jugadores, archivos guardados en ANSI, BD) pasa sin alterarse.
// Codepoints fuera de Latin-1 (emojis, CJK, etc.) se reemplazan por '?'.
std::string utf8ToLatin1(const std::string& utf8String)
{
    std::string result;
    result.reserve(utf8String.size());

    const size_t size = utf8String.size();
    for (size_t i = 0; i < size; ++i) {
        unsigned char c = static_cast<unsigned char>(utf8String[i]);

        // ASCII puro (0-127): directo
        if (c < 0x80) {
            result += static_cast<char>(c);
            continue;
        }

        // Secuencia UTF-8 de 2 bytes (U+0080 - U+07FF).
        // Leads validos son C2-DF: C0/C1 serian "overlong" y en Latin-1
        // son A-grave/A-acute, asi que caen al passthrough de abajo.
        if (c >= 0xC2 && c <= 0xDF && i + 1 < size && isUtf8Continuation(utf8String, i + 1)) {
            unsigned int codepoint = ((c & 0x1F) << 6) | (static_cast<unsigned char>(utf8String[i + 1]) & 0x3F);
            result += (codepoint <= 0xFF) ? static_cast<char>(codepoint) : '?';
            ++i;
            continue;
        }

        // Secuencia de 3 bytes (U+0800 - U+FFFF): fuera de Latin-1
        if ((c & 0xF0) == 0xE0 && i + 2 < size
            && isUtf8Continuation(utf8String, i + 1) && isUtf8Continuation(utf8String, i + 2)) {
            result += '?';
            i += 2;
            continue;
        }

        // Secuencia de 4 bytes (U+10000+, emojis): fuera de Latin-1
        if ((c & 0xF8) == 0xF0 && i + 3 < size
            && isUtf8Continuation(utf8String, i + 1) && isUtf8Continuation(utf8String, i + 2)
            && isUtf8Continuation(utf8String, i + 3)) {
            result += '?';
            i += 3;
            continue;
        }

        // No es UTF-8 valido: asumimos Latin-1 y pasa tal cual
        result += static_cast<char>(c);
    }

    return result;
}

}

void NetworkMessage::addString(const std::string& value)
{
    // Fast path: si es ASCII puro no hay nada que convertir
    bool hasHighByte = false;
    for (size_t i = 0; i < value.size(); ++i) {
        if (static_cast<unsigned char>(value[i]) >= 0x80) {
            hasHighByte = true;
            break;
        }
    }

    std::string converted;
    if (hasHighByte) {
        converted = utf8ToLatin1(value);
    }

    const std::string& out = hasHighByte ? converted : value;
    size_t stringLen = out.length();
    if (!canAdd(stringLen + 2) || stringLen > 8192) {
        return;
    }

    add<uint16_t>(stringLen);
    memcpy(buffer + position, out.c_str(), stringLen);
    position += stringLen;
    length += stringLen;
}

Este código é para TFS 1.5 com addString(const std::string& value) (como os downgrades do Nekiro). Se a sua revisão usa addString(std::string_view value) (master mais novo, como o código do post original), basta mudar a assinatura de utf8ToLatin1 e addString para std::string_view, e também a de isUtf8Continuation — o corpo é idêntico. Em engines antigas como TFS 0.4/OTX 2, a função equivalente é putString/AddString com m_MsgBuf/m_ReadPos — mesma lógica, nomes de membros diferentes.


This code is for TFS 1.5 with addString(const std::string& value) (like Nekiro's downgrades). If your revision uses addString(std::string_view value) (newer master, like the code in the original post), just change the signature of utf8ToLatin1 and addString to std::string_view, and isUtf8Continuation as well — the body is identical. On older engines like TFS 0.4/OTX 2, the equivalent function is putString/AddString with m_MsgBuf/m_ReadPos — same logic, different member names.

Este código es para TFS 1.5 con addString(const std::string& value) (como los downgrades de Nekiro). Si tu revisión usa addString(std::string_view value) (master más nuevo, como el código del post original), solo cambia la firma de utf8ToLatin1 y addString a std::string_view, y también la de isUtf8Continuation — el cuerpo es idéntico. En engines viejos tipo TFS 0.4/OTX 2, la función equivalente es putString/AddString con m_MsgBuf/m_ReadPos — misma lógica, distintos nombres de miembros.


Captura de pantalla 2026-07-22 211019.webp
 
Last edited:
Back
Top