piątek, 30 grudnia 2011

Ukryta gra na Steamie - Rusty Hearts

Tak oto kolejny wpis traktować będzie o świątecznej promocji na Steamie, jednak tym razem jego bohaterem będzie ukryta (na pewno przed polakami) gra. Gra, której z wewnątrz klienta nie odnajdziemy, ale już z przeglądarki jak najbardziej. Sęk w tym, że nie wiadomo jakiej gry szukać...

A nie wiadomo, gdyż w wielkim stosie prezentów (The Great Gift Pile) gra ta figuruje jako "Hidden" - a z darmowych Hiddenów to wyszukiwarka pokazuje jedynie mod do HL2, który to już wcale taki darmowy nie jest.

Wyszukiwarka w kliencie Steam nie pokazuje gry
Na szczęście w innych krajach gra ta jest dostępna, tak więc dość łatwo można wyszukać jej tytuł :)
A jeśli chodzi o samą instalację - wystarczy w przeglądarce wejść na stronę sklepową Rusty Hearts i kliknąć "ZAINSTALUJ", lub po prostu kliknąć w ten link (install).
A aby zdobyć to upragnione osiągnięcie (All I Want for Christmas is Sewers) trzeba jak mi się zdaje wykonać pierwszy quest dla człowieka od milicji (join militia), gdyż dopiero wtedy Ryan będzie chciał dać nam tego "A Simple Task" questa.

niedziela, 25 grudnia 2011

Steam a zajmowane miejsce na dysku

Tak jakoś ostatnio przy okazji tej wielkiej świątecznej promocji na Steamie zauważyłem, że miejsce na dysku, które gra ma rzekomo zajmować ma się nijak do miejsca faktycznie używanego.
Odkrycia tego nie dokonałbym po prawdzie gdybym nie postanowił zdobyć świątecznego osiągnięcia (achievement) w grze CrimeCraft GangWars. Dlaczego bym nie odkrył? Ano dlatego, że przy większości gier różnica między deklarowanym a faktycznym użyciem dysku jest niewielka - w dzisiejszych czasach 100 czy nawet 500 MiB nie robi wielkiej różnicy.
Z CrimeCraft jest jednak inaczej - tutaj różnica sięga 4GiB, co jakby nie patrzeć jest prawie 2x większą ilością niż deklarowane 5500MiB...

Według Steam'a gra zajmuje 5500MB
Na dysku zaś system pokazuje, że jest to praktycznie 10GB
Na początku sądziłem, że to po prostu jakiś bug związany z tą grą, ale przypadek ten nie jest odosobniony - dotyczy on praktycznie każdego tytułu, a widoczny jest zwłaszcza przy tych, które "rozwinęły się" od momentu dodania je na Steam'a.

Dodatkowo  dane ze "Spiral Knights" - kolejnej darmowej gry



W mojej opinii jest to problem wynikający z faktu, że Steam po otrzymaniu wersji "do dystrybucji" wbija w systemie rozmiar gry i później już jej nie aktualizuje, podczas gdy producent może ją cały czas aktualizować i powiększać jej rozmiar. 





Rozwiązanie jest proste - ot wystarczyłoby by po każdej aktualizacji producent zmuszony był wbić nowy rozmiar... bo nie czarujmy się mieć 4GB a nie mieć to jest już różnica - zwłaszcza, gdy te 4GB musimy ściągnąć przez Internet.

wtorek, 1 listopada 2011

Stronghold 3: engine fail

Po długim wyczekiwaniu pojawiła się trzecia odsłona twierdzy (link). O samej grze rozpisywać się nie będę - bo nie miejsce na to - wspomnę jednak o problemie, który zapewne dotyka większość użytkowników posiadających 2 karty graficzne na komputerze (czyli głównie posiadacze laptopów z zarówno zintegrowanymi kartami "energooszczędnymi" jak i zwykłymi).

Problem ów dotyczy inicjalizacji silnika gry a objawia się takim oto komunikatem zaraz po uruchomieniu gry:
Failed to initialize the engine
Problem leży właśnie w tym, że gra wykrywa, iż mamy dwie karty i z nieznanych przyczyn sprawdza czy ta słabsza poradzi sobie z tym wielkim tworem studia firefly. Oczywiście w większości przypadków wynikiem tego testu jest uroczy komunikat o błędzie.

Rozwiązanie
Remedium na ten problem jest (tymczasowe) wyłączenie karty graficznej :)



  1. Najpierw musimy dostać się do menedżera urządzeń - tak więc znajdujemy skrót do "komputera" i klikamy go PPM, a następnie wybieramy "Właściwości"

  2. Gdy już ujrzymy właściwości naszego komputera to klikamy w "Menedżer urządzeń".

  3. W okienku, które się pojawiło wybieramy adaptery graficzne i wyłączamy ten, który odnosi się do naszej wbudowanej/zintegrowanej karty graficznej
  4. Obok ikonki powinna pojawić się taka mała strzałeczka lub inne oznaczenie (w przypadku starszych wersji Windowsa). Teraz już możemy odpalić Twierdzę 3, a po ukończeniu gry ponownie włączyć odpowiednią kartę graficzną.
Zadziwiające jest, że taka nowa gra ma takie problemy... choć ten lapsus przestaje dziwić gdy się w nią trochę dłużej pogra... 

piątek, 14 października 2011

Array vs Object

Tak jakoś nastał obiektowy szał i wszystko wszędzie musi być obiektowe. Oczywiście PHP ta mania nie ominęła i tak oto mamy coraz to większą społeczność krzyczących, że Object i Class jest super fajne, a Array to zło i anachroniczny przeżytek. Ale czy jest tak na pewno?
Oczywiście tablice nie pozwalają na implementacje funkcji odwołujących się jedynie do danych z konkretnej tablicy - takie coś musimy zrealizować poprzez metodę jakiejś klasy lub po prostu funkcję nie należącą do żadnej klasy (co oczywiście jest mało eleganckie). Jednak jeśli zależy nam jedynie na przechowaniu tudzież przekazaniu danych z jednej funkcji do drugiej to czy warto definiować nowy typ danych (zakładając, że struktura danych nigdy nie ulegnie zmianie)? W innych językach programowania w 99% przypadków odpowiedź brzmiała by "tak", jednak w przypadku PHP nie jest ona wcale taka oczywista.

Dlaczego? Ano załóżmy, że chcemy przesłać dane dotyczące elementu opisywanego za pomocą trzech atrybutów (id, name, color). Przykładowy kod realizujący takie zadanie wyglądałby następująco:
function getArray()
{
 $item = Array();
 $item['id'] = 1;
 $item['name'] = "Pure item";
 $item['color'] = "none";
 return $item;
}
 
function getObject()
{
 $item = new stdClass();
 $item->id = 1;
 $item->name = "Pure item";
 $item->color = "none";
 return $item;
}
function getItem()
{
 $item = new Item();
 $item->id = 1;
 $item->name = "Pure item";
 $item->color = "none";
 return $item;
}
Dla ostatniej funkcji konieczne jest zdefiniowanie typu "Item":

class Item
{
    public $id;
    public $name;
    public $color;
}

Wykonanie tych funkcji jak widać tworzy struktury o tych samych atrybutach - raz jest to tablica, a w dwóch przypadkach są to instancje klas.
getArray      0.1664547920
getObject      0.2381231785
getItem       0.2375209332
Jak widać stworzenie i zapełnienie tablicy trwa ok. 70% czasu potrzebnego na utworzenie odpowiadającej jej klasie. Warto zauważyć, że czasy potrzebne na utworzenie instancji obiektu za pomocą zdefiniowanej klasy (Item) jak i użycia stdClass() są porównywalne.

A jak przedstawia się proces pobierania wartości z tych trzech utworzonych struktur danych?
Acquire from Array     1.7432529926
Acquire from Object     2.1544189453
Acquire from Item     2.3280811310
Znów nie ma większych zaskoczeń- używanie tablic okazuje się o wiele szybsze niż zaprzęganie klas. Co ciekawe pobieranie danych z instancji utworzonych za pomocą stdClass() okazuje się szybsze (choć nieznacznie) od pobierania wartości atrybutów z klas zdefiniowanych przez użytkownika.

