;------------------------------------------------------------------------------ ; VGM File Info Display for CP/M ;------------------------------------------------------------------------------ ; ; Scans all .VGM files in current directory and displays chip information ; in a formatted table ; ; (c) 2025 Joao Miguel Duraes ; Licensed under the MIT License ; ; Version: 1.0 - 06-Dec-2025 ; ; Assemble with: ; TASM -80 -b vgminfo.asm vgminfo.com ; ;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------ ; CP/M definitions ;------------------------------------------------------------------------------ BOOT .equ 0000H ; boot location BDOS .equ 0005H ; bdos entry point FCB .equ 005CH ; file control block FCBCR .equ FCB + 20H ; fcb current record BUFF .equ 0080H ; DMA buffer PRINTF .equ 9 ; BDOS print string function OPENF .equ 15 ; BDOS open file function CLOSEF .equ 16 ; BDOS close file function READF .equ 20 ; BDOS sequential read function SETDMA .equ 26 ; BDOS set DMA address SFIRST .equ 17 ; BDOS search first SNEXT .equ 18 ; BDOS search next CR .equ 0DH ; carriage return LF .equ 0AH ; line feed ;------------------------------------------------------------------------------ ; VGM Header offsets ;------------------------------------------------------------------------------ VGM_IDENT .equ 00H ; "Vgm " identifier VGM_VERSION .equ 08H ; Version VGM_DATAOFF .equ 34H ; VGM data offset (relative to 0x34) ;------------------------------------------------------------------------------ ; VGM Command codes (subset) ;------------------------------------------------------------------------------ VGM_PSG1_W .equ 050H ; PSG (SN76489) write VGM_PSG2_W .equ 030H ; PSG #2 write VGM_YM26121_W .equ 052H ; YM2612 port 0 write VGM_YM26122_W .equ 053H ; YM2612 port 1 write VGM_YM26123_W .equ 0A2H ; YM2612 #2 port 0 write VGM_YM26124_W .equ 0A3H ; YM2612 #2 port 1 write VGM_YM21511_W .equ 054H ; YM2151 write VGM_YM21512_W .equ 0A4H ; YM2151 #2 write VGM_AY_W .equ 0A0H ; AY-3-8910 write VGM_ESD .equ 066H ; End of sound data VGM_WNS .equ 061H ; Wait n samples VGM_W735 .equ 062H ; Wait 735 samples VGM_W882 .equ 063H ; Wait 882 samples ;------------------------------------------------------------------------------ ; Program Start ;------------------------------------------------------------------------------ .ORG 100H START: LD SP, STACK ; Setup stack ; Display header LD DE, MSG_HEADER CALL PRTSTR LD DE, MSG_DIVIDER CALL PRTSTR ; Setup search for *.VGM files LD DE, SEARCH_FCB LD C, SFIRST CALL BDOS CP 0FFH ; No files found? JP Z, NO_FILES FILE_LOOP: ; A contains directory entry index (0-3) ; Each entry is 32 bytes, so multiply by 32 AND 03H ; Mask to 0-3 RLCA RLCA RLCA RLCA RLCA ; Multiply by 32 LD L, A LD H, 0 LD DE, BUFF ADD HL, DE ; HL now points to directory entry ; Copy filename from directory entry to our FCB INC HL ; Skip user number LD DE, FILE_FCB+1 ; Destination LD BC, 11 ; 8+3 filename LDIR ; Open and process this file CALL PROCESS_FILE ; Search for next file LD DE, SEARCH_FCB LD C, SNEXT CALL BDOS CP 0FFH JP NZ, FILE_LOOP ; Done LD DE, MSG_DIVIDER CALL PRTSTR JP BOOT ; Exit to CP/M NO_FILES: LD DE, MSG_NOFILES CALL PRTSTR JP BOOT ;------------------------------------------------------------------------------ ; Process a VGM file - read header and display info ;------------------------------------------------------------------------------ PROCESS_FILE: ; Reset FCB XOR A LD (FILE_FCB), A ; Default drive LD (FILE_FCB+12), A ; Clear extent LD (FILE_FCB+32), A ; Clear current record ; Open file LD DE, FILE_FCB LD C, OPENF CALL BDOS CP 0FFH RET Z ; Can't open, skip ; Set DMA to our buffer for first block LD DE, VGMBUF LD C, SETDMA CALL BDOS ; Read first 128 bytes (header) LD DE, FILE_FCB LD C, READF CALL BDOS OR A JR NZ, READ_DONE ; EOF or error ; Read second 128 bytes (to allow scanning right after header) LD DE, VGMBUF+128 LD C, SETDMA CALL BDOS LD DE, FILE_FCB LD C, READF CALL BDOS ; Read third 128 bytes LD DE, VGMBUF+256 LD C, SETDMA CALL BDOS LD DE, FILE_FCB LD C, READF CALL BDOS ; Read fourth 128 bytes LD DE, VGMBUF+384 LD C, SETDMA CALL BDOS LD DE, FILE_FCB LD C, READF CALL BDOS READ_DONE: ; Restore DMA LD DE, BUFF LD C, SETDMA CALL BDOS ; Close file LD DE, FILE_FCB LD C, CLOSEF CALL BDOS ; Check if valid VGM LD HL, VGMBUF LD A, (HL) CP 'V' RET NZ INC HL LD A, (HL) CP 'g' RET NZ INC HL LD A, (HL) CP 'm' RET NZ INC HL LD A, (HL) CP ' ' RET NZ ; Display filename (exactly 8 chars from FCB) LD HL, FILE_FCB+1 LD B, 8 PRINT_NAME: LD A, (HL) CALL PRTCHR INC HL DJNZ PRINT_NAME ; Add 2-space gap LD A, ' ' CALL PRTCHR LD A, ' ' CALL PRTCHR PAD_DONE: ; Check and display chip info CALL CHECK_CHIPS ; New line CALL CRLF RET ;------------------------------------------------------------------------------ ; Check which chips are used by scanning VGM command stream (first ~100 cmds) ;------------------------------------------------------------------------------ CHECK_CHIPS: ; Initialize chip flags XOR A LD (CHIP_FLAGS), A ; Compute absolute data start within VGMBUF window LD HL, (VGMBUF+VGM_DATAOFF) LD A, H OR L JR NZ, GOT_OFFSET LD HL, 000CH ; Default for VGM < 1.50 (0x40-0x34) GOT_OFFSET: LD DE, VGMBUF+VGM_DATAOFF ADD HL, DE ; HL = VGMBUF + 0x34 + offset ; Constrain to our 256-byte buffer LD DE, VGMBUF SBC HL, DE ; HL = offset from VGMBUF base ADD HL, DE ; restore HL absolute inside VGMBUF ; Scan up to 100 commands or until EOD LD C, 100 SCAN_LOOP: LD A, (HL) INC HL CP VGM_ESD JP Z, SCAN_DONE CP VGM_PSG1_W JP NZ, CHK_PSG2 LD A, (CHIP_FLAGS) OR 01H ; bit 0 = SN #1 LD (CHIP_FLAGS), A INC HL ; Skip data byte JP SCAN_NEXT CHK_PSG2: CP VGM_PSG2_W JP NZ, CHK_YM2612 LD A, (CHIP_FLAGS) OR 02H ; bit 1 = SN #2 LD (CHIP_FLAGS), A INC HL JP SCAN_NEXT CHK_YM2612: CP VGM_YM26121_W JR Z, GOT_YM2612_1 CP VGM_YM26122_W JR Z, GOT_YM2612_1 CP VGM_YM26123_W JR Z, GOT_YM2612_2 CP VGM_YM26124_W JP NZ, CHK_YM2151 GOT_YM2612_2: LD A, (CHIP_FLAGS) OR 08H ; bit 3 = YM2612 #2 LD (CHIP_FLAGS), A INC HL INC HL ; Skip 2 data bytes JP SCAN_NEXT GOT_YM2612_1: LD A, (CHIP_FLAGS) OR 04H ; bit 2 = YM2612 #1 LD (CHIP_FLAGS), A INC HL INC HL JP SCAN_NEXT CHK_YM2151: CP VGM_YM21511_W JR Z, GOT_YM2151_1 CP VGM_YM21512_W JP NZ, CHK_AY LD A, (CHIP_FLAGS) OR 20H ; bit 5 = YM2151 #2 LD (CHIP_FLAGS), A INC HL INC HL JP SCAN_NEXT GOT_YM2151_1: LD A, (CHIP_FLAGS) OR 10H ; bit 4 = YM2151 #1 LD (CHIP_FLAGS), A INC HL INC HL JP SCAN_NEXT CHK_AY: CP VGM_AY_W JP NZ, CHK_WAIT LD A, (HL) ; Get register/chip byte BIT 7, A ; Bit 7 = chip 2? JR Z, GOT_AY1 LD A, (CHIP_FLAGS) OR 80H ; bit 7 = AY #2 LD (CHIP_FLAGS), A INC HL INC HL JP SCAN_NEXT GOT_AY1: LD A, (CHIP_FLAGS) OR 40H ; bit 6 = AY #1 LD (CHIP_FLAGS), A INC HL INC HL ; Skip 2 data bytes JP SCAN_NEXT CHK_WAIT: CP VGM_WNS JR NZ, CHK_W735 INC HL INC HL ; Skip 2-byte wait value JP SCAN_NEXT CHK_W735: CP VGM_W735 JR Z, SCAN_NEXT CP VGM_W882 JR Z, SCAN_NEXT ; Unknown command or short wait 0x70-0x7F -> just continue CP 70H JR C, SCAN_NEXT CP 80H JR NC, SCAN_NEXT SCAN_NEXT: DEC C JP NZ, SCAN_LOOP SCAN_DONE: ; Display chips found LD B, 0 ; Chip counter LD A, (CHIP_FLAGS) LD C, A ; Save flags ; SN76489 AND 03H ; bits 0-1 JP Z, NO_SN LD A, B OR A CALL NZ, PRINT_COMMA LD A, C AND 03H CP 03H ; Both chips? JR Z, SN_DUAL LD DE, MSG_SN76489 CALL PRTSTR JR SN_DONE SN_DUAL: LD DE, MSG_SN76489X2 CALL PRTSTR SN_DONE: INC B NO_SN: ; YM2612 LD A, C AND 0CH ; bits 2-3 JR Z, NO_YM2612 LD A, B OR A CALL NZ, PRINT_COMMA LD A, C AND 0CH CP 0CH ; Both chips? JR Z, YM2612_DUAL LD DE, MSG_YM2612 CALL PRTSTR JR YM2612_DONE YM2612_DUAL: LD DE, MSG_YM2612X2 CALL PRTSTR YM2612_DONE: INC B NO_YM2612: ; YM2151 LD A, C AND 30H ; bits 4-5 JR Z, NO_YM2151 LD A, B OR A CALL NZ, PRINT_COMMA LD A, C AND 30H CP 30H ; Both chips? JR Z, YM2151_DUAL LD DE, MSG_YM2151 CALL PRTSTR JR YM2151_DONE YM2151_DUAL: LD DE, MSG_YM2151X2 CALL PRTSTR YM2151_DONE: INC B NO_YM2151: ; AY-3-8910 LD A, C AND 0C0H ; bits 6-7 JR Z, NO_AY LD A, B OR A CALL NZ, PRINT_COMMA LD A, C AND 0C0H CP 0C0H ; Both chips? JR Z, AY_DUAL LD DE, MSG_AY8910 CALL PRTSTR JR AY_DONE AY_DUAL: LD DE, MSG_AY8910X2 CALL PRTSTR AY_DONE: INC B NO_AY: ; None LD A, B OR A RET NZ LD DE, MSG_UNKNOWN CALL PRTSTR RET PRINT_COMMA: LD A, ',' CALL PRTCHR LD A, ' ' CALL PRTCHR RET ;------------------------------------------------------------------------------ ; Print string pointed to by DE (terminated by 0) ;------------------------------------------------------------------------------ PRTSTR: LD A, (DE) OR A RET Z CALL PRTCHR INC DE JR PRTSTR ;------------------------------------------------------------------------------ ; Print character in A ;------------------------------------------------------------------------------ PRTCHR: PUSH BC PUSH DE PUSH HL LD E, A LD C, 2 CALL BDOS POP HL POP DE POP BC RET ;------------------------------------------------------------------------------ ; Print CR/LF ;------------------------------------------------------------------------------ CRLF: LD A, CR CALL PRTCHR LD A, LF CALL PRTCHR RET ;------------------------------------------------------------------------------ ; Messages ;------------------------------------------------------------------------------ MSG_HEADER: .DB CR, LF .DB "VGM Music Chip Scanner v1.0 - 06-Dec-2025", CR, LF .DB "(c)2025 Joao Miguel Duraes - MIT License", CR, LF .DB CR, LF .DB "Filename Chips Used", CR, LF .DB 0 MSG_DIVIDER: .DB "======== =====================", CR, LF .DB 0 MSG_NOFILES: .DB "No .VGM files found in current directory", CR, LF .DB 0 MSG_SN76489: .DB "SN76489", 0 MSG_SN76489X2: .DB "2xSN76489", 0 MSG_YM2612: .DB "YM2612", 0 MSG_YM2612X2: .DB "2xYM2612", 0 MSG_YM2151: .DB "YM2151", 0 MSG_YM2151X2: .DB "2xYM2151", 0 MSG_AY8910: .DB "AY-3-8910", 0 MSG_AY8910X2: .DB "2xAY-3-8910", 0 MSG_UNKNOWN: .DB "Unknown/None", 0 ;------------------------------------------------------------------------------ ; Data area ;------------------------------------------------------------------------------ ; Search FCB for *.VGM SEARCH_FCB: .DB 0 ; Default drive .DB '?','?','?','?','?','?','?','?' ; Filename (wildcard) .DB 'V','G','M' ; Extension .FILL 24, 0 ; Rest of FCB ; FCB for opening files FILE_FCB: .DB 0 ; Default drive .FILL 35, 0 ; Rest of FCB DIR_CODE: .DB 0 ; Directory code from search CHIP_FLAGS: .DB 0 ; Detected chip flags ; bit0 SN76489 #1, bit1 SN76489 #2 ; bit2 YM2612 #1, bit3 YM2612 #2 ; bit4 YM2151 #1, bit5 YM2151 #2 ; bit6 AY #1, bit7 AY #2 ; Buffer for VGM header + first data sector (256 bytes) VGMBUF: .FILL 512, 0 ; Stack space .FILL 64, 0 STACK: .DW 0 .END