I made this on paint in 25 seconds lol
Super Battle Golf https://en.wikipedia.org/wiki/Super_Battle_Golf is a fun multiplayer video game where you and your friends can play golf with wild, dangerous tools to hinder them. One might even call this game an iteration of "friend slop". It's fun! Try with friends.
However, I don't recommend playing this game with random players. As always online, you get unsavoury people with even more unsavoury chat messages. Thankfully, the game's text chat censors bad words. But how does this censor work? What are the forbidden words? Let's dive in.
- How I extracted the list of bad words from the game
- Analyzing the words and finding interesting stuff
Extracting censored words
First, we need to find out how the censoring work, and how to extract the logic of that censor. After a bit of testing, I concluded that chat censoring is performed client-side (i.e. your chat messages aren't sent to a server to be analyzed and then censored). There is an algorithm in the video game program that censors chat, somewhere, and we need to look for it.
Using ILSpy https://github.com/icsharpcode/ilspy, we can explore the game's code,
since it's a Unity game. Browsing around all the different classes, we can find a class named
TextChatManager, which contains a function named SendChatMessage:
public static void SendChatMessage(string message)
{
if (!SingletonNetworkBehaviour<TextChatManager>.HasInstance)
{
return;
}
if (GameSettings.All.General.MuteChat)
{
TextChatUi.ShowMessage(string.Format(Localization.UI.TEXTCHAT_Info_ChatMuted, GameManager.UiSettings.TextRedHighlightStartTag + "<b>", "</b>" + GameManager.UiSettings.TextColorEndTag));
return;
}
if (message.Length > SingletonNetworkBehaviour<TextChatManager>.Instance.maxMessageLength)
{
message = message.Substring(0, SingletonNetworkBehaviour<TextChatManager>.Instance.maxMessageLength);
}
GameManager.FilterProfanity(message, out message);
SingletonNetworkBehaviour<TextChatManager>.Instance.CmdSendMessageInternal(message);
}
The second to last line is the most interesting: there is a function named
FilterProfanity somewhere in the game that seemingly performs the entirety of the
censoring. Let's see how it works!
public static bool FilterProfanity(string verifyString, out string filteredString)
{
try
{
filteredString = badWordsRegex.Replace(verifyString, (Match x) => GenerateCensorString(x.Length));
return verifyString != filteredString;
}
catch (Exception exception)
{
Debug.LogError("Encountered exception when filtering string for profanity!");
Debug.LogException(exception);
filteredString = verifyString;
return false;
}
static string GenerateCensorString(int len)
{
char[] collection = new char[6] { '!', '$', '@', '#', '%', '&' };
List<char> value;
using (CollectionPool<List<char>, char>.Get(out value))
{
string text = string.Empty;
for (int i = 0; i < len; i++)
{
if (value.Count == 0)
{
value.AddRange(collection);
value.Shuffle();
}
int index = UnityEngine.Random.Range(0, value.Count);
char c = value[index];
value.RemoveAt(index);
text += c;
}
return text;
}
}
}
Besides the very funny and inefficient censored string generator, we can see that they filter out bad
words using a simple regex query, which tells us that the game is most probably using a set
list of words to be filtered out, and not a complex algorithm to detect context or word
associations. But how is this badWordsRegex generated in the first place?
By digging around the GameManager class, I found a function named
InitializeBadWords that does exactly that:
private void InitializeBadWords()
{
if (badWordsRegex != null)
{
return;
}
try
{
List<string> value;
using (CollectionPool<List<string>, string>.Get(out value))
{
HashSet<string> whitelist;
using (CollectionPool<HashSet<string>, string>.Get(out whitelist))
{
string[] array = StringToArray(badWordsWhitelist.text);
foreach (string item in array)
{
whitelist.Add(item);
}
TextAsset[] array2 = badWordsTextAssets;
foreach (TextAsset textAsset in array2)
{
value.AddRange(from x in StringToArray(textAsset.text)
where !string.IsNullOrWhiteSpace(x) && !whitelist.Contains(x)
select x);
}
badWordsRegex = new Regex("\\b(" + string.Join("|", Enumerable.Select(value, Regex.Escape)) + ")\\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
}
}
catch (Exception exception)
{
Debug.LogError("Failed to load bad words list!");
Debug.LogException(exception);
}
static string[] StringToArray(string text)
{
return text.ToLower().Split(new string[3] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);
}
}
Interestingly, there seems to be 2 different sources of words: a whitelist and a list of bad words, separated in different assets. The whitelist probably contains words that could be profanities but are allowed specifically for the game, or maybe very common words that shouldn't be caught but are included in the set. The presence of a whitelist, to me, indicates that the list of bad words is not bespoke, and is part of some kind of package or open-source list, and that the developpers chose to allow specific words that were otherwise included in that list.
Unfortunately, this is where ILSpy stops being useful. As written in the code, the list of bad words is contained in Unity text assets, and ILSpy is unable to extract these assets. For this, I used AssetRipper https://assetripper.org/. This is not an endorsement or an advertisement, I just found this tool to work. It starts a local web server that processes your assets. I just fed it the folder where Super Battle Golf was installed, and after a minute or two, it extracted all the assets.
The web interface of Asset Ripper
Searching for "TextAsset" in AssetRipper gave me a list of text files with two-letter (or three-letters, for Filipino) country codes, in addition to "whitelist" and "banned". These are the censored words! It makes sense the code specified "bad word text assets" in plural: there is a file with censored words for multiple languages, in addition to a general "banned" list and the white list. We can quickly download it all, and have fun with it.
Analyzing the bad words
First, the files themselves. There are 28 text files used for censoring: whitelist.txt,
banned.txt, as well as censored words for 26 supported languages:
| File name | Language | Size | Number of entries |
|---|---|---|---|
ar.txt |
Arabic1 | 370 B | 38 |
cs.txt |
Czech | 322 B | 39 |
da.txt |
Danish | 167 B | 19 |
de.txt |
German | 594 B | 65 |
en.txt |
English | 22,801 B | 2,487 |
eo.txt |
Esperanto | 316 B | 37 |
es.txt |
Spanish | 525 B | 58 |
fa.txt |
Farsi | 356 B | 362 |
fi.txt |
Finnish | 1,216 B | 122 |
fil.txt |
Filipino | 65 B | 9 |
fr.txt |
French | 692 B | 79 |
hi.txt |
Hindi | 633 B | 78 |
hu.txt |
Hungarian | 876 B | 96 |
it.txt |
Italian | 1,440 B | 156 |
ja.txt |
Japanese | 2,408 B | 172 |
ko.txt |
Korean | 660 B | 72 |
nl.txt |
Dutch | 1,574 B | 163 |
no.txt |
Norwegian3 | 334 B | 38 |
pl.txt |
Polish | 374 B | 43 |
pt.txt |
Portuguese | 528 B | 64 |
ru.txt |
Russian | 2,135 B | 1514 |
sv.txt |
Swedish | 313 B | 39 |
th.txt |
Thai | 524 B | 31 |
tr.txt |
Turkish | 1,416 B | 127 |
uk.txt |
Ukrainian | 2,253 B | 150 |
zh.txt |
Chinese5 | 2,974 B | 316 |
banned.txt |
N/A | 46 B | 6 |
whitelist.txt |
N/A | 58 B | 9 |
1 While ar refers to all Arabic languages, some
entries in this file refer to specific languages, mostly Egyptian Arabic.
2 The very first entry in the Farsi list is an empty line, so technically there are 37 entries, with the first entry being the empty string.
3 As far as I can tell using Wiktionary, the entries in this one are words that are present in both Bokmål and Nynorsk.
4 Some of the entries are duplicated from their standard Cyrillic written form with a transliteration into the Latin alphabet.
5 While zh refers to Chinese languages as a
whole, the entries in this file use simplified characters and are specific to Standard
(mainland) Chinese.
The files can be downloaded from my website ./files/; a ZIP file containing all the text files is included for your convenience.
The whitelist
The filewhitelist.txt contains 9 entries:
- am
- words
- mama
- fan
- balls
- prutt
- banger
- stroke
- pistol
These were probably added by the developpers of the game after including the list of bad words and seeing seemingly innocuous English words be caught by a censor from another language, or is useful in the current context. Some of them are obvious, for example "balls" should probably not be censored in a game where you play with balls. Others are a bit more obscure. Here is my attempt at explaining each entry:
- "am" is included in the Turkish censored list because it means "pussy" https://en.wiktionary.org/wiki/am#Turkish, but is a very common word in English.
- "words" is included in the English censored list. I have no idea why.
- "mama" is included in the Portuguese censored list because it means "breast" https://en.wiktionary.org/wiki/mama#Portuguese, but is otherwise a very common way to refer to a mother in English (and a lot of other languages!).
- "fan" is included in both the Norwegian and Swedish censored lists because it is a very common swear word https://en.wiktionary.org/wiki/fan#Swedish, but is a very normal and common word in English.
- "balls" is included in the English censored list because it can refer to testes, but it is a very common word used in golf.
- "prutt" is included in the Swedish censored list because it means "fart" (or sometimes "asshole") https://en.wiktionary.org/wiki/prutt#Swedish. Since Brimstone, the developer team, is based in Sweden, my theory is that they think "prutt" is innocent and innocuous enough to be allowed, based on their native understanding of the language (as opposed to, say, grabbing a list of swear words from a language you don't know).
- "banger" is included in the English censored list because it has various slang meanings related to sex https://en.wiktionary.org/wiki/banger#English, but is used colloquially nowadays in a positive, innocent way to mean "a very good or impressive thing".
- "stroke" is included in the English censored list for the same reasons as "banger", but is a very common term in golf.
- "pistol" is included in the English censored list for some reason. The list doesn't include other firearm nouns, and the only other thing related to "pistol" in the list is "lovepistol", which I cannot find any references online for, except for a yaoi manga with almost the same name. I really don't know.
The banned list
The file banned.txt functions as the opposite of the whitelist: words that are not
present in the set of files the developpers downloaded, but felt like should be censored. It is not
very interesting: it only contains 6 entries. The first three are variations on "epstein" (with
symbols to replace the "i"), and the last three are variations on "diddy".
The interesting stuff in the English censored list
Most of the stuff in the English file is standard fare for censored words: slurs, sexual terms and slangs, etc. However, there are very surprising entries in there. I encourage you to look for yourself, but here are the most interesting entries I found:
- "acrotomophilia" (amputee fetishism)
- "apeshit"
- "assblaster" (there are soooo many entries that start with "ass")
- "backdoor"
- "booobs", "boooobs", "booooobs", "booooooobs"
- "cockmongruel"
- "cockwaffle" (what a sad, sad entry)
- "crabs"
- "cumquat" (and also "kumquat")
- "demon"
- "doggie"
- "dommes"
- "enlargement"
- "fourtwenty"
- "fuckingshitmotherfucker"
- "gaymuthafuckinwhore"
- "geezer"
- "hajji" (as in, the honorific title given to muslims who performed the Hajj pilgrimmage. Apparently it's also a slur for muslim people in some places)
- "hardcore"
- "hoare" (god forbid you talk about the dining philosophers problem https://en.wikipedia.org/wiki/Dining_philosophers_problem in chat)
- "ike"
- "illegal"
- "israels" (but not "israel")
- "jacktheripper"
- "jerry"
- "kinkyjesus"
- "knob"
- "leatherrestraint"
- "lmfao"
- "lucifer"
- "magicwand"
- "orgasim;" (yes, with the typo and the semicolon)
- "pedobear"
- "poof"
- "puke"
- "purinapricness" (yes, with the typo)
- "pussydestroyer" (I wonder if Joel had a hand in this https://youtu.be/zlUQgbxR2Ic?t=555...)
- "queerbait"
- "shemale"
- "shitoutofluck"
- "sixsixsix"
- "smoker"
- "trois" (we'll come back to this)
- "vixen"
- "vodka"
- "whitey"
- "whorealicious"
- "worldsex"
- "wtf"
- "yed"
- "yiffy" (but not "yiff")
The Brest problem
There are some words that are profane or otherwise very censorable words in a specific language, that mean totally normal things in other languages. This is, in my humble opinion, a generalization of the Scunthorpe problem https://en.wikipedia.org/wiki/Scunthorpe_problem, that I have chosen to call the Brest problem, after the French city of Brest. "Brest" means nothing in French, but sounds like (and is almost written the same way as) "breast" in English.
This problem occurs in Super Battle Golf's censoring system. Some of the occurences of the problem were caught during development and/or quality assurance, and were included in the whitelist; but others flew under the radar. Some examples I found, based on my knowledge of English and French:
- "trois", included in the English list. Probably included to try to censor "ménage à trois" (a borrowing from French used as a fancy way to say "threesome"), especially since "menage" (without the accent) is also present in the file. However, on its own, "trois" just means "three", as in the number three. As a result, you cannot write the French word for the number "three" in the chat. This hole is "par 3"? For French speakers, it is "par &!@#".
- "dix", included in the English list. One of the many variations of "dicks" in the file, it is unfortunately also a French number: "ten". So not only can you not say "three", but you can't say "ten" either!
- "chute", included in the English list. Not only do I have no idea why it is included in there in the first place, but "chute" also means "(a) fall" in French, and is used often to refer to a "tumble".
- "fanny", included in the English list. Slang for female genitalia, it is also a common given name in French.
- "loin", included in the English list. Could be used to refer to genitalia, but it means "far" in French.
- "retard", included in the English list. Obviously a slur in English, it is also a very common word in French, free from negative connotations, and means "late" or "lateness". If you are late to school, you would say that you are "en retard".
- "sac", included in the English list. Used as slang for the testes in English, it is a very common word in French, meaning "bag".
- "bobo", included in the Filipino list. While it means "stupid" in Filipino, it also means "booboo" (as in, the childish way to refer to a small injury) in French.
- "bitte", included in the French list. Used as a variation on "bite" ("cock", "dick"), it also means "please" in German.
- "balle", included in the Italian list. While it is used in Italian as slang for the testes, it is the French word for "ball". Yes, you read that right: you cannot talk about a golf ball in French in the text chat of Super Battle Golf.
- "sega", included in the Italian list. A slang term for "handjob", it is also the name of a famous video game company.
- "del", included in the Dutch list. A slang term to refer to prostitute women, it is also a very common word in many languages, used as a preposition (e.g. in Italian, it means "of the").
So, if you were to write: "Mon sac est loin, j'ai fait une chute de dix mètres et je suis en retard de trois minutes" in the Super Battle Golf text chat (which means "My bag is far away, I fell from ten meters on high and I am three minutes late"), it would instead look like "Mon ! est @%!&, j'ai fait une %!#&! de @ mètres et je suis en #!&%%@ de @!% minutes.".
You really can't say anything in this game.
Portuguese list observations
I'm really lucky to have such good friends!
A good friend of mine, who speaks Portuguese (from Portugal) natively, shared these observations with me:
- "amador" just means "amateur". It is a bit unclear why the word is included in the list; maybe related to some kind of pornographic material category?
- "aranha" is Brazilian Portuguese specific. It is used as slang for the female genitalia, similar to the English "pussy", but in Portugal Portuguese, it simply means "spider", the animal.
- "cerveja" just means "beer". Unclear why this was added to the list; maybe in line with the English inclusion of "vodka", but then why isn't "beer" banned?
- "grelho" is a conjugation of the verb "grelhar", which means "to grill" (like, food). Extremely unclear why this was added, especially since "grelo", a slang term for the clitoris, is not present in the list.
- "inferno" means "hell". It is in line with the English list that bans terms like "lucifer" and "demon". Still very funny that they ban terms related to Christian hell.
- "queca" is a Portugal-specific term that means a "fuck", as in an act of intercourse https://en.wiktionary.org/wiki/queca.
- "torneira" is another weird example: it means "tap", as in "tap water". Neither my friend nor I know why this is included in the list.
Miscellaneous observations
- The French list includes "MALPT". Not only is it one of the only entries with capital letters, it is also a very weird inclusion: it means "merde à la puissance treize", roughly translated as "shit to the thirteenth power". In French, it is considered bad luck to wish "good luck" to theatre actors, and we say "merde" ("shit") instead. Invoking "shit" to the power of thirteen is thus a way to wish an extreme amount of good luck to actors. Why it is included in the list is beyond me.
- A majority of non-English word lists contain English words that have otherwise no meaning in that language. For example, the Danish list contains both "cock" and "fuck".
- The longest word in this list is "rosypalmandherefivesisters", at 26 characters. It is a misspelling of a slang term for the hand, specifically used for masturbation https://en.wiktionary.org/wiki/Rosie_Palmer_and_her_five_sisters.