Wnioski
To co chciałem pokazać w tym wpisie to to, że nie warto bezkrytycznie podchodzić do obecnych "trendów". Jeśli potrzebujemy maksymalnie wydajnego sposobu na przekazanie zestawu danych z jednego miejsca w programie do drugiego - to definiowanie klas mija się z celem. Oczywiście jeśli chcemy by nasz kod był maksymalnie przenośny to warto (a wręcz należy) użyć klas - ale tych definiowanych przez użytkownika. Wszak po to tworzymy definicję struktury klasy aby móc dołączyć do niej metody, a w przypadku używania stdClass otrzymujemy (pod względem czystej funkcjonalności) tablicę, która w dodatku działa wolniej od tej "standardowo" tworzonej za pomocą Array().

Plik z testami do pobrania z chomika (link).

czwartek, 29 września 2011

unexpected T_PAAMAYIM_NEKUDOTAYIM

Wpis ku pamięci :)

Komunikat ten (unexpected T_PAAMAYIM_NEKUDOTAYIM) otrzymujemy, gdy używamy :: w kontekście, którego PHP nie obsługuje (out of scope), czyli np. odwołujemy się do zwykłych metod, a nie do metod statycznych. Co ciekawe błąd ten wyskakuje w PHP < 5.3 również gdy wywołujemy istniejącą metodę statyczną przy użyciu zmiennych:
$className::$methodName();
Problem ten oczywiście nie istnieje w PHP >= 5.3, jednak przy starszych wersjach wciąż można natknąć się na tego dziwoląga :)

Alternatywą dla tego rozwiązania jest użycie funkcji call_user_func(), ta jednak działa wolniej od takiego bezpośredniego wywołania:
call_user_func($class."::".$method);
Dla porównania sprawdziłem również czas wykonywania zwykłych (niestatycznych) metod:
class::method				    0.0067858696
call_user_func				    0.0170218945
call_user_func (array)			    0.0149610043
class->method				    0.0044798851
call_user_func (array non-static)	    0.0125558376
Kod z testu do pobrania z chomika (link).

poniedziałek, 26 września 2011

Dzielenie ciągów przy pomocy wielu separatorów

Czasem zdarza się tak, że musimy podzielić nasz ciąg tekstowy w wyznaczonych miejscach na kilka mniejszych. W tej sytuacji funkcja explode() sprawdza się wręcz znakomicie. Problem jednak pojawia się gdy chcemy dzielić za pomocą więcej niż jednego separatora, gdyż samo explode() obsługuje jedynie separator w postaci pojedynczego "stringu". Na poniższych przykładach pokazałem różne sposoby rozbicia ciągu przy użyciu trzech separatorów: & ? /.

Rozwiązania
Najtrywialniejszym sposobem na poprawne rozbicie wg kilku separatorów byłoby wywołanie ponownie explode() dla każdego elementu wynikającego z pierwszego użycia tej funkcji, jednak ten sposób aż bije po oczach swym prostactwem więc nawet nie będę bawił się w jego poprawną implementację, a użyję jedynie takiego (dużego i nierealizującego zadania) przybliżenia:
for ($i = 0; $i < 100000; $i++)
{
	$temp = explode ( "/" , $string);
	foreach($temp as $elem)
	{
		$e = explode("?", $elem);
		$e = explode("&", $elem);
	}
}

Kolejnym pomysłem jest iteracyjne podejście - używanie na przemian explode() i implode() w celu stworzenia pojedynczej tablicy z wynikiem.
for ($i = 0; $i < 100000; $i++)
{
	$temp = explode ( "/" , $string);
	$temp = explode("&", implode("&", $temp));
	$temp = explode("?", implode("?", $temp));
}

Skoro już postanowiliśmy iść w stronę podmian to warto by również rozważyć wielokrotne użycie str_replace().
for ($i = 0; $i < 100000; $i++)
{
	$temp = str_replace("&", "/", $string);
	$temp = str_replace("?", "/", $temp);
	$temp = explode ( "/" , $temp);
}

Nim przejdziemy do wyrażeń regularnych przytoczę tu jeszcze jeden przykład, który niestety działa jedynie w przypadku separatorów będących pojedynczymi znakami. Co ciekawe mimo, iż wykorzystanie funkcji strtok() wydaje się być potwornie nieefektywne to wyniki zbliżone są do naprzemiennego wywoływania funkcji explode() i implode().

for ($i = 0; $i < 100000; $i++)
{
	$tok = strtok($string, "/&?");
	$temp = array();
	while ($tok !== false)
	{
		$temp[] = $tok;
		$tok = strtok("/&?");
	}
	unset($tok);
	strtok('', '');
}

Ale czy mamy ograniczać się jedynie do takich kombinowanych sposobów? Może jest jedna "magiczna" funkcja, która zrobi to o czym myślimy? Ano jest i wykorzystuje magię wyrażeń regularnych, a zwie się ona preg_split().
for ($i = 0; $i < 100000; $i++)
{
	$temp = preg_split("/[\/&?]/",$string);
}

Tutaj jednak warto zwrócić uwagę na fakt, że jeśli naszym separatorem jest znak wielobajtowy to funkcja ta będzie generować dodatkowe puste elementy w wynikowej tablicy (jeden dodatkowy element na każde wystąpienie separatora). Sytuacja ta nie ma jednak miejsca jeśli separatorem nie jest znak a ciąg znaków, nawet jeśli wszystkie zawarte w nim elementy są znakami wielobajtowymi.
Tak czy inaczej remedium na ten problem jest funkcja mb_split():
for ($i = 0; $i < 100000; $i++)
{
	$temp = mb_split("[\/&?]",$string);
}

Ostatnim przytoczonym tu przykładem będzie połączenie wyrażeń regularnych wykorzystanych w funkcji preg_replace() wraz z pojedynczym wywołaniem explode():
for ($i = 0; $i < 100000; $i++)
{
	$temp = preg_replace("/[&?]/","/",$string);
	$temp = explode("/", $temp);
}

Testy wydajności
Jak widać istnieje wiele możliwości wykonania zadania dzielenia ciągu z użyciem wielu równorzędnych separatorów, ale które rozwiązanie jest najefektywniejsze? Popatrzmy na wyniki:

single explode:		    0.1573359966
multiple explode:	    1.0633759499
explode & implode:	    0.7556319237
replace & explode:	    0.4618999958
strtok:			    0.6737470627
preg_split:		    0.9344220161
mb_split:		    1.1594960690
preg_replace & explode:	    1.1028780937

Dla separatorów składających się również z kilku znaków otrzymujemy takie wyniki:

single explode:		    0.1560928822
multiple explode:	    1.3294098377
explode & implode:	    1.1651928425
replace & explode:	    1.0913951397
preg_split:		    1.8065340519
mb_split:		    4.9289889336
preg_replace & explode:	    1.8077979088

Jak widać wszystkie czasy wzrosły, przy czym wzrost mb_split() wynika w dużej mierze z używania wielobajtowych znaków.
Jeśli jednak zostaniemy przy tych trzech jedno-znakowych separatorach, a jedynie zwiększymy rozmiar badanego ciągu otrzymamy takie oto wyniki (10x mniej iteracji pętli testujących):

single explode:		    0.2046608925
multiple explode:	    1.8381199837
explode & implode:	    1.0454828739
replace & explode:	    0.5224039555
strtok:			    1.0597200394
preg_split:		    1.8259940147
mb_split:		    2.1428530216
preg_replace & explode:	    1.8069810867

Jeśli dodatkowo zamiast trzech separatorów użyjemy 5 będziemy mieć takie oto wyniki:

single explode:		    0.2125821114
multiple explode:	    4.1121268272
explode & implode:	    3.3804459572
replace & explode:	    1.6378400326
strtok:			    3.4061551094
preg_split:		    2.7953231335
mb_split:		    6.4811940193
preg_replace & explode:	    3.0788750648

Wnioski
Jak widać wydajność metod różni się w zależności tak od długości badanych ciągów, jak i ilości oraz postaci separatorów. Porównując wyniki możemy jednak dojść do wniosku, że najlepszym rozwiązaniem jest użycie duetu str_replace() wraz z explode(), który to we wszystkich testach okazał się najszybszym rozwiązaniem. Pozostałe metody są w zasadzie porównywalne, choć stosunek ich wydajności różni się w zależności od parametrów zadania (długość ciągu i postać separatorów). Pomimo to możemy jasno stwierdzić, że wielokrotne używanie explode() (multiple explode) jest całkowicie nieefektywne w porównaniu do pozostałych metod (zwłaszcza, że nie zaimplementowano tej funkcji w całości). Podobnie sytuacja wygląda w przypadku mb_split(), którą używać należałoby jedynie w przypadku skomplikowanych podziałów, a nie prostego dzielenia wg podanych słów.

