Problem ze wskaźnikiem w Quake 2.

0

Hej, od ładnych kilku lat nie zadawałem pytań : D
No ale do rzeczy.

Mam forka Quake-2 (wziętego z ID software) i w jednym momencie dzieje się coś co w tej chwili mi psuje zabawę.
Piszę sobie funkcję która zastępuje macro EDICT_NUM:

edict_t* EdictNum( int n )
{
    if( ge.edict_size == 0 )
    {
        return NULL;
    }

    edict_t* result = &ge.edicts[n];
    return result;
}
	SV_InitGameProgs ();
	for (i=0 ; i<maxclients->value ; i++)
	{
		ent = EdictNum(i); // ent to edict_t*
		ent->s.number = i+1;
		svs.clients[i].edict = ent;
		memset (&svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd));
	}

EdictNum zwraca:
0x0000026289726070 (podgląd autosów w visualu na linijce ent = , no i na końcu funkcji EdictNum jest też ta wartość.).
a potem okazuje się, że zmienna ent (w linijce ent->...) ma już wartość:
0xffffffff89726070
Jak można się domyślać, kończy się to crashem, najpewniej jest to adres w jakimś chronionym rejonie.
Wie ktoś o co tu może chodzić?

Ps. dzieje się tak dla i = 0, case kiedy i = 1, 2 ... zostawiam sobie na potem.

Ps2. moje repo: https://github.com/bartekordek/Quake-2/tree/cmake-c
plik to server/sv_init.c i win32/qgl_win.c

1
  1. Brak walidacji n i wyniku EdictNum
  2. prawdopodobnie lastcmd jest wskaźnikiem
  3. zamiast &ge.edicts[n] możesz napisać ge.edicts + n
0

Napisz skąd masz ge (deklaracja, inicjalizacja) bo chyba nie z GetGameAPI.

0

Tworzona jest w pliku:
shared\shared_objects.c

game_export_t ge; gdzieś u góry.
extern game_export_t ge; znajduje się w nagłówku shared_objects.h.

0

Inicjalizacja w funkcji void initGame() która znajduje się też w shared_objects.h.

1

To initGame to sam napisałeś? Wypełniasz tam ge.edict_size?
Jaka jest tego wartość?
Wiesz co to dupa debugging i dprintf?

0

Śmierdzi mi castowaniem poprzez typ ze znakiem, stąd się rodzą FF.

0

Za małe wartości, dla mnie to raczej jak jakiś memory stomp albo gdzieś po drodze niejawna konwersja przez 32 bity (niższe 32-bity są zachowane, wyższe mają ustawioną debugową-lub-losową-lub-ujemną wartość). +1 dla printf debuggingu. Nie miałem okazji spojrzeć dokładniej kod, może jest mieszanie wstawek asm albo kodu x64/x86?

0

Quake-2-cmake-c\ctf\game.h:
struct edict_s *edicts;

Czyli prędzej:
&ge->edicts[n];
Choć calości nie przeglądałem, to że to jest tablica przyjałem na wiarę.

0

Kurcze, może taki śmiertelnik jak ja nie powinien się dotykać Świętego Kodu Johna Carmacka .
Tak na marginesie, ten kod jest, kurcze, jakiś mega innnowacyjny, a co dopiero na lata 90'.
Jest tam podmeinianie w locie kodu dll. Czyli coś czego dziś C++ nie do końca chce implementować.

0

Ale pomogło? Bo to tak "na czuja", nie chciało mi się żadnego komplikatora C szukać.

może taki śmiertelnik jak ja

Nadmiar krytycyzmu, jeśli się uczysz, mogłeś nie znać tego idiomu. Formalnie, wskaźnik na tablice powinien wyglądać:
int (*tabint)[];
Ale to jest składniowy potworek. Dlatego przyjęło się stosować sztuczkę int *tabint2; "a wszyscy wiedzą że to tak naprawdę wskaźnika na tablicę". No a ty tego jeszcze nie znałeś i dałeś się złapać.

