FF6 Hacking
MSU-1 Hacks - Printable Version

+- FF6 Hacking (https://www.ff6hacking.com/forums)
+-- Forum: Hacks, Resources and Tutorials (https://www.ff6hacking.com/forums/forum-1.html)
+--- Forum: Opera House Rehearsal Room (https://www.ff6hacking.com/forums/forum-71.html)
+--- Thread: MSU-1 Hacks (/thread-2916.html)



MSU-1 Hacks - madsiur - 07-10-2015

Recently a Chrono Trigger and Secret of Mana MSU-1 hacks have been released. Another guy is also working on a similar hack for FF3us, but he ran into some problems when modifying the NMI routine to fade out tracks, resulting in graphical glitches.

I'm only making this post for those who would be interested in attempting this for FF3us and the two source code could help maybe. I might do a MSU-1 version of my hack but that would means this version could only be played on Higan/bsnes and on a SD2SNES cartridge.

Here's the code for both hacks:

Secret of Mana
Code:
arch snes.cpu

// MSU memory map I/O
constant MSU_STATUS($002000)
constant MSU_ID($002002)
constant MSU_AUDIO_TRACK_LO($002004)
constant MSU_AUDIO_TRACK_HI($002005)
constant MSU_AUDIO_VOLUME($002006)
constant MSU_AUDIO_CONTROL($002007)

// SPC communication ports
constant SPC_COMM_0($2140)
constant SPC_COMM_1($2141)
constant SPC_COMM_2($2142)
constant SPC_COMM_3($2143)

// MSU_STATUS possible values
constant MSU_STATUS_TRACK_MISSING($8)
constant MSU_STATUS_AUDIO_PLAYING(%00010000)
constant MSU_STATUS_AUDIO_REPEAT(%00100000)
constant MSU_STATUS_AUDIO_BUSY($40)
constant MSU_STATUS_DATA_BUSY(%10000000)

// SNES Multiply register
constant SNES_MUL_OPERAND_A($004202)
constant SNES_MUL_OPERAND_B($004203)
constant SNES_DIV_DIVIDEND_L($004204)
constant SNES_DIV_DIVIDEND_H($004205)
constant SNES_DIV_DIVISOR($004206)
constant SNES_DIV_QUOTIENT_L($004214)
constant SNES_DIV_QUOTIENT_H($004215)
constant SNES_MUL_DIV_RESULT_L($004216)
constant SNES_MUL_DIV_RESULT_H($004217)

// Constants
if {defined EMULATOR_VOLUME} {
    constant FULL_VOLUME($60)
} else {
    constant FULL_VOLUME($FF)
}

// Game variables
variable MusicCommand($7E1E00)
variable RequestedSong($7E1E01)
variable PreviousSong($7E1E05)

// My variables
variable FadeState($7E1EDD)
variable FadeVolume($7E1EDE)
variable FadeStep($7E1EE0)
variable FadeTemp($7E1EE2)
variable FadeCount($7E1EE4)
variable FrameCount($7E1EE5)

// **********
// * Macros *
// **********
// seek converts SNES HiROM address to physical address
macro seek(variable offset) {
  origin (offset & $3FFFFF)
  base offset
}

macro CheckMSUPresence(labelToJump) {
    lda MSU_ID
    cmp.b #'S'
    bne {labelToJump}
}

macro WaitMulResult() {
    nop
    nop
    nop
    nop
}

macro WaitDivResult() {
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
}

// ********
// * Code *
// ********
seek($008008)
    jml Reset_Hijack
    
// Override NMI vector to call a custom RAM address
// with my hijack
seek($C0FFEA)
    db $22,$01
    
seek($C30004)
    jmp MSU_Hijack
    
seek($C324C0)
MSU_Hijack:
    jsl MSU_Main
    bcc ReturnToCode
    jmp $0160
    
ReturnToCode:
    rtl
    
// MSU code
seek($CB3400)
scope MSU_Main: {
    php
    rep #$30
    pha
    phx
    phy
    
    sep #$20
    CheckMSUPresence(OriginalCode)
    
    lda MusicCommand
    beq DoNothing

    cmp #$01
    bne +;
    stz MusicCommand
    jsr MSU_PlayMusic
    bcs OriginalCode
    bcc DoNothing
+;
    cmp #$80
    bne +;
    stz MusicCommand
    jsr MSU_Fade
    bra OriginalCode
+;
    cmp #$F1
    bne +;
    stz MusicCommand
    jsr MSU_Stop
+;
    
OriginalCode:
    rep #$30
    ply
    plx
    pla
    plp
    sec
    rtl

DoNothing:
    rep #$30
    ply
    plx
    pla
    plp
    clc
    rtl
}

scope MSU_PlayMusic: {
    // Check if the song is already playing
    xba
    lda $7E1E02
    and #$0F
    pha
    lda RequestedSong
    pha
    plx
    cpx PreviousSong
    beq Exit

    // Current current SPC song playing, if any
    jsr Stop_SPC

    // Set MSU-1 track
    lda RequestedSong
    sta MSU_AUDIO_TRACK_LO
    lda.b #$00
    sta MSU_AUDIO_TRACK_HI

IsMSUReady:
    lda MSU_STATUS
    and.b #MSU_STATUS_AUDIO_BUSY
    bne IsMSUReady
    
    // Check if the track is missing
    lda MSU_STATUS
    and.b #MSU_STATUS_TRACK_MISSING
    bne TrackMissing
    
    // Play the song and add repeat if needed
    lda RequestedSong
    jsr TrackNeedLooping
    sta MSU_AUDIO_CONTROL
    
    // Set volume
    lda.b #FULL_VOLUME
    sta MSU_AUDIO_VOLUME
    sta FadeVolume+1
    
    // Set previous song for game
    lda RequestedSong
    sta PreviousSong
    lda $7E1E02
    and #$0F
    sta $7E1E06
    lda $7E1E03
    sta $7E1E07
    
Exit:
    clc
    rts
    
TrackMissing:
    lda.b #$01
    sta MusicCommand
    
    lda.b #$00
    sta MSU_AUDIO_VOLUME

    sec
    rts
}

scope TrackNeedLooping: {
    // $17 / 23 = Victory ! (Jingle)
    cmp.b #$17
    beq noLooping
    // $19 Cannon Travel Lunch (SFX)
    cmp.b #$19
    beq noLooping
    // $1A Cannon Travel (SFX)
    cmp.b #$1A
    beq noLooping
    // $1E New Contient Rises (SFX)
    cmp.b #$1E
    beq noLooping
    // $21 Unused Jingle 1
    cmp.b #$21
    beq noLooping
    // $22 Midge Mallet (SFX)
    cmp.b #$22
    beq noLooping
    // $23 Unknown Jingle 2
    cmp.b #$23
    beq noLooping
    // $28 Flammie Coming (SFX)
    cmp.b #$28
    beq noLooping
    // $2D Mysterious Moaning
    cmp.b #$2D
    beq noLooping
    // $2E Mara's Key (Jingle)
    cmp.b #$2E
    beq noLooping
    // $2F Got an Item (Jingle)
    cmp.b #$2F
    beq noLooping
    // $30 Elemental Acquired (Jingle)
    cmp.b #$30
    beq noLooping
    // $35 Ally Joins (Jingle)
    cmp.b #$35
    beq noLooping
    
    // Track loops
    lda.b #$03
    rts
noLooping:
    lda #$01
    rts
}

scope MSU_Fade: {
    lda RequestedSong
    sta FadeCount
    and #$0F
    sta SNES_MUL_OPERAND_A
    
    lda FadeCount
    and #$F0
    sta FadeCount
    
    lda #$11
    sta SNES_MUL_OPERAND_B
    WaitMulResult()
    
    lda FadeCount
    bne +;
    
    sta FadeStep
    jmp Exit
    
+;
    lda SNES_MUL_DIV_RESULT_L
    sec
    sbc FadeVolume+1
    bcs +;
    eor #$FF
    inc
+;
    sta SNES_DIV_DIVIDEND_L
    lda #$00
    sta SNES_DIV_DIVIDEND_H
    lda FadeCount
    sta SNES_DIV_DIVISOR
    WaitDivResult()
    
    lda SNES_DIV_QUOTIENT_L
    sta FadeTemp+1
    
    lda #$00
    sta SNES_DIV_DIVIDEND_L
    lda SNES_MUL_DIV_RESULT_L
    sta SNES_DIV_DIVIDEND_H
    lda FadeCount
    sta SNES_DIV_DIVISOR
    WaitDivResult()
    
    lda SNES_DIV_QUOTIENT_L
    sta FadeTemp
    bcs +;
    
    rep #$20
    lda FadeTemp
    eor #$FFFF
    inc
    sta FadeTemp
    sep #$20
+;
    rep #$20
    lda FadeTemp
    sta FadeStep
    sep #$20
    lda #$00
    sta FadeVolume
    
    lda #$01
    sta FadeState
Exit:
    rts
}

scope MSU_Stop: {
    lda #$00
    sta MSU_AUDIO_CONTROL
    rts
}

scope Stop_SPC: {
    // Stop SPC music
    lda #$F1
    sta SPC_COMM_0
Handshake:
    cmp SPC_COMM_0
    bne Handshake
    
    lda #$00
    sta SPC_COMM_0
    
    // Stop looping SFX
    lda #$F2
    sta SPC_COMM_0
Handshake2:
    cmp SPC_COMM_0
    bne Handshake2
    
    lda #$00
    sta SPC_COMM_0
    rts
}

// NMI & Reset hijack code
scope Reset_Hijack: {
    lda #$FF
    sta FadeVolume
    sta FadeVolume+1
    lda #$00
    sta FadeState
    
    ldx #$00

hijackLoop:
    lda codeData,x
    sta $0122,x
    inx
    cpx #$04
    bne hijackLoop

    // Original code hijacked
    jml $C10010
    
codeData:
    jml NMI_Update
}

scope NMI_Update: {
    php
    rep #$20
    pha
    
    sep #$20
    
    lda FrameCount
    inc
    sta FrameCount
    
    lda FadeState
    beq Exit
    
    lda FrameCount
    lsr
    bcs Exit
    
    rep #$20
    clc
    lda FadeVolume
    adc FadeStep
    sta FadeVolume
    
    sep #$20
    lda FadeVolume+1
    sta MSU_AUDIO_VOLUME
    
    lda FadeCount
    dec
    bne +;
    
    lda #$00
    sta FadeState
+;
    sta FadeCount
Exit:
    rep #$20
    pla
    plp
    // Call actual NMI code
    jml $000100
}

if (pc() > $CB3FFF) {
    error "Overflow detected"
}

Chrono Trigger
Code:
arch snes.cpu

// MSU memory map I/O
constant MSU_STATUS($002000)
constant MSU_ID($002002)
constant MSU_AUDIO_TRACK_LO($002004)
constant MSU_AUDIO_TRACK_HI($002005)
constant MSU_AUDIO_VOLUME($002006)
constant MSU_AUDIO_CONTROL($002007)

// SPC communication ports
constant SPC_COMM_0($2140)
constant SPC_COMM_1($2141)
constant SPC_COMM_2($2142)
constant SPC_COMM_3($2143)

// MSU_STATUS possible values
constant MSU_STATUS_TRACK_MISSING($8)
constant MSU_STATUS_AUDIO_PLAYING(%00010000)
constant MSU_STATUS_AUDIO_REPEAT(%00100000)
constant MSU_STATUS_AUDIO_BUSY($40)
constant MSU_STATUS_DATA_BUSY(%10000000)

// SNES Multiply register
constant SNES_MUL_OPERAND_A($004202)
constant SNES_MUL_OPERAND_B($004203)
constant SNES_DIV_DIVIDEND_L($004204)
constant SNES_DIV_DIVIDEND_H($004205)
constant SNES_DIV_DIVISOR($004206)
constant SNES_DIV_QUOTIENT_L($004214)
constant SNES_DIV_QUOTIENT_H($004215)
constant SNES_MUL_DIV_RESULT_L($004216)
constant SNES_MUL_DIV_RESULT_H($004217)

// Constants
constant FULL_VOLUME($FF)
constant DUCKED_VOLUME($30)

constant BATTLE1_MUSIC($45)
constant THEME_LOOP($18)
constant THEME_ATTRACT($54)

// =============
// = Variables =
// =============
// Game Variables
variable musicCommand($1E00)
variable musicRequested($1E01)
variable targetVolume($1E02)

// My own variables
variable currentSong($1EE0)
variable fadeState($1EE1)
variable fadeVolume($1EE2)
variable fadeTarget($1EE4)
variable fadeStep($1EE6)

// fadeState possibles values
constant FADE_STATE_IDLE($00)
constant FADE_STATE_FADEOUT($01)
constant FADE_STATE_FADEIN($02)

// **********
// * Macros *
// **********
// seek converts SNES HiROM address to physical address
macro seek(variable offset) {
  origin (offset & $3FFFFF)
  base offset
}

macro CheckMSUPresence(labelToJump) {
    lda MSU_ID
    cmp.b #'S'
    bne {labelToJump}
}

macro WaitMulResult() {
    nop
    nop
    nop
    nop
}

macro WaitDivResult() {
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
}

// ========================
// = Original code hijack =
// ========================

// NMI hijack
seek($00FF10)
    jml MSU_UpdateLoop

// Wait for song to finish command
seek($C03CC6)
    jsl MSU_WaitSongFinish
    nop
    nop
    nop
    nop

// Wait for song to start (found when switching characters on overworld)
seek($C2CBE0)
    jsl MSU_WaitSongStart
    nop
    nop
    nop
    nop

// Wait for title screen fix
// This chunk of code is copied into RAM
// by the decompression routine, that's why
// it got a db $80 in the middle
seek($C335AF)
    nop
    db $80
    jsl MSU_WaitSongStart
    nop
    nop

// Epoch 1999 AD sound check
seek($C30C28)
SoundCheckSuccessful:
    // Equivalent to bra $0bb5
    db $80,$8B
    jsl MSU_EpochMode7Fix
    bcs SoundCheckSuccessful
    rts

// Epoch 1999 AD event modification
// This is a destructive modification
// Remove syncronisation with music in the event
seek($FA96B1)
    // 10 10 is Event Command Jump Forward ten bytes
    // This is compressed data so the modification is
    // replicated 3 times
    db $10, $10, 0, 0, 0
    
// Relevant calls to $C70004
// Found via hex editor by searching for JSL $C70004

seek($C01B73)
    jsl MSU_Main
    
// Entering area
seek($C01B8B)
    jsl MSU_Main
    
// Entering battle
seek($C01BCE)
    jsl MSU_Main
    
seek($C01C2A)
    jsl MSU_Main
    
// Exiting battle
seek($C01C3A)
    jsl MSU_Main
    
seek($C01C52)
    jsl MSU_Main
    
seek($C01CA9)
    jsl MSU_Main
    
seek($C01CB7)
    jsl MSU_Main
    
seek($C01CCF)
    jsl MSU_Main
    
// Called during attract
seek($C03C43)
    jsl MSU_Main
    
seek($C03C69)
    jsl MSU_Main
    
seek($C03CB4)
    jsl MSU_Main
    
seek($C161DF)
    jsl MSU_Main
    
seek($C20462)
    jsl MSU_Main
    
seek($C223F9)
    jsl MSU_Main
    
seek($C22F49)
    jsl MSU_Main
    
seek($C2CBF3)
    jsl MSU_Main
    
seek($C2CC09)
    jsl MSU_Main
    
seek($C309D1)
    jsl MSU_Main
    
seek($C30BE9)
    jsl MSU_Main
    
// Title Screen
seek($C31647)
    jsl MSU_Main

seek($CD03AB)
    jsl MSU_Main
    
seek($CD0D70)
    jsl MSU_Main
    
seek($CD0D81)
    jsl MSU_Main
    
// Hijack for music in attract mode
seek($DB6E03)
    db THEME_ATTRACT
seek($FA24A4)
    db THEME_ATTRACT
seek($FA28FA)
    db THEME_ATTRACT
seek($FA4925)
    db THEME_ATTRACT
seek($FA4962)
    db THEME_ATTRACT
seek($FA659C)
    db THEME_ATTRACT

// ============
// = MSU Code =
// ============
seek($C5F370)
scope MSU_Main: {
    php
// Backup A and Y in 16bit mode
    rep #$30
    pha
    phx
    phy
    phd
    phb
    
    sep #$30 // Set all registers to 8 bit mode
    
    CheckMSUPresence(.CallOriginalRoutine)
    
    lda.w musicCommand
    // Play Music
    cmp.b #$10
    bne +
    jsr MSU_PlayMusic
    bcs .CallOriginalRoutine
    bcc .DoNotCallSPCRoutine
+
    // Resume
    cmp.b #$11
    bne +
if {defined RESUME_EXPERIMENT} {
    jsr MSU_PlayMusic
} else {
    jsr MSU_ResumeMusic
}
    
    bcs .CallOriginalRoutine
    bcc .DoNotCallSPCRoutine
+
    // Interrupt
    cmp.b #$14
    bne +
    jsr MSU_PauseMusic
    bcs .CallOriginalRoutine
    bcc .DoNotCallSPCRoutine
+
    // Fade
    cmp.b #$81
    bne +
    jsr MSU_PrepareFade
+
// Call original routine
.CallOriginalRoutine:
    // Restore original theme when MSU-1 is not present
    lda.b #$10
    cmp.w musicCommand
    bne Original
    lda.b #THEME_ATTRACT
    cmp.w musicRequested
    bne Original
    
    lda.b #THEME_LOOP
    sta.w musicRequested
    
Original:
    rep #$30
    plb
    pld
    ply
    plx
    pla
    plp
    
    jsl $C70004
    rtl
    
.DoNotCallSPCRoutine:
    rep #$30
    plb
    pld
    ply
    plx
    pla
    plp
    rtl
}

scope MSU_PlayMusic: {
    lda.w musicRequested
    beq .StopMSUMusic
    cmp.b #$FF
    beq .SongAlreadyPlaying
    cmp.w currentSong
    beq .SongAlreadyPlaying
    sta MSU_AUDIO_TRACK_LO
    lda.b #$00
    sta MSU_AUDIO_TRACK_HI

.CheckAudioStatus:
    lda MSU_STATUS
    
    and.b #MSU_STATUS_AUDIO_BUSY
    bne .CheckAudioStatus
    
    // Check if track is missing
    lda MSU_STATUS
    and.b #MSU_STATUS_TRACK_MISSING
    bne .StopMSUMusic

    // Play the song
    lda.w musicRequested
    jsr TrackNeedLooping
    
if {defined RESUME_EXPERIMENT} {
    ldx musicCommand
    cpx #$11
    bne .SetAudioControl
    
    // Add resume flag
    ora.b #$4
.SetAudioControl:
}

    sta MSU_AUDIO_CONTROL
    
    // Set volume
    lda.b #FULL_VOLUME
    sta.w MSU_AUDIO_VOLUME
    sta.w fadeVolume

    // Only store current song if we were able to play the song
    lda.w musicRequested
    sta currentSong
    
    // Set SPC music to silence and disable any fade if any was active
    lda #$00
    sta $1E01
    sta.w fadeState
    sec
    bra .Exit

.SongAlreadyPlaying:
    clc
.Exit:
    rts
    
.StopMSUMusic:
    lda.b #$00
    sta MSU_AUDIO_CONTROL
    sta MSU_AUDIO_VOLUME
    sta.w currentSong
    sec
    bra .Exit
}

scope MSU_ResumeMusic: {
    lda MSU_STATUS
    and.b #MSU_STATUS_TRACK_MISSING
    bne .CallOriginalCode

    lda.w musicRequested
    cmp.w currentSong
    beq +
    
    jmp MSU_PlayMusic
    
+
    sta currentSong
    lda.b #$03
    sta MSU_AUDIO_CONTROL
    
    // Play silence after resuming music to
    // reload correct SFX samples
    lda.b #$10
    sta.w musicCommand
    lda.b #$00
    sta.w musicRequested
.CallOriginalCode:
    sec
    rts
}

scope MSU_PauseMusic: {
if {defined RESUME_EXPERIMENT} {
    lda.b #$4
    sta MSU_AUDIO_CONTROL
    
    jml MSU_PlayMusic
} else {
    lda.w musicRequested
    cmp.b #BATTLE1_MUSIC
    beq .PauseMSUMusic
    
    jml MSU_PlayMusic
    
.PauseMSUMusic:
    lda MSU_STATUS
    and.b #MSU_STATUS_TRACK_MISSING
    bne +
    
    lda.b #$00
    sta MSU_AUDIO_CONTROL
+
    sec
    rts
}

}

scope MSU_PrepareFade: {
    rep #$20
    lda #$0000
    sep #$20
    // musicRequested = Fade Time
    lda.w musicRequested
    beq .SetVolumeImmediate
    
    // fadeStep = (targetVolume-fadeVolume)/fadeTime
    lda.w targetVolume
    sta.w fadeTarget
    
    rep #$20
    sec
    sbc.w fadeVolume
    // If carry is set, the result is a positive number
    bcs +
    
    // Reverse sign of the result (which in two-complements)
    // A negative result means a fade-out
    eor #$FFFF
    inc
    
    sep #$30
    ldx.b #FADE_STATE_FADEOUT
    stx.w fadeState
    bra .DoDivision
+
    sep #$30
    ldx.b #FADE_STATE_FADEIN
    stx.w fadeState
.DoDivision:
    // Do division using SNES division support
    sta $4204 // low
    stz $4205 // high byte
    lda.w musicRequested // fadeTime
    sta $4206
    
    // Wait 16 CPU cycles
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop

    // Result in 4214 / 4215
    lda $4214
    beq .ResetToIdle
    sta fadeStep
    bra .Exit

.SetVolumeImmediate:
    lda.w targetVolume
    sta.w fadeVolume
    sta MSU_AUDIO_VOLUME
.Exit:
    rts
.ResetToIdle:
    lda.b #FADE_STATE_IDLE
    sta.w fadeState
    sta.w fadeStep
    bra .SetVolumeImmediate
}

scope TrackNeedLooping: {
    // 1.01 A Premonition
    cmp.b #48
    beq .noLooping
    // 1.02 Theme of Chrono Trigger (Attract)
    cmp.b #THEME_ATTRACT
    beq .noLooping
    // 1.03 Morning Glow
    cmp.b #15
    beq .noLooping
    // 1.10 Good Night
    cmp.b #43
    beq .noLooping
    // 1.14 Huh ?!
    cmp.b #37
    beq .noLooping
    // 1.16 A Prayer for the Wayfarer
    cmp.b #36
    beq .noLooping
    // 2.02 Mystery from the Past
    cmp.b #46
    beq .noLooping
    // 2.12 Fanfare 2
    cmp.b #28
    beq .noLooping
    // 2.15 Fanfare 3
    cmp.b #61
    beq .noLooping
    // 2.22 Fiedlord's Keep
    cmp.b #72
    beq .noLooping
    lda.b #$03
    rts
.noLooping:
    lda.b #$01
    rts
}

scope MSU_UpdateLoop: {
    php
    rep #$20
    pha
    
    sep #$20

    CheckMSUPresence(.CallNMI)
    
    lda.w fadeState
    beq .CallNMI
    
    cmp.b #FADE_STATE_FADEOUT
    beq .FadeOutUpdate
    cmp.b #FADE_STATE_FADEIN
    beq .FadeInUpdate
    bra .CallNMI
    
.FadeOutUpdate:
    lda.w fadeVolume
    sec
    rep #$20
    sbc.w fadeStep
    cmp.w fadeTarget
    bpl +
    sep #$20
    lda fadeTarget
+
    sep #$20
    sta.w fadeVolume
    sta MSU_AUDIO_VOLUME
    cmp.w fadeTarget
    beq .SetToIdle
    bra .CallNMI

.FadeInUpdate:
    lda.w fadeVolume
    clc
    rep #$20
    adc.w fadeStep
    cmp.w fadeTarget
    bcc +
    sep #$20
    lda fadeTarget
+
    sep #$20
    sta.w fadeVolume
    sta MSU_AUDIO_VOLUME
    cmp.w fadeTarget
    beq .SetToIdle
    bra .CallNMI
    
.SetToIdle:
    lda.b #FADE_STATE_IDLE
    sta.w fadeState
    
.CallNMI:
    rep #$20
    pla
    plp
    jml $000500
}

scope MSU_WaitSongStart: {
    php
    rep #$20
    pha
    
    sep #$20

    CheckMSUPresence(.OriginalCode)
    
    rep #$20
    pla
    plp
    rtl
    
.OriginalCode:
    rep #$20
    pla
    plp
    
    // Original code
-
    lda $2143
    and.b #$0F
    beq -
    
    rtl
}

scope MSU_WaitSongFinish: {
    php
    rep #$20
    pha
    
    sep #$20
    CheckMSUPresence(.OriginalCode)
    
    lda MSU_STATUS
    and.b #(MSU_STATUS_AUDIO_PLAYING|MSU_STATUS_AUDIO_REPEAT)
    bne +
    inx
+
    rep #$20
    pla
    plp
    rtl
    
.OriginalCode:
    rep #$20
    pla
    plp
    
    lda $2143
    and.b #$0F
    bne +
    inx
+
    rtl
}

scope MSU_EpochMode7Fix: {
    rep #$20
    pha
    sep #$20
    
    CheckMSUPresence(.OriginalCode)
    
    rep #$20
    pla
    sec
    rtl
    
.OriginalCode:
    rep #$20
    pla
-
    sep #$20
    lda SPC_COMM_2
    cmp SPC_COMM_2
    bne -
    
    and.b #$0F
    cmp ($20)
    bpl .RoutineSuccessful
    rep #$20
    dec $20
    clc
    rtl
    
.RoutineSuccessful:
    sec
    rtl
}



RE: MSU-1 Hacks - dn - 07-11-2015

FF6 is going to require a lot more work due to battle NMI being air tight. I've already attempted to implement it, if crudely, and while it seems to work out of battle, in-battle it corrupts graphics since it hijacks the NMI routine. Further, all of the music-changing events (fading in, fading out, transitioning, hardcoded transitions for battle, et cetera) would need to be coded to handle it. It's not a simple swap job. I'm not even sure it's doable.


RE: MSU-1 Hacks - Catone - 07-11-2015

Can it be made to only effect out of battle? Or is it a all or none type deal?


RE: MSU-1 Hacks - madsiur - 07-11-2015

(07-11-2015, 08:49 AM)Catone Wrote: Can it be made to only effect out of battle? Or is it a all or none type deal?

There's no restriction. You can have it only available for out of battle songs or have the SPC-700 play the default songs if PCM files are not found in the library.

(07-11-2015, 02:05 AM)dn Wrote: I've already attempted to implement it, if crudely, and while it seems to work out of battle, in-battle it corrupts graphics since it hijacks the NMI routine.

The guy on the Qhimm forum has a proof of concept video in battle: https://www.youtube.com/watch?v=vKgSqG2Tbfc. I know it's just not a swap and several event commands would need to be edited. The problem seems to be with fading out tracks, but hey, I have nothing to lose if I attempt it.

There's also this guy who made the opera scene: https://www.youtube.com/watch?v=tGlIKAukm1U


RE: MSU-1 Hacks - madsiur - 06-26-2016

insidious611's project is still alive and has entering an open beta phase: http://forums.qhimm.com/index.php?topic=16077.0

He has not yet implemented fade in / out or used NMI for other purpose. There's a few issues on the tracker but the guy seems to want bringing this project fully enjoyable on bsnes 0.75, Higan 0.97+ and SD2SNES cartridge. The code is now public: https://bitbucket.org/insidious611/dancingmad


RE: MSU-1 Hacks - madsiur - 05-18-2018

I'm closing this thread and moved last post in a more fitting place:

https://www.ff6hacking.com/forums/thread-3634-post-36104.html#pid36104