Wykorzystany kod do testowania można pobrać z mojego chomika (link).

niedziela, 18 września 2011

Co zmienia var?

Tematem tego posta będzie znaczenie słówka "var" występującego w JavaScript. Otóż wielu z programistów traktuje po macoszemu deklarację zmiennej, bo wszak w większości przypadków dodanie tych 3 literek nic nie zmienia. Jednak są takie momenty, gdy to słowo kluczowe decyduje o tym, czy skrypt będzie działał poprawnie, czy też będzie skrycie psuł nam krew.
Generalnie problemy z inicjacją zmiennych w JavaScript pojawiają się gdy zaczynamy tworzyć równolegle wykonywane fragmenty kodu, czyli używamy window.setTimeout() oraz window.setInterval(). Szkopuł tkwi w tym, że zmienna utworzona bez użycia "var" zachowuje się jak zmienna statyczna, a dopiero dodanie wspomnianego słówka powoduje, że będzie się ona zachowywać jak normalna zmienna lokalna.

Może jednak pokażę na przykładzie o co mi chodzi :) Przykład będzie trywialny, ot mamy dwa guziki z liczbami, z których pierwszy po kliknięciu ma dodawać "0.1" co 1/10 sekundy, a drugi ma "0.1" odejmować. Oczywiście te 0.1 nie mogą być na sztywno zapisane wewnątrz funkcji.

<script type="text/javascript">

function __countNoVar(node, finalValue)
{
delta = finalValue - Number(node.innerHTML);
delta = Number(Number(delta / 10 ).toFixed(1));

var intervalIdent = self.setInterval(function(){
	    node.innerHTML = (Number(node.innerHTML)+delta).toFixed(1);
	},100);
}

</script>
<button onclick="__countNoVar(this, 1);">0</button>
<button onclick="__countNoVar(this, -1);">0</button>
Dodaje: Odejmuje:

Jak widać jeśli pojedynczo wciśniemy guziki to działają bez problemu, ale w momencie wciśnięcia drugiego buttona nagle również pierwszy zaczyna liczyć w tym samym "kierunku" co ostatnio wciśnięty. Dzieje się tak właśnie z powodu braku "var" przed deklaracją zmiennej "delta", czego wynikiem jest używanie nowej wartości tej zmiennej tak w ostatnio utworzonym "wątku" jak i we wszystkich poprzednich utworzonych za pomocą funkcji "__countNoVar".

Wersja z "varem", która w wyglądzie różni się dosłownie 3 literami działa już jednak poprawnie:
<script type="text/javascript">

function __countWithVar(node, finalValue)
{
var delta = finalValue - Number(node.innerHTML);
delta = Number(Number(delta / 10 ).toFixed(1));

var intervalIdent = self.setInterval(function(){
	    node.innerHTML = (Number(node.innerHTML)+delta).toFixed(1);
	},100);
}

</script>
<button onclick="__countWithVar(this, 1);">0</button>
<button onclick="__countWithVar(this, -1);">0</button>
Dodaje: Odejmuje:

środa, 14 września 2011

Zabezpieczenie najwyższych lotów

Kolega z roku udostępnił dziś pięknego linka do forum iiuj (instytut informatyki Uniwersytetu Jagielońskiego). Nic by w tym nie było ciekawego gdyby nie iście mistrzowski sposób kontroli dostępu.

http://www.ii.uj.edu.pl/dokumenty/weryfikacja.php

Pomijam tu już "skarby" zgromadzone na owym forum.. jednak sam fakt stosowania takich zabezpieczeń przez instytut informatyki uczelni wyższej nie świadczy raczej dobrze o jej administratorze...

środa, 31 sierpnia 2011

Packet Tracer: Network Address Translation

 NAT czyli Network Address Translation tak naprawdę umożliwia większości z nas dostęp do Internetu. To właśnie dzięki niemu nasz lokalny adres IP (np. 192.168.1.111) jest tłumaczony na adres globalny, który to widzą np. serwery sieciowe. Jednak to co jest ważniejsze to fakt, że NAT (a raczej PAT) umożliwia wielu urządzeniom jednoczesną komunikację z "zewnętrznym światem" przy użyciu pojedynczego globalnego adresu IP.

Jednak nic tak nie wspomaga nauki jak bodźce wizualne :) Dlatego też przygotowałem taki mały projekt sieci wykorzystującej różne odmiany mechanizmu NAT:

A jak sama translacja działa w praktyce? Router przy wykorzystaniu tablicy translacji podmienia docelowy/źródłowy (w zależności od kierunku przesyłania) adres IP w nagłówku pakietu, dzięki czemu odzwierciedla on aktualną "przestrzeń adresów". By lepiej zrozumieć polecam krótką prezentację w formie filmiku.



