Problem
Każdy kto choć raz miał przyjemność tworzyć konkurs oparty o grę online wie jakim problemem są oszuści. Jeśli nie dysponujemy dużym budżetem pozwalającym nam na utrzymanie farmy serwerów odpowiadających za logikę gry i zmuszeni jesteśmy cały silnik umieścić po stronie klienta tj we flashu możemy być pewni, że za chwilę pojawią się osoby, chcące uprzykrzyć życie innym graczom udowadniając, że są w stanie podrobić wynik uzyskany w grze.
Problem jest właściwie nie do rozwiązania i każdy jeden sposób na zabezpieczenie ma swoje wady.
Idea
Jednym ze sposobów na wyeliminowanie oszustów jaki ostatnio zastosowaliśmy jest rejestracja rozgrywek. Najprościej będzie to zobrazować na przykładzie czarnej skrzynki z samolotu. Wszystkie informacje jakie mogą mieć wpływ na przebieg rozgrywki są rejestrowane od początku do końca gry. W przeciwieństwie jednak do czarnych skrzynek, my dane o rozgrywce przesyłaliśmy cyklicznie w postaci paczek skompresowanych danych na nasz serwer podczas trwania gry, a nie dopiero po jej zakończeniu.
Po zakończonej grze wierna kopia SWFa z grą, która różni się od oryginału tylko klasami odpowiadającymi za nawigowanie w grze (zamiast informacji o położeniu myszy czy wciśniętych przyciskach klawiatury gra przyjmuje dane z rejestratora) pozwalała moderatorom na obejrzenie całej rozrywki dokładnie tak jak ona przebiegała na komputerze gracza.
Rozwiązanie
Jakie dane zapisywaliśmy?
W skrócie: wszystko co się dało, wszystko co mogło mieć jakikolwiek wpływ na przebieg rozgrywki. Jedna z gier polegała na jak najdłuższym odbijaniu głową piłki. Nawigacja piłkarzem odbywała się za pomocą myszy. Za symulację zachowania piłki odpowiadał silnik Box2D.
Zagraj w grę LongJohn (SWF)
Wypróbuj online
By móc odtworzyć rozgrywkę musiliśmy zapisywać jej stan z każdej kolejnej klatki. W odpowiedzi na zdarzenie Event.ENTER_FRAME zapisywaliśmy więc m.in.:
- aktualny numer klatki (po każdym zdarzeniu Event.ENTER_FRAME licznik powiększał się o jeden)
- aktualne położenie myszy X/Y
- aktualne położenie x/y piłkarza (przesuwał się on z pomocą tweena – nie był więc zawsze tam gdzie kursor myszy)
- aktualna realna ilość FPSów (nie ma to wpływu na grę, ale pomogło wyłapać oszustów, którzy spowalniali flash playery – była to gra zręcznościowa i im wolniej działała tym latwiej było w nią grać)
- aktuane położenie x/y odbijanej piłki
- aktualny stan grawitacji (w grze występowało utrudnienie w postaci wiatru, który miał wpływ na siłę i kierunek przyciągania)
Jak zapisywaliśmy te dane
Zdecydowaliśmy się na ich zapis jako tablicę obiektów reprezentujących dane z kolejnych klatek. By uniknąć nadmiernej ilości danych, do nowo stworzonego modelu dodawaliśmy nowe dane tylko wtedy gdy uległa ona zmianie w stosunku do modelu stworzonego klatkę wcześniej. Jeśli więc nie poruszyliśmy myszką w klatce nr 12, nie zapisywaliśmy o tym informacji w modelu nr 5, a dopiero w np. modelu nr 10 kiedy wykonany został ruch.
// f - numer klatki w grze
// gX - grawitacja pionowa (zmienna silnika Box2D)
// gY - grawitacja pozioma (zmienna silnika Box2D)
// gt - ilosc klatek, ktore minely od ostatniej zmiany zmiennych gX lub gY
// bY - polozenie pilki na osi Y
// bX - polozenie pilki na osi X
// f2 - aktualny framerate w grze
// uX - polozenie pilkarza na osi X
// t - czas gry w milisekundach
// mX - polozenie kursora myszy na osi X
// mY - polozenie kursora myszy na osi Y
==========================================
["{'f':2,'gX':0,'mY':26,'gY':8,'gt':2,'bY':-349.2,'f2':23.369036027263874,'uX':0,'t':4,'mX':13,'bX':0}",
"{'f':3,'mY':32,'gY':8.012,'bY':-348.4,'uX':2.45,'t':8,'mX':15,'gt':3}",
"{'f':4,'mY':34,'bY':-347.3,'uX':4.85,'t':12,'mX':13,'gt':4}",
"{'f':5,'mY':36,'bY':-345.95,'uX':6.35,'t':16,'mX':12,'gt':5}",
"{'bY':-344.35,'f':6,'uX':7.45,'t':20,'mY':35,'gt':6}",
"{'bY':-342.5,'f':7,'uX':8.4,'t':24,'gt':7}",
"{'bY':-340.35,'f':8,'uX':9.25,'t':28,'gt':8}",
"{'bY':-337.95,'f':9,'uX':9.95,'t':32,'gt':9}",
"{'bY':-335.3,'f':10,'uX':10.55,'mX':14,'t':36,'gt':10}"
[...]
Co dziesięć sekund tworzyliśmy dodatkowo screena w postaci mocno skompresowanego pliku JPG o niewielkiej rozdzielczości w celu późniejszej analizy czy dane z paczek odpowiadają obrazowi, czy nijak się mają do tego co user widział na ekranie (np inna ilość punktów na screenie a inna w paczce).

screen dołączony do paczki. JPG 30%
Screen oraz plik tekstowy zawierający zserializowaną JSONem (biblioteka as3corelib od Adobe) tablicę modeli pakowaliśmy do pliku ZIP za pomocą biblioteki Davida Changa i otrzymawszy paczkę o wadze nie przekraczającej 15kB wysyłaliśmy ją na serwer. Następnie dane historyczne były usuwane z tablicy i cała procedura rozpoczynała się od początku.
Przykładowa paczka (ZIP 10Kb)
Pobierz próbkę do badań
Odczyt i analiza danych
Nie było to trudne – wystarczyło podmienić w grze klasy odpowiadające za sterowanie i zamiast myszy użyć do sterowania danych otrzymanych z paczek (oczywiście uprzednio je rozkompresowując i deserializując). Dodatkowo na bieżąco zmienialiśmy framerate swfa na taki jaki został zapisany w paczkach co pozwoliło nam obserwować jak przerażająco nudna i długa jest nasza gra, gdy ktoś spowolnił sobie Flash Playera do 2 klatek na sekundę :).
Dodatkowo wyświetlaliśmy sobie dwa liczniki punktów – jeden, gdzie punkty liczyły się na bieżąco przez silnik gry i drugi – z punktami odczytanymi z paczek. Rekordziści dodawali sobie po kilkaset procent punktów gratis :)
Prócz samego oglądania nagranych gier ważnym czynnikiem był dla nas czas zapisu na serwerze kolejnych paczek.
Wysyłaliśmy je równo co 250 klatek. Zakładając, że ktoś miał wolny komputer i gra nie działała mu z pełną predkością 25 klatek na sekundę a np z 12fps/sek – wtedy wysyłka paczki odbywała się co 20 sek. Przy 6fps/sek – co 40 sek. Jeśli więc FPS zapisany w paczkach (zmienna f2) był relatywnie wysoki a czas zapisu na serwerze kolejnych paczek był bardzo długi (niektóre gry miały odstęp pomiędzy kolejnymi zapisami powyżej 5 minut!) – jasne było, że ktoś ingerował w ich treść. Analiza FPSów i czasów zapisu pozwaliła nam również wyłapać wszystkich, którzy rozpoczynali grę mając płynny obraz, ale już po chwili włączali spowalniacze by móc w spokoju zdobywać punkty.
Zobacz przykładowy odczyt rozgrywki (SWF)
Wypróbuj online
Wady
Niestety rozwiązanie to nie jest lekarstwem na całe zło. Jeśli tylko jego będziemy używać do moderacji, ktoś może nas zasypać milionami podrobionych paczek co uniemożliwi ich sprawną analizę. Jest to główna wada tej techniki, pozwala nam ona jednak, w połączeniu z innymi mechanizmami obrony przed oszustami na ograniczenie ilości podrobionych gier w końcowym rankingu.
Ponadto potrafi ono obciążyć serwer jeśli gra jest wyjątkowo popularna. Da się to oczywiście zminimalizować wysyłając paczki rzadziej i jeszcze bardziej kompresując screeny lub wręcz z nich rezygnując, ale zawsze będzie to duża ilość danych wysyłanych do serwera – trzeba więc wcześniej uprzedzić administratorów o tym fakcie, by mieli szanse na przygotowanie się na spory ruch przychodzący.
