summaryrefslogtreecommitdiff
path: root/rsp/rspboot_ap.s
blob: d3708c2fa5f0c93d6400d0f57da8dc2b4d151c58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/**
 * @file rspboot_ap.s
 *
 * This RSP microcode program acts as a loader for other microcodes. This ucode:
 * - (Optionally) Waits for the RDP to go idle
 * - Loads a ucode's .data section to the start of DMEM
 * - Loads a ucode's .text section to RSPBOOT_ENTRYPOINT
 * - Jumps to the loaded ucode
 *
 * Along the way it also checks whether the task scheduler running on the CPU has
 * requested a yield and, if so, halts the RSP.
 *
 * This is a special variant of rspboot that is designed to work alongside IPL3 X105
 * and the cic6105 microcode to attempt to detect whether the running game is a pirated
 * copy. If it is so determined (in the antipiracy_test routine) cic6105 will not set
 * signal 7 in SP_STATUS, causing subsequent runs of rspboot to corrupt random regions
 * of memory (see the dpclock_corrupt routine)
 */
.rsp
#include "rcp.h"
#include "sptask.h"
#include "rspboot.h"

#define OSTASK_FIELD(field) (-0x40 + OS_TASK_OFF_##field)($zero)

.create CODE_FILE, IMEM_START

entry:
    // This jump is overwritten once rspboot runs for the first time (see set_status_and_patch below)
    j       antipiracy_test
     lw     $17, OSTASK_FIELD(FLAGS)

load_ucode_text_and_enter:
    li      $ra, RSPBOOT_ENTRYPOINT
    li      $1, (IMEM_SIZE - (RSPBOOT_ENTRYPOINT - IMEM_START))-1
    mtc0    $16, SP_DRAM_ADDR
    mtc0    $ra, SP_MEM_ADDR
    mtc0    $1, SP_RD_LEN
@@while_dma_busy:
    mfc0    $1, SP_DMA_BUSY
    bnez    $1, @@while_dma_busy
check_yield:
     mfc0   $1, SP_STATUS
    andi    $1, $1, SP_STATUS_YIELD
    bnez    $1, yield_break
     li     $1, OSTASK_ADDR // $1 is expected to contain the OSTask pointer by many ucodes
    jr      $ra

yield_break:
     li     $17, (SP_SET_SIG2 | SP_SET_SIG1 | SP_CLR_SIG0)
    mtc0    $17, SP_STATUS
    break

antipiracy_test:
    // Checks that $4, $5, $6 and $11 all have expected register values set by IPL3 X105
    lui     $1, (SP_CLR_SIG7 >> 16)
    li      $3, 0x3D8
    bne     $11, $3, set_status_and_patch
     addi   $16, $zero, entry_failure
    lui     $3, 0x3A0
    ori     $3, $3, 0x4820
    bne     $4, $3, set_status_and_patch
     lui    $3, 0x2529
    ori     $3, $3, 0x4
    bne     $6, $3, set_status_and_patch
     srl    $3, $5, 12
    addi    $2, $3, -76
    bltz    $2, set_status_and_patch
     addi   $2, $3, -79
    bgtz    $2, set_status_and_patch
     vxor   $v13, $v13, $v13[1q]
    vaddc   $v13, $v13, $v13[2h]
    vnxor   $v13, $v13, $v13[4]
    mfc2    $3, $v13[0]
    xori    $3, $3, 0x1234  // $3 is calculated and then never used before it is repurposed
    nop
    lui     $1, (SP_SET_SIG7 >> 16)
    addi    $16, $zero, entry_success
set_status_and_patch:
    mtc0    $1, SP_STATUS
    lw      $1, OSTASK_FIELD(UBOOT)
    // fallthrough to dma write 8 bytes, patches the first 8 bytes of the ucode in dram
    // with a new jump target that avoids the DPC_CLOCK code if SP_STATUS_SIG7 is set

dma_write_8:
@@while_dma_full:
    mfc0    $3, SP_DMA_FULL
    bne     $3, $3, @@while_dma_full
     nop
    mtc0    $16, SP_MEM_ADDR
    mtc0    $1, SP_DRAM_ADDR
    j       dp_wait
     mtc0   $zero, SP_WR_LEN // 0 = 8-byte write to RDRAM

.align 8
entry_success:
    // antipiracy check passed, rspboot is patched to check signal 7 instead of performing the check again
    j       check_sig7
     lw     $17, OSTASK_FIELD(FLAGS)

.align 8
entry_failure:
    // antipiracy check failed, rspboot is patched to run the failure codepath until reset
    j       dpclock_corrupt
     lw     $17, OSTASK_FIELD(FLAGS)

check_sig7: // if signal 7 is set, skip DPC_CLOCK sampling + corruption
    mfc0    $16, SP_STATUS
    andi    $16, $16, SP_STATUS_SIG7
    bnez    $16, dp_wait
dpclock_corrupt:
     mfc0   $16, DPC_CLOCK
    andi    $1, $16, 0x1FFF
    bnez    $1, @@stepover // if the 13 low-order bits of the sampled clock are 0, set DPC_END to DPC_CLOCK
     andi   $1, $16, 0x7F
    mtc0    $16, DPC_END
@@stepover:
    beqz    $1, dma_write_8 // if the 7 low-order bits of the sampled clock are 0, corrupt 8 random bytes of DRAM
     srl    $1, $16, 2
dp_wait:
    sll     $17, $17, (32 - OS_TASK_DP_WAIT)
    bgezal  $17, load_ucode_data
     lw     $16, OSTASK_FIELD(UDATA_SZ)
    mfc0    $1, DPC_STATUS
    andi    $1, $1, DPC_STATUS_DMA_BUSY
    bnez    $1, check_yield // yield check while waiting for RDP Command FIFO to become idle

load_ucode_data:
@@while_dma_full:
     mfc0   $1, SP_DMA_FULL
    bnez    $1, @@while_dma_full
     lw     $1, OSTASK_FIELD(UDATA)
    addi    $16, $16, -1
    mtc0    $zero, SP_MEM_ADDR
    mtc0    $1, SP_DRAM_ADDR
    mtc0    $16, SP_RD_LEN
@@while_dma_busy:
    mfc0    $1, SP_DMA_BUSY
    bnez    $1, @@while_dma_busy
     li     $ra, load_ucode_text_and_enter
    mtc0    $zero, SP_SEMAPHORE // Release the semaphore just in case a prior task or the CPU failed to release it
    vxor    $v16, $v16, $v16    // set $v16 to 0, some F3DZEX versions rely on this being set to function properly
    sw      $4, OSTASK_FIELD(UBOOT)
    j       check_yield
     lw     $16, OSTASK_FIELD(UCODE)

.align 0x10

.if . > IMEM_END
    .error "Not enough room in IMEM"
.endif

.close