A jak taka tablica translacji wygląda? Cóż możemy ją obejrzeć wpisując w konsoli w trybie uprzywilejowanym (#) polecenie:
show ip nat translations



Nim jednak przejdziemy do omówienia tych różnych odmian translacji to słów parę o ogólnej konfiguracji routera. Otóż by NAT w ogóle chciał działać konieczne jest określenie, który interfejs jest "wewnątrz" (inside), a który "na zewnątrz" (outside) sieci, której adresy chcemy tłumaczyć. Informację tą przekazujemy routerowi wpisując w konfiguracji interfejsów kolejno
ip nat inside
dla interfejsu wewnętrznego oraz
ip nat outside
dla interfejsu zewnętrznego.

Po ustawieniu tej podstawowej informacji możemy zacząć konfigurację sposobu translacji.

Statyczna translacja adresów sieciowych
Jest to odmiana translacji, w której każdy wpis musi zostać ustawiony ręcznie. Z tego też powodu w zastosowaniach życia codziennego metoda ta nadaje się jedynie do udostępnienia "na zewnątrz" serwerów lub innych urządzeń, z którymi chcemy nawiązywać łączność z maszyn znajdujących się poza naszą siecią.

Konfiguracja:
ip nat inside source static (IP wewnętrzny) (IP zewnętrzny)
ip nat inside source static (tcp/udp) (IP wewnętrzny) (port wewnętrzny) (IP zewnętrzny) (port zewnętrzny)

Wydruk z tablicy translacji:
Warte zauważenia jest to, że komunikaty pochodzące z jednego komputera używają tego samego adresu IP globalnego. Dodatkowo na końcu wydruku pojawia się de facto lista zdefiniowanych translacji statycznych (zaznaczona na zielono).

NAT z PAT
NAT z PAT to domyślny sposób translacji umożliwiający łączenie się przez jeden adres IP przez wiele maszyn.

Konfiguracja składa się z dwóch etapów. Napoczątku należy utworzyć listę dostępu, która będzie mówić routerowi, które adresy należy tłumaczyć (oczywiście można użyć rozszerzonych list ACL):
access-list (numer listy) permit any
Po jej utworzeniu możemy już ustawić samą translację:
ip nat inside source list (numer list) interface (nazwa interfejsu) overload
Warto zauważyć, że jeśli nie podamy słówka "overload" to Packet Tracer i tak je automatycznie doda do konfiguracji (dotyczy to jednak tylko definiowania NAT z wykorzystaniem interfejsu).

Wydruk z tablicy translacji:
 Jak widać wszystkie maszyny lokalne używają tego samego adresu IP (czerwony kolor), ale różnych numerów portów (zielony).

NAT z PAT przy użyciu wielu adresów IP
Podobnie jak w powyższej metodzie tak i tu wiele maszyn może korzystać z jednego zewnętrznego adresu IP. To co jest "nowego" to właśnie mnogość adresów, które są wykorzystywane, gdy pierwszemu zabranie wolnych portów.

Konfiguracja składa się z trzech etapów.  Poza omówioną powyżej konfiguracją listy dostępu musimy jeszcze utworzyć pulę adresów IP, które router będzie mógł przydzielać:
ip nat pool (nazwa puli) (początkowy adres IP) (końcowy adres IP) netmask (maska podsieci)
Samą translację zaś konfigurujemy następująco:

ip nat inside source list (numer listy) pool (nazwa puli) overload

Wydruk z tablicy translacji jest praktycznie taki sam jak przy NAT z PAT. Jedyna różnica polega na tym, że gdy skończą się wolne porty na pierwszym adresie IP to router zaczyna używanie kolejnego adresu z puli.

NAT przy użyciu wielu adresów IP
Ta metoda jest w zasadzie podobna do poprzednich, z tą różnicą, że tutaj każda maszyna używa tylko jednego adresu IP. Można zatem powiedzieć, że jest to taki dynamiczny odpowiednik zwykłej statycznej translacji.

Konfiguracja wygląda identycznie jak w przypadku powyższym (NAT z PAT z wieloma adresami). Jedyna zmiana to brak słówka "overload":
ip nat inside source list (numer listy) pool (nazwa puli)

Wydruk z tablicy translacji:
 Tablica translacji jak widać jest bardzo podobna do tej ze statycznego tłumaczenia adresów i różni się jedynie tym, że przed wykonaniem translacji nie mamy możliwości stwierdzenia, który adres globalny zostanie przypisany do danego adresu lokalnego.


Chcących przetestować działanie NAT na "żywym" organizmie zapraszam do pobrania pliku z konfiguracją sieci: link (PT wersja 5.3)

wtorek, 30 sierpnia 2011

Dithering

Tak się jakoś złożyło, że przez ostatnie dni zagłębiłem się w tematykę ditheringu. Cóż implementacja algorytmów sama w sobie nie jest ciężka i po prawdzie przynosi ogrom radości, gdy w końcu udaje się wygenerować w miarę podobny do pierwowzoru obraz :P
Tak czy inaczej w celu uproszczenia programu używałem plików PGM/PBM.

Ale czym jest sam dithering? W skrócie to jest to taka transformacja obrazu by przy mniejszej palecie kolorów dawał złudzenie większej palety, czy jak kto woli - najbardziej przypominał on pierwowzór.
Innymi słowy chodzi o to by np. taki 8 bitowy/px obraz (256 kolorów)
Orginal grayscale picture
Po redukcji do 1 bita na piksel (2 kolory) nie wyglądał tak:
Threshold dithering
ale np. tak:
Ordered dithering
Floyd-Steinberg combined with ordered dithering
Floyd-Steinberg combined with ordered dithering and negative error propagation
Prawda, że widać różnicę? Algorytmów do ditheringu jest cała masa i mimo iż niegdyś używano ich prawie jedynie do przełamywania technologicznych ograniczeń (np. wyświetlacze o małej gamie barw) to obecnie używa się ich również w artystycznym ujęciu. Ot choćby obraz wektorowy poddany ditheringowi może okazać się ciekawszy dla widza niż przed taką obróbką (ludzie to takie dziwne stworzenia co nie lubią wyraźnych konturów i jednolitych powierzchni).

niedziela, 28 sierpnia 2011

Łączenie ciągów tekstowych

Chyba jednym z najpowszechniejszych działań w PHP jest łączenie różnej maści ciągów. Czy to przy budowaniu zapytań do bazy, czy też przy generowaniu treści do wyświetlenia. Jednak czy zastanawialiście się nad różnicami w sposobie łączenia ciągów? W tym wpisie porównam metodę doklejania kolejnych elementów za pomocą ".", dołączania wartości zmiennych przy pomocy "{$var}" oraz działanie funkcji sprintf. A na koniec porównam wydajność rzutowań bezpośrednich oraz konwersji z użyciem sprintfa.

Na początek mały test pokazujący różnice pomiędzy pustymi sprintf, "" oraz ''.

Sprintf:	    5.5083110332
" ":		    1.8283259869
' ':		    1.7890319824

oraz sam test dla "" i '':

" ":	    1.1731438637
' ':	    1.1480429173
" ":	    1.1413729191
' ':	    1.1829788685

Jak widać różnice w wydajności pomiędzy "" a '' są praktycznie nieistniejące, natomiast sprintf wykonuje się 3 razy dłużej.
Porównajmy zatem wydajność przy dołączaniu tekstu zapisanego w zmiennej pomiędzy inne elementy.

Sprintf:	7.5031311511993
" "." ":	2.9455320835114
" ":		3.1112060546875
{ }:		3.1566998958588

Wyniki te kolejno odnoszą się do kodu:
$temp = sprintf("prefix %s postfix",$text);
$temp = "prefix ".$text." postfix";
$temp = "prefix $text postfix";
$temp = "prefix {$text} postfix";

Okazuje się, że najszybciej działa łączenie kolejnych niezależnych ciągów, następnie w szeregu wydajności znajdują się metody "na skróty", a na końcu wywołanie funkcji sprintf.

Na sam koniec zostawiłem porównanie wydajności rzutowań na typy liczbowe - integer i float. Wyniki prezentują się następująco:
Sprintf %d:	    0.7861609459	666
Sprintf %u:	    0.7842721939	666
Sprintf %f:	    1.2757029533	666.999000
(int):		    0.2504780293	666
(float):	    0.2895848751	666.999

Funkcja sprintf znowu jest trzykrotnie, a w przypadku rzutowania na float ponad czterokrotnie wolniejszy. Jednak sytuacja ta zmienia się diametralnie gdy zaczynamy tworzyć ciąg składający się z wielu zmiennych:
Sprintf %d %f:		    6.8353290558
Sprintf %1$d %2$f:	    7.3985271454
(int), (float)		    8.1965639591

Wyniki te odpowiadają kodowi:
$temp = sprintf("%d %f %d",$numeric, $float, $int);
$temp = sprintf('%1$d %2$f %3$d',$numeric, $float, $int);
$temp = (int)$numeric." ".(float)$float." ".(int)$int;

Przy tworzeniu rozbudowanych ciągów tekstowych jednak warto użyć funkcji sprintf. Jednak z drugiej strony przy pojedynczych zmiennych o wiele lepiej używać bezpośredniego rzutowania. To na co również warto zwrócić uwagę to fakt, że odnoszenie się do zmiennych poprzez %1$, %2$ itd. mimo iż jest z pewnością szybsze do napisania to jednak działa nieco wolniej od bezpośredniego podawania każdego z parametrów (nawet jeśli musimy je podawać wielokrotnie w jednym wywołaniu funkcji).

Wykorzystany kod do testowania można pobrać jak zwykle z mojego chomika (link).

piątek, 5 sierpnia 2011

Dostęp do prywatnych pól klas przodków w PHP

Ten wpis jest w zasadzie rozszerzeniem poprzedniego wpisu. To co tutaj zostanie omówione to stricte dostęp do pól prywatnych danej klasy, jak i również troszkę bardziej rozbudowany przykład wykorzystania klas z rodziny Reflection.

Jak można było zauważyć w poprzednim wpisie funkcja get_object_vars() realizuje de facto zadanie pobrania wartości i nazw pól klas. Jej wadą jest jednak niemożność dojścia do metod prywatnych przodków naszej klasy i właśnie w celu dodania tej funkcjonalności niezbędne okażą się klasy Reflection.

Na początek przypomnijmy jednak sobie naszą hierarchię klas...
class First
{
    private $firstPrivate;
    protected $firstProtected;
    public $firstPublic="DEFAULT";
}

class Second extends First
{
    private $secondPrivate;
    protected $secondProtected;
    public $secondPublic;
}

class Third extends Second
{
    private $thirdPrivate;
    protected $thirdProtected;
    public $thirdPublic;
}

Aby dobrać się do metod prywatnych potrzebujemy o wiele bardziej rozbudowanego kodu niż ten umieszczony w poprzednim wpisie, oraz rekurencji koniecznej do przejścia po grafu dziedziczenia klas.
Takim chyba najprostszym przykładem pobrania wartości wszystkich parametrów jest funkcja:
function getAllFields($object)
{
    $globalFields = array();
    $reflectionClass = new ReflectionClass($object);
    do
    {
        $reflections = $reflectionClass->getProperties();

        foreach($reflections as $reflectionProperty)
        {
            $reflectionProperty->setAccessible(true);
            $globalFields[$reflectionProperty->name] = $reflectionProperty->getValue($object);
        }
        $reflectionClass = $reflectionClass->getParentClass();
    }
    while($reflectionClass);
    return $globalFields;
}
dająca taki oto rezultat
Array
(
    [thirdPrivate] => 
    [thirdProtected] => 
    [thirdPublic] => NEW
    [secondProtected] => 
    [secondPublic] => 
    [firstProtected] => 
    [firstPublic] => DEFAULT
    [secondPrivate] => 
    [firstPrivate] => NEW PRIVATE
)
Funkcję ta jednak ma główną wadę w postaci nadpisywania zmiennych klas bliższych badanemu obiektowi przez zmienne jego przodków. Na szczęście można tą funkcję dowolnie rozwijać i sprawić by generowała wszelkie informacje dotyczące badanej klasy:
function getAllFieldsStrict($object)
{
    $globalFields = array();
    $reflectionClass = new ReflectionClass($object);
    do
    {
        $reflections = $reflectionClass->getProperties();

        foreach($reflections as $reflectionProperty)
        {
            if($reflectionProperty->getDeclaringClass()->name == $reflectionProperty->name)
            {
                switch(true)
                {
                    case $reflectionProperty->isPrivate(): $globalFields[$reflectionProperty->name][$reflectionProperty->name]['access'] = 'private'; break;
                    case $reflectionProperty->isProtected(): $globalFields[$reflectionProperty->name][$reflectionProperty->name]['access'] = 'protected'; break;
                    case $reflectionProperty->isPublic(): $globalFields[$reflectionProperty->name][$reflectionProperty->name]['access'] = 'public'; break;
                }
                $reflectionProperty->setAccessible(true);
                $globalFields[$reflectionProperty->name][$reflectionProperty->name]['value'] = $reflectionProperty->getValue($object);
                $globalFields[$reflectionProperty->name][$reflectionProperty->name]['static'] = $reflectionProperty->isStatic();
            }
        }
        $reflectionClass = $reflectionClass->getParentClass();
    }
    while($reflectionClass);
    return $globalFields;
}
czego rezultatem jest
Array
(
    [Third] => Array
        (
            [thirdPrivate] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => private
                )
            [thirdProtected] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => protected
                )
            [thirdPublic] => Array
                (
                    [value] => NEW
                    [static] => 
                    [access] => public
                )
        )
    [Second] => Array
        (
            [secondPrivate] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => private
                )
            [secondProtected] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => protected
                )
            [secondPublic] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => public
                )
        )
    [First] => Array
        (
            [firstPrivate] => Array
                (
                    [value] => NEW PRIVATE
                    [static] => 
                    [access] => private
                )
            [firstProtected] => Array
                (
                    [value] => 
                    [static] => 
                    [access] => protected
                )
            [firstPublic] => Array
                (
                    [value] => DEFAULT
                    [static] => 
                    [access] => public
                )
        )
)
Wydajność
get_object_vars:  0.28459501266479
getAllFields:   4.3010709285736
getAllFieldsStrict:  4.3191359043121
Podczas testu wykonano każde z zapytań 100000 razy i jak widać wygenerowanie rozbudowanej struktury klas zajmuje praktycznie tyle samo czasu co stworzenie prostszej. Mimo wszystko jednak generowanie trwa ok. 15 razy dłużej w porównaniu do wyciągnięcia informacji za pomocą get_object_vars(), dlatego też jeśli potrzebujemy dostać się do zmiennych klas i mamy możliwość deklarowania pól jako protected a nie private - to zdecydowanie lepiej tak zrobić i używać get_object_vars().

