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