Przygotowałem ci przykład ilustrujący jakim potworkiem jest formalne podejście: https://godbolt.org/z/nqn3PfaYf

int ints[12];

struct abc {
    int (*tabint)[12];
    int *tabint2;
};

int main()
{
    struct abc a;
    struct abc * pa = &a;

    a.tabint = &ints;
    int j = (*pa->tabint)[0];
     
    a.tabint2 = ints;
    int i = pa->tabint2[0];


    return 0;
}

edit: A żeby było jeszcze śmieszniej:

    a.tabint2 = ints;
    int i = pa->tabint2[3];

    int* pz = ints;
    int z = pz[1];

Wygląda na to, że czasem sie wywołuje tak jak ty użyleś, a czasem inaczej.

1

Tak na marginesie, ten kod jest, kurcze, jakiś mega innnowacyjny, a co dopiero na lata 90'.
Jest tam podmeinianie w locie kodu dll. Czyli coś czego dziś C++ nie do końca chce implementować.

Z tym akurat mocno przesadzasz. Podmieniany jest kod renderera ze sprzętowego na software'owy i z powrotem, a konkretnie podmieniane są wskaźniki do funkcji poprzez funkcję pobieraną przez GetProcAddress. Nie do końca dzieje się w locie bo trzeba zrestartować level jak się zmieni renderer w trakcie rozgrywki. Najzwyklejszy w świecie polimorfizm w C używany również w innych systemach jak naprzykład OpenSSL gdzie można podmieniać sobie silnik kryptograficzny na sprzętowy nie rekompilując samej binarki openssl. Nie ma w tym nic czego brakowałoby w C++.

Prawdziwa magia to zaczyna się w software'owym rendererze, tam chyba ze 20% kodu to haki w asemblerze pisane z palca przez jednego maniaka, nie pamiętam niestety jak się nazywał. Carmack też dodał tam coś specjalnego od siebie w postaci jakiegoś oświetlenia, albo iluminacji, niestety nazwa tegoż również wyleciała mi z pamięci.

Pomimo tych cudów, silnik następcy, quake 3, jest IMHO dużo ciekawszy.

3
pylaochos napisał(a):

Piszę sobie funkcję która zastępuje macro EDICT_NUM:

Po co psuć coś co działa? :-)

0
several napisał(a):

Jest tam podmeinianie w locie kodu dll. Czyli coś czego dziś C++ nie do końca chce implementować.

Z tym akurat mocno przesadzasz. Podmieniany jest kod renderera ze sprzętowego na software'owy i z powrotem, a konkretnie podmieniane są wskaźniki do funkcji poprzez funkcję pobieraną przez GetProcAddress. Nie do końca dzieje się w locie bo trzeba zrestartować level jak się zmieni renderer w trakcie rozgrywki. Najzwyklejszy w świecie polimorfizm w C używany również w innych systemach jak naprzykład OpenSSL gdzie można podmieniać sobie silnik kryptograficzny na sprzętowy nie rekompilując samej binarki openssl. Nie ma w tym nic czego brakowałoby w C++.

O, znowu się wykazałem brakiem wiedzy. Nigdzie do tej pory nie widziałem by ktoś w C++ robił coś takiego (GetProcAddress + podmiana wskaźników).

0
pylaochos napisał(a):
several napisał(a):

Nigdzie do tej pory nie widziałem by ktoś w C++ robił coś takiego (GetProcAddress + podmiana wskaźników).

Zawsze kiedy lądujemy DLL dynamicznie.

  • Jest to często używany chwyt dla wtyczek w postaci DLL.
  • Oraz kiedy w czasie tworzenia kodu nie wiadomo czy podłączamy daną DLL czy nie - np sprzedaż programu modułowa.
  • Albo kiedy DLL niosą informację którą należy zsumować (ilość zakupionych stanowisk) po czym DLL nie jest potrzebna i można ją wyładować z pamięci. I jeszcze wiele innych

1 użytkowników online, w tym zalogowanych: 0, gości: 1