What's new

Welcome to HvH Forum!

SignUp Now! Download Free HvH CS2/CS:GO Cheats, CFG, LUA/JS Scripts, And More!


SignUp Now!

Miscellaneous Beginner Source Engine Explanation of the Chat Color "exploit" in TF2

Newbie HvHer
User ID
138768
Messages
2
Reactions
4
Level
2
I was notified about an "exploit" regarding colored text. (I pardon for the first image, the community is filled with weird people)
1744982558897.png
Figure 1. Initial sighting of the "exploit".

It intrigued me. Ideated with a friend and started off trying to replicate it. First idea was to use colored character codes that I was familiar with due to dealing with colored messages on SourceMod plugins (0x01-0x07 (or 0x08 i don't remember)). After a bit of testing, I realised what I did wasn't working (but I got some interesting results)
1744982747013.png
Figure 2. "ab:", aka messing around with DispatchUserMessage and messagetypes.

Now this was all in February.

*and then...*

My friend brought it up again (today)
He had figured out a way to break chat. With colors. A variation of the same "exploit" we saw before (no images, sorry!).

So, he got colors working. How? Honestly I still don't know, but he found out an issue...
1744983238504.png
Figure 3. Apparently color characters didn't work with that variation.

Also, all of those colors were visible to people who got the broken message
1744983282598.png
Figure 4. Explanation.

Oh and then, his friend found a video on how to do the bug.

1744983316664.png
Figure 5. Friend of a friend found a video.

1744983340970.png
Figure 6. How the "exploit" works.

Notice what they said: "This will work in all chat if you are dead, it only works in team chat when alive"

...

I swear I tried that, right? Back in February? I must've tried it???

I hadn't

I completely ignored all of the signs on every colored message report I got. In ALL OF THE IMAGES the sender was either dead (ingame) or sent it to team chat.
1744983430752.png
Figure 7. I felt very very dumb.

That's it! The server must be not removing the colored characters from the message (we'll come back to this in a second). So I started off again.

after a while...

1744983471963.png
Figure 8. Crashout.

And then he said something to me
1744983502895.png
Figure 8. Server doesn't clean the color characters.

And then I started experimenting, different chats, what changes. And after a while, my screen was filled with analysis.
1744985033724.png
Figure 8b. One hell of a screenshot.

1744984743692.png
Figure 9. Color byte from server.

The server did send the color byte (0x07)! He was correct, I completely missed it. I was so sure before, that the server would clean out the colors.
(but what can you expect from a 20+ year old game)

Now, for a long time I though this line was somehow messing up the colors, didn't make a lot of sense to me (because it's after chat printing), but it was one of my thinking steps (this was actually to log chat history iirc)

1744984800940.png
Figure 10. RemoveColorMarkup.

So I started reversing, instruction by instruction. This was the "bf_read" object passed to "CHudBaseChat::SayText2" (name might be wrong, something `SayText2`).
(image is bad but the custom color byte was there (`0x07`))
1744984885044.png
Figure 11. bf_read passed in one of the registers on CHudChat::MsgFunc_SayText2.

Now, remember "ChatPrintF" from before? The one that had "RemoveColorMarkup" after it? This is the string buffer that was passed to it. The color byte. 0x07.
1744985128607.png
Figure 12. String buffer passed to ChatPrintF.

1744985261436.png
Figure 13. String buffer is stored in rsp+100.

1744985303569.png
Figure 14. Going to rsp+100 in Cheat Engine (color byte 0x07 is still there).

So something inside that had to clean it, but what?
1744985165533.png
Figure 15a. ChatPrintF from the source free.

"// Strip leading \n characters ( or notify/color signifiers ) for empty string check"

Hm could be but probably not. Everything else I doubted, but the last line...
1744985370681.png
Figure 15b. ChatPrintF from the source free.

Now, the last function called sparked my interest. InsertAndColorizeText. Sounds about right with what we're investigating.
AqEfY4E.png