Przykładowy kod do testowania można pobrać z mojego chomika (link).

czwartek, 4 sierpnia 2011

Listowanie nazw pól klasy w PHP

Chcąc dobrać się do zmiennych klasy - ich nazw oraz ewentualnie wartości - dokopałem się do trzech sposobów. Dwa z nich (get_class_vars() i get_object_vars()) pochodzą jeszcze z czasów strukturalnego pisania kodu, zaś trzeci (ReflectionClass) jest już cudem współczesnego przerośniętego obiektowego PHP5.

No ale przechodząc do rzeczy, a właściwie do przykładu obrazującego o co mi chodzi :)

class First
{
    private $firstPrivate;
    protected $firstProtected;
    public $firstPublic="DEFAULT";
}

class Second extends First
{
    private $secondPrivate;
    protected $secondProtected;
    public $secondPublic;
}

class Third extends Second
{
    private $thirdPrivate;
    protected $thirdProtected;
    public $thirdPublic;
}
Otóż mamy 3 klasy, a w każdej z nich 3 zmienne - private, protected i public... Na początku miałem nadzieję, że uda się dobrać do nich wszystkich, jednak okazało się, że do pól prywatnych można dostać się jedynie z kontekstu tej samej klasy - tak więc musiałbym w każdej klasie umieszczać dedykowany kod (lub użyć przerośniętej funkcji działającej w oparciu o Reflection), a takie rozwiązanie w grę nie wchodziło :x Tak więc poświęciłem prywatne zmienne i zamieniłem je na protected i ten problem zniknął ^.^

Pobieranie nazw parametrów
Jednak wracając do zagadnienia - samo uzyskanie nazw zmiennych jest dość banalne i można je osiągnać choćby dzięki dowolnej z poniższych metod:

function getClassVars()
{
    return get_class_vars(get_class($this));
}

function getObjectVars()
{
    return get_object_vars($this);
}

function reflectionGetDefaultProperties()
{
    $reflectionClass = new ReflectionClass($this);
    return $reflectionClass->getDefaultProperties();
}

function reflectionGetProperties()
{
    $reflectionClass = new ReflectionClass($this);
    return $reflectionClass->getProperties();
}

3 pierwsze z nich zwracają tablicę, w której klucze elementów mają nazwę parametrów, a wartości do tych kluczy przypisane równe są wartościom obiektów. Tylko, że tutaj uwaga, gdyż get_class_vars() oraz ->getDefaultProperties zwracają jedynie wartości na sztywno przypisane (domyślne) w kodzie do poszczególnych zmiennych, a nie aktualne wartości tych zmiennych w instancji obiektu.

Tak więc po wykonaniu kodu:

class First
{
    private $firstPrivate;
    protected $firstProtected;
    public $firstPublic="DEFAULT";
    static $firstStatic = "STATIC";

/** metody identyczne do podanych w poprzednim przykładzie */
 function getClassVars() {...}
 function getObjectVars() {...}
 function reflectionGetDefaultProperties() {...}
 function reflectionGetProperties() {...}
}

$object = new A();
$object->firstPublic = "NEW";

print_r($d->getClassVars());
print_r($d->getObjectVars());
print_r($d->reflectionGetDefaultProperties());
print_r($d->reflectionGetProperties());
otrzymamy:
Array
( 
    [firstStatic] => STATIC
    [firstPrivate] => 
    [firstProtected] => 
    [firstPublic] => DEFAULT
)
Array
(
    [firstPrivate] => 
    [firstProtected] => 
    [firstPublic] => NEW
)
Array
(
    [firstPrivate] => 
    [firstProtected] => 
    [firstPublic] => DEFAULT
    [firstStatic] => STATIC
)
Array
(
    [0] => ReflectionProperty Object
        (
            [name] => firstPrivate
            [class] => First
        )

    [1] => ReflectionProperty Object
        (
            [name] => firstProtected
            [class] => First
        )

    [2] => ReflectionProperty Object
        (
            [name] => firstPublic
            [class] => First
        )

    [3] => ReflectionProperty Object
        (
            [name] => firstStatic
            [class] => First
        )
)

Jednak jeśli zależy nam tylko na nazwach zmiennych to generalnie nie ma wielkiej różnicy w wybranej metodzie. Poza tym jak widać ostatnia metoda (->getProperties) zwraca nam obiekty klasy ReflectionProperty.. ale o tym w innym wpisie. Warto jeszcze wspomnieć o tym, że get_object_vars() nie zwraca statycznych pól klasy, ale po co nam one jeśli chodzi nam o stan konkretnej instancji obiektu?
Wydajność
Generalnie jeśli zależy nam jedynie na uzyskaniu nazw parametrów to dlaczego by nie wybrać najszybszej metody? Testy przeprowadziłem na hostingu ovh (większa stabilność wyników niż przy testowaniu na własnym kompie):

get_class_vars:   3.3929491043091
get_object_vars:   2.7406477928162
->getDefaultProperties:  3.6021101474762
->getProperties:   8.8276469707489

