Admit it, who as a child sat for hours on end playing a game of Dandy or Segou? And who, as the game progressed, wrote down the passwords on a piece of paper or in a specially created notebook? If this is you, and since you are reading this site, then you probably have at least once the question: “how does it work?”
I still remember the passwords of some levels from the games of my childhood — for example, BCHK from Trolls in Crazyland (Doki! Doki! Yuuenchi), 4660 from Choujin Sentai - Jetman. It can be said that these passwords are ideal in terms of convenience: they are easy to remember and difficult to make a mistake when entering. But how much information can they contain, and what is the chance to accidentally pick up such a password?
In the second case, everything is different. Obviously, a set of 4 digits gives us exactly 10,000 (10 4 ) combinations. The game contains five levels that can be completed in a different order and two levels of difficulty. Those. The password stores information about the levels and difficulty levels passed. Thus, the number of existing passwords is 2 × 2 5 , i.e. 64. Hence the probability of choosing a password is 0.0064, i.e. more than half a percent. Is it enough? On average, approximately every 156th password will be correct, and given the fairly high search speed, searches will not last long. And, frankly, in childhood we often “brute force” the game, when we didn’t want to start from the very beginning.const char* s_passwords[12] = { " ", "BLNK", // ... "MZSX" }; // ... if (strncmp(pass, s_secretPassword1, 4) == 0) { callSecret1(); return 0; } // ... for (int level = 0; level < 12; level++) { if (strncmp(pass, s_passwords[level], 4) == 0) { return level; } } return -1; uint16 toBCD(const char* pass) { uint16 result = 0; for (int i = 0; i < 4; i++) { result <<= 4; result |= (pass[i] - '0') & 0xF; } return result; } s_passwords[2][32] = { { 0x0000, // ... 0x4660 }, { 0x7899, // ... 0x5705 } }; // ... const uint16 pass = toBCD(passStr); for (int difficulty = 0; difficulty < 2; difficulty++) { for (int clearedLevels = 0; clearedLevels <= 0x1F; clearedLevels++) { if (pass == s_passwords[difficulty][clearedLevels]) { setState(difficulty, clearedLevels); return true; } } } return false;
I do not dare to ignore the classics: I am sure that many played in the original “Prince of Persia”, and not only on the “Dandy”. Game passwords are also sequences of decimal digits, but this time they do encode some data. const uint8 rand0 = rand() % 10; const uint8 rand1 = rand() % 10; char pass[8] = {0, 0, rand0, 0, 0, rand1, 0, 0}; // pass[7] = ((level >> 2) + rand1) % 10; // pass[1] = ((level & 3) + rand0) % 10; // pass[0] = ((time / 10) + rand0) % 10; // pass[3] = ((time % 10) + rand1) % 10; // sum = pass[0] + pass[1] + pass[2] + pass[3]; sum += (sum % 10) + pass[5]; sum += (sum / 10) + pass[7]; // pass[4] = sum % 10; pass[6] = sum / 10; 
Surely many are familiar with Base64 and Base32 . By definition, these are positional number systems with bases 64 and 32, respectively. The principle is simple: we divide the bit stream into values of a fixed bit length, and then, using a defined dictionary, we take the symbols as indexes by the values obtained.
The state of the game includes a set of available items (up to 12), abilities (up to 3), special items (up to 6), collected hearts (up to 8), location with an egg and information about the levels completed. But in fact, for everything except hearts and special items, one value is responsible - the progress index. This is an index in the array, where each element stores information about available items, abilities, and so on. Simply put, it is this byte that defines the set of items, abilities, and stages completed. // const uint8 s_itemsInProgress[] = { 0x8000, 0xC000, 0xC000, 0xC000, 0xE000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF800, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFE00, 0xFF00, 0xFF00, 0xFF00, 0xFF00, 0xFF00, 0xFF80, 0xFF80, 0xFF80, 0xFFC0, 0xFFC0, 0xFFE0, 0xFFE0, 0xFFF0, 0xFFF0, 0xFFF0, 0xFFFC, 0xFFFE, 0xFFFF, 0xFFFF }; // const uint8 s_powersInProgress[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0 }; // const uint8 s_accessibleEggLocations[] = { 0x04, 0x07, 0x16, 0x1B, 0x2F, 0x31, 0x41, 0x43, 0x45, 0x47, 0x4E, 0x52, 0x57, 0x87, 0x98, 0x9C, 0x9E, 0xA0, 0xA1, 0xA2, 0xA4, 0xB1, 0xB3, 0xB5, 0xFF, 0x0C }; // const uint8 s_accessibleProgressValues[] = { 0, 1, 4, 5, 6, 9, 10, 11, 14, 15, 16, 17, 20, 21, 22, 23, 26, 27, 28, 29 }; const uint8 s_version = 1; // ... uint8 data[4] = {progress, specItems | s_version, eggLocation, hearts}; uint8 password[8] = {}; for (var i = 0; i < 7; i++) { password[i] = takeBits(data, 5); password[7] ^= password[i]; } password[6] |= (tableIndex << 3); password[7] ^= password[6]; const char* s_encodeTables[] = { "3CJV?N4Y0FP78BS1GW2QL6ZM9TR5KDXH", "JT1W9M3DV5?ZKX6GC0FB2SPHR4N8LY7Q", "R0CXM8TWB3G56PKY4FVND7QL2JZ19HS?", "8JWB3PD0?RVG5L2KX4QFZ9TN1S6MH7YC" }; char passwordStr[11] = ""; int index = 0; for (var i = 0; i < 8; i++) { passwordStr[index++] = s_encodeTables[tableIndex][password[i]]; if (i == 3 || i == 5) { passwordStr[index++] = '-'; } }
In some cases, developers show originality and use graphical passwords. They could be encountered, for example, in the games of the Mega Man series. Of course, the convenience of such passwords is doubtful - especially from the habit. But this is nothing compared to the long passwords in Japanese, which, alas, I did not have the strength to cover in this article.| 0 | one | 2 | 3 | four | five | 6 | 7 |
![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
uint8 pass[12] = {}; // pass[7] = (clearedLevelsMask & 0x3) << 1; // pass[9] = (clearedLevelsMask & 0xC) >>> 1; // pass[11] = (clearedLevelsMask & 0x10) >>> 2; // pass[8] = (suitsMask & 0x3) << 1; // pass[10] = (suitsMask & 0xC) >>> 1; uint8 calcChecksum(const uint8* pass) { uint8 checksum = 0; for (int i = 0; i < 12; i++) { checksum += pass[i]; } for (int i = 0; i < 6; i++) { pass[i + 6] |= (checksum >> i) & 1; } } 
char s_swizzleTableFinal[] = {0, 6, 5, 4, 10, 1, 9, 3, 7, 8, 2, 11}; char s_swizzleTables[][] = { {0, 2, 3, 1, 4, 6, 9, 5, 7, 8, 10, 11}, {8, 2, 3, 6, 10, 1, 9, 5, 7, 0, 4, 11}, {5, 4, 3, 10, 6, 0, 9, 8, 7, 1, 2, 11}, {3, 4, 1, 2, 6, 5, 9, 10, 7, 8, 0, 11} }; void swizzlePassword(uint8* pass, uint8 clearedLevelsMask) { const uint8* swizzTable = (clearedLevelsMask == 0x1F) ? s_swizzleTableFinal : s_swizzleTables[clearedLevelsMask % 4]; uint8 swizzledPass[12] = {}; for (var i = 0; i < 12; i++) { swizzledPass[i] = pass[swizzTable[i]]; } for (var i = 0; i < 12; i++) { pass[i] = swizzledPass[i]; } } void applyIncrementTable(uint8* pass) { for (var i = 0; i < 12; i++) { pass[i] = (pass[i] + s_incrementTable[i]) % 8; } }
This case is more interesting than the previous ones, if only because the password has a variable length: as the game progresses, new characters are added. Moreover, the password stores much more data than in all previous ones taken together. As can be seen from the screenshot, the alphabet is 32 characters, and the maximum length of the entered password is 54 characters. This gives us 32 54 possible passwords of maximum length, and taking into account the variable length, there are 32 1 + 32 2 + ... + 32 54 variants. If you count the amount of information that can accommodate one password of maximum length, it will be 270 bits (log 2 32 54 ).



const char s_groupsBytes[4] = {8, 8, 8, 6}; const char* s_passDataMap[30] = { // Group 1 &currLocation, &bells, &moneyLo, &expLo &moneyMed, &expHi, &moneyHi, &kicks, // Group 2 &visitedCitiesLo, &visitedCitiesHi, &mBonusCount, &tStarsTypes, &punch, &usedTreasures, &tStars, &treasures, // Group 3 &sword, &bombs, &shield, &skboards, &robe, &dragster, &talisman, &meatbuns, // Group 4 &amulet, &sweetbuns, &light, &batteries, &whirlyBirds, &medicine }; uint8 valuesMask(const uint8* data, int group) { uint8 valuesMask = 0; const int startIndex = group * 8; for (int i = startIndex + s_groupsBytes[group] - 1; i >= startIndex; i--) { valuesMask <<= 1; if (data[i] != 0) { valuesMask |= 1; } } return valuesMask; } 
const char s_bitLengths[] = { 8, 7, 8, 8, 8, 8, 1, 7, 8, 2, 3, 4, 4, 2, 4, 2, 3, 4, 3, 4, 3, 1, 3, 1, 3, 4, 3, 4, 4, 1 }; void pushBits(uint8* data, uint8 value, int bitCount) { shiftRight(data, bitCount); writeBits(data, value, bitCount); } // ... uint8 encodedData[30] = {}; uint8 groupsMask = 0; for (int i = 3; i >= 0; i--) { groupsMask >>= 1; uint8 currMask = valuesMask(passData, i); if (currMask != 0) { groupsMask |= 0x80; const uint8 valuesCount = s_groupsBytes[i]; const int startIndex = i * 8; for (int j = startIndex + valuesCount - 1; j >= startIndex; j--) { if (passData[j] != 0) { pushBits(encodedData, passData[j], s_bitLengths[j]); } } pushBits(encodedData, currMask, valuesCount); } } uint32 calcChecksum(uint8* data, int count) { uint32 sum = 0; for(int i = 0; i < count; i++) { sum += data[i] * (i + 1); } return sum; } // ... const uint8 increment = rand() % 32; shiftRight(encodedData, 32); encodedData[3] = increment; uint32 checksum = calcChecksum(&encodedData[3], (encodedDataBitLength + 7) / 8); encodedData[0] = checksum & 0xFF; encodedData[1] = (checksum >> 8) & 0xFF; encodedData[2] = ((checksum >> 16) & 0xF) | groupsMask; uint8 password[54] = {}; uint8* header = encodedData; uint8* body = &encodedData[4]; // Encode header (3 bytes + increment = 6 chars) for (int i = 0; i < 5; i++) { password[i] = takeBits(header, 3, 5); } password[5] = increment; const int charCount = (((byteCount + 1) * 8 + 4) / 5) - 1; // Encode password data for (var i = 0; i < charCount; i++) { password[i + 6] = takeBits(body, byteCount, 5); } // Apply increment skipping increment char for (var i = 0; i < password.length; i++) { if (i != 5) { password[i] = (password[i] + increment) % 32; } } const wchar_t* s_passwordCharTable = L"—BCD\u25a0FGH+JKLMN\u25cfPQRST\u25b2VWXYZ234567"; for (int i = 0; i < charCount; i++) { std::cout << s_passChars[password[i]]; if (i % 6 == 5) { std::cout << ' '; } } uint8 data[32] = {}; for (int index = 0; index < 34; index++) { register = index; if (register == 0 && !(mask & 0x80)) { register = 9; index = register; } if (register == 9 && !(mask & 0x40)) { register = 18; // ! mask = register; } if (register == 18 && !(mask & 0x20)) { // register = 27; index = register; } if (register == 27 && !(mask & 0x10)) { return; } decodeValue(input, &data[index]); } 











Source: https://habr.com/ru/post/150958/
All Articles