Portal-Zone Gothic-Zone Gothic II-Zone Gothic 3-Zone Gothic 4-Zone Modifikationen-Zone Download-Zone Foren-Zone RPG-Zone Almanach-Zone Spirit of Gothic

 

Ergebnis 1 bis 7 von 7
  1. Beiträge anzeigen #1 Zitieren
    Knight Avatar von Draxes
    Registriert seit
    Aug 2007
    Ort
    Mainz
    Beiträge
    1.920
     
    Draxes ist offline

    Ununterbrechbare Musik

    Hi! Ich stehe im Moment vor folgender Herausforderung: Ich möchte in einer bestimmten Musikzone dafür sorgen, dass das Thema dort nicht automatisch unterbrochen werden kann durch die Fight-Music. Probleme machen mir im Moment die Wechsel zwischen Std und Fgt Themen, die trotzdem durchgeführt werden.

    Das Thema einer "normalen" Musikzone ist ja folgendermaßen definiert:
    Code:
    INSTANCE OWD_Day_Std	(C_MUSICTHEME_STANDARD)		{	file			= "owd_daystd.sgt";		};
    INSTANCE OWD_Day_Thr	(C_MUSICTHEME_THREAT)		{	file			= "owd_daystd.sgt";		};
    INSTANCE OWD_Day_Fgt	(C_MUSICTHEME_FIGHT)		{	file			= "owp_dayfgt.sgt";		};
    Ich habe mal versucht nur die Std zu definieren (erste Zeile). Weil wenn keine Fgt definiert ist, kann Gothic auch nicht darauf wechseln. Allerdings passiert es trotzdem ab und an, dass die Musik plötzlich von vorne beginnt oder noch schlimmer: zwei mal minimal zeitlich versetzt beginnt zu spielen. Ich denke, dass es gerade immer dann passiert, wenn man aus einem Kampf kommt. Dann "wechselt" Gothic nochmal in das Std-Thema, das gerade schon spielt. Weiß Gothic aber nicht und interessiert es auch nicht.

    Jetzt meine Frage: Kann man diesen Mechanismus irgendwie für eine gewisse Zeitspanne außer Kraft setzen? Also dass Gothic nicht automatisch zwischen Std und Fgt wechselt? Ganz andere Lösungsansätze für ununterbrochene Musik sind mir auch herzlich willkommen!

    Edit: Eine ganz abgefahrene Idee: Ich habe gesehen, dass es die Engine-Funktion
    Code:
    static enum oHEROSTATUS __cdecl oCZoneMusic::GetHerostatus(void) (Adresse: 0x00642420)
    gibt. Vielleicht könnte man diese für eine bestimmte Zeit lang so modifizieren, dass hier immer STD zurückgegeben wird? Falls ja: Könnte mir da jemand erklären, wie das funktioniert? Ich tue mich mit damit noch schwer.
    Geändert von Draxes (20.09.2020 um 23:04 Uhr)

  2. Beiträge anzeigen #2 Zitieren
    Dea
    Registriert seit
    Jul 2007
    Beiträge
    10.447
     
    Lehona ist offline
    Zitat Zitat von Draxes Beitrag anzeigen
    Edit: Eine ganz abgefahrene Idee: Ich habe gesehen, dass es die Engine-Funktion
    Code:
    static enum oHEROSTATUS __cdecl oCZoneMusic::GetHerostatus(void) (Adresse: 0x00642420)
    gibt. Vielleicht könnte man diese für eine bestimmte Zeit lang so modifizieren, dass hier immer STD zurückgegeben wird? Falls ja: Könnte mir da jemand erklären, wie das funktioniert? Ich tue mich mit damit noch schwer.
    oCZoneMusic::GetHeroStatus verweist einfach auf oCGame::GetHeroStatus. Der STD-Status hat den Wert 0, also musst du nur dafür sorgen, dass 0 zurückgegeben wird. Allerdings kannst du nicht einfach eine Hook in den Return-Block setzen, da in dieser Funktion die Tail-Call-Optimization greift (bzw. gegriffen hat beim Kompilieren). Das siehst du daran, dass an 0x6C2D48 in eine andere Funktion gesprungen wird (normalerweise wird nur innerhalb einer Funktion gesprungen, für andere Funktionen wird CALL verwendet). Der Graph-View in IDA zeigt das auch ganz gut: Der BasicBlock, in dem dieser Jump ausgeführt wird, hat keinen Nachfolger. Im Normalfall wird also der Return-Block gar nicht ausgeführt.

    Also ändern wir stattdessen den jump an Adresse 0x6C2D1B. Dieser Jump testet, ob es überhaupt gerade einen Spieler gibt und - falls nicht - wird direkt 0 bzw. STD zurückgegeben. Das kannst du erreichen, indem du den jz (jump if zero) zu einem normalen jmp änderst. Dafür musst du nur das erste Byte an dieser Adresse mit einem 0xEB überschreiben (um auf 0xEB zu kommen kann man entweder die Intel Handbücher wälzen oder einfach in IDA diese Zeile anwählen und über Edit -> Patch Program -> Assemble den selben Befehl, nur mit jmp anstatt jz, eingeben. Wenn du dir dann die Opcodes anzeigen lässt, kannst du das Byte direkt ablesen. Nicht vergessen, diese Änderung wieder rückgängig zu machen). Alternativ kannst du an Adresse 0x6C2D15 hooken und EAX mit 0 überschreiben. Kurzzeitig simulierst du so den Fall, dass der Spieler nicht existiert.


    Direkt das Byte zu patchen ist performanter und daher würde ich es vorziehen.

  3. Beiträge anzeigen #3 Zitieren
    Knight Avatar von Draxes
    Registriert seit
    Aug 2007
    Ort
    Mainz
    Beiträge
    1.920
     
    Draxes ist offline
    Zitat Zitat von Lehona Beitrag anzeigen
    oCZoneMusic:etHeroStatus verweist einfach auf oCGame:etHeroStatus. Der STD-Status hat den Wert 0, also musst du nur dafür sorgen, dass 0 zurückgegeben wird. Allerdings kannst du nicht einfach eine Hook in den Return-Block setzen, da in dieser Funktion die Tail-Call-Optimization greift (bzw. gegriffen hat beim Kompilieren). Das siehst du daran, dass an 0x6C2D48 in eine andere Funktion gesprungen wird (normalerweise wird nur innerhalb einer Funktion gesprungen, für andere Funktionen wird CALL verwendet). Der Graph-View in IDA zeigt das auch ganz gut: Der BasicBlock, in dem dieser Jump ausgeführt wird, hat keinen Nachfolger. Im Normalfall wird also der Return-Block gar nicht ausgeführt.

    Also ändern wir stattdessen den jump an Adresse 0x6C2D1B. Dieser Jump testet, ob es überhaupt gerade einen Spieler gibt und - falls nicht - wird direkt 0 bzw. STD zurückgegeben. Das kannst du erreichen, indem du den jz (jump if zero) zu einem normalen jmp änderst. Dafür musst du nur das erste Byte an dieser Adresse mit einem 0xEB überschreiben (um auf 0xEB zu kommen kann man entweder die Intel Handbücher wälzen oder einfach in IDA diese Zeile anwählen und über Edit -> Patch Program -> Assemble den selben Befehl, nur mit jmp anstatt jz, eingeben. Wenn du dir dann die Opcodes anzeigen lässt, kannst du das Byte direkt ablesen. Nicht vergessen, diese Änderung wieder rückgängig zu machen). Alternativ kannst du an Adresse 0x6C2D15 hooken und EAX mit 0 überschreiben. Kurzzeitig simulierst du so den Fall, dass der Spieler nicht existiert.


    Direkt das Byte zu patchen ist performanter und daher würde ich es vorziehen.
    Danke für deine ausführliche Antwort! Ich werde mich wohl einfach mal mit IDA und allem drum und dran beschäftigen müssen.

    Ich habe der Einfachheit halber mal versucht, den Hook ans Laufen zu bringen. Ist deine angegebene Adresse 0x6C2D15 richtig? Die finde ich in meiner Excel-Tabelle nicht und kann deshalb nicht ablesen, wie groß die Hook length ist. Ich habe mal die Adressen versucht, die ich herauslesen kann, und bei allen Adressen EAX auf 0 gesetzt. Das hat leider nicht zum gewünschten Erfolg geführt. zSpy sagt, dass immer noch von Std auf Fgt und umgekehrt gewechselt wird.

    Code:
    HookEngineF(7089424, 5, onlyPlayStdTheme_hook); // oCGame::GetHeroStatus
    HookEngineF(6908240, 6, onlyPlayStdTheme_hook); // oCAIHuman::GetEnemyThreat
    HookEngineF(6562848, 6, onlyPlayStdTheme_hook); // oCZoneMusic::GetHerostatus
    Ist hier ein offensichtlicher Fehler meinerseits zu erkennen?

  4. Beiträge anzeigen #4 Zitieren
    Dea
    Registriert seit
    Jul 2007
    Beiträge
    10.447
     
    Lehona ist offline
    Die Adresse ist richtig, ja :P

    In der Liste, in die du vermutlich reinschaust, sind natürlich nur die Adressen der Funktionen an sich gegeben, d.h. die markieren den Anfang der Funktion. Allerdings wird am Anfang von oCGame::GetHeroStatus der Zeiger auf den Spieler (oCNpc::player) in EAX geladen. In dem Fall (d.h. dein erster Fall an Adresse 7089424) setzt du zwar EAX auf 0, das wird aber direkt danach mit dem korrekten Wert überschrieben.

    Ich dachte du hättest IDA schon benutzt, dewegen habe ich das nicht weiter spezifiziert. An 0x6C2D15 (Dez: 7089429) solltest du eine Hook mit der Länge 6 anbringen können. Zu dem Zeitpunkt wurde EAX bzw. der Zeiger auf den Spieler schon geladen und du kannst es überschreiben.

  5. Beiträge anzeigen #5 Zitieren
    Knight Avatar von Draxes
    Registriert seit
    Aug 2007
    Ort
    Mainz
    Beiträge
    1.920
     
    Draxes ist offline
    Zitat Zitat von Lehona Beitrag anzeigen
    Die Adresse ist richtig, ja :P

    In der Liste, in die du vermutlich reinschaust, sind natürlich nur die Adressen der Funktionen an sich gegeben, d.h. die markieren den Anfang der Funktion. Allerdings wird am Anfang von oCGame:etHeroStatus der Zeiger auf den Spieler (oCNpc:layer) in EAX geladen. In dem Fall (d.h. dein erster Fall an Adresse 7089424) setzt du zwar EAX auf 0, das wird aber direkt danach mit dem korrekten Wert überschrieben.

    Ich dachte du hättest IDA schon benutzt, dewegen habe ich das nicht weiter spezifiziert. An 0x6C2D15 (Dez: 7089429) solltest du eine Hook mit der Länge 6 anbringen können. Zu dem Zeitpunkt wurde EAX bzw. der Zeiger auf den Spieler schon geladen und du kannst es überschreiben.
    Ah, verstehe! Das ergibt Sinn. Ich habe es gerade mal ausprobiert und es funktioniert wie erwartet. Jetzt muss ich nur noch schauen, dass mir keine anderen Musikzonen in die Quere kommen. Aber das ist dann auch wieder ein anderes Thema^^ Danke dir für die guten Erklärungen und die Hilfe!

    Ich werde mal schauen, dass ich dieses Beispiel mit IDA selbst nachvollziehen kann, damit ich nicht immer nachfragen muss. Es sieht einfach immer wie Raketenwissenschaft aus, wenn man selbst noch nichts damit gemacht hat.

  6. Beiträge anzeigen #6 Zitieren
    Dea
    Registriert seit
    Jul 2007
    Beiträge
    10.447
     
    Lehona ist offline
    Mud-freak hat eine sehr gute Anleitung für Einsteiger geschrieben, mitsamt schön bunten Bildern

    Die hier von mir untersuchten Funktonen (oCZoneMusic::GetHeroStatus und oCGame::GetHeroStatus) sind ziemlich kurz und relativ simpel. Sie sollten sich also hervorragend dafür eignen, den Prozess, wie ich jetzt genau auf diese Adresse kam, nachzuvollziehen. Zusätzlich zu dem oben genannten Tutorial habe ich noch zwei Sachen zu sagen:

    Der Befehl 'xor X, X' wird verwendet, um X auf 0 zu setzen. Wer den XOR-Operator kennt sollte verstehen, warum das funktioniert.
    Der Graph-View in IDA (im Disassembly-View kann man mit Leertaste zwischen Text- und Graph-View umschalten) ist hervorragend, um den Control-Flow einer Funktion zu verstehen. Das ist viel einfacher, als selber die Jumps und somit Ausführungsreihenfolge nachzuvollziehen. Jeder Block dieses Graphen ist dabei ein sogenannter BasicBlock, d.h. dieer Code wird immer zusammen ausgeführt und sollte daher immer als Gesamtpaket betrachtet werden. Nach einem Basicblock kann es an bis zu zwei verschiedenen Stellen weitergehen, dargestellt durch die Pfeile, die auf andere Blöcke zeigen (Warum kann es 2 Nachfolger geben? Bei einem konditionellen Jump gibt es entweder den Fall dass die Bedingung erfüllt wurde oder eben nicht erfüllt wurde.).

  7. Beiträge anzeigen #7 Zitieren
    Knight Avatar von Draxes
    Registriert seit
    Aug 2007
    Ort
    Mainz
    Beiträge
    1.920
     
    Draxes ist offline
    Ich grabe den Thread nochmal aus, da ich hier gerade wieder an meine Grenzen stoße. Ich habe mich mittlerweile ein wenig mit IDA vertraut gemacht, aber die Stelle, die mich gerade interessiert, scheint mir doch ein wenig komplexer zu sein.

    Problem ist, dass Musikthemen mit nur einer Note (wie es im WoG Tutorial zum integrieren einer Wav-Datei mithilfe des Direct Music Producers vorgeschlagen wird) das Potential haben, zwei mal leicht zeitversetzt gleichzeitig zu starten, wenn die Engine zu schnell zwischen Musikthemen hin- und herspringt. Deshalb wollte ich mal versuchen, das Switch-Verhalten ein wenig zu beeinflussen. Dabei würde ich gerne kontrollieren können, ob der Hero gerade in einem Kampf ist oder nicht. Um genauer zu sein: Ich möchte kontrollieren können, wann in der Adresse 0x9A4A20 eine 0 und wann eine 1 steht. Mit dem Ziel, dass während eines Kampfes nicht dauernd zwischen Std und Fgt Theme geswitched wird, wenn man z.B. mit einem Begleiter kämpft.

    Ich habe mir dafür mal in IDA
    Code:
    oCAIHuman::GetEnemyThreat(void)
    angeschaut, da mir das am sinnvollsten Klang. Allerdings verstehe ich da gerade nur Bahnhof. Vielleicht kann mir jemand einen Tipp geben, was man hier vernünftigerweise hooken kann? Am schönsten wäre einfach ein Hook, wo ich EAX oder so überschreiben kann und dort meine eigene "Hero ist im Kampf Prüfung" implementieren kann.

    Eine andere oder sogar zusätzliche Idee, um das Problem zu anzugehen, war es dafür zu sorgen, das die interne Funktion PlayThemeByScript nur Effekt hat, wenn der letzte Aufruf eine Sekunde oder so her ist. Oder manuell die Musik stoppen, wenn ich weiß, dass vorher eins dieser 1-Note-Themes gespielt hat, um sicherzugehen. Da hatte ich jetzt beim IDA durchforsten allerdings auch keine Möglichkeit erkannt.


    EDIT:
    Mit der Adresse 6909502 (Länge 7) kann schon mal der HeroStatus beeinflusst werden. Dieser steht an der stelle in EAX und kann überschrieben werden.
    0 = Std; 1 = Thr; 2 = Fgt
    Geändert von Draxes (28.11.2020 um 00:34 Uhr)

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
Impressum | Link Us | intern
World of Gothic © by World of Gothic Team
Gothic, Gothic 2 & Gothic 3 are © by Piranha Bytes & Egmont Interactive & JoWooD Productions AG, all rights reserved worldwide