Test polegał na wykonaniu pętli
for ($i = 0; $i < 1000000; $i++){ $d->bref(); }
dla każdego z zapytań i zmierzenie czasu za pomocą funkcji microtime(true). Jak widać po wynikach najszybciej działa funkcja get_object_vars(), a najwolniejsze w użyciu są klasy z rodziny Reflection. Oczywiście w przypadku wpisania nazwy klas na sztywno (literka s przy opisie) zamiast używania get_class() wyniki są trochę lepsze (wyniki w dwóch zestawach gdyż sumarycznie przekraczają 30 sekundowy maksymalny czas wykonania skryptu):

get_class_vars:   3.3419530391693
get_class_vars (s):   3.0308630466461
get_object_vars:   2.6966669559479

get_object_vars:   2.6544170379639
->getDefaultProperties:  3.5368349552155
->getDefaultProperties (s):  3.5258779525757
->getProperties:   8.8363699913025
->getProperties (s):   8.7472901344299 

Co ciekawe najbardziej na tej zmianie zyskuje get_class_vars().

Przykładowy kod do przeprowadzenia testów można pobrać (jak zawsze) z mojego chomika (link).

czwartek, 28 lipca 2011

Battlefield 2142 Deluxe

Skoro już od kilku postów jestem na tej fali rozwiązywania problemów z grami, to podzielę się również swą receptą na uruchomienie BF2142. Otóż z tą grą jest taki podstawowy problem, że (przynajmniej wersja Deluxe) dostępna jest w wersji 1.4, a do gry na oficjalnych serwerach potrzebna jest wersja 1.5... tylko, że przy próbach aktualizacji (lub później już przy próbie gry) wyskakują różnego rodzaju problemy: a to klient niezgodny, a to nie da się zaktualizować gry.. ot cała gama różnych dziwacznych problemów. Rozwiązanie tych problemów jest proste, aczkolwiek jego odnalezienie wymaga poświęcenia odrobiny czasu i cierpliwości.

Na wstępie zaznaczę, że jeśli zainstalowałeś patcha i coś nie śmiga - cóż możesz próbować wykonać jakiś fragment tego poradnika, ale zapewne łatwiej i szybciej będzie przeinstalować grę i postępować zgodnie z tym co tu napisałem. :)

Spis wszystkich rzeczy jakie będziemy potrzebowali:
Pierwszym krokiem po instalacji gry jest uruchomienie edytora rejestru (Uruchom->regedit).
W starszych wersjach Windowsa wystarczy kliknąć guzik "Uruchom" i tam wpisać "regedit"
Po jego uruchomieniu należy wyszukać wpisy dotyczące Battlefielda 2142.
Na Windows 7 (na moim komputerku) ścieżka do nich jest następująca:

Zapewne dokładna ścieżka może się różnić pomiędzy systemami, ale generalnie gdzieś tam powinien być odpowiedni wpis

Teraz należy zmienić zapisaną wersję z 1.40 na 1.10. Dzięki temu patch zostanie bez problemów zainstalowany, a sama gra będzie zgodna z wymaganiami serwerów.

Zaznaczamy "Version" i klikamy ją prawym przyciskiem myszy
Pojawi się takie menu - wybieramy "Modyfikuj"
Tutaj wpisujemy nową wartość i wciskamy "OK"
 
 Po zmianie w rejestrze patchujemy grę pełnym patchem do wersji 1.50 (link)
Ach ten nowoczesny wygląd patchera :>

Teraz znów edytujemy wersję gry w rejestrze i wpisujemy 1.50.
Nasz rejestr powinien teraz wyglądać mniej więcej tak:


Teraz możemy już odpalić grę i spróbować czy wszystko działa. W tym momencie mogą w zasadzie pojawić się dwa zasadnicze problemy: gra może nie chcieć się nam uruchomić, albo możemy być wyrzucani z serwerów przez punkbustera :)

Punkbuster wyrzuca nas z serwera
Uruchamiamy pbsetup.exe (link) i dodajemy Battlefielda 2142 do punkbustera.
  • Klikamy (1) "Add a Game"
  • Z selectboxa (2) wybieramy "Battlefield 2142"
  • Jeśli punkbuster samemu nie znajdzie nam ścieżki do gry to klikając w (3) "Browse" i wybieramy katalog z grą
  • Klikamy (4) "Add Game" a BF powinien pokazać się na liście :)

Teraz uruchomiamy pbsvx.exe (link) i instalujemy usługę Punkbustera.
Po tych operacjach bez problemu powinniśmy móc łączyć się z serwerami.

Gra nie chce uruchomić się po spatchowaniu mimo, iż wcześniej chodziła
 Nie wiem jakim cudem, ale jakoś tak się złożyło, że "nowszy" i "lepszy" BF2142 w wersji 1.50 nie chce odpalić się na Windows 7 x64 mimo iż 1.40 (świeżo po instalacji) chodził, że aż miło. Rozwiązanie tego problemu zapewne łamie warunki nie jednej licencji, gdyż wymaga użycia zcrackowanej wersji pliku BF2142.exe. Generalnie nie popieram używania cracków, ale jeśli to ma być jedyny sposób na uruchomienie gry, którą zakupiłem... Tak czy inaczej plik BF2142.exe z katalogu z grą należy zastąpić crackiem (link). Przy okazji będziemy mogli grać bez włożonej płytki DVD, za co tak ona jak i odtwarzacz bardzo nam podziękują ;P

wtorek, 19 lipca 2011

Red Alert 2 przez Internet

Red Alert 2 to piękna gra, jak dla mnie ostatnia z serii C&C stworzona w starej, dobrej konwencji. Nie ma tu spektakularnych efektów graficznych (choć jak na ówczesne czasy to grafika była imponująca), a i sama rozgrywka jest przyjemnie powolna. Samej gry ząb czasu nie nadgryzł aż tak straszliwie... niestety losu tego jej twórca - Westwood Studios - nie uniknął, co spowodowało problem ogromny. Bo jak grać przez sieć na serwerach firmy, która już nie istnieje (gdyż wchłonęło ją EA i zaciera po niej ślady [1] [2] [3])?
Na szczęście pojawiło się kilka rozwiązań tego problemu, począwszy od używania do gry sieci LAN, Hamachi, a skończywszy na nowych dedykowanych serwerach. I to właśnie tę ostatnią metodę pragnę tutaj przybliżyć, a konkretniej sposób łączenia się z serwerami xwis.net :)

Jednak nim o łączeniu się z serwerem powiem - zacznijmy od podstaw, wszak do gry potrzebujemy zainstalowanej gry. Na szczęście bez problemów możemy ją zainstalować na praktycznie każdym Windowsie od XP zaczynając a na 64 bitowym Windows 7 kończąc.
Po instalacji nasza gra musi zostać zaktualizowana do wersji 1.006 (link), dzięki czemu będziemy mogli grać z innymi osobami (większość ludzi aktualizuje swoje gry ^.^ ).
A na sam koniec musimy zainstalować aktualizację do bibliotek internetowych stworzonych przez Westwood (link|link). WOLAPI instalujemy do folderu z grą.
Instalator WOLAPI - aktualizacji do bibliotek internetowych dostarczonych przez Westwood
Z tak ubogaconą grą możemy przejść do drugiego etapu - tworzenia konta na serwerze xwis.net i łączenia się z serwerem w grze :)
Ten etap najlepiej zacząć od wejścia tutaj i przeprowadzenia rejestracji na forum. Login z forum będziemy również używać do logowania się na konto na stronie xwis.net, a na podany e-mail przyjdzie link aktywacyjny :)

Po zakończeniu rejestracji i aktywowaniu konta wchodzimy tutaj i logujemy się loginem i hasłem podanym przy rejestracji na forum.
Formularz logowania na serwer XWIS
Jeśli stronka nie chce nas zalogować mimo podawania prawidłowego hasła (pole hasła [2] podświetla się na żółto), to możliwe, że mamy w haśle znaki inne niż cyfry i litery, a tego ten formularz nie lubi. Jeśli tak jest to wracamy na forum i zmieniamy hasło tym formularzem.

