WARNING - THIS ARTICLE IS POORLY FORMATTED - LOOK AT OWN RISK
2009-05-01 - Tenhou has been informed of this "exploit".
2009-05-05 - Issue fixed: shuffle atrribute no longer sent with INIT
Tenhou allows people to watch recent games with a live delay of 5 minutes, so as to prevent cheating. When the server sends the information about a game that is to be watched (following a CHAT /wg ######## command), it sends more information than is required. Particularly, it sends the shuffle value of the round, from which the entire wall, and hence player hands, can be generated. There are some drawbacks, though.
To begin with, have a look at a typical sequence of commands from the server for a game to be watched.
(client->server) <CHAT text="%2Fwg%20236C59B5&tw=0" />
(server->client) <GO type="97" kansen="1"/><UN n0="%E7%B4%85%E3%81%AE%E3%81%AC%E3%81%93" n1="%E3%83%9E%E3%83%BC%E3%82%AF%EF%BC%92" n2="%73%65%69%69%63%68%69" n3="%E3%82%B1%E3%83%BC%E3%83%8E" dan="15,15,14,14" rate="1925,1999,1830,1917" sx="F,F,M,M"/><KANSEN msg="%E8%A6%B3%E6%88%A6%E9%96%8B%E5%A7%8B 22:45 JST" oya="0"/><INITBYLOG><INIT seed="2,4,0,4,5,1" ten="186,157,323,334" oya="2" hai0="48,86,0,19,98,103,11,16,14,21,133,3,41" hai1="109,47,56,135,127,59,44,75,60,110,12,126,128" hai2="134,66,92,72,33,29,46,2,30,112,121,125,79" hai3="94,49,100,70,89,69,80,104,20,5,42,93,23" shuffle="mt19937ar,796A1FDA,FFC1B652,B2E576EC,8E8053DB,21AD9358,9DAE3D36,12C03E2F,3C2FF057"/><V102/><F2/> ... </WGC>
Most of the above is rather irrelevant, but there are two things to note. First of all, although pretty insignificant, the INITBYLOG tag is never closed, and the WGC never opened, so they seem to complete each other. I just noticed this as I was writing it, but it's not that important. The second point and the focus of this text is the shuffle argument to the INIT tag. mt19937ar specifies the RNG, Mersenne Twister with improved initialization (although regular MT is fine). shuffle gives eight values to seed MT with; in the above example, it is 796A1FDA,FFC1B652,B2E576EC,8E8053DB,21AD9358,9DAE3D36,12C03E2F,3C2FF057. I'll work with this and the above log extract from hereon.
The wall is generated by the following algorithm:
unsigned long randseed; // INITのshuffleから生成 // INIT shuffle argument
for(i=0;i<136-1;++i) swap(yama[i],yama[i + (genrand_int32()%(136-i))]); // サンマは108 // 108 for 3-man
(source: Tenhou manual)
Given the above values, let's have a look at the first 54 tiles, as seen in the above log extract (first four hands, dealer's first draw and then the next tile in the wall). The first thing to note about the generated wall is that players draw from the end assuming it is a one-dimensional array. That is, yama[135..132] is the first set of 4 tiles that would be dealt as part of the dealer's hand. Following is the end 54 tiles of the generated wall array, then (i.e. the first value is yama): 134,66,92,72,94,49,100,70,48,86,0,19,109,47,56,135,33,29,46,2,89,69,80,104,98,103,11,16,127,59,44,75,30,112,121,125,20,5,42,93,14,21,133,3,60,110,12,126,79,23,41,128,102,96,...
As you can see, the tiles match with how they would be dealt in real life. Given this, a player's hand can immediately be generated and observed throughout the round. This is the basis on which this exploit works. Note the last two commands in the log extract above, V102 and F2; which mean opposite player draws 8sou, opposite player throws 1man respectively. Given this, we can predict the next tile to be drawn by the south player, the 7sou. Brilliant!
Of course, this is indeed too good to be true. While this does work, you may have figured that it relies on the game watching being in sync with the gameplay, which just doesn't happen. The 5 minute delay can be solved by delaying the game such that the game watching catches up to that round, but then the Tenhou server will stall the game watching by just as long as has been stalled in-game. That's a bit of a mouthful, so let's look in detail at the possible solutions, and why they don't work.
Games can be stalled more or less indefinitely on Tenhou by not sending your discard/cut/throw/whatever command to the server. The game will halt on your turn until you do so. The server does expect that you send Z commands frequently, though, or it will disconnect you (not a problem). This method is the most effective way of having the game replay catch up with the round. The game will halt while the 5 minute delay passes, and then the game replay starts. Once it has caught up, the shuffle can be retrieved and the wall and all players' hands generated. Fine.
The problem with this however is how Tenhou handles watching games on a delay. It is quite literal in that every turn will last just as long as it does when actually playing. The actual format goes a bit like this:
The sum of the numbers delimiting the commands is how long until the next set of turns is sent to the watching client. Suffice to say, stalling the game for five minutes means the replay will be stalled for 10 minutes until the next round. So on and so forth, the cheater would have to stall each successive round by ten, then twenty, then forty, ..., all for the game watch to catch up. This is not practical.
This seemed the only other real solution. In reality, it is actually impossible to improve the delay at all. This is another stall method, this time by not sending the NEXTREADY command to confirm the start of a round. This is more anonymous, so it would be preferable if it worked. It doesn't however, and in fact the game is not even viewable provisionally until the first round has started (which, of course, it hasn't yet because the server is awaiting a NEXTREADY from one or more clients). Useless.
If you have loads of time, this is the exploit for you. I think you would actually save time by just getting better at the game, though.