To Moderators: If I picked an incorrect forum location please move to the appropriate area. Thank you.
The purpose of this tool is to find and list ambient files assigned within area files that do not exist within the current installation. There are then other functions and macros which can be ran that will deal with the different types of situations found. I have ran and tested it on an installation of BG: ToTSC. I see no reason why it would not work on other installations... If it does not work for you please inform me of your specific setup and I can see if the situation can be duplicated...
It requires the use of the modified Qwinn Area Macros (by Ardanis/GeN1e) and as such the necessary components are included within this posting. You may choose to use this in a directory location other than what I have specified within the tp2 file. If so, you must change the necessary directory structure locations. It might be easier to just create a directory in your game folder called ambfixer and save the .tp2 data there as setup-ambfixer.tp2 and then create another directory called lib within the ambfixer directory and save the AB_AreaAmbient.tph data there. Don't forget to get a recent copy of weidu and rename it to setup-ambfixer.exe
First the tp2 file contents:
BACKUP ~ambfixer/backup~ AUTHOR ~plainab~ VERSION ~Beta 2.1~ //updated version level so you'd know that it was different BEGIN ~Area Ambient Fixer~ <<<<<<<< .../ambfixer-inlined/AB_Bad_Sound_Refs.txt >>>>>>>> COPY ~.../ambfixer-inlined/AB_Bad_Sound_Refs.txt~ ~ambfixer~ //copy the blank.file to a real usable file INCLUDE ~ambfixer/lib/AB_AreaAmbient.tph~ //call up file that contains the all the functions/macros both qwinn and non COPY_EXISTING_REGEXP GLOB ~^\([^xX].*\|[xX][^rR].*\|[xX][rR][^2].*\|[xX][rR]2[^46].*\)\.are$~ ~override~ //don't understand why we can't just do ~*\.are~ but oh well it's modified from Miloch's original code and kept what he had SPRINT current_file ~%SOURCE_RES%~ PATCH_IF SOURCE_SIZE > 0x11b BEGIN //If a valid area LAUNCH_PATCH_MACRO ~wapt_Q_ARE_InitVars~ //launch basic variable initiation macro LAUNCH_PATCH_MACRO ~wapt_Q_AREAdd_InitVars~ //launch variable initiation macro for use with updating when adding or deleting sections LAUNCH_PATCH_FUNCTION ~wapt_Report_Bad_Sound_Refs~ END //launch function that lists bad sound refs -- so you can determine if some are fixable LAUNCH_PATCH_FUNCTION ~wapt_Erase_Bad_Sound_Refs~ END //launch function that erases bad sound refs LAUNCH_PATCH_FUNCTION ~wapt_Shift_Good_Sounds_Into_Count~ END //launch function that shifts good files into good range if needed LAUNCH_PATCH_MACRO ~wapt_Remove_Ambie_no_sound~ //launch macro that deletes bad sound refs if entire ambient entry is bad END //end the patch BUT_ONLYThis would need to be modified per use. Why? When first running you may not wish to fix anything but only get a report in which case you'd comment out the functions ~wapt_Erase_Bad_Sound_Refs~, ~wapt_Shift_Good_Sounds_Into_Count~ and the macro ~wapt_Remove_Ambie_no_sound~
The contents of ~ambfixer/lib/AB_AreaAmbient.tph~:
////////////////////////////////////////////// // Note by Ardanis: // As you can tell by the prefix, it originally was a Qwinn's macro. I compressed it // into a more accessible format. A fair example of how to make a good use of arrays. // Bigg, Weidu's readme says it needs a real example of array usage. Does this pass for one? DEFINE_PATCH_MACRO ~wapt_Q_ARE_InitVars~ BEGIN PATCH_IF (GAME_IS ~pst~) BEGIN Q_Game=1 END PATCH_IF (GAME_IS ~bg2 tob tutu tutu_totsc~) BEGIN Q_Game=2 END PATCH_IF (GAME_IS ~bg1 totsc iwd how~) BEGIN Q_Game=3 END DEFINE_ARRAY object BEGIN Actor Trigg Spawn Entra Conta Items Ambie Varia Doors Tiled Vertx Explo Anima Songs RestS MapNo ProTr END DEFINE_ARRAY Siz BEGIN 0x110 0xc4 0xc8 0x68 0xc0 0x14 0xd4 0x54 0xc8 0x6c 0x4 0x0 0x4c 0x90 0xe4 0x34 0x1A END DEFINE_ARRAY OoN BEGIN 0x58 0x5a 0x64 0x6c 0x74 0x76 0x82 0x8c 0xa4 0xb4 0x80 0x0 0xac 0x0 0x0 0xc8 0xd0 END DEFINE_ARRAY OoO BEGIN 0x54 0x5c 0x60 0x68 0x70 0x78 0x84 0x88 0xa8 0xb8 0x7c 0xa0 0xb0 0xbc 0xc0 0xc4 0xcc END DEFINE_ARRAY SoL BEGIN 2 2 4 4 2 2 2 4 4 4 2 0 4 0 0 4 4 END FOR (i=0;i<17;i+=1) BEGIN SET $Q_Siz($object("%i%"))=$Siz("%i%") // size of object's section SET $Q_OoN($object("%i%"))=$OoN("%i%") // offset of number of objects SET $Q_OoO($object("%i%"))=$OoO("%i%") // offset of offset of objects SET $Q_SoL($object("%i%"))=$SoL("%i%") // SHORT or LONG offset of number of objects PATCH_IF i=15 & Q_Game=1 BEGIN // PST uses different values Q_OoN_MapNo=0xcc Q_OoO_MapNo=0xc8 END PATCH_IF $SoL("%i%")=2 BEGIN // number of objects READ_SHORT $Q_OoN($object("%i%")) $Q_Num($object("%i%")) END // if SoL = 2 = SHORT, then READ_SHORT PATCH_IF $SoL("%i%")=4 & ( i!=16 | Q_Game=2) BEGIN // non-BG2 games can't have projectiles READ_LONG $Q_OoN($object("%i%")) $Q_Num($object("%i%")) END // if SoL = 4 = LONG, then READ_LONG PATCH_IF $SoL("%i%")=0 BEGIN // if SoL = 0 = not needed, don't READ, instead SET $Q_Num($object("%i%"))=1 END // SET it to 1, as this can't be any other PATCH_IF i!=16 | Q_Game=2 BEGIN // offset of objects READ_LONG $Q_OoO($object("%i%")) $Q_Off($object("%i%")) END // offsets' READs always LONG END PATCH_IF Q_Game!=2 BEGIN // if it's non-BG2 then set everything Q_OoN_ProTr=0 Q_OoO_ProTr=0 Q_Num_ProTr=0 Q_Off_ProTr=0 END // related to projectiles to zero END ///////////////////////////////////////////////// // Note by Ardanis: // Again, Qwinn's stuff that was revamped. DEFINE_PATCH_MACRO ~wapt_Q_AREAdd_InitVars~ BEGIN FOR (i=0;i<17;i+=1) BEGIN SET $Q_New($object("%i%"))=0 // number of new objects SET $Q_NewOffset($object("%i%"))=0 // writing offset for new objects END Q_ManualInsert=0 // unnecessary, but may be required for RESHAPE_AREA_POLYGON, should it be added later (unlikely) END ////////////////////////////////////////// // Note by Ardanis: // Unlike the other two Qwinn's macros, this one is almost untouched, except of adding the 'wapt_' prefix to // it's name and correcting the name of the macro LAUNCHed at the very end, which now has that prefix as well DEFINE_PATCH_MACRO ~wapt_Q_AREAdd_Process~ BEGIN // DO NOT use this macro without first running Q_AREAdd_InitVars. // Documentation for the use of this macro is contained within that macro definition. PATCH_FOR_EACH "S1" IN ~Actor~ ~Trigg~ ~Spawn~ ~Entra~ ~Conta~ ~Items~ ~Ambie~ ~Varia~ ~Doors~ ~Tiled~ ~Vertx~ ~Anima~ ~MapNo~ ~ProTr~ BEGIN SET "Q_NewSect" = $Q_New("%S1%") // How many new sections user has asked for PATCH_IF !("Q_NewSect" = 0) THEN BEGIN // WRITE_ASCII 0x33c ~%S1%~ #32 // DEBUG SET "Q_OoNSect" = $Q_OoN("%S1%") // Offset where count of each section is stored SET "Q_NumSect" = $Q_Num("%S1%") // Original count for that section SET "Q_SoLSect" = $Q_SoL("%S1%") // Whether original count is stored as long or short SET "Q_OoOSect1" = $Q_OoO("%S1%") // Offset of offset for the section SET "Q_Offset1" = $Q_Off("%S1%") // Offset of the section being added to SET "Q_SizSect" = $Q_Siz("%S1%") // The size of one new section PATCH_FOR_EACH "S2" IN ~Actor~ ~Trigg~ ~Spawn~ ~Entra~ ~Conta~ ~Items~ ~Ambie~ ~Varia~ ~Doors~ ~Tiled~ ~Vertx~ ~Explo~ ~Anima~ ~Songs~ ~RestS~ ~MapNo~ ~ProTr~ BEGIN // WRITE_ASCII 0x33c ~%S1% %S2%~ #32 // DEBUG SET "Q_Offset2" = $Q_Off("%S2%") // Offset of each other section SET "Q_OoOSect2" = $Q_OoO("%S2%") // Offset of that offset SET "Q_OldInsert" = $Q_NewOffset("%S2%") // Previous insert offsets need to be updated too PATCH_IF ("Q_Offset2" >= "Q_Offset1") AND NOT ("%S1%" STRING_EQUAL "%S2%") THEN BEGIN WRITE_LONG "Q_OoOSect2" ("Q_Offset2" + ("Q_NewSect" * "Q_SizSect")) END PATCH_IF ("Q_OldInsert" >= "Q_Offset1") AND NOT ("%S1%" STRING_EQUAL "%S2%") THEN BEGIN SET $Q_NewOffset("%S2%") = $Q_NewOffset("%S2%") + ("Q_NewSect" * "Q_SizSect") END END SET $Q_NewOffset("%S1%") = "Q_Offset1" + ("Q_NumSect" * "Q_SizSect") SET "Q_InsertOffset" = $Q_NewOffset("%S1%") PATCH_IF "Q_ManualInsert" = 0 THEN BEGIN INSERT_BYTES "Q_InsertOffset" ("Q_NewSect" * "Q_SizSect") END PATCH_IF "Q_SoLSect" = 2 THEN BEGIN WRITE_SHORT "Q_OoNSect" ("Q_NumSect" + "Q_NewSect") END ELSE BEGIN WRITE_LONG "Q_OoNSect" ("Q_NumSect" + "Q_NewSect") END LAUNCH_PATCH_MACRO ~wapt_Q_ARE_InitVars~ // Reset all our variables to their new values END END END //////////////////////////////// //Contents of Find_List_Bad_Sound_Refs //this can be function as nothing gets transfered to anywhere else -- ie next thing reads everything again DEFINE_PATCH_FUNCTION ~wapt_Report_Bad_Sound_Refs~ BEGIN //define function FOR (index=0;index<%Q_Num_Ambie%;index+=1) BEGIN //loop through ambient entries SET amb_e = (%Q_Siz_Ambie% * %index%) //set index into ambient entries READ_SHORT (%Q_Off_Ambie% + %amb_e% + 0x80) s_num //Sound count SET ss = 0x30 //start location of sounds refs SET sz = 0x8 //size per entry of sound refs FOR (index2=0;index2<%s_num%;index2+=1) BEGIN //loop through sound refs SET si = (%index2% * %sz%) //index into sounds entries in current ambient entry SET location = (%ss% + %si%) //define location so it can be used in appended text READ_ASCII (%Q_Off_Ambie% + %amb_e% + %location%) wav //read sound file //blank out non-existant sound file references <-- this is in case other sound file references do exist PATCH_IF (NOT FILE_EXISTS_IN_GAME ~%wav%.wav~) BEGIN //if registered sound file does not exist INNER_ACTION BEGIN //start an inner action append is an action not a patch //append to a file in main game dir APPEND_OUTER ~ambfixer/AB_Bad_Sound_Refs.txt~ ~%current_file%: %wav%.wav placed in #%index2% sound ref entry at %location% within Ambient Entry #%index% has an invalid name (ie Does not exist)~ END //end inner action END //end patch that checks existance of sound file END //end loop through sound refs END //end loop through ambient entries END //end of function definition //////////////////////////////// //Contents of Erase_Bad_Sound_Refs //this can be function as nothing gets transfered to anywhere else -- ie next thing reads everything again DEFINE_PATCH_FUNCTION ~wapt_Erase_Bad_Sound_Refs~ BEGIN //define a function FOR (index=0;index<%Q_Num_Ambie%;index+=1) BEGIN //loop through ambient entries SET amb_e = (%Q_Siz_Ambie% * %index%) //set index into ambient entries READ_SHORT (%Q_Off_Ambie% + %amb_e% + 0x80) s_num //Sound count SET ss = 0x30 //start location of sounds refs SET sz = 0x8 //size per entry of sound refs SET to_remove = 0 FOR (index2=0;index2<%s_num%;index2+=1) BEGIN //loop through sound refs SET si = (%index2% * %sz%) //index into sounds entries in current ambient entry READ_ASCII (%Q_Off_Ambie% + %amb_e% + %ss% + %si%) wav //read sound file //blank out non-existant sound file references <-- this is in case other sound file references do exist PATCH_IF (NOT FILE_EXISTS_IN_GAME ~%wav%.wav~) BEGIN //if registered sound file does not exist SET to_remove += 1 WRITE_ASCII (%Q_Off_Ambie% + %amb_e% + %ss% + %si%) ~~ (8) //write blank value INNER_ACTION BEGIN //start an inner action append is an action not a patch APPEND_OUTER ~ambfixer/AB_Bad_Sound_Refs.txt~ ~%current_file%: %wav%.wav was erased because it did not exist.~ END //end inner action END //end patch that checks existance of sound file END //end loop through sound refs PATCH_IF (%to_remove% >0) BEGIN WRITE_SHORT (%Q_Off_Ambie% + %amb_e% + 0x80) (%s_num% - %to_remove%) //lower Sound count by total blanked out INNER_ACTION BEGIN APPEND_OUTER ~ambfixer/AB_Bad_Sound_Refs.txt~ ~%current_file%: Ambient Entry #%index% -- Sound count decreased by %to_remove%~ END //end inner action END END //end loop through ambient entries END //end of function definition //////////////////////////////// //Contents of Delete_No_Sound_Ambient_Entries //since we return a variable it maybe best to use MACRO yes I can define a returning variable in a FUNCTION but I feel safer DEFINE_PATCH_MACRO ~wapt_Remove_Ambie_no_sound~ BEGIN //define a macro SET num_del = 0 //initialize variable SET index=(%Q_Num_Ambie%-1) WHILE (index>0) BEGIN //loop through ambient entries SET amb_e = (%Q_Siz_Ambie% * %index%) //set index into ambient entries READ_SHORT (%Q_Off_Ambie% + %amb_e% + 0x80) s_num //Sound count PATCH_IF (%s_num% <=0) BEGIN //See if number of sounds is 0 or less SET Q_ManualInsert = 1 //tell qwinn's macros that we are deleting sections SET Q_New_Ambie = %num_del% - 1 //increase our variable by one LAUNCH_PATCH_MACRO ~wapt_Q_AREAdd_Process~ //launch macro that does the actual update of the file DELETE_BYTES (%Q_Off_Ambie% + %amb_e%) %Q_Siz_Ambie% //delete current entry INNER_ACTION BEGIN //start an inner action append is an action not a patch APPEND_OUTER ~ambfixer/AB_Bad_Sound_Refs.txt~ ~%current_file%: Ambient Entry #%index% was deleted because it had no existing ambient sounds.~ END //end inner action SET index = %Q_Num_Ambie% END //end sound count check SET index -= 1 END //end backward loop through ambients END //end macro //////////////////////////////// //Contents of Shift_Good_Sounds_Into_Count_If_Out //this has no variables to carry over so it can be a function DEFINE_PATCH_FUNCTION ~wapt_Shift_Good_Sounds_Into_Count~ BEGIN //define function FOR (index=0;index<%Q_Num_Ambie%;index+=1) BEGIN //loop through ambient entries SET amb_e = (%Q_Siz_Ambie% * %index%) //set index into ambient entries READ_SHORT (%Q_Off_Ambie% + %amb_e% + 0x80) s_num //Sound count SET ss = 0x30 //start location of sounds refs SET sz = 0x8 //size per entry of sound refs FOR (index3=0;index3<10;index3+=1) BEGIN //loop through all entries despite sound count SET si = (%index3% * %sz%) //index into sounds entries in current ambient entry READ_ASCII (%Q_Off_Ambie% + %amb_e% + %ss% + %si%) wav //read sound file PATCH_IF (FILE_EXISTS_IN_GAME ~%wav%.wav~) BEGIN PATCH_IF !(%index3% < %s_num% ) BEGIN //sound file exists and slot is greater than the count number FOR (index2=0;index2<%s_num%;index2+=1) BEGIN //loop through sounds entries within sound count SET si2 = (%index2% * %sz%) //index into sound entries within sound count in current ambient entry READ_ASCII (%Q_Off_Ambie% + %amb_e% + %ss% + %si2%) sound //read sound file again PATCH_IF (NOT FILE_EXISTS_IN_GAME ~%sound%.wav~) //if file does not exist AND ( (%index2% < %index3%) AND (%index2% <= %s_num%) ) BEGIN //sound file does not exist and slot is less than earlier slot INNER_ACTION BEGIN //start an inner action append is an action not a patch APPEND_OUTER ~ambfixer/AB_Bad_Sound_Refs.txt~ ~%current_file%: %wav%.wav in sound slot %index3%. %s_num% sounds in Ambient Entry #%index%.~ APPEND_OUTER ~ambfixer/AB_Bad_Sound_Refs.txt~ ~%current_file%: Moved %wav%.wav from slot %index3% to slot %index2%. This puts %wav%.wav within %s_num% slots from the start of sound file offsets.~ END //end inner action WRITE_EVALUATED_ASCII (%Q_Off_Ambie% + %amb_e% + %ss% + %si2%) ~%wav%~ (8) //write good file in current slot WRITE_EVALUATED_ASCII (%Q_Off_Ambie% + %amb_e% + %ss% + %si%) ~~ (8) //erase good file from old slot END END //end patch where sound does not exist and slot is within count END //end loop through sound slots END //end patch where sound exists but slot is greater than count END //end PFE END //end ambient entry loop END //end function