Po udanym zalogowaniu naszym oczom ukaże się mniej więcej taki widok:
Panel zarządzania kontem
Legenda do obrazka:
nick - nasz login do serwera
email - nasz e-mail
serial - po aktywacji jakiejś gry pojawią się tutaj różne cyferki, które niekoniecznie będą fragmentem naszego klucza.
XWIS Password - hasło do logowania się na konto w grze (logujemy się tym hasłem, a nie tym podawanym przy rejestracji na forum)
1 - by aktywować klucz wpisujemy go w to pole i klikamy "Add Serial"
2 - w celu stworzenia konta wpisujemy w to pole nasz nick, wybieramy grę, której ma on dotyczyć i klikamy "Create Nick"
3 - tutaj znajduje się lista naszych kont, którymi możemy zarządzać (wystarczy kliknąć w nick) - ta nazwa będzie używana do logowania się w grze.

To co powinniśmy teraz zrobić to podać nasz klucz produktu (serial) oraz stworzyć konto dla RA2. Warto również spisać nazwę naszego konta oraz nasze hasło XWIS bo bez tych danych nie zalogujemy się na konto w grze.

Po wykonaniu tych czynności możemy uruchomić Red Alert 2, w menu wybrać Internet, a następnie Custom Match. Teraz pozostaje nam jedynie zalogować się przy użyciu nicka i hasła XWIS w takim oto formularzu:
Formularz logowania wewnątrz Red Alert 2
Jeśli nasz nick i hasło są prawidłowe to powinniśmy ujrzeć mniej więcej taki ekran z widokiem całego lobby i innych graczy tudzież dostępnych gier.
Widok lobby Red Alert 2
Teraz do rozpoczęcia gry droga już bardzo krótka, a chwila ujrzenia ładującej się gry jest iście bezcenna i aż łezka sentymentu w oku kręci się :)

Ach ta radość gdy gra pobiera nową mapę...
...a jeszcze większa radość gdy widzi się, że faktycznie inni gracze do gry dołączają

Pomocne strony przy kłopotach z grą poprzez serwery XWIS:

środa, 13 lipca 2011

Packet Tracer: Access Control List

Tematem tego wpisu są listy dostępu, za pomocą których to możemy dowolnie filtrować ruch sieciowy przechodzący przez poszczególne routery. Istotnym jest fakt, że listy dostępu o ile mogą być definiowane globalnie (dla całego routera) to są używane lokalnie na interfejsach. Co więcej listy osobno przypisuje się dla pakietów przychodzących ("inbound") i wychodzących ("outbound") danego interfejsu. Samo zaś określenie "lista" wzięło się z możliwości łączenia wielu wpisów odnoszących się do kontroli dostępu w pojedynczy obiekt - właśnie listę. Wpisy te można w dowolnym momencie dodawać do już używanych list, co sprawia np. blokowanie nowych adresów IP bardzo łatwą i szybką czynnością.
Jak więc widać listy kontroli dostępu są potężnym narzędziem umożliwiającym szczegółową kontrolę ruchu. By jednak łatwiej zrozumieć działanie list dostępu posłużymy się prostym przykładem sieci (PT wersja 5.3):
Mała przykładowa "sieć"
To co na powyższym obrazku od razu rzuca się w oczy to strzałki opisane jako "IN" oraz "OUT". Strzałki te pokazują kierunek ruchu pakietów, a opisy mówią o opcji konfiguracji odpowiedzialnej za moment filtrowania (inbound/outbound).
W powyższym przykładzie zastosowano tylko kilka list, których zadaniem było wytworzenie przykładowych środowisk:
  • PC0 może komunikować się z routerem i jednocześnie nie może z PC1
    • ACL na interfejsie Fa0/0
      interface FastEthernet0/0
       ip address 192.168.1.1 255.255.255.0
       ip access-group 100 in
       duplex auto
       speed auto
      !
      access-list 100 deny ip host 192.168.1.2 host 192.168.2.2
      access-list 100 permit ip any any 
    • ACL na interfejsie Fa1/0
      interface FastEthernet1/0
       ip address 192.168.2.1 255.255.255.0
       ip access-group 1 out
       duplex auto
       speed auto
      !
      access-list 1 deny host 192.168.1.2
      access-list 1 permit any
  • PC1 może komunikować się z dowolną maszynę w sieci 192.168.1.0/24, ale nie może z routerem (na żadnym interfejsie)
    interface FastEthernet1/0
     ip address 192.168.2.1 255.255.255.0
     ip access-group 120 in
     duplex auto
     speed auto
    !
    access-list 120 deny ip 192.168.2.0 0.0.0.255 host 192.168.2.1
    access-list 120 deny ip 192.168.2.0 0.0.0.255 host 192.168.1.1
    access-list 120 permit ip any any 
Przykłady te nie są z pewnością niczym wyszukanym i przedstawiają jedynie podstawowe możliwości ACL oferowanych przez Packet Tracer. Dla porównania złożoności komend poniżej zamieszczone zostało całe drzewko możliwości komendy access-list (możliwe do pobrania po kliknięciu).
Packet Tracer CLI Info: ACL
Warto również wspomnieć, iż listy domyślnie blokują każdy pakiet, który nie jest dozwolony (stąd polecenia "permit ip any any"), toteż lista zdefiniowana następująco:
access-list 3 deny 192.168.0.0 0.0.255.255
w praktyce nie tylko zablokuje wszelki ruch z podsieci 192.168.0.0/16, ale również jakikolwiek ruch przechodzący przez interfejs z włączoną listą w ustalonym kierunku. Tak więc lista ta jest generalnie równoważna wpisowi:
access-list 3 deny any
Jeśli jednak dodamy wpis:
access-list 3 permit any
to lista zacznie blokować jedynie ruch pochodzący ze wskazanej podsieci.

Co ciekawe zachowanie to nie zawsze jest emulowane poprawnie w starszych wersjach Packet Tracera o czym należy pamiętać przy konfigurowaniu sieci w oparciu o wyniki z tegoż programu.

poniedziałek, 4 lipca 2011

X-COM Apocalypse i Windows 7

Dawno, dawno temu, jeszcze za czasów działalności MicroProse powstał cykl gier, które ukształtowały wyobrażenie graczy o kosmitach. Ta saga opowiadała o losach organizacji X-COM (Extraterrestrial Combat Unit), która to miała bronić naszej planety i całej ludzkiej cywilizacji przed agresją mieszkańców z innych światów...

Kilka lat temu borykałem się z odpaleniem Apokalipsy pod Windows XP i Windows Vista, niestety uświadczyłem postępu technologicznego i platformy 64 bitowej, która bez "wspomagacza" w postaci DOSBoxa nie chce pozwolić mi tej cudnej gierki odpalić. Jakże ogromnie zawiodłem się gdy nieoczekiwany problem wyłonił się również po samej stronie DOSBoxa. O ile na XP oraz Viście udało się bez problemów uruchomić grę bez płytki CD tak pod Win 7 w żaden sposób nie daje się tej czarnej magii odprawić (owszem gra w wersji RIP [1] [2] działa bez problemów bez CD). Dlatego też skazany zostałem na używanie płytki CD (co ciekawe nawet montowanie obrazu dysku z pliku ISO nie pomaga, czym zostałem zaskoczony maksymalnie).
We wpisie tym postanowiłem połączyć dawne doświadczenia z tymi ostatnio nabytymi, tak by stanowił on swoiste kompendium mej wiedzy na temat "uruchamiania XCOM Apocalypse na nowych systemach operacyjnych" (brzmi prawie jak temat pracy magisterskiej) ;]



