Informacje o woluminach logicznych w systemie z wykorzystaniem C++ i WinAPI Drukuj
Ocena użytkowników: / 0
SłabyŚwietny 
Wpisany przez Tomasz Stasiak   
środa, 10 sierpnia 2011 22:38
    W tym tutorialu postaram się pokazać, jak w C/C++ uzyskać podstawowe informacje o podłączonych do systemu woluminach logicznych. Ale od początku - co to są te woluminy logiczne? Inaczej zwane partycjami lub dyskami logicznymi są wydzielonymi obszarami fizycznego nośnika danych (takiego jak dysk twardy, pendrive, płyta CD czy DVD) i służą do przechowywania informacji. W systemie Windows (bo tego systemu ten tekst dotyczy) każdy dysk logiczny posiada przypisaną do niego literę alfabetu. Pierwsze dwie (A i B) są zarezerwowane dla dyskietek, litera C jest pierwszą dostępną dla dysków twardych i reprezentuje zwykle partycję systemową (na której jest umieszczony system). Należy uważać, żeby nie pomylić fizycznego nośnika z partycją, bo, mimo iż popularnie mówi się np. "dysk c" mowa tu o wydzielonej partycji na którymś z nośników (jeden dysk może być podzielony na wiele woluminów/partycji, każdą reprezentowaną inną literą). Także tutaj będzie pokazane, w jaki sposób można uzyskać dane o partycjach (między innymi o numerze seryjnym woluminu, nie wolno mylić z serialem HDD!).
    Ktoś może się zapytać - ale po co nam takie informacje? No cóż, mogą się przydać w wypadku, gdy chcemy zabezpieczyć swój program (aby utrudnić jego rozpowszechnianie bez licencji), lub (jak to jest wykonane np. w GG v8) użyć np. numeru seryjnego jednego z woluminów do szyfrowania danych (w tym wypadku to było szyfrowanie plików komunikatora zawierających dane użytkownika). Następne narzucające się pytanie to: co to jest to WinAPI? Jest to zbiór funkcji, stałych, zmiennych czy struktur umożliwiający działanie programu w systemie Windows. Udostępnia ono wiele nowych możliwości, często niemożliwych do osiągnięcia korzystając z elementów udostępnianych przez język.
    Tyle teorii powinno wystarczyć - czas na praktykę! Co nam będzie potrzebne:
        - Edytor (w którym będziemy pisali kod), np. Code::Blocks, Microsoft Visual Studio, Notepad++ etc.
        - Kompilator, np. MinGW
        - System Windows, dzięki któremu będziemy mogli zobaczyć efekty naszej pracy
        - Podstawowa wiedza o języku C++
        - Trochę cierpliwości (bo mam zwyczaj strasznie lać wodę :P)
    Na początku musimy określić, co nam będzie potrzebne do wykonania zadania, czyli jakich funkcji i bibliotek będziemy musieli użyć. Do uzyskania podstawowych informacji o partycji służą 2 funkcje WinAPI - GetDriveType() oraz GetVolumeInformation(). Pierwsza służy do określenia typu nośnika danych, druga udostępnia już pożądane przez nas informacje (np. nazwa woluminu, system plików czy serial). Obie funkcje są udostępnione w pliku nagłówkowym "windows.h", dodatkowo będziemy potrzebowali funkcji umożliwiających wypisanie wyniku na konsolę, czyli standardowe cout z biblioteki "iostream" oraz biblioteki string, która powinna nam ułatwić później zadanie:     
