Ultima Online obchodzi 25-lecie istnienia! Wiecie, co to oznacza, prawda? Czas na opowieści!
W duchu #gamedev i z inspiracji klasykiem UO postmortem video autorstwa Raph Koster, et alPomyślałem, że fajnie byłoby podzielić się kilkoma historiami ze środkowych lat Ultimy Online, pomiędzy moimi czasami w EA Redwood Shores i Mythic Fairfax.
Dzisiejsza opowieść: techniczna analiza spalenia domów oszustów po znalezieniu “przestępczego” pierścienia nielegalnych item dupers.
Kiedy Ultima Online wystartowała po raz pierwszy, my, gracze, znaleźliśmy kilka błędów.
Mówiąc niektóre, mam na myśli, wiele.
A najcenniejszym, diabolicznym, wartym zbudowania tajnego forum internetowego do handlowania, był błąd: duping.
Możliwość powielenia przedmiotu, lub lepiej, torba pełna przedmiotówto najbardziej korzystny z możliwych exploitów w grze. Lepszy niż speed-hacking, lepszy niż bezpośrednie przenoszenie obrażeń – nawet lepszy niż włamania do domów sprzed lockdown.
Niektórzy z beta testerów mieli swój sekret. Nie wszyscy, oczywiście, ale były pewne podejrzane elementy tłumu beta testerów, które zamiast kierować się cnotą uczciwości, wolały ukrywać swoją wiedzę o exploitach, niż zgłaszać je zespołowi programistów.
Po zakończeniu bety UO i wprowadzeniu gry na rynek, nagle w gospodarkę uderzyła fala duplikacji przedmiotów.
Co to był za “pilnie strzeżony” exploit?
Nadużycie granic areaserv.
Do dziś pamiętam, jak po raz pierwszy zobaczyłem to na własne oczy.
Był rok 1997 i biegałem na północ od obozowiska orków w pobliżu miasta Cove, gdy zobaczyłem, że jacyś dziwacy robią najdziwniejszą rzecz w życiu: biegali tam i z powrotem po tym irytująco lagowatym fragmencie mapy gry, zrzucając przy tym na ziemię małe skrzynie.
Wiedziałem, że ten obszar jest… najgorszy.
Widzi Pan, była taka naprawdę irytująca część mapy na tej polnej ścieżce między górami a twierdzą orków, która nie reagowała i szarpała się, gdy się po niej przechodziło.
W rzeczywistości, jeżeli *ran* W przeciwnym razie może dojść do sytuacji, w której stracą Państwo kilka sekund czasu gry, ponieważ odbiją się Państwo od poprzedniej pozycji, zanim serwer “nadąży”.
(Przypomnijcie mi Państwo, żebym kiedyś opowiedziała o interpolacji serwera/klienta UO i tick-rate!)
Było to szczególnie złe, gdy gonił Pana jakiś potwór, na przykład niewygodnie umieszczony na południe od Pana kapitan orków, który podczas żałośnie opóźnionej próby ucieczki zatłukł Pana na śmierć.
Proszę sobie wyobrazić moje zdziwienie, gdy ci dwaj gracze zaczęli z podnieceniem krzyczeć do siebie w tekście (który unosił się nad ich głowami), że “udało się! omgz!”.
Tak, udało im się, jak się chwalili, wymyślić sztuczkę, aby upuścić skrzynię po jednej stronie “laggy patch”, jednocześnie próbując ją podnieść/przekazać drugiemu graczowi, gdy obaj przechodzili z jednej strony na drugą i teraz każdy z nich miał kopię tej samej skrzyni: i jej zawartość.
Duplikaty!
To, co widziałem, to exploit oparty na granicach “areaserv”, które dzielą grywalny świat Ultimy Online.
UO nigdy nie robiła niczego w prosty sposób; w końcu jej pierwsza generacja projektantów próbowała rzeczy – wymyślała zupełnie nowe rzeczy – na które późniejsze gry online po prostu kręciły głowami i mówiły “nah, to zbyt trudne”.
Na przykład: jak w 1997 roku zrównoważyć obciążenie tysięcy graczy na ogromnej mapie o powierzchni 29.360.128 metrów²?
Zamiast tworzyć różne “strefy” z ekranami ładowania lub “długie, zamglone przełęcze górskie”, twórcy UO wymyślili po prostu prostokątne, bezszwowe “pod-mapy” z (dość!) niewidoczną metodą, która pozwalała po prostu przejść z jednej strony na drugą, podczas gdy rzeczy po drugiej stronie były nadal widoczne i aktualizowane w czasie rzeczywistym.
Tak więc każdy serwer gry (lub “shard”), na którym Państwo grali, był w rzeczywistości podzielony na “areaervs” i istniał niewiarygodnie dobrze napisany kod “mirror”, który obsługiwał przekazywanie stanu gry, warunków obiektów i zdarzeń z jednej strony granicy na drugą.
Ale przekraczanie tych granic jako gracz, czasami, było zauważalnie lagowate.
Poza tym: Jeśli spojrzy Pan na powyższą mapę areaserv, może Pan zauważyć, że Britannia powinna być podzielona nieco ostrożniej, aby granice areaserv nie przecinały ważnych/zaludnionych obszarów gry… proszę spojrzeć na biedne Buccaneer’s Den: Nie bez powodu te ukryte, podziemne tunele nigdy nie były przyjemne dla PvP..
Serwery obszarowe kopiowały zasadniczo postać gracza i przesyłały między sobą wiadomość zawierającą wszystkie informacje. Gdy gracz przekraczał granicę, niszczył starą kopię gracza po stronie początkowej.
I to nie tylko gracze: autonomiczne obiekty ruchome (moby), jak potwory, zwierzęta i postacie niezależne, również mogły je przekraczać.
(Uwaga: dla przyszłych exploitów te potwory i zwierzęta również mieli własne plecaki/inwentarze, na których gracze mogliby wykorzystać próby exploitów; wiele dramatów z udziałem lam).
Oczywiście każdy błąd w kodzie areaerv stanowił największe prawdopodobieństwo wystąpienia exploitów typu “duping”, zwłaszcza w połączeniu z celową manipulacją stanem gry gracza przed wykonaniem backupu/wyłączeniem serwera każdego ranka.
Po dołączeniu do zespołu UO dowiedziałem się kilku rzeczy:
- Kod areaserv jest genialny. Był wielokrotnie poprawiany i przepisywany, ale w moich późniejszych latach stał się już całkiem “inteligentny”, jeśli chodzi o sposób przewidywania stanu gracza i wstępnego serializowania obiektów, zanim jeszcze doszło do przeniesienia granic.
- Jednak niezależnie od tego, jak dobry był kod, my, programiści, wprowadziliśmy do gry tyle złożoności, że źródeł duplikatów nie brakowało – i często nie były to już tylko problemy związane z areaserv. Nie pomogło to, że projektanci gry mogli pisać kod na poziomie produkcyjnym w Wombat (nasz własny język skryptowy oparty na zdarzeniach), który zawierał mnóstwo wrapperów dla kodu zarządzania obiektami C++.
Nie mogliśmy być bardzo proaktywni w kwestii duplikatów i zależało nam na zgłoszeniach graczy & obsłudze klienta, aby zidentyfikować rażące exploity.
Pewnego dnia wpadłem na dziwny pomysł.
Jest kilka rzeczy, które trzeba wiedzieć o UO, jak to było w połowie lat 2000.
- Każdy shard wykonywał sekwencję zamykania/tworzenia kopii zapasowych o określonej godzinie we wczesnych godzinach porannych. (Świetnie, że nie trzeba było naprawiać wycieków pamięci!).
- Kompletny stan gry każdego z obszarów był zrzucany z pamięci do dużego binarnego pliku kopii zapasowych – w momencie, gdy dołączyłem do zespołu, ich rozmiar zbliżał się do 4 GB.
- Po zakończeniu backupu areaservy wyłączały się, restartowały i przechodziły w stan gotowości, podczas gdy “gameserv” robił to samo. Można pomyśleć, że gameserv to serwer, który koordynuje wszystkie areaservy i przekazuje zalogowanych graczy do właściwego areaservu.
- Serwer gier wyłączał się, restartował, a następnie wydawał polecenie, aby każdy areaerv załadował swoją ostatnią znaną dobrą kopię zapasową.
- Każdy areaerv załaduje binarną kopię zapasową i odtworzy zapisany wcześniej stan gry; będzie to również wykonać wszelkie wyzwalacze/haki w kodzie skryptowym dla obiektów/mobili, które mówiły “Zrób X, gdy serwer się załaduje”.
- [A bunch of other stuff here, including spawning new mobs or daily rares]
- Serwery obszarowe informowały o tym, że wszystko jest w porządku i informowały serwer gier, a serwer gier ponownie ogłaszał się na serwerach logowania, że jest dostępny do gry.
Największym problemem w poszukiwaniu duplikatów był problem #2: Stan gry w UO nie znajdował się w “bazie danych” – nie było sposobu na odszukanie rzeczy należących do graczy lub obiektów w celu znalezienia nielegalnych towarów. To było 4gbs binarnego kleksa; dane miały sens tylko wtedy, gdy zostały wczytane do samej gry.
Próbowałem jednak: *chciałem* czytać binarne plamy. Chciałem* dać obsłudze klienta narzędzia do wyszukiwania zduplikowanych lub skradzionych przedmiotów. Nie było to jednak możliwe przy użyciu ówczesnych narzędzi.
Wtedy przypomniałem sobie o #5 i to podsunęło mi pomysł na inną linię ataku.
Każdy dynamiczny obiekt w UO – czy to mobile, gracz, czy przedmiot – jest w stanie przechowywać na sobie zarówno dane, jak i skrypty.
Skrypty nie zachowują stanu po zakończeniu wykonywania kodu, dlatego potrzebne dane przechowywaliśmy w “objvars” i dołączaliśmy je do tych samych obiektów, do których dołączone były skrypty.
Same skrypty zawierały mnóstwo “wyzwalaczy”, które były obsługą zdarzeń związanych z warunkami gry. Jak wspomniałem w #5 powyżej, jeden z nich miał nazwę “beforeServerLoad” (cholera, czy pamiętam nazwę), który wykonywał się w szczególności podczas fazy ładowania kopii zapasowej w procesie uruchamiania areaserv.
Pomyślałem: “A gdyby tak *zaznaczyć* najcenniejsze przedmioty w grze przy ładowaniu z kopii zapasowej?”.
Poszedłem do głównego inżyniera, Supreemi poprosił o jeden dodatek do kodu C++ i związane z nim mapowanie do “Wombat func” dla niego: funkcja haszująca (hello crypto!).
Chciałem, aby gameserv przechowywał bieżącą listę “zaznaczonych” pozycji podczas procesu ładowania areaserv, aby zidentyfikować duplikaty.
Globalny rejestr hash.
Oto jak to działało:
- Każdy obiekt w grze, po załadowaniu, miał hak do ogólnego skryptu “preload”, który dołączał się, wykonywał i odłączał, zanim pozwolił obiektowi wykonać inne skrypty. Było to bardzo przydatne przy czyszczeniu błędów wdrażania, jeżeli coś zrobiliśmy źle i w trakcie łatania uszkodziliśmy jakieś obiekty w grze.
- W skrypcie preload dodałem wyzwalacz ze zdarzeniem “beforeServerLoad”, który sprawdzał typ przedmiotu: jeżeli znajdował się na ustalonej wcześniej liście najdroższych/najrzadszych typów przedmiotów, kontynuował plan.
- Jeżeli przedmiot nie posiadał “tagu”, został on teraz utworzony: objvar typu “string”, który zawierał hash aktualnego czasu, identyfikator areaserv i gameserv (oraz inne unikalne dane). Był to naprawdę długi, losowo wyglądający fragment tekstu (większy niż 32-bitowa liczba całkowita), jak “29bb546a415ff874e5129549fe8064249e8f1b2996fa2e7d52879d2ec24e06fd”.
- Po uzyskaniu tagu (lub jeśli już go posiadał), wysyłał wiadomość do gameserv (pamiętajmy, że na każdym shardzie był tylko jeden taki tag) i prosił gameserv o umieszczenie go w rejestrze wyszukiwania. Jeśli *tag* istniał już w rejestrze, przedmiot był trwale oznaczany jako “I AM DUPED” (ponownie, z objvar) w jawnym tekście – ponieważ przedstawiciele Działu Obsługi Klienta w kliencie Boga mogli to zobaczyć.
- Rejestr lookup utrzymywał się tylko do momentu załadowania wszystkich areaservs, potem był usuwany, aby zaoszczędzić pamięć.
Chodziło o to, że jeżeli wartościowy przedmiot (warty duplikatu) ma unikalny hash zapisany na sobie jako objvar, to proces duplikacji – jakikolwiek by nie był – skopiowałby tę samą wartość objvar. W ten sposób powstałyby dwa unikalne obiekty posiadające ten sam hash.
Najlepiej sprawdzać to (masowo) podczas obciążenia serwera, a system ten oznaczał wszystkie obiekty za pomocą objvar “I AM DUPED”.
(Uwaga: moja pamięć może być tu niewyraźna i być może trzeba było powiązać skrypt również ze zdarzeniem onPlayerLoad; nie pamiętam tylko, czy wszyscy gracze byli ładowani z areaserv, czy też byli pobierani na żądanie z danych zapasowych).
To było jak stemplowanie rzadkich przedmiotów jakimś niewidzialnym tuszem, który fluoryzował tylko w określonych warunkach.
Udostępniliśmy więc zaktualizowany kod i pozwoliliśmy mu działać globalnie przez kilka tygodni.
I wtedy zaczęło się polowanie.
Mwaha. Mwahaha. MWAHAHAHAHA!
W ciągu kilku tygodni zdaliśmy sobie sprawę, że pierwsza faza się udała: znaleźliśmy mnóstwo zduplikowanych rzadkich przedmiotów, które w większości należały do tego samego zestawu kont graczy na wielu serwerach gry.
Pomimo naszego podekscytowania, w tym miejscu zatrzymaliśmy się: co teraz zrobimy?
Usiedliśmy, aby porozmawiać z kierownictwem, działem obsługi klienta i naszym Community Managerem.
Zarząd
Nasz producent był podekscytowany, że może wyjaśnić nasze osiągnięcia dyrektorowi generalnemu studia. Pamiętam, że siedziałam na spotkaniu z uśmiechem na twarzy, zadowolona jak mało kto (jak tylko arogancki młody gamedev może być), bo usłyszałam coś, czego się nie spodziewałam:
“Mmm, nie sądzę, aby usunięcie ich wszystkich [the duped items] to dobry pomysł, skrzywdzi Pan zbyt wielu graczy”.
W zasadzie nie brałem tego pod uwagę.
W ogóle.
Byłam zbyt podekscytowana faktem, że osiągnęłam swój długo utrzymywany cel, aby “złapać kilka dupereli”.
Chciałam się skrzywić i coś powiedzieć, może zapewnić, że on po prostu nie zrozumiałPo spotkaniu wróciłem do zespołu i powiedziałem im, że nie wolno nam automatycznie usuwać wszystkich duplikatów.
Obsługa klienta
Udaliśmy się więc do zespołu CS po dane.
To była prawda. Byłby to fatalny pomysł.
Duplikaty tak szybko się rozprzestrzeniały, że gdybyśmy je po prostu usunęli spod wszystkich graczy, którzy kupili je (za swoje ciężko zarobione złoto) od duplikatów, to wpłynęlibyśmy na wielu graczy. znaczące część bazy graczy.
Z pewnością część z nich nie miałaby nic przeciwko “moralności” naszego działania – ale w skali setek czy tysięcy poszkodowanych graczy (na każdym shardzie) tylko prosiliśmy się o to, aby frustracja wywołała falę cichych odejść.
Naprawdę nie powinno mnie to dziwić: dyrektor generalny naszego studia prowadzi gry MMO o wiele dłużej niż ja.
Aby jeszcze bardziej skomplikować sprawę, Dział Obsługi Klienta zaczął zadawać nam trudne pytania, takie jak: “Ile dokładnie zduplikowanych runicznych młotów walorytowych powinna mieć osoba, zanim ją zbanujemy?”
Chwila, mieliśmy przypisać arbitralną wartość do tego, ile duplikatów w posiadaniu jednej osoby jest podejrzane? Aha. O rany.
To było trudniejsze niż kod. To był projekt społeczności, a nie tylko projekt gry.
Community Manager
Na szczęście mieliśmy rozsądnego community managera, który był przyzwyczajony do naszej nadmiernej bujności. Szczerze mówiąc, byli po prostu przyzwyczajeni do zarządzania mną zawsze, gdy za bardzo się czymś pasjonuję.
“CS zabroni im [the actual dupers] i tak. Dlaczego nie zrobić z tego wydarzenia?” – tak mniej więcej przebiegała ta rozmowa.
Byłam głównym projektantem wydarzeń na żywo, więc… ustaliliśmy.
Zidentyfikowaliśmy samych duplerów i ich składy: mieli domy pełne swoich zduplikowanych przedmiotów i sprzedawców NPC, którzy sprzedawali je graczom.
Pierścień duplikatów” rozciągał się na wielu serwerach, składał się z różnych grup, niekoniecznie współpracujących ze sobą. Wszyscy oni mieli jednak te same zachowania: zarabiali tony złota UO na sprzedaży duplikatów, a następnie sprzedawali je na rynkach wtórnych za twardą gotówkę.
Napisaliśmy więc z Adidą skrypt, który po podłączeniu do domu:
- Skasować dom i całą jego zawartość. Wszystko. Natychmiast. Rekursywnie. *poof*
- W wyznaczonym wcześniej prostokątnym obszarze, pasującym do wymiarów domu, powstaje masa nieruchomego “gruzu budowlanego”. Został on zabarwiony na ciemno czarny kolor, aby wyglądał jak pokryty sadzą.
- Wśród gruzów wysypać kilka wiecznych “pól ogniowych”.
- Stworzyć słomianą kukłę z napisem “Kukła zdrajcy”, aby umieścić ją pośrodku płonących gruzów.
Następnie wybraliśmy dzień i uderzyliśmy.
Dział Obsługi Klienta masowo zbanował dupków na czas, tuż przed uruchomieniem ich serwera stowarzyszonego.
Następnie wstrzymaliśmy połączenie gameserv/loginserv z tymi samymi serwerami, a Adida i ja pobiegliśmy (teleportowaliśmy się, naprawdę) do każdego miejsca zamieszkania, podłączyliśmy skrypt, popatrzyliśmy z radością, jak wybuchają pożary i ruszyliśmy dalej.
Robiliśmy to w partiach na powiązanych serwerach, tak aby każdy “pierścień dupingowy” nie zdążył się zorientować, że został zbanowany i próbował zalogować się na konta alt-account, aby opróżnić swoje domy, zanim my się do nich dostaniemy.
Zamieszanie! Chaos! Radość! Śmiech!
Na Stratics pojawiły się wątki dyskusyjne i MMORPG.com prawie od razu, pełne zdziwienia i domysłów – i kilka fałszywych sugestii, jak to, że pracownik CS został wciągnięty w banowanie (fałszywe!).
No to fajnie, ale nie róbcie tego więcej
Dziesiątki domów zostało zniszczonych w całym multiwersum Ultimy Online, a płomienie liżące sine gruzy były widocznym świadectwem determinacji naszego zespołu w walce z oszustami.
To było fantastyczne uczucie!
I powiedziano nam, żebyśmy nie robili tego więcej.
Lol.
Oberwało nam się za to, że zrobiliśmy coś tak śmiałego, publicznego wobec oszukujących graczy – mimo że zadbaliśmy o to, aby nie zidentyfikować je bezpośrednio innym graczom – ale i tak ledwo prześlizgnęli się po nich z wyższym kierownictwem.
Dział obsługi klienta został poinstruowany, aby od tej pory traktować pozycje typu “JESTEM DUPED” według własnego uznania i szczerze mówiąc, do tej pory nie wiem, czy ktoś w ogóle pamięta, że taki system istnieje (o ile w ogóle istnieje).
Kilka wyciągniętych wniosków (Satoshi Surprise?)
Czytelnicy, którzy mieli styczność z NFT, mogą zauważyć dziwne podobieństwo naszego oznaczania tych rzadkich przedmiotów (które były niezmywalne!) unikalnymi hashami do tego, jak dziś działają oparte na blockchainie niezmywalne tokeny (NFT).
Jak na ironię, nasza metoda nie działała w przypadku *stacków* obiektów (przedmiotów zamiennych, takich jak złote monety).
Ale Satoshi Nakamoto rozwiązał i ten problem! Pierwszy dokument o Bitcoinie opisuje doskonałą metodę (online lub offline) do walidacji zdarzeń oznaczonych czasem, takich jak tworzenie waluty. Pomogłoby to zapobiec duplikatom *i* zapewniłoby, że wewnętrzni źli aktorzy (z uprawnieniami do tworzenia przedmiotów) nie mogliby po prostu wygenerować dużej ilości złota, aby sprzedać je poza grą.
Nie mówię, że MMO potrzebują blockchainów, ale okazuje się, że technologia Proof-of-Work ledger miała tutaj zastosowanie! (Nic dziwnego Amazon oferuje teraz podobną usługę: QLDB).
Wreszcie, jedną z największych rzeczy, jakich się nauczyliśmy, była konieczność radzenia sobie z ekonomią *po* pożarze: zwłaszcza gdy gracze chcieli rywalizować o dostępne teraz, bardzo premium, miejsca na mieszkania.
Ale to już inna historia.
Tim “Draconi” Cotten był głównym projektantem gry Ultima Online: Stygian Abyss, a kiedy nie pisze historii z dzikich czasów gier online, pracuje nad zastosowaniem wszystkich tych ciężko zdobytych lekcji w projektowaniu Metaverse.
Proszę śledzić go na Twitterze po więcej historii związanych z Ultimą!