Windows XP / VistaWindows XP / Vista DOSBoxWindows 7 DOSBox
INSTALACJA
Przy większych dyskach XCOMA nie wykrywa odpowiednio wolnego miejsca na dysku, więc może się okazać, że fizycznie nie będzie możliwości instalacji. W zasadzie można to obejść kopiując pliki z „XCOMA” z CD na dysk. Alternatywnym rozwiązaniem jest odpalenie DOSBoxa i zamontowanie dysku z argumentem „freesize” ('mount c c:\ -freesize 1024') oraz zamontowanie płyty cd ('mount d d:\ -t cdrom'). Teraz możemy wejść na cd ('d:') i odpalić instalator ('install').
DODATKOWE OPCJE
Aby słyszeć dźwięki, wymagany jest VDMSound (umożliwia emulację soundblasterów i innych przedpotopowych kart dźwiękowych). Po instalacji nie trzeba nic zmieniać w jego opcjach. By grać bez płytki, musimy wpierw przekopiować z niej plik 'MUSIC'. Najlepiej przekopiować go do katalogu w którym zainstalowaliśmy grę (np. 'C:\XCOMA'). Następnie uruchamiamy DOSBox i montujemy dysk z zainstalowaną grą ('mount c c:\' lub od razu 'mount c c:\XCOMA') oraz napęd cd – tym razem czysto wirtualny ('mount d c:\XCOMA -t cdrom' – ważne by litera dysku na którą montujemy była taka sama jak ta z której instalowaliśmy grę). Teraz możemy już odpalić Apokalipsę bez płytki. Niestety nie udało się uruchomić gry bez umieszczania płyty CD w napędzie (testowałem na wersjach DOSBoxa 0.72 - 0.74).
Do gry potrzeba kilka plików, które znajdują się w TEJ paczce. Po ściągnięciu należy rozpakować ją do katalogu, w którym zainstalowano grę. Po rozpakowaniu pojawi się 8 plików, z których bezpośrednio korzystać będziemy jedynie z ‘WINXCOM3.BAT'
KONFIGURACJA DŹWIĘKU
Jeśli chcemy mieć dźwięk w grze musimy odpalić 'SETUP.EXE' z katalogu w którym mamy grę. Zazwyczaj gra po wyborze autodetekcji karty sama wszystko ustawi, jednak czasem trzeba zrobić to ręcznie, w takiej sytuacji wybieramy kolejno: SoundBlaster 16/AWE32, Port: 220, IRQ: 7, DMA: 5 (są to domyślne wartości zarówno dla DOSBox jak i VDMSound).


Jeśli zdecydujemy się na grę w DOSBoxie zapewne będziemy chcieli skorzystać z opcji pełnego ekranu - tu jednak przy domyślnych opcjach konfiguracji (lokalizacja pliku konfiguracyjnego) pojawia się problem z częstotliwością odświeżania. Zaradzić temu można w dwojaki sposób (dla każdej opcji podana została konfiguracja DOSBoxa):
Pełny ekranPrzeskalowane okno
 [sdl]
fullscreen=true
fulldouble=true

output=overlay
[sdl]
windowresolution=1280x680
output=overlay

[render]
scaler=2xsai

Rozdzielczość i typ skalowania można dobrać dowolnie według własnego uznania i rozdzielczości własnego monitora :) Co do skalowania warto zajrzeć na wiki DOSBoxa by poznać różnice pomiędzy typami skalarów oraz szeroką gamę możliwości konfiguracyjnych tegoż programu.

Masz inny problem? Zajrzyj: X-COM na Steamie / DOSBox

niedziela, 19 czerwca 2011

Packet Tracer: Redistribute RIP + OSPF

Tak jakoś przy tworzeniu projektu na sieci komputerowe dłużej nad tym problemem musiałem się pochylić. Co ciekawe nie tyle z racji błędnej metody, ale z racji problemów Pacet Tracera ^.^ Bo jakże wytłumaczyć fakt, że raz jedna konfiguracja nie działa, a za chwilę po wyczyszczeniu trasowania RIPem i ustawienia wszystkiego identycznie już działać zaczyna?

Tak czy inaczej niechaj wpis ten będzie taką małą pomocą dla tych wszystkich, którzy na ten sam problem natrafią - pamiętajcie jak nie działa to skonfigurujcie to samo od nowa ;>




A teraz trochę CLI i opisu sieci, której logiczny schemat wygląda tak:


Generalnie mamy 3 sieci, w których po lewej stronie rutera 1 zastosowano OSPF, a po prawej RIP. To czym się różnią to sposób konfiguracji:
  • 1sza sieć jest podzielona na podsieć dla rutingu z RIPem (200.100.2.0) i na podsieć dla rutingu z OSPF (200.100.1.0)
  • 2ga sieć cały ruting prowadzi w jednej podsieci (200.101.1.0) i używa wildcardów na OSPF 0.0.0.255
  • 3cia sieć jest de facto tym co 2ga, z tą różnicą, że wildcardy na OSPF są równie 0.0.0.3, więc ustawione dla każdej podsieci z osobna
 I przykładowy listening z konfiguracji routerów dla drugiej sieci:
Router0
interface FastEthernet0/0
 ip address 192.169.1.1 255.255.255.0
 duplex auto
 speed auto
!
interface Serial0/0
 ip address 200.101.1.2 255.255.255.252
!
router ospf 7
 log-adjacency-changes
 network 192.169.1.0 0.0.0.255 area 0
 network 200.101.1.0 0.0.0.255 area 0

Router1
interface Serial0/0
 ip address 200.101.1.1 255.255.255.252
 clock rate 4000000
!
interface Serial0/1
 ip address 200.101.1.5 255.255.255.252
!
router ospf 7
 log-adjacency-changes
 redistribute rip 
 passive-interface Serial0/1
 network 200.101.1.0 0.0.0.255 area 0
!
router rip
 version 2
 redistribute ospf 7 metric 3 
 passive-interface Serial0/0
 network 200.101.1.0

Router2
interface FastEthernet0/0
 ip address 192.169.2.1 255.255.255.0
 duplex auto
 speed auto
!
interface Serial0/0
 ip address 200.101.1.6 255.255.255.252
 clock rate 4000000
!
router rip
 version 2
 network 192.169.2.0
 network 200.101.1.0

i tablice trasowania:

Router0
C    192.169.1.0/24 is directly connected, FastEthernet0/0
O E2 192.169.2.0/24 [110/20] via 200.101.1.1, 00:08:32, Serial0/0
     200.101.1.0/30 is subnetted, 2 subnets
C       200.101.1.0 is directly connected, Serial0/0
O       200.101.1.4 [110/128] via 200.101.1.1, 00:08:32, Serial0/0

Router1
O    192.169.1.0/24 [110/65] via 200.101.1.2, 00:08:17, Serial0/0
R    192.169.2.0/24 [120/1] via 200.101.1.6, 00:00:10, Serial0/1
     200.101.1.0/30 is subnetted, 2 subnets
C       200.101.1.0 is directly connected, Serial0/0
C       200.101.1.4 is directly connected, Serial0/1

Router2
R    192.169.1.0/24 [120/3] via 200.101.1.5, 00:00:02, Serial0/0
C    192.169.2.0/24 is directly connected, FastEthernet0/0
     200.101.1.0/30 is subnetted, 2 subnets
R       200.101.1.0 [120/1] via 200.101.1.5, 00:00:02, Serial0/0
C       200.101.1.4 is directly connected, Serial0/0

(pogrubione trasy poznano dzięki redystrybucji)

A na koniec jeszcze link do pliku z tą siecią: link (PT wersja 5.3)

poniedziałek, 30 maja 2011

StarCraft przez Hamachi

Niestety Blizzard już dawno zapomniał o StarCrafcie i o problemach jego posiadaczy z grą na Battle.net (a może nie potrafi ich rozwiązać). Dlatego też obecnie najlepszym rozwiązaniem jest gra przez LAN lub właśnie Hamachi :> Jednak taka gra nie jest łatwą sztuką, bo SC ma tendencję do nie wykrywania komputerów połączonych przez sieć Hamachi.
Jednak i na to znalazło się lekarstwo -> ForceBindIP :D Jak więc odpalić SC tak by móc pograć sobie ze znajomymi przez Hamachi? :>
  1. Ściągnij i zainstaluj ForceBindIP (strona | instalator | plik zip)
  2. Ściągnij i zainstaluj Hamachi (oficjalna strona | starsze wersje)
  3. Uruchom Hamachi i skopiuj swój adres IP
  4. Utwórz skrót do StarCraft.exe i wejdź w jego właściwości

  5. Przy polu "element docelowy" dopisz ForceBindIP 5.xxx.xxx.xxx (twój IP z Hamachi)
  6. Kliknij OK lub Zastosuj
  7. Jeśli Windows potulnie przyjął polecenie to za pomocą tego skrótu powinieneś móc łączyć się przez sieć Hamachi i bez problemów grać w SC z innymi ludźmi :)
    Jeśli jednak Windows spłatał figla i obwieścił iż nie wie co to ForceBindIP to przejdź do punktu 8.
  8. Ściągnij plik zip z ForceBind (link) i wypakuj go do folderu "System32" w katalogu Windowsa (jeśli UAC będzie pytał się czy jesteś pewien - wciśnij, że jesteś ^^). Powróć do punktu 5. :)
Jeśli mimo tego StarCraft nie pozwala na podłączenie się do gry - spróbuj sposobu z tego wpisu: Gdy program nie widzi sieci Hamachi.