01.#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) // Makro, dzięki któremu możemy obliczyć wielkość tablicy z elementami stałej wielkości
02.#include <windows.h>
03.#include <iostream>
04.#include <sstream> // Przyda się później
05.#include <string>
06./**
07. * Główna funkcja programu
08. */
09. int main(void)
10.{
11.    return 0;
12.} // End main()
    No dobrze, mamy już szkielet programu, możemy przejść dalej - trzeba ustalić jakie zmienne będą nam potrzebne (w nich będziemy przechowywać informacje o woluminach). Jak możemy zobaczyć na stronach msdn funkcja GetDriveType() zwraca żądaną wartość, więć nie musimy jej nigdzie zapisywać (choć możemy, nikt nam przecież nie broni :)), a jako parametr przyjmuje główny katalog partycji. Nnatomiast GetVolumeInformation() zwraca kod błędu, a argumenty przyjmuje jako pointery (wskaźniki); są to kolejno (w nawiasie typ): głowny katalog partycji (char*), bufor na nazwę partycji (char*), jej wielkość (DWORD), numer seryjny (DWORD*), maksymalna długość elementu ścieżki (DWORD*), flagi systemu plików (DWORD*), bufor na nazwę systemu plików (char*), jego wielkość (DWORD).
    Dlaczego DWORD? Dlaczego nie można użyć zwykłego inta? Ma to związek z architekturą komputera PC i Asemblerem, mianowicie int (w językach wysokiego poziomu) ma MINIMUM 16 bitów, czyli 2 bajty, natomiast DWORD (skrót o double WORD, czyli podwójne słowo) ma zawsze 32 bity, czyli 4 bajty. Coś takiego jak DWORD istnieje nawet w Asemblerze, dzięki czemu możliwe jest użycie WinAPI w języku dowolnego poziomu (w c/c++ dword jest zadeklarowany następująco: typedef unsigned long DWORD;).
    Wracając do tematu: tworząc wszystkie wymienione powyżej zmienne można od razu stworzyć tablicę zawierającą listę możliwych rodzajów nośników na podstawie wartości zwracanej przez GetDriveType(), czyli (w nawiasie wartość z GetDriveType()): nieznany typ nośnika (0), katalog główny jest błędny (1), napęd wymienny (2), napęd stały (3), zdalny napęd (4), CDROM (5), dysk RAM (6).
    Tak więc dopisujemy ten kod do ciała funkcji main programu:     
01.// Zmienne winapi do wyciągania dokładniejszych info o dysku
02.char fileSystemName[MAX_PATH*5 + 1] = {0}; // Nazwa systemu plików
03.char volumeName[MAX_PATH*5 + 1]     = {0}; // Nazwa partycji
04.DWORD maxComponentLen               = 0;   // Maksymalna długość elementu ścieżki
05.DWORD fileSystemFlags               = 0;   // Flagi systemu plików (tu pominięte)
06.DWORD serialNumber                  = 0;   // Numer seryjny woluminu
07.// Typy dysków
08.std::string drive_types[] = {"nieznany typ",
09.                             "katalog glowny jest bledny",
10.                             "naped wymienny (np. pendrive)",
11.                             "naped staly (np. dysk twardy lub dysk flash)",
12.                             "zdalny naped",
13.                             "CDROM",
14.                             "dysk RAM"
15.                            };
    Pozostaje jeszcze to, o co nam chodziło, czyli uzyskanie informacji o wszystkich woluminach zamontowanych w systemie (jak już wspominałem, każda partycja jest reprezentowana przez literę alfabetu). Tak więc musimy "przejść" przez wszystkie możliwe partycje, sprawdzić, czy są one prawidłowe (czyli nie jest to nieznany typ lub z błędnym katalogiem głównym) i jeśli tak, to wyświetlić o nich informacje:
    
01./// Rendering outputu
02. for(char i = 'C'; i <= 'Z'; ++i) // Sprawdzanie woluminów
03.{
04.     if(GetDriveType((to_string(i)+":/").c_str()) != 0 && GetDriveType((to_string(i)+":/").c_str()) != 1) // Jeśli wolumin istnieje
05.    {
06.        /// Wyciąganie info o woluminie
07.         if ( GetVolumeInformation((to_string(i)+":/").c_str(), volumeName, ARRAYSIZE(volumeName), &serialNumber, &maxComponentLen, &fileSystemFlags, fileSystemName, ARRAYSIZE(fileSystemName)) )
08.        {
09.            /// Dane dotyczące woluminów logicznych
10.            // Etykieta woluminu
11.            std::cout << "Etykieta woluminu.....................: ";
12.            std::cout << volumeName << "\n";
13.            // Numer seryjny
14.            std::cout << "Numer seryjny woluminu................: ";
15.            std::cout << serialNumber;
16.            // System plików
17.            std::cout << "System plikow.........................: ";
18.            std::cout << fileSystemName << "\n";
19.            // Maksymana długość komponentu
20.            std::cout << "Maksymalna dlugosc komponentu sciezki.: ";
21.            std::cout << maxComponentLen << "\n";
22.            // Typ nośnika
23.            std::cout << "Typ nosnika...........................: ";
24.            std::cout << drive_types[GetDriveType((to_string(i)+":/").c_str())] << "\n";
25. 
26.            std::cout << "\n";
27.        } // End if
28.    } // End if
29.} // End for
    Funkcja to_string użyta tu kilkukrotnie zamienia typ char na std::string i ma następującą postać (tutaj przydaje się właśnie biblioteka sstream):     