Figure 16. Interesting function.

It has to be this! Finding that was also easier than expected!
Ta2atZL.png

Figure 17. Decompiled ChatPrintF in Cheat Engine.

Stepping into the function and walking through some instructions with cheat engine, our message buffer (rdx) will be (on an AllChat type message)
JgRbQr6.png

Figure 18. rdx in Cheat Engine.

We see the chat type byte 0x02 (row 1, col 1) and our color byte 0x07 (row 2, col 2).
Now if we bring up the Source SDK, we can ideate a bit.
J92z2Dd.png

Figure 19. InsertAndColorizeText from the SDK.

At first I was confused

This function is not removing colors.

Hm.

But what if...

What if the colors aren't added there at all? That if statement looks very weird...
if-statement:
if (
    m_text[0] == COLOR_PLAYERNAME    ||
    m_text[0] == COLOR_LOCATION      ||
    m_text[0] == COLOR_NORMAL        ||
    m_text[0] == COLOR_ACHIEVEMENT   ||
    m_text[0] == COLOR_CUSTOM        ||
    m_text[0] == COLOR_HEXCODE       ||
    m_text[0] == COLOR_HEXCODE_ALPHA
)
// ...

So it's checking the first byte in the message buffer passed to it. We know that the first byte shows what type of chat message we're dealing with. So this if statement accepts all (aka colors all) chat messages with those types.

...simplified...

3 || 4 || 1 || 5 || 6 || 7 || 8

At first I chuckled at this, why couldn't it be an range check, another Valve code moment? But...

What's missing? 2. Number 2. Now if we step a few images back, what was the first byte on AllChat messages?
YqjGMUj.png

Figure 20. First byte of AllChat type message.

It's 2. Just to double check my sanity, what if it isn't 2? Try sending a message when we're dead/sending a team message.
19a01324-bbf6-4b7a-9901-9117d0bba3e1

Figure 21a. Team message.

77ad6579-52b4-43b2-bdc7-ba38760bd70f

Figure 21b. AllChat message.

77ad6579-52b4-43b2-bdc7-ba38760bd70f

Figure 21c. DeadAllChat message.

656218c6-b8a4-4eb2-a39a-1e964add34f5

Figure 21d. Output of Team and DeadAllChat in chat.

4b9166d6-9659-4a2b-a46c-56a1cd1baa80

Figure 22. What does InsertAndColorizeText do exactly?

The function only adds colors for specific color types, and ignores what AllChat uses (0x02).

Mystery solved. For some reason AllChat sends messages with TextColor::COLOR_USEOLDCOLORS. Maybe it was forgotten?
04a0bdc0-1f81-4bd7-99cb-f5fc8c9185f8

Figure 23. TextColor enum.

Okay so what was the actual cause?

So the culprit was with IVguiLocalize's Find method. This function takes in our chat mode string (TF_Chat_Team, TF_Chat_AllDead etc) and spits out the localized string. Issue is, some of these formats have a prefix infront of them (the funny color character `0x01` and `0x02`), so they allow ChatPrintF to print their specified start segment in a given color!
648931ff-5d1c-45b2-aef0-0f42ca639146

Figure 24. Culprit.

(These translations are in `Team Fortress 2\tf\resource\tf_[language].txt`)

d77837cb-26c0-43c6-995d-a4ed099d1d34

Figure 25. Hidden color bytes for all message types.

As we can see, the first byte is `0x02` on AllChat messages and `0x01` everywhere else. Same what we saw with PrintChatF's "to print" buffer. Also here are all the messages (minus name and class change) that custom colors could work with.

Funnily enough, the widely known "updated localizations" update would actually fix this... who could've thought.

And thanks for reading - Franz F., out.
 

Create an account or login to comment

You must be a member in order to leave a comment

Create account

Create an account on our community. It's easy!

Log in

Already have an account? Log in here.

Top