01./**
02. * Konwersja znaku (char) na std::string
03. *
04. * @param  const char text znak do konwersji
05. * @return std::string
06. */
07. std::string to_string(const char text)
08.{
09.    std::stringstream ss;
10.    std::string       str;
11.    ss << text;
12.    ss >> str;
13. 
14.    return str;
15.} // End to_string()
    No dobrze, ktoś powie, ale co to jest niby za numer seryjny, jak jest on 10 cyfrową liczbą, a polecenie dir (które twierdzi, że też pokazuje serial woluminu) zwraca dziewięcioznakowy ciąg znaków z myślnikiem na piątej pozycji? Rzeczywiście, oba wyniki się od siebie całkowicie różnią, ale tylko na pozór. Pamiętacie, jak mówiłem, że typ DWORD ma 32 bity? Te 8 znaków z wyniku polecenia dir nieprzypadkowo ZAWSZE ma 8 znaków z zakresu od 0 do 9 lub od A do F(nie licząc separatora w postaci myślnika). Już wyjaśniam: w systemie szesnastkowym każda cyfra jest reprezentowana kolejno jako jeden z tych znaków: [0123456789ABCDEF] czyli wartośći od 0 do 15 (szesnaście cyfr). Każdą cyfrę tego systemu można zakodować na 4 bitach (0 - 0000. F - 1111), dzięki czemu 32 bity (wielkość typu DWORD) podzielone przez 4 bity (wielkość potrzebna na jedną cyfrę szesnastkową) daje właśnie te 8 znaków (myślnik pełni rolę ozdobną i pomaga w ewentualnym przepisywaniu). Aby zamienić liczbę z wartości dziesiętnej (którą dostaliśmy z funkcji GetVolumeInformation()) na wartość jak ta z polecenia dir wykorzystamy następującą funkcję:     
01./**
02. * Zmiana DWORD'a (32 bity) na liczbę szesnastkową po 16 bitach rozdzieloną myślnikiem zawartą w std::string
03. *
04. * @param  DWORD data 32-bitowa liczba do konwersji
05. * @return std::string
06. */
07. std::string to_string_upper_hex(DWORD data)
08.{
09.    /// Konwersja na stringa (z równoczesną zmianą podstawy systemu liczenia)
10.    std::stringstream ss;
11.    std::string       str;
12.    ss << std::hex << data;
13.    ss >> str;
14.    /// Zmiana wielkości liter na wielkie
15.     for(unsigned short i = 0; i < str.length(); ++i)
16.        str[i] = toupper(str[i]);
17.    /// Podział na 2 połowy i rozdzielenie ich średnikiem
18.    str = str.substr(0, 4) + "-" + str.substr(4, 4);
19. 
20.    return str;
21.} // End to_string_upper_hex()
    Musimy jeszcza zamienić linijkę odpowiedzialną za wypisanie numeru seryjnego, będzie ona miała taką postać:     
1.std::cout << serialNumber << "(dec)/" << to_string_upper_hex(serialNumber) << "(hex)" << "\n";
    No dobrze, osiągnęliśmy już zamierzony efekt, więc czy coś jeszcze można zrobić? Skoro już rozpoczęliśmy zabawę z WinAPI, to czemu nie? Na razie program wygląda monotonnie i jednokolorowo, więc czemu by go trochę nie ożywić? Do zmiany koloru tekstu/tła (UWAGA! tło zmienia się tylko pod tekstem, nie dla całej konsoli) służy funkcja SetConsoleTextAttribute() (także udostępniana przez nagłówek "windows.h"). Przyjmuje ona 2 argumenty - uchwyt do bufora ekranu konsoli oraz atrybuty (opisane dokładniej tutaj ). Aby uzyskać wymagany uchwyt skorzystamy z funkcji GetStdHandle() która zwraca pożądany uchwyt, a jako jedyny argument wymaga podania informacji, do czego chcemy uzyskać dostęp (stdin, stdout, stderr). Jako iż chcemy zmienić kolor tekstu wyświetlanego na stdout podajemy do niej argument STD_OUTPUT_HANDLE:     
1.HANDLE console_out = GetStdHandle(STD_OUTPUT_HANDLE); // Uchwyt do konsoli (aby można było zmieniać kolory)
    Dzięki temu uchwyt znajduje się w zmiennej console_out i możemy go już używać. Zmieńmy np. kolor etykiet na intensywny czerwony (atrybuty FOREGROUND_RED i FOREGROUND_INTENSITY), natomiast odczytane wartości na kolor zielony (atrybut FOREGROUND_GREEN):     
01./// Dane dotyczące woluminów logicznych
02.// Etykieta woluminu
03.SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
04.std::cout << "Etykieta woluminu.....................: ";
05.SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
06.std::cout << volumeName << "\n";
07.// Numer seryjny
08.SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
09.std::cout << "Numer seryjny woluminu................: ";
10.SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
11.std::cout << serialNumber << "(dec)/" << to_string_upper_hex(serialNumber) << "(hex)" << "\n";
12.// System plików
13.SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
14.std::cout << "System plikow.........................: ";
15.SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
16.std::cout << fileSystemName << "\n";
17.// Maksymana długość komponentu
18.SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
19.std::cout << "Maksymalna dlugosc komponentu sciezki.: ";
20.SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
21.std::cout << maxComponentLen << "\n";
22.// Typ nośnika
23.SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
24.std::cout << "Typ nosnika...........................: ";
25.SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
26.std::cout << drive_types[GetDriveType((to_string(i)+":/").c_str())] << "\n";
    Czy wszystko jest ok? Nie do końca - jeśli przyjrzymy się bliżej naszemu kodowi, okaże się, że jedyne, co się w tym fragmencie zmienia, to etykieta (np. "Numer seryjny woluminu......:") oraz wartość parametru. Mając to na względzie można stworzyć dodatkową funkcję (nazwijmy ją np. print_data()), która będzie wyświetlała te dane:     
01./**
02. * Wyświetlenie kolejnych parametrów woluminu
03. *
04. * @param  std::string heading nagłówek do wyświetlenia
05. * @param  std::string val     wartość do wyświetlenia
06. * @return void
07. */
08. void print_data(std::string heading, std::string val)
09.{
10.    HANDLE console_out = GetStdHandle(STD_OUTPUT_HANDLE); // Uchwyt do konsoli (aby można było zmieniać kolory)
11. 
12.    SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
13.    std::cout << heading;
14.    SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
15.    std::cout << val << "\n";
16.} // End print_data()
    Jak widać także zmienna HANDLE console_out została tu przeniesiona z funkcji main, gdzie nie będzie już dłużej potrzebna.     I tu pojawia się problem: napisaliśmy, że wartość do wyświetlenia ma typ std::string, a przecież zmienne mają typy std::string, char* lub DWORD! Na szczęście w C++ zostały stworzone szablony (ang. templates), dzięki którym możemy stworzyć funkcję operującą na dowolnym typie danych. Jak to będzie wyglądać? Możemy np. zmodyfikować wcześniejszą funkcję to_string(), aby konwertowała nie tylko typ char, lecz dowolny (bo konwersja dowolnego typu prostego na std::string wygląda tak samo):     
01./**
02. * Konwersja danych na std::string
03. *
04. * @param  T data znak do konwersji
05. * @return std::string
06. */
07.template<typename T>
08. std::string to_string(T data)
09.{
10.    std::stringstream ss;
11.    std::string       str;
12.    ss << data;
13.    ss >> str;
14. 
15.    return str;
16.} // End to_string()
    I jej definicja:     
1./// Konwersja danych na zmienną typu string
2.template<typename T>
3. std::string to_string(T data);
    Jak widać, zmiany ograniczyły się jedynie do dodania w 2 miejscach linii "template" oraz zmiany "char text" na "T data" (przy okazji zmieniłem nazwę zmiennej, aby nie wprowadzała w błąd). Dzięki temu kod wyświetlający dane nie zawiera powtórzeń (zgodnie z regułą DRY) i wygląda przejrzyściej (dodatkowe nowe linie dodane dla przejrzystości):     
01.print_data("Etykieta woluminu.....................: ",
02.           to_string<char*>(volumeName));
03.print_data("Numer seryjny woluminu................: ",
04.           to_string<DWORD>(serialNumber) + "(dec)/" + to_string_upper_hex(serialNumber) + "(hex)");
05.print_data("System plikow.........................: ",
06.           to_string<char*>(fileSystemName));
07.print_data("Maksymalna dlugosc komponentu sciezki.: ",
08.           to_string<DWORD>(maxComponentLen));
09.print_data("Typ nosnika...........................: ",
10.           drive_types[GetDriveType((to_string<char>(i)+":/").c_str())] );
    Jak widać, kodu trochę przybyło, ale program zaczął się wyróżniać :). Jeszcze tylko dodajmy łapanie wyjątków (żeby progrm się brzydko nie wysypał):     
01.// ...
02.     int main(void)
03.    {
04.         try
05.        {
06.        } // End try
07.         catch(const std::exception &e)
08.        {
09.            MessageBox(NULL, e.what(), "Podczas dzialania wystapil blad!", MB_OK);
10.        } // End catch
11.         
12.        return 0;
13.    } // End main()
14.// ...
    Użyta tu funkcja MessageBox(), będąca także elementem WinAPI odpowiada za wyświetlenie małego okienka z komunikatem. Jej parametry to, kolejno: okno rodzica (NULL oznacza brak rodzica), treść komunikatu, tekst na pasku tytułu i dodatkowe parametry (MB_OK oznacza wyświetlenie przycisku OK). Zbierzmy teraz wszystko razem i powstanie taki kod:     
001./************************************************************************************
002. * Name:      hddinfo.cpp                                                           *
003. * Purpose:   Extracting basic information about partitions installed in the system *
004. * Author:    Tomasz Stasiak                                                        *
005. * Created:   2011-08-10                                                            *
006. * Copyright: Tomasz Stasiak                                                        *
007. * License:   http://creativecommons.org/licenses/by/2.5/pl/ ;                       *
008. ***********************************************************************************/
009.#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
010.#include <windows.h>
011.#include <iostream>
012.#include <sstream>
013.#include <string>
014. 
015./// Konwersja danych na zmienną typu string
016.template<typename T>
017. std::string to_string(T data);
018./// Konwersja numeru seryjnego z liczby dziesiętnej na format zwracany przez np. dir
019. std::string to_string_upper_hex(DWORD data);
020./// Wyświetlenie pokolorowanego tekstu
021. void print_data(std::string heading, std::string val);
022. 
023./**
024. * Główna funkcja programu
025. */
026. int main(void)
027.{
028.     try
029.    {
030.        // Zmienne winapi do wyciągania dokładniejszych info o dysku
031.        char fileSystemName[MAX_PATH*5 + 1] = {0}, volumeName[MAX_PATH*5 + 1] = {0};
032.        DWORD maxComponentLen = 0, fileSystemFlags = 0, serialNumber = 0;
033.        // Typy dysków
034.        std::string drive_types[] = {"nieznany typ",
035.                                     "katalog glowny jest bledny",
036.                                     "naped wymienny (np. pendrive)",
037.                                     "naped staly (np. dysk twardy lub dysk flash)",
038.                                     "zdalny naped",
039.                                     "CDROM",
040.                                     "RAM"
041.                                    };
042.        /// Rendering outputu
043.         for(char i = 'C'; i <= 'Z'; ++i) // Sprawdzanie woluminów
044.        {
045.             if(GetDriveType((to_string(i)+":/").c_str()) != 0 && GetDriveType((to_string(i)+":/").c_str()) != 1) // Jeśli wolumin istnieje
046.            {
047.                /// Wyciąganie info o woluminie
048.                 if ( GetVolumeInformation((to_string(i)+":/").c_str(), volumeName, ARRAYSIZE(volumeName), &serialNumber, &maxComponentLen, &fileSystemFlags, fileSystemName, ARRAYSIZE(fileSystemName)) )
049.                {
050.                    /// Dane dotyczące woluminów logicznych
051.                    print_data("Etykieta woluminu.....................: ",
052.                               to_string<char*>(volumeName));
053.                    print_data("Numer seryjny woluminu................: ",
054.                               to_string<DWORD>(serialNumber) + "(dec)/" + to_string_upper_hex(serialNumber) + "(hex)");
055.                    print_data("System plikow.........................: ",
056.                               to_string<char*>(fileSystemName));
057.                    print_data("Maksymalna dlugosc komponentu sciezki.: ",
058.                               to_string<DWORD>(maxComponentLen));
059.                    print_data("Typ nosnika...........................: ",
060.                               drive_types[GetDriveType((to_string<char>(i)+":/").c_str())] );
061. 
062.                    std::cout << "\n";
063.                } // End if
064.            } // End if
065.        } // End for
066.    } // End try
067.     catch(const std::exception &e)
068.    {
069.        MessageBox(NULL, e.what(), "Podczas dzialania wystapil blad!", MB_OK);
070.    } // End catch
071. 
072.    return 0;
073.} // End main()
074. 
075./**
076. * Konwersja danych na std::string
077. *
078. * @param  T data znak do konwersji
079. * @return std::string
080. */
081.template<typename T>
082. std::string to_string(T data)
083.{
084.    std::stringstream ss;
085.    std::string       str;
086.    ss << data;
087.    ss >> str;
088. 
089.    return str;
090.} // End to_string()
091. 
092./**
093. * Zmiana DWORD'a (32 bity) na liczbę szesnastkową po 16 bitach rozdzieloną myślnikiem zawartą w std::string
094. *
095. * @param  DWORD  data 32-bitowa liczba do konwersji
096. * @return std::string
097. */
098. std::string to_string_upper_hex(DWORD data)
099.{
100.    /// Konwersja na stringa (z równoczesną zmianą podstawą systemu liczenia)
101.    std::stringstream ss;
102.    std::string       str;
103.    ss << std::hex << data;
104.    ss >> str;
105.    /// Zmiana wielkości liter na wielkie
106.     for(unsigned short i = 0; i < str.length(); ++i)
107.        str[i] = toupper(str[i]);
108.    /// Podział na 2 połowy i rozdzielenie ich średnikiem
109.    str = str.substr(0, 4) + "-" + str.substr(4, 4);
110. 
111.    return str;
112.} // End to_string_upper_hex()
113. 
114./**
115. * Wyświetlenie kolejnych parametrów woluminu
116. *
117. * @param  std::string heading nagłówek do wyświetlenia
118. * @param  std::string val     wartość do wyświetlenia
119. * @return void
120. */
121. void print_data(std::string heading, std::string val)
122.{
123.    HANDLE console_out = GetStdHandle(STD_OUTPUT_HANDLE); // Uchwyt do konsoli (aby można było zmieniać kolory)
124. 
125.    SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
126.    std::cout << heading;
127.    SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
128.    std::cout << val << "\n";
129.} // End print_data()
    Program powinien działać na wszystkich systemach od Windows XP (włącznie) w górę. Dlaczego nie na starszych? Winowajcą jest funkcja GetDriveType() udostępniona dopiero w tymże systemie.

    Jeszcze tylko paczka z kodem + skompilowany program.