├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── antipiracy-issue.md │ └── generic-issue.md ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── boot-cd ├── .gitignore ├── Makefile ├── cd.xml ├── licensee.dat └── system.cnf ├── docs └── ap_v2.c ├── entrypoints ├── Makefile ├── brunswick1-eu-tpl.mcs ├── brunswick1-us-tpl.mcs ├── brunswick2-eu-tpl.mcs ├── brunswick2-us-tpl.mcs ├── castrolsb-eu-tpl.mcs ├── castrolsb-us-tpl.mcs ├── castrolvtr-eu-tpl.mcs ├── cc-us-tpl.mcs ├── coolbrd4-eu-tpl.mcs ├── coolbrd4-us-tpl.mcs ├── crash2-eu-tpl.mcs ├── crash2-us-tpl.mcs ├── crash3-eu-tpl.mcs ├── crash3-us-tpl.mcs ├── entry.S ├── fix-cb4-checksum.sh ├── fix-crash-checksum.sh ├── superbike1-eu-tpl.mcs ├── superbike2-eu-tpl.mcs ├── thps2-de-tpl.mcs ├── thps2-eu-tpl.mcs ├── thps2-fr-tpl.mcs ├── thps2-us-tpl.mcs ├── thps3-de-tpl.mcs ├── thps3-eu-tpl.mcs ├── thps3-fr-tpl.mcs ├── thps3-us-tpl.mcs ├── thps4-de-tpl.mcs ├── thps4-eu-tpl.mcs ├── thps4-fr-tpl.mcs ├── thps4-us-tpl.mcs ├── xsmoto-eu-tpl.mcs └── xsmoto-us-tpl.mcs ├── freepsxboot └── Makefile ├── loader ├── Makefile ├── audio.c ├── audio.h ├── bios-asm.S ├── bios.c ├── bios.h ├── cdrom.c ├── cdrom.h ├── cfgparse.c ├── cfgparse.h ├── crc.c ├── crc.h ├── debugscreen.c ├── debugscreen.h ├── generate-tonyhax-exe.sh ├── generate-tonyhax-mcs.sh ├── gpu.c ├── gpu.h ├── insert-tonyhax-crc.sh ├── integrity.c ├── integrity.h ├── io.h ├── orca.act ├── orca.img ├── patch-ap.S ├── patch-fpb.S ├── patch-uart.S ├── patch-vandal-hearths-2.S ├── patcher.c ├── patcher.h ├── secondary.c ├── secondary.ld ├── str.c ├── str.h ├── tonyhax-tpl.mcs ├── util.c └── util.h ├── util ├── bin2h.sh ├── mcs2raw.sh ├── print-ps2-romdir.py └── psx-exe-crc.sh └── variables.mk /.gitattributes: -------------------------------------------------------------------------------- 1 | # Version: 2021.05.25.01 2 | 3 | # Force all text files to be LF 4 | * text=auto eol=lf 5 | 6 | # Project source files 7 | *.bat text eol=crlf 8 | *.c text diff=cpp 9 | *.conf text 10 | *.config text 11 | *.cmd text eol=crlf 12 | *.cnf text 13 | *.cs text diff=csharp 14 | *.csproj text diff=html 15 | *.css text 16 | *.diff text 17 | *.h text diff=cpp 18 | *.html text 19 | *.ino text diff=cpp 20 | *.java text diff=java 21 | *.js text 22 | *.json text 23 | *.md text 24 | *.properties text 25 | *.ps1 text 26 | *.py text diff=python 27 | *.scss text 28 | *.s text 29 | *.sql text 30 | *.sln text 31 | *.sh text eol=lf 32 | *.txt text 33 | *.xml text diff=html 34 | *.yml text 35 | .gitattributes text 36 | .gitignore text 37 | .htaccess text 38 | Dockerfile text 39 | 40 | # Images 41 | *.bmp binary 42 | *.jpg binary 43 | *.png binary 44 | *.svg text 45 | 46 | # Binaries 47 | *.act binary 48 | *.dll binary 49 | *.dylib binary 50 | *.elf binary 51 | *.exe binary 52 | *.img binary 53 | *.jnilib binary 54 | *.o library 55 | *.so binary 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/antipiracy-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Antipiracy issue 3 | about: Game displays an antipiracy screen 4 | title: '' 5 | labels: antipiracy 6 | assignees: '' 7 | 8 | --- 9 | 10 | **tonyhax version**: Which version are you running? Please ensure you are running the latest stable, or a newer beta. 11 | **Game name**: Self-describing 12 | **Game code**: Game's serial number, eg "SCES-12345" 13 | **Executable hash**: SHA-1, SHA-256 or MD-5 of the game's main executable or the disc. This is important since there could be multiple revisions of the same game. 14 | **Triggered at**: Please explain when is the screen shown, eg "after the Sony logo", or "after the first video" 15 | **Is the console chipped?**: Yes or no. 16 | **Is it an original disc or a burned copy?**: Self-explaining. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/generic-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Generic issue 3 | about: Other issue, not related to antipiracy 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | Before opening this kind of issue, please ensure: 11 | - Are you using a compatible console version? 12 | - Are you using CD-R media, and not CD-RW? 13 | - Did you copy all the files in the expected format to the memory card? 14 | 15 | **tonyhax version**: Which version are you running? Please ensure you are running the latest stable, or a newer beta. 16 | **Installation method**: How did you get tonyhax on the memory card? 17 | **Entry point game**: Which game are you using to launch tonyhax? 18 | **Console model**: Console product code, written on the bottom of the unit, such as "SCPH-7502" 19 | **Integrity check**: If it boots, does the built-in integrity check succeed? 20 | **BIOS version**: If you can get to boot, which version of the BIOS does it report? 21 | **Target game**: If the bug happens when launching a game, what's its name and game code? Example: "Spyro 3 (SCES-02835)" 22 | 23 | **Bug explanation**: 24 | Please write a detailed explanation on which issue are you experiencing. 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | SL* 2 | SC* 3 | *.bak 4 | *.elf 5 | *.bin 6 | *.o 7 | *.inc 8 | BES* 9 | BAS* 10 | *.mcs 11 | !*-tpl.mcs 12 | *.exe 13 | !*-tpl.exe 14 | *.zip 15 | *.mcd 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "freepsxboot/FreePSXBoot"] 2 | path = freepsxboot/FreePSXBoot 3 | url = https://github.com/brad-lin/FreePSXBoot 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Thanks to whoever made https://devhints.io/makefile! 3 | 4 | include variables.mk 5 | 6 | PACKAGE_FILE = tonyhax-$(TONYHAX_VERSION).zip 7 | PACKAGE_CONTENTS = $(ENTRY_FILES:%=entrypoints/%) $(LOADER_FILES:%=loader/%) $(FREEPSXBOOT_IMAGES:%=freepsxboot/%) $(BOOT_CD_FILES:%=boot-cd/%) README.md LICENSE 8 | 9 | .PHONY: modules clean 10 | 11 | all: modules $(PACKAGE_FILE) 12 | 13 | $(PACKAGE_FILE): $(PACKAGE_CONTENTS) 14 | $(RM) $(PACKAGE_FILE) 15 | zip -9 $(PACKAGE_FILE) $(PACKAGE_CONTENTS) 16 | 17 | $(PACKAGE_CONTENTS): modules 18 | 19 | modules: 20 | $(MAKE) -C entrypoints all 21 | $(MAKE) -C loader all 22 | $(MAKE) -C freepsxboot all 23 | $(MAKE) -C boot-cd all 24 | 25 | clean: 26 | $(MAKE) -C entrypoints clean 27 | $(MAKE) -C loader clean 28 | $(MAKE) -C freepsxboot clean 29 | $(MAKE) -C boot-cd clean 30 | $(RM) tonyhax-*.zip 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | tonyhax 3 | ======= 4 | 5 | ![Download counter](https://img.shields.io/github/downloads/socram8888/tonyhax/total.svg) 6 | 7 | Software backup loader exploit thing for the Sony PlayStation 1. 8 | 9 | For more information, look at [its section at orca.pet](https://orca.pet/tonyhax/). 10 | -------------------------------------------------------------------------------- /boot-cd/.gitignore: -------------------------------------------------------------------------------- 1 | tonyhax-boot-cd.bin 2 | tonyhax-boot-cd.cue 3 | -------------------------------------------------------------------------------- /boot-cd/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Thanks to whoever made https://devhints.io/makefile! 3 | 4 | include ../variables.mk 5 | 6 | .PHONY: clean 7 | 8 | all: $(BOOT_CD_FILES) 9 | 10 | clean: 11 | $(RM) $(BOOT_CD_FILES) 12 | 13 | $(BOOT_CD_FILES): ../loader/tonyhax.exe cd.xml licensee.dat system.cnf 14 | mkpsxiso -y cd.xml 15 | -------------------------------------------------------------------------------- /boot-cd/cd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /boot-cd/licensee.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/boot-cd/licensee.dat -------------------------------------------------------------------------------- /boot-cd/system.cnf: -------------------------------------------------------------------------------- 1 | BOOT=cdrom:\TONYHAX.EXE;1 2 | TCB=4 3 | EVENT=10 4 | STACK=801FFFF0 5 | -------------------------------------------------------------------------------- /docs/ap_v2.c: -------------------------------------------------------------------------------- 1 | 2 | inline uint8_t bcd_to_binary(uint8_t val) { 3 | return (val >> 4) * 10 + (val & 0xF); 4 | } 5 | 6 | inline uint8_t binary_to_bcd(uint8_t val) { 7 | return (val / 10) << 4 | (val % 10); 8 | } 9 | 10 | enum { 11 | CD_RET_TRAY_OPEN = -2, 12 | CD_RET_FAIL = -1, 13 | CD_RET_CONTINUE = 0, 14 | CD_RET_SUCCESS = 1 15 | }; 16 | 17 | volatile uint8_t * const CD_REGS = (volatile uint8_t *) 0x1F801800; 18 | 19 | struct cd_operation_t { 20 | uint8_t command; 21 | uint8_t req_len; 22 | uint8_t reply_len; 23 | uint8_t expected_ints; 24 | }; 25 | 26 | enum { 27 | CD_OP_GET_STATUS = 0, 28 | CD_OP_GET_TN = 1, 29 | CD_OP_GET_TD = 2, 30 | CD_OP_SET_LOCATION = 3, 31 | CD_OP_SEEK_AUDIO = 4, 32 | CD_OP_SET_MODE = 5, 33 | CD_OP_INIT = 6, 34 | CD_OP_MUTE = 7, 35 | CD_OP_PLAY = 8, 36 | CD_OP_SUBFUNC_X = 9, 37 | CD_OP_SUBFUNC_Y = 10, 38 | CD_OP_PAUSE = 11, 39 | CD_OP_READ_TOC = 12, 40 | CD_OP_GET_ID = 13 41 | }; 42 | 43 | const struct cd_operation_t[] CD_OPERATIONS = { 44 | { 0x01, 0x00, 0x01, 0x03 }, // CD_OP_GETSTAT 45 | { 0x13, 0x00, 0x03, 0x03 }, // CD_OP_GET_TN 46 | { 0x14, 0x01, 0x03, 0x03 }, // CD_OP_GET_TD 47 | { 0x02, 0x03, 0x01, 0x03 }, // CD_OP_SET_LOCATION 48 | { 0x16, 0x00, 0x01, 0x05 }, // CD_OP_SEEK_AUDIO 49 | { 0x0E, 0x01, 0x01, 0x03 }, // CD_OP_SET_MODE 50 | { 0x0A, 0x00, 0x01, 0x05 }, // CD_OP_INIT 51 | { 0x0B, 0x00, 0x01, 0x03 }, // CD_OP_MUTE 52 | { 0x03, 0x00, 0x01, 0x03 }, // CD_OP_PLAY 53 | { 0x19, 0x01, 0x01, 0x03 }, // CD_OP_SUBFUNC_X 54 | { 0x19, 0x01, 0x02, 0x03 }, // CD_OP_SUBFUNC_Y 55 | { 0x09, 0x00, 0x01, 0x05 }, // CD_OP_PAUSE 56 | { 0x1E, 0x00, 0x01, 0x05 }, // CD_OP_READ_TOC 57 | { 0x1A, 0x00, 0x01, 0x05 } // CD_OP_GET_ID 58 | }; 59 | 60 | uint8_t cd_req_buffer[3]; 61 | uint32_t cd_ackd_ints = 0; 62 | 63 | void cd_start_op(int op) { 64 | // Clear all interrupts 65 | CD_REGS[0] = 0x01; 66 | CD_REGS[3] = 0x07; 67 | 68 | // Waste some cycles 69 | for (int i = 0; i < 4; i++) { 70 | *((uint32_t *) 0) = i; 71 | } 72 | 73 | // Disable interrupts for responses so we can check them synchronously 74 | CD_REGS[0] = 0x01; 75 | CD_REGS[2] = 0x18; 76 | 77 | // Write the request bytes (if any) 78 | CD_REGS[0] = 0x00; 79 | for (int i = 0; i < CD_OPERATIONS[op].req_len; i++) { 80 | CD_REGS[2] = cd_req_buffer[i]; 81 | } 82 | 83 | // Start the operation 84 | CD_REGS[0] = 0x00; 85 | CD_REGS[1] = CD_OPERATIONS[op].command; 86 | } 87 | 88 | int cd_check_op(int op) { 89 | // Check current interrupts 90 | CD_REGS[0] = 0x01; 91 | int curints = CD_REGS[3] & 7; 92 | int curints2 = CD_REGS[3] & 7; 93 | if (curints != curints2 || curints == 0) { 94 | return 0; 95 | } 96 | 97 | // Shouldn't this be an OR? 98 | cd_ackd_ints += curlen; 99 | 100 | // Acknowledge all of them 101 | CD_REGS[0] = 0x01; 102 | CD_REGS[3] = 0x07; 103 | 104 | // Waste some cycles 105 | for (i = 0; i < 4; i++) { 106 | *((uint32_t *) 0) = i; 107 | } 108 | 109 | // This is really bizarre, why a less than? 110 | if (cd_ackd_ints < CD_OPERATIONS[op].expected_ints) { 111 | return 0; 112 | } 113 | cd_ackd_ints = 0; 114 | 115 | // An INT5 means a fail 116 | if (curints == 5) { 117 | // Read fail reason 118 | cd_reply_buf[0] = CD_REGS[1]; 119 | cd_reply_buf[1] = CD_REGS[1]; 120 | 121 | // Restore all interrupts 122 | CD_REGS[0] = 0x01; 123 | CD_REGS[2] = 0x1F; 124 | 125 | // Check the status byte, and if the lid is open then fail 126 | if (cd_reply_buf[0] & 0x10) { 127 | // Failed because of an open lid 128 | return CD_RET_TRAY_OPEN; 129 | } 130 | 131 | // Unknown 132 | return CD_RET_FAIL; 133 | } else { 134 | // Read the reply 135 | for (int i = 0; i < CD_OPERATIONS[op].reply_len; i++) { 136 | cd_reply_buf[i] = CD_REGS[1]; 137 | } 138 | 139 | // Restore all interrupts 140 | CD_REGS[0] = 0x01; 141 | CD_REGS[2] = 0x1F; 142 | 143 | // Check the status byte 144 | if (op != 0x0A && (cd_reply_buf[0] & 0x10) != 0) { 145 | // Failed because of an open lid 146 | return CD_RET_TRAY_OPEN; 147 | } 148 | 149 | // Succeeded 150 | return CD_RET_SUCCESS; 151 | } 152 | } 153 | 154 | int ap_current_step; 155 | int ap_track_count; 156 | uint8_t ap_center_mm, ap_center_ss; 157 | 158 | void antipiracy_main(int blocking) { 159 | int retval = 0; 160 | int mm, ss, center; 161 | 162 | do { 163 | switch (ap_current_step) { 164 | case 0: 165 | retval = 0; 166 | break; 167 | 168 | case 1: 169 | cd_start_op(CD_OP_GET_TN); 170 | ap_cur_step = 2; 171 | retval = 1; 172 | break; 173 | 174 | case 2: 175 | switch (cd_read_reply(CD_OP_GET_TN)) { 176 | case CD_RET_FAIL: 177 | cd_start_op(CD_OP_GET_TN); 178 | break; 179 | 180 | case CD_RET_TRAY_OPEN: 181 | cd_start_op(CD_OP_GET_STATUS); 182 | ap_cur_step = 17; 183 | break; 184 | 185 | case CD_RET_SUCCESS: 186 | ap_track_count = cd_reply_buf[2]; 187 | cd_start_op(CD_OP_INIT); 188 | ap_cur_step = 3; 189 | break; 190 | } 191 | 192 | retval = 2; 193 | break; 194 | 195 | case 3: 196 | switch (cd_read_reply(CD_OP_INIT)) { 197 | case CD_RET_FAIL: 198 | ap_cur_step = 1; 199 | break; 200 | 201 | case CD_RET_TRAY_OPEN: 202 | cd_start_op(CD_OP_GET_STATUS); 203 | ap_cur_step = 17; 204 | break; 205 | 206 | case CD_RET_SUCCESS: 207 | // Request end of first track 208 | cd_req_buffer[0] = ap_track_count < 2 ? 0 : 2; 209 | cd_start_op(CD_OP_GET_TD); 210 | ap_cur_step = 4; 211 | break; 212 | } 213 | 214 | retval = 3; 215 | break; 216 | 217 | case 4: 218 | switch (cd_read_reply(CD_OP_GET_TD)) { 219 | case CD_RET_FAIL: 220 | ap_cur_step = 1; 221 | break; 222 | 223 | case CD_RET_TRAY_OPEN: 224 | cd_start_op(CD_OP_GET_STATUS); 225 | ap_cur_step = 17; 226 | break; 227 | 228 | case CD_RET_SUCCESS: 229 | // Calculate center of data track 230 | mm = bcd_to_binary(cd_reply_buf[1]); 231 | ss = bcd_to_binary(cd_reply_buf[2]); 232 | center = (60 * mm + ss) / 2; 233 | 234 | // Convert back into minutes and seconds 235 | ap_center_mm = binary_to_bcd(center / 60); 236 | ap_center_ss = binary_to_bcd(center % 60); 237 | 238 | cd_start_op(CD_OP_READ_TOC); 239 | ap_cur_step = 5; 240 | break; 241 | } 242 | 243 | retval = 7; 244 | break; 245 | 246 | case 5: 247 | retval = cd_read_reply(CD_OP_READ_TOC); 248 | 249 | switch (retval) { 250 | case CD_RET_FAIL: 251 | if ((cd_reply_buf[0] & 1) == 0) { 252 | ap_cur_step = 1; 253 | } else if ((cd_reply_buf[1] & 0x40) != 0) { 254 | cd_req_buffer[0] = ap_center_mm; 255 | cd_req_buffer[1] = ap_center_ss; 256 | cd_req_buffer[2] = 0; 257 | cd_start_op(CD_OP_SET_LOCATION); 258 | ap_cur_step = 7; 259 | retval = 5; 260 | } 261 | break; 262 | 263 | case CD_RET_TRAY_OPEN: 264 | cd_start_op(CD_OP_GET_STATUS); 265 | ap_cur_step = 17; 266 | retval = 5; 267 | break; 268 | 269 | case CD_RET_SUCCESS: 270 | cd_start_op(CD_OP_GET_ID); 271 | ap_cur_step = 7; 272 | retval = 5; 273 | break; 274 | 275 | default: 276 | retval = 5; 277 | } 278 | break; 279 | 280 | case 6: 281 | retval = cd_read_reply(CD_OP_GET_ID); 282 | 283 | switch (retval) { 284 | case CD_RET_FAIL: 285 | antipiracy_triggered(); 286 | break; 287 | 288 | case CD_RET_TRAY_OPEN: 289 | cd_start_op(CD_OP_GET_STATUS); 290 | ap_cur_step = 17; 291 | retval = 6; 292 | break; 293 | 294 | case CD_RET_SUCCESS: 295 | cd_req_buffer[0] = ap_center_mm; 296 | cd_req_buffer[1] = ap_center_ss; 297 | cd_req_buffer[2] = 0; 298 | cd_start_op(CD_OP_SET_LOCATION); 299 | ap_cur_step = 7; 300 | retval = 6; 301 | break; 302 | 303 | default: 304 | retval = 6; 305 | } 306 | break; 307 | 308 | case 7: 309 | retval = cd_read_reply(CD_OP_SET_LOCATION); 310 | 311 | switch (retval) { 312 | case CD_RET_FAIL: 313 | ap_cur_step = 1; 314 | break; 315 | 316 | case CD_RET_TRAY_OPEN: 317 | cd_start_op(CD_OP_GET_STATUS); 318 | ap_cur_step = 17; 319 | retval = 7; 320 | break; 321 | 322 | case CD_RET_SUCCESS: 323 | cd_req_buf[0] = 1; 324 | cd_start_op(CD_OP_SET_MODE); 325 | ap_cur_step = 8; 326 | retval = 7; 327 | break; 328 | 329 | default: 330 | retval = 7; 331 | } 332 | break; 333 | 334 | case 8: 335 | retval = cd_read_reply(CD_OP_SET_MODE); 336 | 337 | switch (retval) { 338 | case CD_RET_FAIL: 339 | ap_cur_step = 1; 340 | break; 341 | 342 | case CD_RET_TRAY_OPEN: 343 | cd_start_op(CD_OP_GET_STATUS); 344 | ap_cur_step = 17; 345 | retval = 8; 346 | break; 347 | 348 | case CD_RET_SUCCESS: 349 | retval = 8; 350 | dword_800738F8 = VSync(-1); 351 | ap_cur_step = 9; 352 | break; 353 | 354 | default: 355 | retval = 8; 356 | break; 357 | } 358 | break; 359 | 360 | case 9: 361 | if (VSync(-1) - dword_800738F8 >= 3) { 362 | cd_send_cmd(CD_OP_SEEK_AUDIO); 363 | ap_cur_step = 10; 364 | } 365 | retval = 9; 366 | break; 367 | 368 | case 10: 369 | 370 | } while (blocking && retval != 0); 371 | 372 | return retval; 373 | } 374 | -------------------------------------------------------------------------------- /entrypoints/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Thanks to whoever made https://devhints.io/makefile! 3 | 4 | include ../variables.mk 5 | 6 | all: $(ENTRY_MCS) $(ENTRY_RAW) 7 | 8 | $(ENTRY_RAW): $(ENTRY_MCS) 9 | bash ../util/mcs2raw.sh $(ENTRY_MCS) 10 | 11 | clean: 12 | $(RM) BES* BAS* $(ENTRY_MCS) *.elf *.bin 13 | 14 | # Entry target 15 | 16 | entry.elf: entry.S 17 | $(CC) $(CFLAGS) entry.S -o entry.elf 18 | 19 | entry.bin: entry.elf 20 | $(OBJCOPY) $(OBJCOPYFLAGS) -j .text $< $@ 21 | 22 | # Brunswick Circuit Pro Bowling NTSC-US target 23 | brunswick1-us.mcs: brunswick1-us-tpl.mcs entry.bin 24 | cp brunswick1-us-tpl.mcs brunswick1-us.mcs 25 | dd conv=notrunc if=entry.bin of=brunswick1-us.mcs bs=1 seek=2016 26 | 27 | # Brunswick Circuit Pro Bowling PAL-EU target 28 | brunswick1-eu.mcs: brunswick1-eu-tpl.mcs entry.bin 29 | cp brunswick1-eu-tpl.mcs brunswick1-eu.mcs 30 | dd conv=notrunc if=entry.bin of=brunswick1-eu.mcs bs=1 seek=2016 31 | 32 | # Brunswick Circuit Pro Bowling 2 NTSC-US target 33 | brunswick2-us.mcs: brunswick2-us-tpl.mcs entry.bin 34 | cp brunswick2-us-tpl.mcs brunswick2-us.mcs 35 | dd conv=notrunc if=entry.bin of=brunswick2-us.mcs bs=1 seek=2272 36 | 37 | # Brunswick Circuit Pro Bowling 2 PAL-EU target 38 | brunswick2-eu.mcs: brunswick2-eu-tpl.mcs entry.bin 39 | cp brunswick2-eu-tpl.mcs brunswick2-eu.mcs 40 | dd conv=notrunc if=entry.bin of=brunswick2-eu.mcs bs=1 seek=2272 41 | 42 | # Castlevania Chronicles NTSC-US target 43 | cc-us.mcs: cc-us-tpl.mcs entry.bin 44 | cp cc-us-tpl.mcs cc-us.mcs 45 | dd conv=notrunc if=entry.bin of=cc-us.mcs bs=1 seek=820 46 | 47 | # Castrol Honda Superbike Racing NTSC-US target 48 | castrolsb-us.mcs: castrolsb-us-tpl.mcs entry.bin 49 | cp castrolsb-us-tpl.mcs castrolsb-us.mcs 50 | dd conv=notrunc if=entry.bin of=castrolsb-us.mcs bs=1 seek=1360 51 | 52 | # Castrol Honda Superbike Racing PAL-EU target 53 | castrolsb-eu.mcs: castrolsb-eu-tpl.mcs entry.bin 54 | cp castrolsb-eu-tpl.mcs castrolsb-eu.mcs 55 | dd conv=notrunc if=entry.bin of=castrolsb-eu.mcs bs=1 seek=1760 56 | 57 | # Castrol Honda VTR PAL-EU target 58 | castrolvtr-eu.mcs: castrolvtr-eu-tpl.mcs entry.bin 59 | cp castrolvtr-eu-tpl.mcs castrolvtr-eu.mcs 60 | dd conv=notrunc if=entry.bin of=castrolvtr-eu.mcs bs=1 seek=824 61 | 62 | # Cool Boarders 4 NTSC-US target 63 | coolbrd4-us.mcs: coolbrd4-us-tpl.mcs entry.bin 64 | cp coolbrd4-us-tpl.mcs coolbrd4-us.mcs 65 | dd conv=notrunc if=entry.bin of=coolbrd4-us.mcs bs=1 seek=3428 66 | bash fix-cb4-checksum.sh coolbrd4-us.mcs 67 | 68 | # Cool Boarders 4 PAL-EU target 69 | coolbrd4-eu.mcs: coolbrd4-eu-tpl.mcs entry.bin 70 | cp coolbrd4-eu-tpl.mcs coolbrd4-eu.mcs 71 | dd conv=notrunc if=entry.bin of=coolbrd4-eu.mcs bs=1 seek=3428 72 | bash fix-cb4-checksum.sh coolbrd4-eu.mcs 73 | 74 | # Crash Bandicoot 2 NTSC-US target 75 | crash2-us.mcs: crash2-us-tpl.mcs entry.bin 76 | cp crash2-us-tpl.mcs crash2-us.mcs 77 | dd conv=notrunc if=entry.bin of=crash2-us.mcs bs=1 seek=688 78 | bash fix-crash-checksum.sh crash2-us.mcs us2 79 | 80 | # Crash Bandicoot 2 PAL-EU target 81 | # redump.org lists two versions of this game: with and without EDC 82 | # This was tested with the non-EDC version (hash b077862d2c6e1b8060c2eae2fe82e708b228de7c) 83 | # Not sure if it works on the EDC one 84 | crash2-eu.mcs: crash2-eu-tpl.mcs entry.bin 85 | cp crash2-eu-tpl.mcs crash2-eu.mcs 86 | dd conv=notrunc if=entry.bin of=crash2-eu.mcs bs=1 seek=432 87 | bash fix-crash-checksum.sh crash2-eu.mcs eu2 88 | 89 | # Crash Bandicoot 3 NTSC-US target 90 | crash3-us.mcs: crash3-us-tpl.mcs entry.bin 91 | cp crash3-us-tpl.mcs crash3-us.mcs 92 | dd conv=notrunc if=entry.bin of=crash3-us.mcs bs=1 seek=688 93 | bash fix-crash-checksum.sh crash3-us.mcs us3 94 | 95 | # Crash Bandicoot 3 PAL-EU target 96 | crash3-eu.mcs: crash3-eu-tpl.mcs entry.bin 97 | cp crash3-eu-tpl.mcs crash3-eu.mcs 98 | dd conv=notrunc if=entry.bin of=crash3-eu.mcs bs=1 seek=432 99 | bash fix-crash-checksum.sh crash3-eu.mcs eu3 100 | 101 | # Sports Superbike PAL-EU target 102 | superbike1-eu.mcs: superbike1-eu-tpl.mcs entry.bin 103 | cp superbike1-eu-tpl.mcs superbike1-eu.mcs 104 | dd conv=notrunc if=entry.bin of=superbike1-eu.mcs bs=1 seek=1888 105 | 106 | # Sports Superbike 2 PAL-EU target 107 | superbike2-eu.mcs: superbike2-eu-tpl.mcs entry.bin 108 | cp superbike2-eu-tpl.mcs superbike2-eu.mcs 109 | dd conv=notrunc if=entry.bin of=superbike2-eu.mcs bs=1 seek=824 110 | 111 | # THPS2 NTSC-US target 112 | thps2-us.mcs: thps2-us-tpl.mcs entry.bin 113 | cp thps2-us-tpl.mcs thps2-us.mcs 114 | dd conv=notrunc if=entry.bin of=thps2-us.mcs bs=1 seek=5208 115 | 116 | # THPS2 PAL-EU target 117 | thps2-eu.mcs: thps2-eu-tpl.mcs entry.bin 118 | cp thps2-eu-tpl.mcs thps2-eu.mcs 119 | dd conv=notrunc if=entry.bin of=thps2-eu.mcs bs=1 seek=5200 120 | 121 | # THPS2 PAL-DE target 122 | thps2-de.mcs: thps2-de-tpl.mcs entry.bin 123 | cp thps2-de-tpl.mcs thps2-de.mcs 124 | dd conv=notrunc if=entry.bin of=thps2-de.mcs bs=1 seek=5200 125 | 126 | # THPS2 PAL-FR target 127 | thps2-fr.mcs: thps2-fr-tpl.mcs entry.bin 128 | cp thps2-fr-tpl.mcs thps2-fr.mcs 129 | dd conv=notrunc if=entry.bin of=thps2-fr.mcs bs=1 seek=5200 130 | 131 | # THPS3 NTSC-US target 132 | thps3-us.mcs: thps3-us-tpl.mcs entry.bin 133 | cp thps3-us-tpl.mcs thps3-us.mcs 134 | dd conv=notrunc if=entry.bin of=thps3-us.mcs bs=1 seek=4612 135 | 136 | # THPS3 PAL-EU target 137 | thps3-eu.mcs: thps3-eu-tpl.mcs entry.bin 138 | cp thps3-eu-tpl.mcs thps3-eu.mcs 139 | dd conv=notrunc if=entry.bin of=thps3-eu.mcs bs=1 seek=4608 140 | 141 | # THPS3 PAL-DE target 142 | thps3-de.mcs: thps3-de-tpl.mcs entry.bin 143 | cp thps3-de-tpl.mcs thps3-de.mcs 144 | dd conv=notrunc if=entry.bin of=thps3-de.mcs bs=1 seek=4608 145 | 146 | # THPS3 PAL-FR target 147 | thps3-fr.mcs: thps3-fr-tpl.mcs entry.bin 148 | cp thps3-fr-tpl.mcs thps3-fr.mcs 149 | dd conv=notrunc if=entry.bin of=thps3-fr.mcs bs=1 seek=4608 150 | 151 | # THPS4 NTSC-US target 152 | thps4-us.mcs: thps4-us-tpl.mcs entry.bin 153 | cp thps4-us-tpl.mcs thps4-us.mcs 154 | dd conv=notrunc if=entry.bin of=thps4-us.mcs bs=1 seek=5260 155 | 156 | # THPS4 PAL-EU target 157 | thps4-eu.mcs: thps4-eu-tpl.mcs entry.bin 158 | cp thps4-eu-tpl.mcs thps4-eu.mcs 159 | dd conv=notrunc if=entry.bin of=thps4-eu.mcs bs=1 seek=5252 160 | 161 | # THPS4 PAL-DE target 162 | thps4-de.mcs: thps4-de-tpl.mcs entry.bin 163 | cp thps4-de-tpl.mcs thps4-de.mcs 164 | dd conv=notrunc if=entry.bin of=thps4-de.mcs bs=1 seek=5252 165 | 166 | # THPS4 PAL-FR target 167 | thps4-fr.mcs: thps4-fr-tpl.mcs entry.bin 168 | cp thps4-fr-tpl.mcs thps4-fr.mcs 169 | dd conv=notrunc if=entry.bin of=thps4-fr.mcs bs=1 seek=5252 170 | 171 | # XS Moto NTSC-US target 172 | xsmoto-us.mcs: xsmoto-us-tpl.mcs entry.bin 173 | cp xsmoto-us-tpl.mcs xsmoto-us.mcs 174 | dd conv=notrunc if=entry.bin of=xsmoto-us.mcs bs=1 seek=1760 175 | 176 | # XS Moto PAL-EU target 177 | xsmoto-eu.mcs: xsmoto-eu-tpl.mcs entry.bin 178 | cp xsmoto-eu-tpl.mcs xsmoto-eu.mcs 179 | dd conv=notrunc if=entry.bin of=xsmoto-eu.mcs bs=1 seek=1760 180 | -------------------------------------------------------------------------------- /entrypoints/brunswick1-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/brunswick1-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/brunswick1-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/brunswick1-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/brunswick2-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/brunswick2-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/brunswick2-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/brunswick2-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/castrolsb-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/castrolsb-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/castrolsb-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/castrolsb-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/castrolvtr-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/castrolvtr-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/cc-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/cc-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/coolbrd4-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/coolbrd4-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/coolbrd4-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/coolbrd4-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/crash2-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/crash2-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/crash2-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/crash2-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/crash3-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/crash3-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/crash3-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/crash3-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/entry.S: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | .text 5 | .globl __start 6 | 7 | # s0 = realstart physical address 8 | # s1 = file descriptor 9 | # s2 = load address 10 | # s3 = real save size (excluding useless trailing zeros) 11 | 12 | __start: 13 | # Restore stack pointer 14 | li t0, 0x801FFF00 15 | move sp, t0 16 | 17 | # Call ourselves to get the current program counter in ra 18 | bal realstart 19 | 20 | realstart: 21 | # Save real start address in s0 22 | move s0, ra 23 | 24 | # Paint blue 25 | li a0, 0xFF0000 26 | bal paintscr 27 | 28 | # Keep only VBlank interrupts enabled 29 | li t1, 0x1 30 | lui t0, 0x1f80 31 | sw t1, 0x1074(t0) 32 | 33 | # Call ChangeClearPad(1) so the BIOS card IRQ clears the interrupt flags. 34 | # This prevents the PsyQ VBlank ISR from detecting that VBlank fired, and thus prevent its 35 | # LIBMCRD from interrupting us. 36 | li t1, 0x5B 37 | li a0, 1 38 | jal 0xB0 39 | 40 | # Call wait_card_status(0) to make sure we can access card on slot 1 safely 41 | li t1, 0x5D 42 | li a0, 0 43 | jal 0xB0 44 | 45 | # Call wait_card_status(1) 46 | # 47 | # You might be wondering, "why do we care about the status of the slot 2?". 48 | # 49 | # You see, LIBMCRD suffers from Bubsyitis and is constantly talking to memory cards, issuing 50 | # _card_info calls on every god damn frame. 51 | # 52 | # This wouldn't be a problem if the BIOS wasn't retarded and, when opening a file, waited 53 | # for the sector read to finish. In line with Sony's marvelous programming practices, it 54 | # instead waits for *any* memory card access to finish. 55 | # 56 | # This combination results in dev_card_open failing because it checks a buffer for the "MC" 57 | # signature before the actual sector read has finished. 58 | li t1, 0x5D 59 | li a0, 1 60 | jal 0xB0 61 | 62 | # Call FileOpen 63 | li t1, 0x32 64 | addi a0, s0, (splname - realstart) 65 | li a1, 0b00000001 66 | jal 0xB0 67 | 68 | # Save handle 69 | move s1, v0 70 | 71 | # If less than zero, it failed 72 | blt v0, zero, fatalerror 73 | 74 | # The kernel will fail to read if we don't wait a bit (here, ~1/10th of a second) 75 | # This is a known issue as specified in LIBOVR46.PDF section 5-11: 76 | # "If read() or write() is issued immediately after open(), an error occurs" 77 | li t0, 1000000 78 | busywait: 79 | addi t0, -1 80 | bne t0, zero, busywait 81 | 82 | # Load temp buffer address 83 | li s2, 0x801FA000 84 | 85 | # Load header using FileRead 86 | li t1, 0x34 87 | move a0, s1 88 | move a1, s2 89 | li a2, 0x100 90 | jal 0xB0 91 | 92 | # If we did not read the correct amount, lock 93 | bne v0, 0x100, fatalerror 94 | 95 | # Load executable load address and size 96 | lw s3, 0x44(s2) 97 | lw s2, 0x40(s2) 98 | 99 | # Load executable using FileRead 100 | li t1, 0x34 101 | move a0, s1 102 | move a1, s2 103 | move a2, s3 104 | jal 0xB0 105 | 106 | # If we did not read the correct amount, lock 107 | bne v0, s3, fatalerror 108 | 109 | # Paint green 110 | li a0, 0x00FF00 111 | bal paintscr 112 | 113 | # Jump to it! 114 | jr s2 115 | 116 | fatalerror: 117 | # Red 118 | li a0, 0x0000FF 119 | bal paintscr 120 | 121 | lock: 122 | b lock 123 | 124 | paintscr: 125 | # Add command byte to a0 126 | li t0, 0x02000000 127 | or t0, a0 128 | 129 | # Calculate effective address 130 | addi a0, s0, (redscreen - realstart) 131 | 132 | # Store color with command on buffer 133 | sw t0, 0(a0) 134 | 135 | # Tail call GPU_cwp to paint the entire screen 136 | li a1, 3 137 | li t1, 0x4A 138 | j 0xA0 139 | 140 | redscreen: 141 | # Space for color and command 142 | .word 0 143 | # Start X and Y = 0 144 | .word 0x00000000 145 | # Width of 1024, height of 512 146 | .word 0x01FF03FF 147 | 148 | splname: 149 | .asciiz "bu00:BESLEM-99999TONYHAX" 150 | -------------------------------------------------------------------------------- /entrypoints/fix-cb4-checksum.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if [ $# -ne 1 ]; then 6 | echo "Usage: $0 mcs-file" 7 | exit 1 8 | fi 9 | 10 | mcs_file="$1" 11 | 12 | # algorithm is on PAL CB4 at 800AE478 13 | # memory card start: 8006844C size 18E8 14 | 15 | pos=0 16 | checksum=0 17 | while read byte; do 18 | checksum=$(( ($checksum + $byte + $pos) & 0xFFFFFFFF )) 19 | pos=$(( $pos + 1 )) 20 | done <<< $(dd if="$mcs_file" bs=1 count=6376 skip=640 | od -v -An -tu1 -w1) 21 | 22 | checksum=$(printf "%08X" $checksum) 23 | 24 | echo "CB4 checksum: ${checksum}" 25 | 26 | echo -ne "\x${checksum:6:2}\x${checksum:4:2}\x${checksum:2:2}\x${checksum:0:2}" | dd conv=notrunc of="$mcs_file" bs=1 seek=7016 27 | -------------------------------------------------------------------------------- /entrypoints/fix-crash-checksum.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if [ $# -ne 2 ]; then 6 | echo "Usage: $0 mcs-file [eu|us]" 7 | exit 1 8 | fi 9 | 10 | mcs_file="$1" 11 | region="$2" 12 | 13 | case "$region" in 14 | eu2) 15 | dataoffset=$(( 0x180 )) 16 | datalen=$(( 0x2A4 * 4 )) 17 | ;; 18 | eu3) 19 | dataoffset=$(( 0x180 )) 20 | datalen=$(( 0x590 * 4 )) 21 | ;; 22 | us2) 23 | dataoffset=$(( 0x280 )) 24 | datalen=$(( 0x2A4 * 4 )) 25 | ;; 26 | us3) 27 | dataoffset=$(( 0x280 )) 28 | datalen=$(( 0x590 * 4 )) 29 | ;; 30 | *) 31 | echo "Invalid region" 32 | exit 1 33 | esac 34 | 35 | # algorithm is on PAL Crash Bandicoot 2 at 80036D24 36 | # algorithm is on PAL Crash Bandicoot 3 at 80071E08, static memory card buffer at 80072AD8, length 0x590 words 37 | 38 | checksum=$(( 0x12345678 )) 39 | pos=0 40 | while read word; do 41 | if [ $pos -ne 9 ]; then 42 | checksum=$(( ($checksum + $word) & 0xFFFFFFFF )) 43 | fi 44 | pos=$(( $pos + 1 )) 45 | done <<< $(dd if="$mcs_file" bs=1 count=$datalen skip=$dataoffset | od -v -An -tu4 -w4) 46 | 47 | checksum=$(printf "%08X" $checksum) 48 | 49 | echo "Crash checksum: ${checksum}" 50 | 51 | echo -ne "\x${checksum:6:2}\x${checksum:4:2}\x${checksum:2:2}\x${checksum:0:2}" | dd conv=notrunc of="$mcs_file" bs=1 seek=$(( $dataoffset + 9 * 4 )) 52 | -------------------------------------------------------------------------------- /entrypoints/superbike1-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/superbike1-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/superbike2-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/superbike2-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps2-de-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps2-de-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps2-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps2-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps2-fr-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps2-fr-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps2-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps2-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps3-de-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps3-de-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps3-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps3-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps3-fr-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps3-fr-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps3-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps3-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps4-de-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps4-de-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps4-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps4-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps4-fr-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps4-fr-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/thps4-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/thps4-us-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/xsmoto-eu-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/xsmoto-eu-tpl.mcs -------------------------------------------------------------------------------- /entrypoints/xsmoto-us-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/entrypoints/xsmoto-us-tpl.mcs -------------------------------------------------------------------------------- /freepsxboot/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Thanks to whoever made https://devhints.io/makefile! 3 | 4 | include ../variables.mk 5 | 6 | .PHONY: clean FreePSXBoot/builder/builder 7 | 8 | all: $(FREEPSXBOOT_IMAGES) 9 | 10 | clean: 11 | $(RM) $(FREEPSXBOOT_IMAGES) FreePSXBoot/builder/builder 12 | 13 | tonyhax_scph-1001_v2.0_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 14 | FreePSXBoot/builder/builder -bios 2.0-19950507-A -slot 1 -in ../loader/tonyhax.exe -out $@ 15 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 16 | 17 | tonyhax_scph-1001_v2.0_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 18 | FreePSXBoot/builder/builder -bios 2.0-19950507-A -slot 2 -in ../loader/tonyhax.exe -out $@ 19 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 20 | 21 | tonyhax_scph-1001_v2.1_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 22 | FreePSXBoot/builder/builder -bios 2.1-19950717-A -slot 1 -in ../loader/tonyhax.exe -slot 1 -out $@ 23 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 24 | 25 | tonyhax_scph-1001_v2.1_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 26 | FreePSXBoot/builder/builder -bios 2.1-19950717-A -slot 2 -in ../loader/tonyhax.exe -out $@ 27 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 28 | 29 | tonyhax_scph-1001_v2.2_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 30 | FreePSXBoot/builder/builder -bios 2.2-19951204-A -slot 1 -in ../loader/tonyhax.exe -out $@ 31 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 32 | 33 | tonyhax_scph-1001_v2.2_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 34 | FreePSXBoot/builder/builder -bios 2.2-19951204-A -slot 2 -in ../loader/tonyhax.exe -out $@ 35 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 36 | 37 | tonyhax_scph-1002_v2.0_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 38 | FreePSXBoot/builder/builder -bios 2.0-19950510-E -slot 1 -in ../loader/tonyhax.exe -out $@ 39 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 40 | 41 | tonyhax_scph-1002_v2.0_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 42 | FreePSXBoot/builder/builder -bios 2.0-19950510-E -slot 2 -in ../loader/tonyhax.exe -out $@ 43 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 44 | 45 | tonyhax_scph-1002_v2.1_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 46 | FreePSXBoot/builder/builder -bios 2.1-19950717-E -slot 1 -in ../loader/tonyhax.exe -out $@ 47 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 48 | 49 | tonyhax_scph-1002_v2.1_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 50 | FreePSXBoot/builder/builder -bios 2.1-19950717-E -slot 2 -in ../loader/tonyhax.exe -out $@ 51 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 52 | 53 | tonyhax_scph-1002_v2.2_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 54 | FreePSXBoot/builder/builder -bios 2.2-19951204-E -slot 1 -in ../loader/tonyhax.exe -out $@ 55 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 56 | 57 | tonyhax_scph-1002_v2.2_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 58 | FreePSXBoot/builder/builder -bios 2.2-19951204-E -slot 2 -in ../loader/tonyhax.exe -out $@ 59 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 60 | 61 | tonyhax_scph-5001_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 62 | FreePSXBoot/builder/builder -model 5001 -slot 1 -in ../loader/tonyhax.exe -out $@ 63 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 64 | 65 | tonyhax_scph-5001_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 66 | FreePSXBoot/builder/builder -model 5001 -slot 2 -in ../loader/tonyhax.exe -out $@ 67 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 68 | 69 | tonyhax_scph-5501_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 70 | FreePSXBoot/builder/builder -model 5501 -slot 1 -in ../loader/tonyhax.exe -out $@ 71 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 72 | 73 | tonyhax_scph-5501_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 74 | FreePSXBoot/builder/builder -model 5501 -slot 2 -in ../loader/tonyhax.exe -out $@ 75 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 76 | 77 | tonyhax_scph-5502_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 78 | FreePSXBoot/builder/builder -model 5502 -slot 1 -in ../loader/tonyhax.exe -out $@ 79 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 80 | 81 | tonyhax_scph-5502_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 82 | FreePSXBoot/builder/builder -model 5502 -slot 2 -in ../loader/tonyhax.exe -out $@ 83 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 84 | 85 | tonyhax_scph-5552_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 86 | FreePSXBoot/builder/builder -model 5552 -slot 1 -in ../loader/tonyhax.exe -out $@ 87 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 88 | 89 | tonyhax_scph-5552_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 90 | FreePSXBoot/builder/builder -model 5552 -slot 2 -in ../loader/tonyhax.exe -out $@ 91 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 92 | 93 | tonyhax_scph-7001_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 94 | FreePSXBoot/builder/builder -model 7001 -slot 1 -in ../loader/tonyhax.exe -out $@ 95 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 96 | 97 | tonyhax_scph-7001_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 98 | FreePSXBoot/builder/builder -model 7001 -slot 2 -in ../loader/tonyhax.exe -out $@ 99 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 100 | 101 | tonyhax_scph-7002_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 102 | FreePSXBoot/builder/builder -model 7002 -slot 1 -in ../loader/tonyhax.exe -out $@ 103 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 104 | 105 | tonyhax_scph-7002_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 106 | FreePSXBoot/builder/builder -model 7002 -slot 2 -in ../loader/tonyhax.exe -out $@ 107 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 108 | 109 | tonyhax_scph-7501_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 110 | FreePSXBoot/builder/builder -model 7501 -slot 1 -in ../loader/tonyhax.exe -out $@ 111 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 112 | 113 | tonyhax_scph-7501_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 114 | FreePSXBoot/builder/builder -model 7501 -slot 2 -in ../loader/tonyhax.exe -out $@ 115 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 116 | 117 | tonyhax_scph-7502_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 118 | FreePSXBoot/builder/builder -model 7502 -slot 1 -in ../loader/tonyhax.exe -out $@ 119 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 120 | 121 | tonyhax_scph-7502_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 122 | FreePSXBoot/builder/builder -model 7502 -slot 2 -in ../loader/tonyhax.exe -out $@ 123 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 124 | 125 | tonyhax_scph-9001_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 126 | FreePSXBoot/builder/builder -model 9001 -slot 1 -in ../loader/tonyhax.exe -out $@ 127 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 128 | 129 | tonyhax_scph-9001_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 130 | FreePSXBoot/builder/builder -model 9001 -slot 2 -in ../loader/tonyhax.exe -out $@ 131 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 132 | 133 | tonyhax_scph-9002_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 134 | FreePSXBoot/builder/builder -model 9002 -slot 1 -in ../loader/tonyhax.exe -out $@ 135 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 136 | 137 | tonyhax_scph-9002_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 138 | FreePSXBoot/builder/builder -model 9002 -slot 2 -in ../loader/tonyhax.exe -out $@ 139 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 140 | 141 | tonyhax_scph-101_v4.4_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 142 | FreePSXBoot/builder/builder -bios 4.4-20000324-A -slot 1 -in ../loader/tonyhax.exe -out $@ 143 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 144 | 145 | tonyhax_scph-101_v4.4_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 146 | FreePSXBoot/builder/builder -bios 4.4-20000324-A -slot 2 -in ../loader/tonyhax.exe -out $@ 147 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 148 | 149 | tonyhax_scph-101_v4.5_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 150 | FreePSXBoot/builder/builder -bios 4.5-20000525-E -slot 1 -in ../loader/tonyhax.exe -out $@ 151 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 152 | 153 | tonyhax_scph-101_v4.5_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 154 | FreePSXBoot/builder/builder -bios 4.5-20000525-E -slot 2 -in ../loader/tonyhax.exe -out $@ 155 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 156 | 157 | tonyhax_scph-102_v4.4_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 158 | FreePSXBoot/builder/builder -bios 4.4-20000324-A -slot 1 -in ../loader/tonyhax.exe -out $@ 159 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 160 | 161 | tonyhax_scph-102_v4.4_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 162 | FreePSXBoot/builder/builder -bios 4.4-20000324-A -slot 2 -in ../loader/tonyhax.exe -out $@ 163 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 164 | 165 | tonyhax_scph-102_v4.5_slot1.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 166 | FreePSXBoot/builder/builder -bios 4.5-20000525-E -slot 1 -in ../loader/tonyhax.exe -out $@ 167 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 168 | 169 | tonyhax_scph-102_v4.5_slot2.mcd: ../loader/tonyhax.exe | FreePSXBoot/builder/builder 170 | FreePSXBoot/builder/builder -bios 4.5-20000525-E -slot 2 -in ../loader/tonyhax.exe -out $@ 171 | echo -n "FPBZ" | dd status=none conv=notrunc of=$@ bs=1 seek=124 172 | 173 | FreePSXBoot/builder/builder: 174 | make -C FreePSXBoot/builder all 175 | -------------------------------------------------------------------------------- /loader/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Thanks to whoever made https://devhints.io/makefile! 3 | 4 | include ../variables.mk 5 | 6 | LOADER_AUTOGEN := orca.inc 7 | LOADER_HEADERS := $(wildcard *.h) $(LOADER_AUTOGEN) 8 | LOADER_OBJECTS := $(patsubst %.c, %.o, $(patsubst %.S, %.o, $(wildcard *.c *.S))) 9 | 10 | all: $(LOADER_FILES) 11 | 12 | clean: 13 | $(RM) $(LOADER_FILES) *.o *.elf $(LOADER_AUTOGEN) 14 | 15 | # Intermediate objects 16 | 17 | %.o: %.c $(LOADER_HEADERS) 18 | $(CC) $(CFLAGS) -c $< 19 | 20 | %.o: %.S $(LOADER_HEADERS) 21 | $(CC) $(CFLAGS) -c $< 22 | 23 | orca.inc: orca.img 24 | bash ../util/bin2h.sh ORCA_IMAGE orca.img orca.inc 25 | 26 | secondary.elf: secondary.ld $(LOADER_OBJECTS) 27 | $(LD) $(LDFLAGS) -T secondary.ld $(LOADER_OBJECTS) -o $@ 28 | bash insert-tonyhax-crc.sh secondary.elf 29 | 30 | # Results 31 | 32 | tonyhax.mcs: tonyhax-tpl.mcs secondary.elf 33 | bash generate-tonyhax-mcs.sh secondary.elf tonyhax-tpl.mcs tonyhax.mcs $(TONYHAX_VERSION) 34 | 35 | BESLEM-99999TONYHAX: tonyhax.mcs 36 | bash ../util/mcs2raw.sh tonyhax.mcs 37 | 38 | tonyhax.exe: secondary.elf 39 | bash generate-tonyhax-exe.sh secondary.elf tonyhax.exe 40 | -------------------------------------------------------------------------------- /loader/audio.c: -------------------------------------------------------------------------------- 1 | 2 | #include "audio.h" 3 | #include "io.h" 4 | 5 | #define SPU_MAIN_VOL_LEFT _REG16(0x1F801D80) 6 | #define SPU_MAIN_VOL_RIGHT _REG16(0x1F801D82) 7 | 8 | #define SPU_REVERB_VOL_LEFT _REG16(0x1F801D84) 9 | #define SPU_REVERB_VOL_RIGHT _REG16(0x1F801D86) 10 | 11 | struct voice_t { 12 | uint32_t volume; 13 | uint16_t sample_rate; 14 | uint16_t start_address; 15 | uint32_t adsr_settings; 16 | uint16_t adsr_current_volume; 17 | uint16_t adsr_repeat_address; 18 | }; 19 | 20 | struct voice_t * const SPU_VOICES = (struct voice_t *) 0x1F801C00; 21 | #define SPU_VOICE_COUNT 24 22 | 23 | void audio_halt(void) { 24 | // Mute SPU 25 | SPU_MAIN_VOL_LEFT = 0; 26 | SPU_MAIN_VOL_RIGHT = 0; 27 | SPU_REVERB_VOL_LEFT = 0; 28 | SPU_REVERB_VOL_RIGHT = 0; 29 | 30 | // Halt the playback of every voice 31 | for (int i = 0; i < SPU_VOICE_COUNT; i++) { 32 | SPU_VOICES[i].volume = 0; 33 | SPU_VOICES[i].sample_rate = 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /loader/audio.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | /** 5 | * Halts the playback of all audio channels. 6 | */ 7 | void audio_halt(void); 8 | -------------------------------------------------------------------------------- /loader/bios-asm.S: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | .text 5 | .align 4 6 | 7 | ########## 8 | # EXTRAS # 9 | ########## 10 | 11 | .global FakeEnqueueCdIntr 12 | FakeEnqueueCdIntr: 13 | lw ra, 0x14(sp) 14 | addi sp, 0x18 15 | jr ra 16 | 17 | ############ 18 | # SYSCALLS # 19 | ############ 20 | 21 | .global EnterCriticalSection 22 | EnterCriticalSection: 23 | li a0, 0x01 24 | syscall 25 | jr ra 26 | 27 | .global ExitCriticalSection 28 | ExitCriticalSection: 29 | li a0, 0x02 30 | syscall 31 | jr ra 32 | 33 | ############### 34 | # A-FUNCTIONS # 35 | ############### 36 | 37 | .global todigit 38 | todigit: 39 | li t1, 0x0A 40 | j 0xA0 41 | 42 | .global strcmp 43 | strcmp: 44 | li t1, 0x17 45 | j 0xA0 46 | 47 | .global strncmp 48 | strncmp: 49 | li t1, 0x18 50 | j 0xA0 51 | 52 | .global strcpy 53 | strcpy: 54 | li t1, 0x19 55 | j 0xA0 56 | 57 | .global strlen 58 | strlen: 59 | li t1, 0x1B 60 | j 0xA0 61 | 62 | .global strchr 63 | strchr: 64 | li t1, 0x1E 65 | j 0xA0 66 | 67 | .global memcpy 68 | memcpy: 69 | li t1, 0x2A 70 | j 0xA0 71 | 72 | .global std_out_puts 73 | std_out_puts: 74 | li t1, 0x3E 75 | j 0xA0 76 | 77 | .global DoExecute 78 | DoExecute: 79 | # Pepsiman (J) crashes if s5 is not zero 80 | # The BIOS leaves them s1-s6 zeroed, so we'll do the same 81 | li s1, 0 82 | li s2, 0 83 | li s3, 0 84 | li s4, 0 85 | li s5, 0 86 | li s6, 0 87 | li t1, 0x43 88 | j 0xA0 89 | 90 | .global FlushCache 91 | FlushCache: 92 | li t1, 0x44 93 | j 0xA0 94 | 95 | .global init_a0_b0_c0_vectors 96 | init_a0_b0_c0_vectors: 97 | li t1, 0x45 98 | j 0xA0 99 | 100 | .global GPU_dw 101 | GPU_dw: 102 | li t1, 0x46 103 | j 0xA0 104 | 105 | .global SendGP1Command 106 | SendGP1Command: 107 | li t1, 0x48 108 | j 0xA0 109 | 110 | .global GPU_cw 111 | GPU_cw: 112 | li t1, 0x49 113 | j 0xA0 114 | 115 | .global GPU_cwp 116 | GPU_cwp: 117 | li t1, 0x4A 118 | j 0xA0 119 | 120 | .global LoadAndExecute 121 | LoadAndExecute: 122 | li t1, 0x51 123 | j 0xA0 124 | 125 | .global CdInit 126 | CdInit: 127 | li t1, 0x54 128 | j 0xA0 129 | 130 | .global SetConf 131 | SetConf: 132 | li t1, 0x9C 133 | j 0xA0 134 | 135 | .global SetMemSize 136 | SetMemSize: 137 | li t1, 0x9F 138 | j 0xA0 139 | 140 | .global CdReadSector 141 | CdReadSector: 142 | li t1, 0xA5 143 | j 0xA0 144 | 145 | ############### 146 | # B-FUNCTIONS # 147 | ############### 148 | 149 | .global SetDefaultExitFromException 150 | SetDefaultExitFromException: 151 | li t1, 0x18 152 | j 0xB0 153 | 154 | .global FileOpen 155 | FileOpen: 156 | li t1, 0x32 157 | j 0xB0 158 | 159 | .global FileRead 160 | FileRead: 161 | li t1, 0x34 162 | j 0xB0 163 | 164 | .global FileClose 165 | FileClose: 166 | li t1, 0x36 167 | j 0xB0 168 | 169 | .global GetLastError 170 | GetLastError: 171 | li t1, 0x54 172 | j 0xB0 173 | 174 | .global GetC0Table 175 | GetC0Table: 176 | li t1, 0x56 177 | j 0xB0 178 | 179 | .global GetB0Table 180 | GetB0Table: 181 | li t1, 0x57 182 | j 0xB0 183 | 184 | ############### 185 | # C-FUNCTIONS # 186 | ############### 187 | 188 | .global InstallExceptionHandlers 189 | InstallExceptionHandlers: 190 | li t1, 0x07 191 | j 0xC0 192 | 193 | .global InstallDevices 194 | InstallDevices: 195 | li t1, 0x12 196 | j 0xC0 197 | 198 | .global AdjustA0Table 199 | AdjustA0Table: 200 | li t1, 0x1C 201 | j 0xC0 202 | -------------------------------------------------------------------------------- /loader/bios.c: -------------------------------------------------------------------------------- 1 | 2 | #include "bios.h" 3 | #include "io.h" 4 | #include "util.h" 5 | #include "debugscreen.h" 6 | #include "str.h" 7 | 8 | void * original_disc_error; 9 | 10 | bool console_has_tty() { 11 | /* 12 | * Check if the console has a SCN2681 TTY used for debug by writing data to the control 13 | * registers and reading it back. 14 | * 15 | * The control is 16 bit wide, and is accessed by writing or reading twice the same register. 16 | */ 17 | volatile uint8_t * scn2681modereg = (uint8_t *) 0x1F802020; 18 | 19 | *scn2681modereg = 0x55; 20 | *scn2681modereg = 0xAA; 21 | return *scn2681modereg == 0x55 && *scn2681modereg == 0xAA; 22 | } 23 | 24 | void bios_reinitialize() { 25 | // Disable interrupts 26 | EnterCriticalSection(); 27 | 28 | // Clear kernel heap space. Not really needed but nice for debugging. 29 | bzero((void *) 0xA000E000, 0x2000); 30 | 31 | // The following is adapted from the WarmBoot call 32 | 33 | // Copy the relocatable kernel chunk 34 | bios_copy_relocated_kernel(); 35 | 36 | // Reinitialize the A table 37 | bios_copy_a0_table(); 38 | 39 | // Restore A, B and C tables 40 | init_a0_b0_c0_vectors(); 41 | 42 | // Fix A table 43 | AdjustA0Table(); 44 | 45 | // Install default exception handlers 46 | InstallExceptionHandlers(); 47 | 48 | // Restore default exception return function 49 | SetDefaultExitFromException(); 50 | 51 | // Clear interrupts and mask 52 | I_STAT = 0; 53 | I_MASK = 0; 54 | 55 | // Setup devices. 56 | InstallDevices(console_has_tty()); 57 | 58 | /* 59 | * Configure with default values 60 | * 61 | * SetConf call does: 62 | * - Configure the system memory (via SysInitMemory) 63 | * - Initialize the exception handler arrays 64 | * - Enqueues the syscall handler (via EnqueueSyscallHandler) 65 | * - Initializes the default interrupt (via InitDefInt) 66 | * - Allocates the event handler array 67 | * - Allocates the thread structure 68 | * - Enqueues the timer and VBlank handler (via EnqueueTimerAndVblankIrqs) 69 | * - Calls a function that re-configures the CD subsystem as follows: 70 | * - Enqueues the CD interrupt (via EnqueueCdIntr) 71 | * - Opens shit-ton of CD-related events (via OpenEvent) 72 | * - Enables the CD-related events (via EnableEvent) 73 | * - Re-enables interrupts (via ExitCriticalSection) 74 | * 75 | * This call is to be used after the CdInit has happened, once we've read the SYSTEM.CNF 76 | * file and we want to reconfigure the kernel before launching the game's executable. 77 | * 78 | * However, for the purpose of reinitializing the BIOS, the CD reinitialization procedure is 79 | * problematic, as CdInit (which we'll call later once the disc is swapped) calls this very 80 | * same function, resulting in the CD interrupt being added twice to the array of handlers, 81 | * as wells as events being opened twice. 82 | * 83 | * We can't patch this code because it's stored in ROM. Instead, before calling this function 84 | * we'll replace the EnqueueCdIntr with a fake version that patches the system state to return 85 | * earlier and avoid the CD reinitialization entirely. 86 | */ 87 | void * realEnqueueCdIntr = BIOS_A0_TABLE[0xA2]; 88 | BIOS_A0_TABLE[0xA2] = FakeEnqueueCdIntr; 89 | SetConf(BIOS_DEFAULT_EVCB, BIOS_DEFAULT_TCB, BIOS_DEFAULT_STACKTOP); 90 | BIOS_A0_TABLE[0xA2] = realEnqueueCdIntr; 91 | 92 | // End of code adapted 93 | 94 | /* 95 | * Set RAM size to 8MB, which is incorrect but it's what the BIOS sets. 96 | * 97 | * This is required because the entrypoint game might've set it to 2MB, and a bugged target 98 | * game might accidentally access an address in the mirror region, causing a fault to be caused 99 | * in real hardware. 100 | */ 101 | SetMemSize(8); 102 | 103 | // Re-enable interrupts 104 | ExitCriticalSection(); 105 | 106 | // Save for later 107 | original_disc_error = BIOS_A0_TABLE[0xA1]; 108 | } 109 | 110 | bool bios_is_ps1(void) { 111 | /* 112 | * All PS2, from firmware 1.00 to 2.50 have a version "System ROM Version 5.0" followed by 113 | * a varying date and region code. 114 | * 115 | * To see if we're running on a PS1, we'll just check that the version is not 5.0. 116 | */ 117 | return strncmp(BIOS_VERSION + 0x13, "5.0", 3) != 0; 118 | } 119 | 120 | bool bios_is_european(void) { 121 | return BIOS_VERSION[0x20] == 'E'; 122 | } 123 | 124 | void * parse_warmboot_jal(uint32_t opcode) { 125 | const uint32_t * warmboot_start = (const uint32_t *) BIOS_A0_TABLE[0xA0]; 126 | uint32_t prefix = (uint32_t) warmboot_start & 0xF0000000; 127 | 128 | uint32_t * jal = (uint32_t *) (warmboot_start + opcode); 129 | uint32_t suffix = (*jal & 0x3FFFFFF) << 2; 130 | 131 | return (void *) (prefix | suffix); 132 | } 133 | 134 | void bios_copy_relocated_kernel(void) { 135 | /* 136 | * This function indirectly calls the BIOS function that copies the relocated kernel code to 137 | * 0x500. 138 | * 139 | * The 12th opcode of WarmBoot is a "jal" to this function for all BIOS I've checked, 140 | * including the PS2 consoles in PS1 mode. 141 | */ 142 | 143 | void * address = parse_warmboot_jal(12); 144 | ((void (*)(void)) address)(); 145 | } 146 | 147 | void bios_copy_a0_table(void) { 148 | /* 149 | * This function indirectly calls the BIOS function that copies the A0 table to 0x200. 150 | * 151 | * As with the kernel relocation function, the 14th opcode of WarmBoot is a "jal" to this 152 | * function. 153 | */ 154 | 155 | void * address = parse_warmboot_jal(14); 156 | ((void (*)(void)) address)(); 157 | } 158 | 159 | handler_info_t * bios_get_syscall_handler(void) { 160 | /* 161 | * This function extracts the pointer to the syscall handler by analyzing the opcodes of the 162 | * EnqueueSyscallHandler function. 163 | */ 164 | uint32_t * func_start = (uint32_t *) GetC0Table()[1]; 165 | 166 | // The fourth instruction is a one opcode "la a1, ADDR". Extract it from there. 167 | return (handler_info_t *) (func_start[4] & 0xFFFF); 168 | } 169 | 170 | void logging_disc_error_handler(char type, int errorcode) { 171 | debug_write("Disc error type %c code %d", type, errorcode); 172 | } 173 | 174 | void bios_inject_disc_error(void) { 175 | BIOS_A0_TABLE[0xA1] = logging_disc_error_handler; 176 | } 177 | 178 | void bios_restore_disc_error(void) { 179 | BIOS_A0_TABLE[0xA1] = original_disc_error; 180 | } 181 | -------------------------------------------------------------------------------- /loader/bios.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | 6 | typedef struct exe_header exe_header_t; 7 | typedef struct exe_offsets exe_offsets_t; 8 | typedef struct handler_info handler_info_t; 9 | typedef struct file_control_block file_control_block_t; 10 | 11 | struct exe_offsets { 12 | void * initial_pc; // 0x00 13 | void * initial_gp; // 0x04 14 | void * load_addr; // 0x08 15 | uint32_t load_size; // 0x0C 16 | uint32_t _reserved0[2]; // 0x10 17 | void * memfill_start; // 0x18 18 | uint32_t memfill_size; // 0x1C 19 | void * initial_sp_base; // 0x20 20 | uint32_t initial_sp_offset; // 0x24 21 | }; 22 | 23 | struct exe_header { 24 | char signature[8]; // 0x00 25 | uint8_t _reserved0[8]; // 0x08 26 | exe_offsets_t offsets; // 0x10 27 | }; 28 | 29 | struct handler_info { 30 | handler_info_t * next; 31 | void (*handler)(int); 32 | int (*verifier)(void); 33 | uint32_t dummy; 34 | }; 35 | 36 | struct file_control_block { 37 | uint32_t status; 38 | uint32_t media_id; 39 | uint32_t transfer_addr; 40 | uint32_t transfer_length; 41 | uint32_t cur_pos; 42 | uint32_t dev_flags; 43 | uint32_t last_error; 44 | void * dcb; 45 | uint32_t size; 46 | uint32_t start_lba; 47 | uint32_t fcb_id; 48 | }; 49 | 50 | #define BIOS_DEFAULT_EVCB 0x10 51 | #define BIOS_DEFAULT_TCB 0x4 52 | #define BIOS_DEFAULT_STACKTOP ((void *) 0x801FFF00) 53 | 54 | /* 55 | * EXTRAS. 56 | */ 57 | 58 | /** 59 | * BIOS version string. Not available on SCPH-1000 consoles. 60 | * 61 | * Example: "System ROM Version 4.1 12/16/97 E" 62 | */ 63 | static const char * const BIOS_VERSION = (const char *) 0xBFC7FF32; 64 | 65 | /** 66 | * A0-table location. 67 | */ 68 | static void ** const BIOS_A0_TABLE = (void **) 0x200; 69 | 70 | /** 71 | * FCB location. 72 | */ 73 | static file_control_block_t ** const BIOS_FCBS = (file_control_block_t **) 0x140; 74 | 75 | /** 76 | * Executes a full reset of the console's BIOS, as if a WarmBoot was issued. 77 | */ 78 | void bios_reinitialize(void); 79 | 80 | /** 81 | * Returns true if the console is running an original PS1 BIOS. 82 | * 83 | * @returns true if console is a PS1. 84 | */ 85 | bool bios_is_ps1(void); 86 | 87 | /** 88 | * Returns true if the console has an European BIOS. 89 | * @returns true if console is European 90 | */ 91 | bool bios_is_european(void); 92 | 93 | /** 94 | * Copies the relocated kernel code to its destination (0x500). 95 | */ 96 | void bios_copy_relocated_kernel(void); 97 | 98 | /** 99 | * Copies the A call table to its destination (0x200). 100 | */ 101 | void bios_copy_a0_table(void); 102 | 103 | /** 104 | * Returns a pointer to syscall handler information structure. 105 | * @returns pointer to the mutable structure. 106 | */ 107 | handler_info_t * bios_get_syscall_handler(void); 108 | 109 | /** 110 | * Replaces the disc error handler with one that logs the code to the screen and returns. 111 | */ 112 | void bios_inject_disc_error(void); 113 | 114 | /** 115 | * Restores the disc error handler. 116 | */ 117 | void bios_restore_disc_error(void); 118 | 119 | /** 120 | * Fake version of the EnqueueCdIntr call that we'll use to skip the CD reinitialization in the 121 | * SetConf call during the BIOS reinitialization. 122 | */ 123 | void FakeEnqueueCdIntr(void); 124 | 125 | /* 126 | * SYSCALLS 127 | */ 128 | 129 | /** 130 | * Enter critical section. 131 | * 132 | * Disables interrupts so code executes atomically. 133 | */ 134 | void EnterCriticalSection(); 135 | 136 | /** 137 | * Exit critical section. 138 | * 139 | * Re-enables interrupts. 140 | */ 141 | void ExitCriticalSection(); 142 | 143 | /* 144 | * A-FUNCTIONS 145 | */ 146 | 147 | /** 148 | * File for read access. 149 | */ 150 | #define FILE_READ 0x00000001 151 | 152 | /** 153 | * File for write access. 154 | */ 155 | #define FILE_WRITE 0x00000002 156 | 157 | /** 158 | * When reading from TTY, returns without waiting to read requested amount of bytes. 159 | */ 160 | #define FILE_TTY_LAZY 0x00000004 161 | 162 | /** 163 | * Create a new file. 164 | */ 165 | #define FILE_CREATE 0x00000200 166 | 167 | /** 168 | * Read asynchronously from memory card. 169 | */ 170 | #define FILE_MC_ASYNC 0x00008000 171 | 172 | /** 173 | * Specify size of file in blocks for memory card. 174 | */ 175 | #define FILE_SIZE(blocks) ((blocks) << 16) 176 | 177 | /** 178 | * No error. 179 | */ 180 | #define FILEERR_OK 0x00 181 | 182 | /** 183 | * File not found 184 | */ 185 | #define FILEERR_NOT_FOUND 0x02 186 | 187 | /** 188 | * Bad device port number (tty2 and up) 189 | */ 190 | #define FILEERR_BAD_DEVICE 0x06 191 | 192 | /** 193 | * Invalid or unused file handle 194 | */ 195 | #define FILEERR_UNUSED_HANDLE 0x09 196 | 197 | /** 198 | * General error (physical I/O error, unformatted, disk changed for old fcb) 199 | */ 200 | #define FILEERR_GENERAL 0x10 201 | 202 | /** 203 | * File already exists 204 | */ 205 | #define FILEERR_ALREADY_EXISTS 0x11 206 | 207 | /** 208 | * Cross device rename 209 | */ 210 | #define FILEERR_CROSS_DEV_RENAME 0x12 211 | 212 | /** 213 | * Unknown device name 214 | */ 215 | #define FILEERR_UNKNOWN_DEV 0x13 216 | 217 | /** 218 | * Alignment error 219 | */ 220 | #define FILEERR_ALIGN 0x16 221 | 222 | /** 223 | * Too many open files 224 | */ 225 | #define FILEERR_TOO_MANY_HANDLES 0x18 226 | 227 | /** 228 | * Device full 229 | */ 230 | #define FILEERR_DEV_FULL 0x1C 231 | 232 | /** 233 | * Converts a digit to its numeric value: 234 | * 235 | * - '0' to '9' returns 0 to 9 236 | * - 'A' to 'Z' returns 10 to 35 237 | * - 'a' to 'z' returns 10 to 35 as well 238 | * 239 | * For other values, this returns 9,999,999 in decimal. 240 | * 241 | * Table A, call 0x0A. 242 | * 243 | * @param c the character 244 | * @returns parsed digit 245 | */ 246 | uint32_t todigit(char c); 247 | 248 | /** 249 | * Prints a text through the TTY. 250 | * 251 | * @param text text to print 252 | */ 253 | void std_out_puts(const char * text); 254 | 255 | /** 256 | * Starts a previously loaded executable. 257 | * 258 | * @param headerbuf executable offsets in header 259 | * @param param1 first argument sent to the executable 260 | * @param param2 second argument sent to the executable 261 | * 262 | * Table A, call 0x43. 263 | */ 264 | void __attribute__((noreturn)) DoExecute(const exe_offsets_t * offsets, uint32_t param1, uint32_t param2); 265 | 266 | /** 267 | * Flushes the CPU cache. Should be called after modifying code in software. 268 | * 269 | * Table A, call 0x44. 270 | */ 271 | void FlushCache(void); 272 | 273 | /** 274 | * Copies the three default four-opcode handlers for the A(NNh),B(NNh),C(NNh) functions to A00000A0h..A00000CFh. 275 | * 276 | * Table A, call 0x45. 277 | */ 278 | void init_a0_b0_c0_vectors(void); 279 | 280 | /** 281 | * Copies the width*height 16-bit half words to Vram at x, y coords. 282 | * 283 | * width*height MUST be an even number, or else it'll lock up. 284 | * 285 | * @param x Vram coord X 286 | * @param y Vram coord Y 287 | * @param width texture width, in 16-bit pixels 288 | * @param height texture height, in lines 289 | * @param src texture data 290 | * 291 | * Table A, call 0x46. 292 | */ 293 | void GPU_dw(uint32_t x, uint32_t y, uint32_t width, uint32_t height, const uint16_t * src); 294 | 295 | /** 296 | * Executes the given GPU GP1 command. 297 | * 298 | * @param gp1cmd the command 299 | * 300 | * Table A, call 0x48. 301 | */ 302 | void SendGP1Command(uint32_t gp1cmd); 303 | 304 | /** 305 | * Synchronizes with the GPU and executes the given GPU GP0 command. 306 | * 307 | * @param gp0cmd the command 308 | * @returns 0 if succeeded, or -1 otherwise. 309 | * 310 | * Table A, call 0x49. 311 | */ 312 | int GPU_cw(uint32_t gp0cmd); 313 | 314 | /** 315 | * Synchronizes with the GPU and sends the buffer to the GPU. 316 | * 317 | * @param src command + parameter list 318 | * @param num number of words in list 319 | * 320 | * Table A, call 0x4A. 321 | */ 322 | void GPU_cwp(uint32_t * src, uint32_t num); 323 | 324 | /** 325 | * Loads and executes the given file. 326 | * 327 | * @param filename executable path 328 | * @param stackbase stack pointer base 329 | * @param stackoffset stack pointer offset 330 | * 331 | * Table A, call 0x51. 332 | */ 333 | bool LoadAndExecute(const char * filename, void * stack_base, uint32_t stack_offset); 334 | 335 | /** 336 | * Initializes the CD drive and filesystem. 337 | * 338 | * @returns true if succeeded, else false 339 | * 340 | * Table A, call 0x54. 341 | */ 342 | bool CdInit(void); 343 | 344 | /** 345 | * (Re-)initializes kernel resources. 346 | * 347 | * @param evcb max number of events 348 | * @param tcb max number of threads 349 | * @param stacktop stack top 350 | * 351 | * Table A, call 0x9C. 352 | */ 353 | void SetConf(uint32_t evcb, uint32_t tcb, void * stacktop); 354 | 355 | /** 356 | * Configured the RAM size. 357 | * 358 | * @param size memory size in megabytes, either 2 or 8. 359 | * 360 | * Table A, call 0x9F. 361 | */ 362 | void SetMemSize(uint32_t size); 363 | 364 | /** 365 | * Reads the requested amount of sectors from the CD. 366 | * 367 | * @param sector_count sector count 368 | * @param start_sector first sector to read 369 | * @param buffer destination buffer 370 | * @returns the amount of sectors read, or -1 on error 371 | * 372 | * Table A, call 0xA5. 373 | */ 374 | int32_t CdReadSector(uint32_t sector_count, uint32_t start_sector, void * buffer); 375 | 376 | /* 377 | * B-FUNCTIONS 378 | */ 379 | 380 | /** 381 | * Restores the default exception exit handler. 382 | * 383 | * Table B, call 0x18. 384 | */ 385 | void SetDefaultExitFromException(void); 386 | 387 | /** 388 | * Opens a file on the target device for I/O. 389 | * 390 | * Table B, call 0x32. 391 | * 392 | * @param path file path 393 | * @param access access flags 394 | * @returns file handle, or -1 on error. 395 | */ 396 | int32_t FileOpen(const char * filename, uint32_t accessmode); 397 | 398 | /** 399 | * Reads the number of bytes from the specified open file. 400 | * 401 | * Must be a multiple of 128 bytes for memory card, and 2048 for CD-ROM. 402 | * 403 | * Table B, call 0x34. 404 | * 405 | * @param fd file handle 406 | * @param dst data buffer 407 | * @param length max data length 408 | * @returns number of bytes read 409 | */ 410 | int32_t FileRead(int32_t fd, void * dst, uint32_t length); 411 | 412 | /** 413 | * Closes an open file. 414 | * 415 | * Table B, call 0x36. 416 | * 417 | * @param fd file handle 418 | */ 419 | void FileClose(int32_t fd); 420 | 421 | /** 422 | * Returns the error code for the last failed file operation. 423 | * 424 | * @returns the error code for the last failed file operation. 425 | * 426 | * Table B, call 0x54. 427 | */ 428 | uint32_t GetLastError(void); 429 | 430 | /** 431 | * Returns the address of the C call table. 432 | * 433 | * @returns the address of the C call table. 434 | * 435 | * Table B, call 0x56. 436 | */ 437 | void ** GetC0Table(void); 438 | 439 | /** 440 | * Returns the address of the B call table. 441 | * 442 | * @returns the address of the B call table. 443 | * 444 | * Table B, call 0x57. 445 | */ 446 | void ** GetB0Table(void); 447 | 448 | /* 449 | * C-FUNCTIONS 450 | */ 451 | 452 | /** 453 | * Copies the default four-opcode exception handler to the exception vector at 0x80000080h~0x8000008F. 454 | * 455 | * Table C, call 0x07. 456 | */ 457 | void InstallExceptionHandlers(void); 458 | 459 | /** 460 | * Initializes the default device drivers for the TTY, CDROM and memory cards. 461 | * 462 | * The flags controls whether the TTY device should be a dummy (retail console) or an actual UART (dev console). 463 | * Note this will call will freeze if the UART is enabled but there is no such device. 464 | * 465 | * @param enable_tty 0 to use a dummy TTY, 1 to use a real UART. 466 | * 467 | * Table C, call 0x12. 468 | */ 469 | void InstallDevices(uint32_t enable_tty); 470 | 471 | /** 472 | * Fixes the A call table, copying missing entries from the C table. 473 | * 474 | * Table C, call 0x1C. 475 | */ 476 | void AdjustA0Table(void); 477 | -------------------------------------------------------------------------------- /loader/cdrom.c: -------------------------------------------------------------------------------- 1 | 2 | #include "cdrom.h" 3 | #include "bios.h" 4 | #include 5 | 6 | volatile uint8_t * const CD_REGS = (volatile uint8_t *) 0x1F801800; 7 | 8 | inline void cd_set_page(uint8_t page) { 9 | CD_REGS[0] = page; 10 | } 11 | 12 | void cd_command(uint_fast8_t cmd, const uint8_t * params, uint_fast8_t params_len) { 13 | 14 | // Wait for previous command to finish, if any 15 | while (CD_REGS[0] & 0x80); 16 | 17 | // Switch to page 0 18 | cd_set_page(0); 19 | 20 | // Clear read and write FIFOs 21 | CD_REGS[3] = 0xC0; 22 | 23 | // Copy request 24 | while (params_len != 0) { 25 | CD_REGS[2] = *params; 26 | params++; 27 | params_len--; 28 | } 29 | 30 | // Switch to page 1 31 | cd_set_page(1); 32 | 33 | // Disable interrupts as we'll poll 34 | CD_REGS[2] = 0x00; 35 | 36 | // Acknowledge interrupts, if there were any 37 | CD_REGS[3] = 0x07; 38 | 39 | // Switch to page 0 40 | cd_set_page(0); 41 | 42 | // Finally write command to start 43 | CD_REGS[1] = cmd; 44 | } 45 | 46 | uint_fast8_t cd_wait_int(void) { 47 | 48 | // Wait for command to finish, if any 49 | while (CD_REGS[0] & 0x80); 50 | 51 | // Switch to page 1 52 | cd_set_page(1); 53 | 54 | // Wait until an interrupt happens (int != 0) 55 | uint_fast8_t interrupt; 56 | do { 57 | interrupt = CD_REGS[3] & 0x07; 58 | } while (interrupt == 0); 59 | 60 | // Acknowledge it 61 | CD_REGS[3] = 0x07; 62 | 63 | // Return it 64 | return interrupt; 65 | } 66 | 67 | uint_fast8_t cd_read_reply(uint8_t * reply_buffer) { 68 | 69 | // Switch to page 1 70 | cd_set_page(1); 71 | 72 | // Read reply 73 | uint_fast8_t len = 0; 74 | while (CD_REGS[0] & 0x20) { 75 | *reply_buffer = CD_REGS[1]; 76 | reply_buffer++; 77 | len++; 78 | } 79 | 80 | // Return length 81 | return len; 82 | } 83 | 84 | bool cd_drive_init() { 85 | cd_command(CD_CMD_INIT, NULL, 0); 86 | 87 | // Should succeed with 3 88 | if (cd_wait_int() != 3) { 89 | return false; 90 | } 91 | 92 | // Should then return a 2 93 | if (cd_wait_int() != 2) { 94 | return false; 95 | } 96 | 97 | return true; 98 | } 99 | 100 | bool cd_drive_reset() { 101 | // Issue a reset 102 | cd_command(CD_CMD_RESET, NULL, 0); 103 | 104 | // Should succeed with 3 105 | if (cd_wait_int() != 3) { 106 | return false; 107 | } 108 | 109 | // Need to wait for some cycles before it springs back to life 110 | for (int i = 0; i < 0x400000; i++); 111 | 112 | return true; 113 | } 114 | -------------------------------------------------------------------------------- /loader/cdrom.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | 6 | #define CD_CMD_GETSTAT 0x01 7 | #define CD_CMD_INIT 0x0A 8 | #define CD_CMD_TEST 0x19 9 | #define CD_CMD_RESET 0x1C 10 | #define CD_TEST_REGION 0x22 11 | 12 | /** 13 | * Starts executing a CD command. 14 | * 15 | * @param cmd the command 16 | * @param params the param buffer, or NULL if length is zero 17 | * @param params_len parameter length in bytes 18 | */ 19 | void cd_command(uint_fast8_t cmd, const uint8_t * params, uint_fast8_t params_len); 20 | 21 | /** 22 | * Waits for an interrupt to happen, and returns its code. 23 | * 24 | * @returns interrupt code 25 | */ 26 | uint_fast8_t cd_wait_int(void); 27 | 28 | /** 29 | * Reads a reply from the controller after an interrupt has happened. 30 | * 31 | * @param reply reply buffer 32 | * @returns reply length 33 | */ 34 | uint_fast8_t cd_read_reply(uint8_t * reply_buffer); 35 | 36 | /** 37 | * Reinitializes the CD drive. 38 | * 39 | * @returns true if succeded, or false otherwise. 40 | */ 41 | bool cd_drive_init(void); 42 | 43 | /** 44 | * Resets the drive. 45 | * 46 | * @returns true if succeded, or false otherwise. 47 | */ 48 | bool cd_drive_reset(void); 49 | -------------------------------------------------------------------------------- /loader/cfgparse.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "str.h" 5 | #include "str.h" 6 | #include "bios.h" 7 | #include "debugscreen.h" 8 | 9 | const char * find_wanted(const char * config, const char * wanted) { 10 | uint32_t wanted_len = strlen(wanted); 11 | 12 | // While first N characters don't match 13 | while (strncmp(config, wanted, wanted_len) != 0) { 14 | // No luck. Advance until next line. 15 | config = strchr(config, '\n'); 16 | 17 | // If this is the last line, abort. 18 | if (config == NULL) { 19 | debug_write("Missing %s", wanted); 20 | return NULL; 21 | } 22 | 23 | // Advance to skip line feed. 24 | config++; 25 | } 26 | 27 | // Advance to after the name 28 | return config + wanted_len; 29 | } 30 | 31 | bool config_get_hex(const char * config, const char * wanted, uint32_t * value) { 32 | config = find_wanted(config, wanted); 33 | if (!config) { 34 | return false; 35 | } 36 | 37 | // Keep parsing until we hit the end of line or the end of file 38 | uint32_t parsed = 0; 39 | while (*config != '\n' && *config != '\0') { 40 | uint32_t digit = todigit(*config); 41 | if (digit < 0x10) { 42 | parsed = parsed << 4 | digit; 43 | } 44 | config++; 45 | } 46 | 47 | // Save and return 48 | *value = parsed; 49 | return true; 50 | } 51 | 52 | bool config_get_string(const char * config, const char * wanted, char * value) { 53 | config = find_wanted(config, wanted); 54 | if (!config) { 55 | return false; 56 | } 57 | 58 | // Advance until the start 59 | while (*config == '=' || isspace(*config)) { 60 | config++; 61 | } 62 | 63 | // Copy until space or end of file 64 | char * valuecur = value; 65 | while (*config != '\0' && !isspace(*config)) { 66 | *valuecur = *config; 67 | config++; 68 | valuecur++; 69 | } 70 | 71 | // Null terminate and return 72 | *valuecur = '\0'; 73 | return true; 74 | } 75 | -------------------------------------------------------------------------------- /loader/cfgparse.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | 6 | bool config_get_hex(const char * config, const char * wanted, uint32_t * value); 7 | 8 | bool config_get_string(const char * config, const char * wanted, char * value); 9 | -------------------------------------------------------------------------------- /loader/crc.c: -------------------------------------------------------------------------------- 1 | 2 | #include "crc.h" 3 | 4 | // Adapted from https://stackoverflow.com/a/15031244/4454028 5 | 6 | uint32_t crc32(const void * data, uint32_t len) { 7 | const uint8_t * bytes = (const uint8_t *) data; 8 | uint32_t crc = 0xFFFFFFFF; 9 | 10 | while (len) { 11 | crc ^= *bytes; 12 | 13 | for (int k = 0; k < 8; k++) { 14 | if (crc & 1) { 15 | crc = (crc >> 1) ^ 0xEDB88320; 16 | } else { 17 | crc = (crc >> 1); 18 | } 19 | } 20 | 21 | bytes++; 22 | len--; 23 | } 24 | 25 | return ~crc; 26 | } 27 | -------------------------------------------------------------------------------- /loader/crc.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | 6 | uint32_t crc32(const void * data, uint32_t len); 7 | -------------------------------------------------------------------------------- /loader/debugscreen.c: -------------------------------------------------------------------------------- 1 | 2 | #include "str.h" 3 | #include "debugscreen.h" 4 | #include "gpu.h" 5 | #include "bios.h" 6 | #include "str.h" 7 | 8 | #define SCREEN_WIDTH 640 9 | #define SCREEN_HEIGHT 480 10 | #define CHAR_HEIGHT 15 11 | #define CHAR_DRAW_WIDTH 10 12 | #define CHAR_VRAM_WIDTH 8 13 | #define FONT_X 640 14 | #define CLUT_X 640 15 | #define CLUT_Y (6 * CHAR_HEIGHT) 16 | 17 | // Orca loaded right next to the font 18 | #define ORCA_WIDTH 40 19 | #define ORCA_HEIGHT 20 20 | 21 | // Divided by 4 because each pixel is 4bpp, or 1/4 of a 16-bit short 22 | #define ORCA_TEXCOORD_X (CHAR_VRAM_WIDTH * 16) 23 | 24 | #define TH_MARGIN 40 25 | #define LOG_LINES 22 26 | #define LOG_MARGIN 32 27 | #define LOG_START_Y 80 28 | #define LOG_LINE_HEIGHT 16 29 | 30 | #define _STRINGIFY(x) #x 31 | #define STRINGIFY(x) _STRINGIFY(x) 32 | 33 | // Grayscale 34 | static const uint16_t PALETTE[16] = { 0x0000, 0x0842, 0x1084, 0x18C6, 0x2108, 0x294A, 0x318C, 0x39CE, 0x4631, 0x4E73, 0x56B5, 0x5EF7, 0x6739, 0x6F7B, 0x77BD, 0x7FFF }; 35 | 36 | #include "orca.inc" 37 | 38 | void decompressfont() { 39 | // Font is 1bpp. We have to convert each character to 4bpp. 40 | const uint8_t * rom_charset = (const uint8_t *) 0xBFC7F8DE; 41 | uint8_t charbuf[CHAR_HEIGHT * CHAR_VRAM_WIDTH / 2]; 42 | 43 | // Iterate through the 16x6 character table 44 | for (uint_fast8_t tabley = 0; tabley < 6; tabley++) { 45 | for (uint_fast8_t tablex = 0; tablex < 16; tablex++) { 46 | uint8_t * bufferpos = charbuf; 47 | 48 | // Iterate through each line of the 8x15 character 49 | for (uint_fast8_t chary = 0; chary < 15; chary++) { 50 | uint_fast8_t char1bpp = *rom_charset; 51 | uint_fast8_t bpos = 0; 52 | rom_charset++; 53 | 54 | // Iterate through each column of the character 55 | while (bpos < 8) { 56 | uint_fast8_t char4bpp = 0; 57 | 58 | if (char1bpp & 0x80) { 59 | char4bpp |= 0x0F; 60 | } 61 | bpos++; 62 | 63 | if (char1bpp & 0x40) { 64 | char4bpp |= 0xF0; 65 | } 66 | bpos++; 67 | 68 | *bufferpos = char4bpp; 69 | bufferpos++; 70 | char1bpp = char1bpp << 2; 71 | } 72 | } 73 | 74 | // GPU_dw takes units in 16bpp units, so we have to scale by 1/4 75 | GPU_dw(FONT_X + tablex * CHAR_VRAM_WIDTH * 4 / 16, tabley * CHAR_HEIGHT, CHAR_VRAM_WIDTH * 4 / 16, CHAR_HEIGHT, (uint16_t *) charbuf); 76 | } 77 | } 78 | } 79 | 80 | char last_printed_line[64]; 81 | uint32_t last_printed_count; 82 | 83 | void debug_init() { 84 | bool pal = bios_is_european(); 85 | debug_switch_standard(pal); 86 | 87 | // Clear display 88 | struct gpu_solid_rect background = { 89 | .pos = { 90 | .x = 0, 91 | .y = 0, 92 | }, 93 | .size = { 94 | .width = SCREEN_WIDTH, 95 | .height = SCREEN_HEIGHT, 96 | }, 97 | .color = 0x000000, 98 | .semi_transp = 0, 99 | }; 100 | gpu_draw_solid_rect(&background); 101 | 102 | // Load font 103 | decompressfont(); 104 | 105 | // Load orca image 106 | // Again, /4 because each pixels is 1/4 of a 16-bit short 107 | GPU_dw(FONT_X + ORCA_TEXCOORD_X / 4, 0, ORCA_WIDTH / 4, ORCA_HEIGHT, (const uint16_t *) ORCA_IMAGE); 108 | 109 | // Load the palette to Vram 110 | GPU_dw(CLUT_X, CLUT_Y, 16, 1, PALETTE); 111 | 112 | // Flush old textures 113 | gpu_flush_cache(); 114 | 115 | // Draw border 116 | debug_text_at(TH_MARGIN, 40, "tonyhax " STRINGIFY(TONYHAX_VERSION)); 117 | struct gpu_solid_rect band = { 118 | .pos = { 119 | .x = 0, 120 | .y = 65, 121 | }, 122 | .size = { 123 | .width = SCREEN_WIDTH, 124 | .height = 2, 125 | }, 126 | .color = 0xFFFFFF, 127 | .semi_transp = 0, 128 | }; 129 | gpu_draw_solid_rect(&band); 130 | 131 | // "orca.pet" website 132 | debug_text_at(SCREEN_WIDTH - 8 * CHAR_DRAW_WIDTH - TH_MARGIN, 40, "orca.pet"); 133 | 134 | // Draw orca 135 | gpu_tex_rect_t orca_rect = { 136 | .texcoord = { 137 | .x = ORCA_TEXCOORD_X, 138 | .y = 0, 139 | }, 140 | .pos = { 141 | .x = SCREEN_WIDTH - 8 * CHAR_DRAW_WIDTH - TH_MARGIN - 10 - ORCA_WIDTH, 142 | .y = 40, 143 | }, 144 | .size = { 145 | .width = ORCA_WIDTH, 146 | .height = ORCA_HEIGHT, 147 | }, 148 | .clut = { 149 | .x = CLUT_X, 150 | .y = CLUT_Y, 151 | }, 152 | .semi_transp = 0, 153 | .raw_tex = 1, 154 | }; 155 | gpu_draw_tex_rect(&orca_rect); 156 | } 157 | 158 | void debug_text_at(uint_fast16_t x_pos, uint_fast16_t y_pos, const char * text) { 159 | // Initialize constants of the rect 160 | gpu_tex_rect_t rect = { 161 | .pos = { 162 | .y = y_pos, 163 | }, 164 | .size = { 165 | .width = CHAR_VRAM_WIDTH, 166 | .height = CHAR_HEIGHT, 167 | }, 168 | .clut = { 169 | .x = CLUT_X, 170 | .y = CLUT_Y, 171 | }, 172 | .semi_transp = 0, 173 | .raw_tex = 1, 174 | }; 175 | 176 | while (*text != 0) { 177 | int tex_idx = *text - '!'; 178 | if (tex_idx >= 0 && tex_idx < 96) { 179 | // Font has a yen symbol where the \ should be 180 | if (tex_idx == '\\' - '!') { 181 | tex_idx = 95; 182 | } 183 | 184 | // Draw text 185 | rect.pos.x = x_pos; 186 | rect.texcoord.x = (tex_idx % 16) * CHAR_VRAM_WIDTH; 187 | rect.texcoord.y = (tex_idx / 16) * CHAR_HEIGHT; 188 | gpu_draw_tex_rect(&rect); 189 | 190 | // Again to overstrike and improve visibility 191 | rect.pos.x++; 192 | gpu_draw_tex_rect(&rect); 193 | } 194 | 195 | x_pos += CHAR_DRAW_WIDTH; 196 | text++; 197 | } 198 | } 199 | 200 | void debug_write(const char * str, ...) { 201 | va_list args; 202 | va_start(args, str); 203 | 204 | // For a render width of 640 at width 10 max line should be 64 chars long 205 | char formatted[64]; 206 | char formatted_repeated[64]; 207 | char * to_print; 208 | mini_vsprintf(formatted, str, args); 209 | va_end(args); 210 | 211 | // Flush old textures 212 | gpu_flush_cache(); 213 | 214 | if (strcmp(last_printed_line, formatted) != 0) { 215 | // Line's text is different, so scroll up 216 | gpu_size_t line_size = { 217 | .width = SCREEN_WIDTH - LOG_MARGIN, 218 | .height = LOG_LINE_HEIGHT, 219 | }; 220 | for (int line = 1; line < LOG_LINES; line++) { 221 | gpu_point_t source_line = { 222 | .x = LOG_MARGIN, 223 | .y = LOG_START_Y + LOG_LINE_HEIGHT * line 224 | }; 225 | gpu_point_t dest_line = { 226 | .x = LOG_MARGIN, 227 | .y = LOG_START_Y + LOG_LINE_HEIGHT * (line - 1) 228 | }; 229 | gpu_copy_rectangle(&source_line, &dest_line, &line_size); 230 | } 231 | 232 | // Copy to last printed buffer and reset counter 233 | strcpy(last_printed_line, formatted); 234 | last_printed_count = 1; 235 | 236 | to_print = formatted; 237 | } else { 238 | last_printed_count++; 239 | 240 | // Same line, so print with a repeat counter 241 | mini_sprintf(formatted_repeated, "%s (x%d)", last_printed_line, last_printed_count); 242 | 243 | to_print = formatted_repeated; 244 | } 245 | 246 | uint32_t lastLinePos = LOG_START_Y + (LOG_LINES - 1) * LOG_LINE_HEIGHT; 247 | 248 | // Clear last line 249 | gpu_solid_rect_t black_box = { 250 | .pos = { 251 | .x = LOG_MARGIN, 252 | .y = lastLinePos, 253 | }, 254 | .size = { 255 | .width = SCREEN_WIDTH - LOG_MARGIN, 256 | .height = CHAR_HEIGHT, 257 | }, 258 | .color = 0x000000, 259 | .semi_transp = 0, 260 | }; 261 | gpu_draw_solid_rect(&black_box); 262 | 263 | // Draw text on last line 264 | debug_text_at(LOG_MARGIN, lastLinePos, to_print); 265 | } 266 | 267 | void debug_switch_standard(bool pal) { 268 | // Restore to sane defaults 269 | gpu_reset(); 270 | 271 | // Configure mode, keeping PAL flag 272 | uint32_t mode = GPU_DISPLAY_H640 | GPU_DISPLAY_V480 | GPU_DISPLAY_15BPP; 273 | if (pal) { 274 | mode |= GPU_DISPLAY_PAL; 275 | } else { 276 | mode |= GPU_DISPLAY_NTSC; 277 | } 278 | gpu_display_mode(mode); 279 | 280 | // Center image on screen 281 | // Values from BIOSes 282 | if (pal) { 283 | gpu_set_hrange(638, 3198); 284 | gpu_set_vrange(43, 282); 285 | } else { 286 | gpu_set_hrange(608, 3168); 287 | gpu_set_vrange(16, 255); 288 | } 289 | 290 | // Set drawing area for entire display port 291 | gpu_point_t area_start = { 0, 0 }; 292 | gpu_size_t area_size = { 640, 480 }; 293 | gpu_set_drawing_area(&area_start, &area_size); 294 | 295 | // Enable display 296 | gpu_display_enable(); 297 | 298 | // Configure Texpage 299 | // - Texture page to X=640 Y=0 300 | // - Colors to 4bpp 301 | // - Allow drawing to display area (fuck Vsync) 302 | GPU_cw(0xE100040A); 303 | 304 | // Configure texture window 305 | GPU_cw(0xE2000000); 306 | } 307 | -------------------------------------------------------------------------------- /loader/debugscreen.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | 6 | void debug_init(); 7 | 8 | void debug_write(const char * str, ...); 9 | 10 | void debug_text_at(uint_fast16_t x, uint_fast16_t y, const char * str); 11 | 12 | void debug_switch_standard(bool pal); 13 | -------------------------------------------------------------------------------- /loader/generate-tonyhax-exe.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if [ $# -ne 2 ]; then 6 | echo "Usage: $0 elf-file exe-file" 7 | exit 1 8 | fi 9 | 10 | elf_file="$1" 11 | exe_file="$2" 12 | 13 | # Extract addresses 14 | ro_start=$(objdump -x "$elf_file" | grep __RO_START__ | cut -d ' ' -f 1) 15 | 16 | # Create temporary file for the binary 17 | bin_file=$(mktemp) 18 | mips-linux-gnu-objcopy -O binary -j .text -j .rodata -j .data -j .crc "$elf_file" $bin_file 19 | 20 | # Round filesize to nearest 2048-byte block 21 | truncate --size=%2048 $bin_file 22 | bin_size=$(printf "%08X" $(stat -c %s $bin_file)) 23 | 24 | # Create executable now 25 | echo "Creating executable" 26 | echo -ne "PS-X EXE" >"$exe_file" 27 | head -c 8 /dev/zero >>"$exe_file" 28 | # Initial PC 29 | echo -ne "\x${ro_start:6:2}\x${ro_start:4:2}\x${ro_start:2:2}\x${ro_start:0:2}" >>"$exe_file" 30 | head -c 4 /dev/zero >>"$exe_file" 31 | # Load address 32 | echo -ne "\x${ro_start:6:2}\x${ro_start:4:2}\x${ro_start:2:2}\x${ro_start:0:2}" >>"$exe_file" 33 | # Load size 34 | echo -ne "\x${bin_size:6:2}\x${bin_size:4:2}\x${bin_size:2:2}\x${bin_size:0:2}" >>"$exe_file" 35 | head -c 16 /dev/zero >>"$exe_file" 36 | # Initial SP 37 | echo -ne "\x00\xFF\x1F\x80" >>"$exe_file" 38 | head -c 24 /dev/zero >>"$exe_file" 39 | # Magic string 40 | echo -n "Sony Computer Entertainment Inc. for Europe area" >>"$exe_file" 41 | 42 | # Pad to 2048 43 | truncate --size=%2048 "$exe_file" 44 | 45 | # Insert binary 46 | cat $bin_file >> "$exe_file" 47 | 48 | # Cleanup 49 | rm $bin_file 50 | -------------------------------------------------------------------------------- /loader/generate-tonyhax-mcs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if [ $# -ne 4 ]; then 6 | echo "Usage: $0 elf-file tpl-file mcs-file version" 7 | exit 1 8 | fi 9 | 10 | elf_file="$1" 11 | tpl_file="$2" 12 | mcs_file="$3" 13 | version="$4" 14 | 15 | # Extract addresses 16 | ro_start=$(objdump -x "$elf_file" | grep __RO_START__ | cut -d ' ' -f 1) 17 | 18 | # Create temporary file for the binary 19 | bin_file=$(mktemp) 20 | mips-linux-gnu-objcopy -O binary -j .text -j .rodata -j .data -j .crc "$elf_file" $bin_file 21 | 22 | # Round filesize to nearest 128-byte block 23 | truncate --size=%128 $bin_file 24 | load_len=$(printf "%08X" $(stat -c %s $bin_file)) 25 | 26 | # Create file 27 | cp "$tpl_file" "$mcs_file" 28 | echo -n "tonyhax ${version}" | dd status=none conv=notrunc bs=1 seek=132 of="$mcs_file" 29 | dd status=none conv=notrunc bs=1 seek=384 if=$bin_file of="$mcs_file" 30 | 31 | # Insert address at 0xC0 and length at 0xC4, which is 0x40 and 0x44 inside the save file header 32 | echo -ne "\x${ro_start:6:2}\x${ro_start:4:2}\x${ro_start:2:2}\x${ro_start:0:2}\x${load_len:6:2}\x${load_len:4:2}\x${load_len:2:2}\x${load_len:0:2}" | dd status=none conv=notrunc of=tonyhax.mcs bs=1 seek=192 33 | 34 | # Cleanup 35 | rm $bin_file 36 | -------------------------------------------------------------------------------- /loader/gpu.c: -------------------------------------------------------------------------------- 1 | 2 | #include "gpu.h" 3 | #include "bios.h" 4 | #include "io.h" 5 | 6 | #define GPU_GP0_FLUSH 0x01 7 | #define GPU_GP0_FILL_VRAM 0x02 8 | #define GPU_GP0_VRAM_TO_VRAM 0x80 9 | 10 | #define GPU_GP1_RESET 0x00 11 | #define GPU_GP1_DISPLAY_ENABLE 0x03 12 | #define GPU_GP1_H_RANGE 0x06 13 | #define GPU_GP1_V_RANGE 0x07 14 | #define GPU_GP1_DISPLAY_MODE 0x08 15 | 16 | bool gpu_is_pal(void) { 17 | return (GPU_STAT & (1 << 20)) != 0; 18 | } 19 | 20 | void gpu_wait_vblank(void) { 21 | // Acknowledge if set 22 | if (I_STAT & INT_VBLANK) { 23 | I_STAT = ~INT_VBLANK; 24 | } 25 | 26 | while (!(I_STAT & INT_VBLANK)); 27 | } 28 | 29 | void gpu_reset(void) { 30 | SendGP1Command(GPU_GP1_RESET << 24); 31 | } 32 | 33 | void gpu_display_mode(uint32_t mode) { 34 | SendGP1Command(GPU_GP1_DISPLAY_MODE << 24 | mode); 35 | } 36 | 37 | void gpu_display_enable(void) { 38 | SendGP1Command(GPU_GP1_DISPLAY_ENABLE << 24 | 0); 39 | } 40 | 41 | void gpu_display_disable(void) { 42 | SendGP1Command(GPU_GP1_DISPLAY_ENABLE << 24 | 1); 43 | } 44 | 45 | void gpu_copy_rectangle(const gpu_point_t * src, const gpu_point_t * dst, const gpu_size_t * size) { 46 | uint32_t buf[4]; 47 | buf[0] = GPU_GP0_VRAM_TO_VRAM << 24; 48 | buf[1] = (uint32_t) src->y << 16 | src->x; 49 | buf[2] = (uint32_t) dst->y << 16 | dst->x; 50 | buf[3] = (uint32_t) size->height << 16 | size->width; 51 | GPU_cwp(buf, 4); 52 | } 53 | 54 | void gpu_draw_solid_rect(const gpu_solid_rect_t * rect) { 55 | uint8_t r = rect->color >> 16; 56 | uint8_t g = rect->color >> 8; 57 | uint8_t b = rect->color >> 0; 58 | 59 | uint32_t buf[3]; 60 | buf[0] = 0x60000000 | (uint32_t) b << 16 | (uint32_t) g << 8 | (uint32_t) r; 61 | buf[1] = (uint32_t) rect->pos.y << 16 | rect->pos.x; 62 | buf[2] = (uint32_t) rect->size.height << 16 | rect->size.width; 63 | GPU_cwp(buf, 3); 64 | } 65 | 66 | void gpu_draw_tex_rect(const gpu_tex_rect_t * rect) { 67 | uint32_t buf[4]; 68 | 69 | if (rect->raw_tex) { 70 | buf[0] = 0x65000000; 71 | } else { 72 | uint8_t r = rect->color >> 16; 73 | uint8_t g = rect->color >> 8; 74 | uint8_t b = rect->color >> 0; 75 | 76 | buf[0] = 0x64000000 | (uint32_t) b << 16 | (uint32_t) g << 8 | (uint32_t) r; 77 | } 78 | 79 | if (rect->semi_transp) { 80 | buf[0] |= 0x02000000; 81 | } 82 | 83 | buf[1] = (uint32_t) rect->pos.y << 16 | rect->pos.x; 84 | buf[2] = (uint32_t) rect->clut.y << 22 | (uint32_t) (rect->clut.x / 16) << 16 | (uint32_t) rect->texcoord.y << 8 | rect->texcoord.x; 85 | buf[3] = (uint32_t) rect->size.height << 16 | rect->size.width; 86 | 87 | GPU_cwp(buf, 4); 88 | } 89 | 90 | void gpu_set_drawing_area(const gpu_point_t * pos, const gpu_size_t * size) { 91 | GPU_cw(0xE3000000 | pos->y << 10 | pos->x); 92 | GPU_cw(0xE4000000 | (pos->y + size->height - 1) << 10 | (pos->x + size->width - 1)); 93 | } 94 | 95 | void gpu_flush_cache(void) { 96 | GPU_cw(GPU_GP0_FLUSH << 24); 97 | } 98 | 99 | void gpu_set_hrange(uint_fast16_t x1, uint_fast16_t x2) { 100 | SendGP1Command(GPU_GP1_H_RANGE << 24 | x2 << 12 | x1); 101 | } 102 | 103 | void gpu_set_vrange(uint_fast16_t y1, uint_fast16_t y2) { 104 | SendGP1Command(GPU_GP1_V_RANGE << 24 | y2 << 10 | y1); 105 | } 106 | -------------------------------------------------------------------------------- /loader/gpu.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | 6 | #define GPU_DISPLAY_H256 0 7 | #define GPU_DISPLAY_H320 1 8 | #define GPU_DISPLAY_H512 2 9 | #define GPU_DISPLAY_H640 3 10 | 11 | #define GPU_DISPLAY_INTERLACED (1 << 5) 12 | #define GPU_DISPLAY_V240 (0 << 2) 13 | #define GPU_DISPLAY_V480 (1 << 2 | GPU_DISPLAY_INTERLACED) 14 | 15 | #define GPU_DISPLAY_NTSC (0 << 3) 16 | #define GPU_DISPLAY_PAL (1 << 3) 17 | 18 | #define GPU_DISPLAY_15BPP (0 << 4) 19 | #define GPU_DISPLAY_24BPP (1 << 4) 20 | 21 | struct gpu_point { 22 | uint16_t x; 23 | uint16_t y; 24 | }; 25 | typedef struct gpu_point gpu_point_t; 26 | 27 | struct gpu_size { 28 | uint16_t width; 29 | uint16_t height; 30 | }; 31 | typedef struct gpu_size gpu_size_t; 32 | 33 | struct gpu_solid_rect { 34 | gpu_point_t pos; 35 | gpu_size_t size; 36 | uint32_t color; 37 | uint8_t semi_transp; 38 | }; 39 | typedef struct gpu_solid_rect gpu_solid_rect_t; 40 | 41 | struct gpu_tex_rect { 42 | gpu_point_t pos; 43 | gpu_size_t size; 44 | gpu_point_t texcoord; 45 | gpu_point_t clut; 46 | uint32_t color; 47 | uint8_t semi_transp; 48 | uint8_t raw_tex; 49 | }; 50 | typedef struct gpu_tex_rect gpu_tex_rect_t; 51 | 52 | bool gpu_is_pal(void); 53 | 54 | void gpu_reset(void); 55 | 56 | void gpu_wait_vblank(void); 57 | 58 | void gpu_display_enable(void); 59 | 60 | void gpu_display_disable(void); 61 | 62 | void gpu_display_mode(uint32_t mode); 63 | 64 | void gpu_copy_rectangle(const gpu_point_t * src, const gpu_point_t * dst, const gpu_size_t * size); 65 | 66 | void gpu_draw_solid_rect(const gpu_solid_rect_t * rect); 67 | 68 | void gpu_draw_tex_rect(const gpu_tex_rect_t * rect); 69 | 70 | void gpu_set_drawing_area(const gpu_point_t * pos, const gpu_size_t * size); 71 | 72 | void gpu_flush_cache(void); 73 | 74 | void gpu_set_hrange(uint_fast16_t x1, uint_fast16_t x2); 75 | 76 | void gpu_set_vrange(uint_fast16_t y1, uint_fast16_t y2); 77 | -------------------------------------------------------------------------------- /loader/insert-tonyhax-crc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | crc32() { 6 | # Super hacky way of calculating a CRC32 7 | # From https://stackoverflow.com/a/49446525/4454028 8 | gzip -c1 | tail -c 8 | od -N4 -An -tx4 | tr -dc [a-f0-9] | tr [a-f] [A-F] 9 | } 10 | 11 | if [ $# -ne 1 ]; then 12 | echo "Usage: $0 elf-file" 13 | exit 1 14 | fi 15 | 16 | elf_file="$1" 17 | 18 | # Calculate CRC 19 | crc=$(mips-linux-gnu-objcopy -O binary -j .text -j .rodata -j .data "$elf_file" /dev/stdout | crc32) 20 | echo "CRC32: 0x${crc}" 21 | 22 | # Create temporary file 23 | tmpsection=$(mktemp) 24 | echo -ne "\x${crc:6:2}\x${crc:4:2}\x${crc:2:2}\x${crc:0:2}" >$tmpsection 25 | 26 | # Insert it 27 | mips-linux-gnu-objcopy --update-section .crc=$tmpsection "$elf_file" 28 | 29 | # Cleanup 30 | rm $tmpsection 31 | -------------------------------------------------------------------------------- /loader/integrity.c: -------------------------------------------------------------------------------- 1 | 2 | #include "integrity.h" 3 | #include "crc.h" 4 | #include 5 | 6 | // Loading address of tonyhax, provided by the secondary.ld linker script 7 | extern uint8_t __RO_START__, __CRC_START__; 8 | 9 | // True if integrity check succeeded 10 | bool integrity_ok = false; 11 | 12 | // Correct CRC value, which will be inserted after compilation 13 | uint32_t __attribute__((section(".crc"))) integrity_correct_crc = 0xDEADBEEF; 14 | 15 | void integrity_test() { 16 | uint32_t calc_value = crc32(&__RO_START__, &__CRC_START__ - &__RO_START__); 17 | integrity_ok = calc_value == integrity_correct_crc; 18 | } 19 | -------------------------------------------------------------------------------- /loader/integrity.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | /** 6 | * True if the integrity check succeeded. 7 | */ 8 | extern bool integrity_ok; 9 | 10 | /** 11 | * Executes the integrity check and updates the integrity_ok variable. 12 | * Should be run before any change to the data section is made. 13 | */ 14 | void integrity_test(void); 15 | -------------------------------------------------------------------------------- /loader/io.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | #define _REG32(x) (*((volatile uint32_t *) x)) 6 | #define _REG16(x) (*((volatile uint16_t *) x)) 7 | 8 | #define I_STAT _REG32(0x1F801070) 9 | #define I_MASK _REG32(0x1F801074) 10 | 11 | #define INT_VBLANK (1 << 0) 12 | #define INT_GPU (1 << 1) 13 | #define INT_CDROM (1 << 2) 14 | #define INT_DMA (1 << 3) 15 | #define INT_TMR0 (1 << 4) 16 | #define INT_TMR1 (1 << 5) 17 | #define INT_TMR2 (1 << 6) 18 | #define INT_JOYPAD (1 << 7) 19 | #define INT_SIO (1 << 8) 20 | #define INT_SPU (1 << 9) 21 | #define INT_LIGHTGUN (1 << 10) 22 | 23 | #define GPU_STAT _REG32(0x1F801814) 24 | -------------------------------------------------------------------------------- /loader/orca.act: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/loader/orca.act -------------------------------------------------------------------------------- /loader/orca.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/loader/orca.img -------------------------------------------------------------------------------- /loader/patch-ap.S: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | .text 5 | 6 | # 7 | # Intercepts the syscall(1) (aka EnterCriticalSection). 8 | # 9 | # When this code is executed, the registers are as follows: 10 | # - v0: saved thread registers, must NOT be modified. 11 | # The rest of the registers are not critical and can be used freely. 12 | # 13 | .globl patch_ap_start 14 | patch_ap_start: 15 | # Load the SP value 16 | lw t0, 0x7C(v0) 17 | 18 | # 19 | # If we are being called from an antimodchip module, the call stack will look like this: 20 | # - (game code) 21 | # - ap_check 22 | # - ap_failed 23 | # - StopCallback 24 | # - disable_ints 25 | # - EnterCriticalSection 26 | # 27 | # For all known modules, the return address from StopCallback to ap_failed sits at SP+0x28. 28 | # 29 | # Before reading from this address, we will check that after adding that offset, we do not 30 | # cross a 2MB boundary, which could cause an illegal memory read. 31 | # 32 | # Without this check the following games crash: 33 | # - Elemental Gearbolt (U) (SLUS-00654), calling with a stack at 0x807FFFE0, as we attempt 34 | # to read from 0x80800008. 35 | # - Rival Schools (U) (SLUS-00681), calling with a stack of 0x801FFFD8, as we attempt to 36 | # read from 0x80200000. A mirror would be generally present here, but not for this game 37 | # as it calls SetMemSize(2) to intentionally block this. 38 | # 39 | # Some games, like Grind Session (U) (SCUS-94568) use the scratchpad during gameplay for the 40 | # stack, which is only 1KB. For now, we will not add a safety check for this case since the 41 | # vast majority of games store the stack in RAM and no game is known to crash without it. 42 | # 43 | addi t1, t0, 0x28 44 | xor t1, t0 45 | srl t1, 21 46 | bne t1, zero, patch_ap_skip 47 | 48 | # Load alledged return address 49 | lw t1, 0x28(t0) 50 | 51 | # 52 | # Check now if the loaded value could be a word-aligned address in either the KUSEG 53 | # (0x00000000-0x007FFFFF) or the KSEG0 (0x80000000-0x807FFFFF) regions, which is were user 54 | # code is executed. 55 | # 56 | # Most games use the KSEG0, except for Emperors New Groove (U) (SCUS-94571) whose programmers 57 | # seemed to prefer the KUSEG region. 58 | # 59 | # We cannot limit ourselves to checking the first 2MB of RAM, because some games, like 60 | # Robbit Mon Dieu (J) (SCPS-10103) use a mirror (0x80600000-0x807FFFFF). 61 | # 62 | li t2, 0x7F800003 63 | and t2, t1 64 | bne t2, zero, patch_ap_skip 65 | 66 | # 67 | # First, we will attempt to handle a version 1 antimodchip module. 68 | # 69 | # This checks only for the presence of a dumb modchip, by checking if the SCEx counter 70 | # increments when it should not. It is also only capable of displaying the stop screen 71 | # in Japanese. 72 | # 73 | # The offsets for some of the checked games are: 74 | # 75 | # Um Jammer Lammy (PAL-E): 76 | # - ap_check (0x801D8008) 77 | # - ap_failed (0x801D83E0, called from 0x801D8174) 78 | # - StopCallback (0x800356C4, called from 0x801D8400) 79 | # - disable_ints (0x80035B54, called from 0x800356E0) 80 | # - EnterCriticalSection 81 | # 82 | # For Saru! Get You (NTSC-J): 83 | # - ap_check (0x80136950) 84 | # - ap_failed (0x80136D28, called from 0x80136ABC) 85 | # - StopCallback (0x8002E814, called from 0x80136D48) 86 | # - disable_ints (0x8002ECA4, called from 0x8002E82C) 87 | # - EnterCriticalSection 88 | # 89 | # The return call from StopCallback to ap_failed is located at SP+0x28. We will check if 90 | # at this address +0x74 exists a "li v0, 0xE6000002", which is a black rentangle passed to 91 | # the DrawPrim function to clear the screen. 92 | # 93 | # If it exists, we will patch the thread state to return back to ap_check, as if the 94 | # ap_failed function had returned. 95 | # 96 | 97 | # Compare signature, and test for v2 if does not match 98 | lw t2, 0x74(t1) 99 | li t3, 0x3C02E600 100 | bne t2, t3, patch_ap_v15 101 | 102 | lw t2, 0x78(t1) 103 | li t3, 0x34420002 104 | bne t2, t3, patch_ap_v15 105 | 106 | # Load return address from ap_failed to ap_check 107 | lw t1, 0xE8(t0) 108 | 109 | # Adjust stack pointer 110 | addi t0, 0xF0 111 | 112 | # Save and return 113 | b patch_ap_save 114 | 115 | # 116 | # Handle another variant of the v1, used by Vandal Hearts II - Tenjou no Mon (J) (SLPM-86251) 117 | # - ap_check (0x800C4868) 118 | # - ap_failed (0x800C4C40, called from 0x800C49D4) 119 | # - StopCallback (0x800D2700, called from 0x800C4C58) 120 | # - disable_ints (0x800D2B90, called from 0x800D2718) 121 | # - EnterCriticalSection 122 | # 123 | # Same idea, except the load is now a "li v1, 0xE6000002" at +0x64 bytes after ap_failed 124 | # returns to ap_check. 125 | # 126 | # The offsets are the same as for v2, so we will reuse those adjusts. 127 | # 128 | patch_ap_v15: 129 | lw t2, 0x64(t1) 130 | li t3, 0x3C03E600 131 | bne t2, t3, patch_ap_v2 132 | 133 | lw t2, 0x68(t1) 134 | li t3, 0x34630002 135 | beq t2, t3, patch_ap_adjust_v2 136 | 137 | # 138 | # We will now attempt to patch an antimodchip v2 module. 139 | # 140 | # This one is smarter and checks that the SCEx wobble is present in the inner tracks, 141 | # to detect CD swapping; and for dumb modchips by checking for absence of the wobble 142 | # in the outer tracks. 143 | # 144 | # The offsets for some of the checked games are: 145 | # 146 | # Rockman 2 - Dr. Wily no Nazo (J) (SLPS-02255): 147 | # - ap_check (0x8006CA58) 148 | # - ap_failed (0x8006D654, called from 0x8006CE5C and 0x8006D238) 149 | # - StopCallback (0x80024524, called from 0x8006D66C) 150 | # - disable_ints (0x800249B4, called from 0x8002453C) 151 | # - EnterCriticalSection 152 | # 153 | # The return address from StopCallback to ap_failed is located at SP+0x28, exactly as above 154 | # so we will not load it again. 155 | # 156 | # For this other version, we will check if at this return address +0x10 bytes exists a 157 | # "sh zero, 0x1F801DAA", which is used to mute the audio. 158 | # 159 | # If that exists, we will patch the thread state to return back to ap_check. 160 | # 161 | patch_ap_v2: 162 | # Compare signature 163 | lw t2, 0x18(t1) 164 | li t3, 0x3C011F80 165 | bne t2, t3, patch_ap_skip 166 | 167 | lw t2, 0x1C(t1) 168 | li t3, 0xA4201DAA 169 | bne t2, t3, patch_ap_skip 170 | 171 | patch_ap_adjust_v2: 172 | # Load return address to from ap_failed to ap_check 173 | lw t1, 0x120(t0) 174 | 175 | # Adjust stack pointer 176 | addi t0, 0x128 177 | 178 | patch_ap_save: 179 | # Zero the s0 and s1 stored in the thread state, so the state machine used by ap_check exits 180 | sw zero, 0x48(v0) 181 | sw zero, 0x4C(v0) 182 | 183 | # Save adjusted stack pointer and return address 184 | sw t0, 0x7C(v0) 185 | sw t1, 0x88(v0) 186 | 187 | .globl patch_ap_success 188 | patch_ap_success: 189 | j 0x12341234 190 | 191 | .globl patch_ap_skip 192 | patch_ap_skip: 193 | j 0x12341234 194 | 195 | .globl patch_ap_end 196 | patch_ap_end: 197 | -------------------------------------------------------------------------------- /loader/patch-fpb.S: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | .text 5 | 6 | # 7 | # The anti-FreePSXBoot patch. 8 | # 9 | # This patch is called right at the very end of the last step in the read sector finite state 10 | # machine: 11 | # https://github.com/grumpycoders/pcsx-redux/blob/f6484e8010a40a81e4019d9bfa1a9d408637b614/src/mips/openbios/sio0/card.c#L194 12 | # 13 | # When this code is executed, the registers are as follows: 14 | # - v0 contains 1, or "success". 15 | # - a1 contains the read buffer 16 | # - a2 contains the current sector number 17 | # 18 | # If the sector being read is sector 0 and it contains "FPBZ" at +0x7C, we modify the read data 19 | # so it is detected as corrupted and the game skips reading from it 20 | # 21 | # The offsets have been checked against BIOSes 2.2, 3.0, 4.1 and 4.4 22 | # 23 | .globl patch_fpb_start 24 | patch_fpb_start: 25 | lw t0, 0x7C(a1) 26 | li t1, 0x5A425046 27 | bne a2, 0, patch_fpb_ret 28 | bne t0, t1, patch_fpb_ret 29 | 30 | sw zero, 0(a1) 31 | patch_fpb_ret: 32 | j 0x5B54 33 | 34 | .globl patch_fpb_end 35 | patch_fpb_end: 36 | -------------------------------------------------------------------------------- /loader/patch-uart.S: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | .text 5 | 6 | # 7 | # This is a complete replacement for the original std_out_putc the BIOS has. 8 | # 9 | # In this function: 10 | # - a0: data to bitbang 11 | # - t0: working registers 12 | # - t1: bits to send 13 | # - t2: last IRQ flag value 14 | # - t3: 0x1F800000 (I/O start address) 15 | # - t4: COP0 SR value 16 | # 17 | .globl patch_uartputc_start 18 | patch_uartputc_start: 19 | # Add start (0) and stop (1) bits to byte 20 | andi a0, 0xFF 21 | sll a0, 1 22 | ori a0, 0x200 23 | 24 | # Newline (after processing) 25 | li t0, (0x0A << 1 | 0x200) 26 | 27 | # Bits to send (1 start + 8 data + 1 stop) 28 | li t1, 10 29 | 30 | # Compare against newline (0x0A) 31 | bne a0, t0, notnl 32 | 33 | # If newline, prepend a 0x0D, like the original function did and increment bit count 34 | sll a0, 10 35 | ori a0, (0x0D << 1 | 0x200) 36 | addi t1, 10 37 | 38 | notnl: 39 | # We will directly manipulate the COP0 status registers instead of using EnterCriticalSection 40 | # to avoid other threads/interrupts from fucking up the timing. 41 | # 42 | # The reason is two-fold: 43 | # - The kernel does not support reentrant calls - if something calls us while we are 44 | # executing kernel code and we generate a syscall, we'd nuke the current thread state. 45 | # 46 | # - SetConf calls printf while re-configuring the TCBs (thread control blocks). Executing 47 | # *any* interrupt at that point (which includes syscalls) will cause the interrupt 48 | # handler to write the current thread state to the zero address, wiping the interrupt 49 | # trampoline at 0x80. 50 | # 51 | # By directly manipulating this register we're opening ourselves to all kinds of race 52 | # conditions, but since this is just for debugging tonyhax, that's good enough for me. 53 | 54 | # Load current SR state in t4 55 | mfc0 t4, $12 56 | 57 | # Clear bits 10 and 0, the same flags WarmBoot clears 58 | li t0, 0xFFFFFBFE 59 | and t0, t4 60 | mtc0 t0, $12 61 | 62 | # Load I/O start 63 | lui t3, 0x1F80 64 | 65 | # Set timer 0 target to 293 cycles (33868800Hz/115200bps-1) 66 | li t0, 293 67 | sw t0, 0x1108(t3) 68 | 69 | # Start timer 0 in: 70 | # - Source clock to SysClk (33868800Hz) 71 | # - Free-running mode 72 | # - Reset on reaching target value 73 | # - IRQ on repeat mode (can be fired multiple times) 74 | # - Toggle IRQ flag (bit 10) on every IRQ 75 | # 76 | # We must not use the "reached target value" flag because that seems to be affected by some 77 | # kind of undocumented hardware errata. In real hardware, that flag can read zero if the 78 | # elapsed cycles between timer start and read and target values are both even or odd. 79 | # 80 | # Also note that although we are using the IRQ bits, interrupts are actually disabled so 81 | # we will busy poll the corresponding bits. 82 | li t0, 0x04D8 83 | sw t0, 0x1104(t3) 84 | 85 | # Current timer IRQ flag status 86 | li t2, 0x0400 87 | 88 | writebit: 89 | # Emit bit via /JOY pin of port 2. 90 | # We need to invert it, then put it into JOY_CTRL.13. 91 | # The XOR also sets the bit JOY_CTRL.2 which enables outputing the /JOY signal 92 | andi t0, a0, 1 93 | sll t0, 13 94 | xori t0, 0x2002 95 | sh t0, 0x104A(t3) 96 | 97 | # Shift right current buffer 98 | srl a0, 1 99 | 100 | # Decrement count while we're waiting 101 | addi t1, -1 102 | 103 | # Wait until the interrupt flag toggles 104 | writewait: 105 | lw t0, 0x1104(t3) 106 | andi t0, 0x0400 107 | beq t0, t2, writewait 108 | 109 | # Save current IRQ flag status 110 | move t2, t0 111 | 112 | # If not done, keep going 113 | bne t1, zero, writebit 114 | 115 | # Restore coprocessor flags 116 | mtc0 t4, $12 117 | 118 | jr ra 119 | 120 | .global patch_uartputc_end 121 | patch_uartputc_end: 122 | -------------------------------------------------------------------------------- /loader/patch-vandal-hearths-2.S: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | .text 5 | 6 | # 7 | # This game needs a special treatment, as it has the regular antipiracy, but also an extra check 8 | # by calling CdGetDiskType. If it detects the disc is a burned one, it aborts. 9 | # 10 | # Furthermore, if the BIOS is an European one, it gets stuck on a loop, calling the AP module 11 | # forever. 12 | # 13 | # So we will just nuke the antipiracy call. This function is supposed to return a nonzero, but 14 | # we do not need to patch v0 as there is a load constant into v0 right before the call. 15 | # 16 | .globl patch_vandal_start 17 | patch_vandal_start: 18 | # Load address where the call to antipiracy sits 19 | la t0, 0x80040C98 20 | 21 | # Check if it matches "jal 0x80042854" 22 | lw t1, 0(t0) 23 | li t2, 0x0C010A15 24 | bne t1, t2, patch_vandal_return 25 | 26 | # If it does, NOP the opcode 27 | sw zero, 0(t0) 28 | 29 | .globl patch_vandal_return 30 | patch_vandal_return: 31 | # This will be replaced with the real address 32 | j 0x12341234 33 | 34 | .globl patch_vandal_end 35 | patch_vandal_end: 36 | -------------------------------------------------------------------------------- /loader/patcher.c: -------------------------------------------------------------------------------- 1 | 2 | #include "bios.h" 3 | #include "debugscreen.h" 4 | #include "str.h" 5 | #include "patcher.h" 6 | 7 | inline void encode_j(void * jump_location, const void * jump_dest) { 8 | uint32_t * words = (uint32_t *) jump_location; 9 | words[0] = 0x08000000 | (((uint32_t) jump_dest >> 2) & 0x3FFFFFF); 10 | } 11 | 12 | inline void encode_jal(void * jump_location, const void * jump_dest) { 13 | uint32_t * words = (uint32_t *) jump_location; 14 | words[0] = 0x0C000000 | (((uint32_t) jump_dest >> 2) & 0x3FFFFFF); 15 | } 16 | 17 | inline void encode_li(void * load_location, int regnum, uint32_t value) { 18 | uint32_t * words = (uint32_t *) load_location; 19 | 20 | // LUI - Load Upper Immediate 21 | words[0] = 0x3C000000 | (regnum << 16) | (value >> 16); 22 | 23 | // ORI - OR Immediate 24 | words[1] = 0x34000000 | (regnum << 21) | (regnum << 16) | (value & 0xFFFF); 25 | } 26 | 27 | uint8_t * install_generic_antipiracy_patch(uint8_t * install_addr) { 28 | // Exports defined by the patch 29 | extern uint8_t patch_ap_start; 30 | extern uint8_t patch_ap_end; 31 | extern uint8_t patch_ap_skip; 32 | extern uint8_t patch_ap_success; 33 | 34 | debug_write(" * Generic antipiracy"); 35 | 36 | // Get the handler info structure 37 | handler_info_t * syscall_handler = bios_get_syscall_handler(); 38 | 39 | // Get the start of the verifier function (the only one set) 40 | uint32_t * verifier = (uint32_t *) syscall_handler->verifier; 41 | 42 | /* 43 | * At opcode 20 it accesses an 4-word array which contain where to jump depending on the 44 | * syscall performed. We're interested in modifying the value for 1 (EnterCriticalSection) 45 | * so we can intercept it and defuse the antimodchip. 46 | */ 47 | uint32_t lw_op = verifier[20]; 48 | if ((lw_op >> 16) != 0x8C39) { 49 | debug_write("Aborted! Please report this!"); 50 | return install_addr; 51 | } 52 | 53 | // Extract location of cases array 54 | void ** cases_array = (void **) (lw_op & 0xFFFF); 55 | 56 | // Copy blob 57 | memcpy(install_addr, &patch_ap_start, &patch_ap_end - &patch_ap_start); 58 | 59 | /* 60 | * Insert the jump to the original code, which we'll use if the call was not originated from 61 | * an antipiracy module. 62 | */ 63 | encode_j(install_addr + (&patch_ap_skip - &patch_ap_start), cases_array[1]); 64 | 65 | /* 66 | * Insert the jump we'll use to exit the exception handler once we have finished patching up 67 | * the thread state if the call was indeed originated from an antipiracy module. 68 | * 69 | * We'll use the address of syscall(0) which behaves as a nop to exit the exception. 70 | */ 71 | encode_j(install_addr + (&patch_ap_success - &patch_ap_start), cases_array[0]); 72 | 73 | // Finally replace 74 | cases_array[1] = install_addr; 75 | 76 | return install_addr + (&patch_ap_end - &patch_ap_start); 77 | } 78 | 79 | uint8_t * install_vandal_patch(uint8_t * install_addr) { 80 | // Exports defined by the patch 81 | extern uint8_t patch_vandal_start; 82 | extern uint8_t patch_vandal_return; 83 | extern uint8_t patch_vandal_end; 84 | 85 | debug_write(" * Vandal Hearths 2 AP"); 86 | 87 | // Copy blob 88 | memcpy(install_addr, &patch_vandal_start, &patch_vandal_end - &patch_vandal_start); 89 | 90 | // Hook into call 16 of table B (OutdatedPadGetButtons), which is called once per frame 91 | void ** b0_tbl = GetB0Table(); 92 | 93 | // Insert call to real function 94 | encode_j(install_addr + (&patch_vandal_return - &patch_vandal_start), b0_tbl[0x16]); 95 | 96 | // Replace it now 97 | b0_tbl[0x16] = install_addr; 98 | 99 | // Advance installation address 100 | return install_addr + (&patch_vandal_end - &patch_vandal_start); 101 | } 102 | 103 | uint8_t * install_fpb_patch(uint8_t * install_addr) { 104 | // Exports defined by the patch 105 | extern uint8_t patch_fpb_start; 106 | extern uint8_t patch_fpb_end; 107 | 108 | debug_write(" * FreePSXBoot"); 109 | 110 | // Copy blob 111 | memcpy(install_addr, &patch_fpb_start, &patch_fpb_end - &patch_fpb_start); 112 | 113 | // Install it 114 | encode_jal((void *) 0x5B40, install_addr); 115 | 116 | // Advance installation address 117 | return install_addr + (&patch_fpb_end - &patch_fpb_start); 118 | } 119 | 120 | void patcher_apply(const char * boot_file) { 121 | // We have plenty of space at the end of table B 122 | uint8_t * install_addr = (uint8_t *) (GetB0Table() + 0x5E); 123 | 124 | // Install patches 125 | debug_write("Installing patches:"); 126 | 127 | // Install a suitable antimodchip patch 128 | if (strcmp(boot_file, "cdrom:\\SLUS_009.40;1") == 0) { 129 | install_addr = install_vandal_patch(install_addr); 130 | } else { 131 | install_addr = install_generic_antipiracy_patch(install_addr); 132 | } 133 | 134 | // FreePSXBoot does not work on PS2 so skip its installation 135 | if (bios_is_ps1()) { 136 | install_addr = install_fpb_patch(install_addr); 137 | } 138 | } 139 | 140 | void patcher_apply_softuart() { 141 | // Exports defined by the patch 142 | extern uint8_t patch_uartputc_start; 143 | extern uint8_t patch_uartputc_end; 144 | 145 | // Overwrite BIOS' std_out_putchar function 146 | memcpy(BIOS_A0_TABLE[0x3C], &patch_uartputc_start, &patch_uartputc_end - &patch_uartputc_start); 147 | } 148 | -------------------------------------------------------------------------------- /loader/patcher.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | /** 5 | * Install and apply suitable BIOS patches. 6 | */ 7 | void patcher_apply(const char * boot_file); 8 | 9 | /** 10 | * Installs the softUART patch. 11 | */ 12 | void patcher_apply_softuart(); 13 | -------------------------------------------------------------------------------- /loader/secondary.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "str.h" 6 | #include "audio.h" 7 | #include "bios.h" 8 | #include "cdrom.h" 9 | #include "cfgparse.h" 10 | #include "crc.h" 11 | #include "debugscreen.h" 12 | #include "gpu.h" 13 | #include "patcher.h" 14 | #include "integrity.h" 15 | #include "io.h" 16 | 17 | // Loading address of tonyhax, provided by the secondary.ld linker script 18 | extern uint8_t __RO_START__, __BSS_START__, __BSS_END__; 19 | 20 | void log_bios_version() { 21 | /* 22 | * "System ROM Version 4.5 05/25/00 A" 23 | * By adding 11 we get to Version, which we'll compare as a shortcut 24 | */ 25 | const char * version; 26 | 27 | if (strncmp(BIOS_VERSION + 11, "Version", 7) == 0) { 28 | version = BIOS_VERSION + 19; 29 | } else { 30 | version = "1.0 or older"; 31 | } 32 | 33 | debug_write("Console: %s", bios_is_ps1() ? "PS1": "PS2"); 34 | debug_write("BIOS: v%s", version); 35 | } 36 | 37 | bool backdoor_cmd(uint_fast8_t cmd, const char * string) { 38 | uint8_t cd_reply[16]; 39 | 40 | // Send command 41 | cd_command(cmd, (const uint8_t *) string, strlen(string)); 42 | 43 | // Check if INT5, else fail 44 | if (cd_wait_int() != 5) { 45 | return false; 46 | } 47 | 48 | // Check length 49 | uint_fast8_t reply_len = cd_read_reply(cd_reply); 50 | if (reply_len != 2) { 51 | return false; 52 | } 53 | 54 | // Check there is an error flagged 55 | if (!(cd_reply[0] & 0x01)) { 56 | return false; 57 | } 58 | 59 | // Check error code 60 | if (cd_reply[1] != 0x40) { 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | bool unlock_drive() { 68 | uint8_t cd_reply[16]; 69 | 70 | // Run "GetRegion" test 71 | uint8_t test = CD_TEST_REGION; 72 | cd_command(CD_CMD_TEST, &test, 1); 73 | 74 | // Should succeed with 3 75 | if (cd_wait_int() != 3) { 76 | debug_write("Region read failed"); 77 | return false; 78 | } 79 | 80 | // Read actual region text and null terminate it 81 | int len = cd_read_reply(cd_reply); 82 | cd_reply[len] = 0; 83 | 84 | // Compare which is the fifth string we have to send to the backdoor 85 | const char * region_name; 86 | const char * p5_localized; 87 | if (strcmp((char *) cd_reply, "for Europe") == 0) { 88 | region_name = "European"; 89 | p5_localized = "(Europe)"; 90 | } else if (strcmp((char *) cd_reply, "for U/C") == 0) { 91 | region_name = "American"; 92 | p5_localized = "of America"; 93 | } else if (strcmp((char *) cd_reply, "for NETEU") == 0) { 94 | region_name = "NetYaroze (EU)"; 95 | p5_localized = "World wide"; 96 | } else if (strcmp((char *) cd_reply, "for NETNA") == 0) { 97 | region_name = "NetYaroze (US)"; 98 | p5_localized = "World wide"; 99 | } else { 100 | // +4 to skip past "for " 101 | debug_write("Unsupported region: %s", (char *) (cd_reply + 4)); 102 | return false; 103 | } 104 | 105 | debug_write("Drive region: %s", region_name); 106 | 107 | // Note the kernel's implementation of strlen returns 0 for nulls. 108 | if ( 109 | !backdoor_cmd(0x50, NULL) || 110 | !backdoor_cmd(0x51, "Licensed by") || 111 | !backdoor_cmd(0x52, "Sony") || 112 | !backdoor_cmd(0x53, "Computer") || 113 | !backdoor_cmd(0x54, "Entertainment") || 114 | !backdoor_cmd(0x55, p5_localized) || 115 | !backdoor_cmd(0x56, NULL) 116 | ) { 117 | debug_write("Backdoor failed"); 118 | return false; 119 | } 120 | 121 | return true; 122 | } 123 | 124 | void wait_lid_status(bool open) { 125 | uint8_t cd_reply[16]; 126 | 127 | uint8_t expected = open ? 0x10 : 0x00; 128 | do { 129 | // Issue Getstat command 130 | // We cannot issue the BIOS CD commands yet because we haven't called CdInit 131 | cd_command(CD_CMD_GETSTAT, NULL, 0); 132 | 133 | // Always returns 3, no need to check 134 | cd_wait_int(); 135 | 136 | // Always returns one, no need to check either 137 | cd_read_reply(cd_reply); 138 | 139 | } while ((cd_reply[0] & 0x10) != expected); 140 | } 141 | 142 | bool exe_file_okay(const exe_header_t * exe_header) { 143 | /* 144 | * Cannot assume this will be present. The first revision of 145 | * Jikkyou Powerful Pro Yakyuu '95 (J) (SLPS-00016) does not have this present in the PSX.EXE. 146 | * 147 | * Just warn about it. 148 | */ 149 | if (strncmp(exe_header->signature, "PS-X EXE", 8)) { 150 | debug_write("Warning: header has invalid signature"); 151 | } 152 | 153 | // Check that the address is within RAM 154 | uint32_t masked_load = (uint32_t) exe_header->offsets.load_addr & 0xFF800000; 155 | if ( 156 | masked_load != 0x00000000 && 157 | masked_load != 0x80000000 && 158 | masked_load != 0xA0000000 159 | ) { 160 | debug_write("Error: header has an invalid load address"); 161 | return false; 162 | } 163 | 164 | // Check that the PC is within RAM *and* aligned to a word 165 | uint32_t masked_pc = (uint32_t) exe_header->offsets.initial_pc & 0xFF800003; 166 | if ( 167 | masked_pc != 0x00000000 && 168 | masked_pc != 0x80000000 && 169 | masked_pc != 0xA0000000 170 | ) { 171 | debug_write("Error: header has an invalid start address"); 172 | return false; 173 | } 174 | 175 | // Maybe we could check if the load+size overlaps with the kernel-reserved area? Dunno. 176 | 177 | return true; 178 | } 179 | 180 | void try_boot_cd() { 181 | int32_t read; 182 | 183 | debug_write("Swap CD now"); 184 | wait_lid_status(true); 185 | wait_lid_status(false); 186 | 187 | debug_write("Initializing CD"); 188 | if (!CdInit()) { 189 | debug_write("Init failed"); 190 | return; 191 | } 192 | 193 | /* 194 | * Use the space the BIOS has allocated for reading CD sectors. 195 | * 196 | * The English translation of Mizzurna Falls (J) (SLPS-01783) depends on the header being 197 | * present here (see issue #95 in GitHub). 198 | * 199 | * This address varies between PS1 and PS2. 200 | */ 201 | uint8_t * data_buffer = (uint8_t *) (bios_is_ps1() ? 0xA000B070 : 0xA000A8D0); 202 | 203 | debug_write("Checking game region"); 204 | if (CdReadSector(1, 4, data_buffer) != 1) { 205 | debug_write("Failed to read sector"); 206 | return; 207 | } 208 | 209 | const char * game_region; 210 | bool game_is_pal = false; 211 | /* 212 | * EU: " Licensed by Sony Computer Entertainment Euro pe " 213 | * US: " Licensed by Sony Computer Entertainment Amer ica " 214 | * JP: " Licensed by Sony Computer Entertainment Inc.",0x0A 215 | * |- character we use, at 0x3C 216 | */ 217 | switch (data_buffer[0x3C]) { 218 | case 'E': 219 | game_region = "European"; 220 | game_is_pal = true; 221 | break; 222 | 223 | case 'A': 224 | game_region = "American"; 225 | break; 226 | 227 | case 'I': 228 | game_region = "Japanese"; 229 | break; 230 | 231 | default: 232 | game_region = "unknown"; 233 | } 234 | 235 | debug_write("Game's region is %s. Using %s video.", game_region, game_is_pal ? "PAL" : "NTSC"); 236 | 237 | // Defaults if no SYSTEM.CNF file exists 238 | uint32_t tcb = BIOS_DEFAULT_TCB; 239 | uint32_t event = BIOS_DEFAULT_EVCB; 240 | void * stacktop = BIOS_DEFAULT_STACKTOP; 241 | const char * bootfile = "cdrom:PSX.EXE;1"; 242 | 243 | char bootfilebuf[32]; 244 | debug_write("Loading SYSTEM.CNF"); 245 | 246 | int32_t cnf_fd = FileOpen("cdrom:SYSTEM.CNF;1", FILE_READ); 247 | if (cnf_fd >= 0) { 248 | read = FileRead(cnf_fd, data_buffer, 2048); 249 | if (read < 0) { 250 | debug_write("Failed to read. Error %d.", read, BIOS_FCBS[cnf_fd]->last_error); 251 | FileClose(cnf_fd); 252 | return; 253 | } 254 | FileClose(cnf_fd); 255 | 256 | // Null terminate 257 | data_buffer[read] = '\0'; 258 | 259 | config_get_hex((char *) data_buffer, "TCB", &tcb); 260 | config_get_hex((char *) data_buffer, "EVENT", &event); 261 | config_get_hex((char *) data_buffer, "STACK", (uint32_t *) &stacktop); 262 | if (config_get_string((char *) data_buffer, "BOOT", bootfilebuf)) { 263 | bootfile = bootfilebuf; 264 | } 265 | 266 | } else { 267 | uint32_t errorCode = GetLastError(); 268 | if (errorCode != FILEERR_NOT_FOUND) { 269 | debug_write("Open error %d", errorCode); 270 | return; 271 | } 272 | 273 | debug_write("Not found"); 274 | } 275 | 276 | // Use string format to reduce ROM usage 277 | debug_write(" * %s = %x", "TCB", tcb); 278 | debug_write(" * %s = %x", "EVENT", event); 279 | debug_write(" * %s = %x", "STACK", stacktop); 280 | debug_write(" * %s = %s", "BOOT", bootfile); 281 | 282 | /* 283 | * SetConf is run by BIOS with interrupts disabled. 284 | * 285 | * If an interrupt happens while the BIOS is reinitializing the TCBs (thread control blocks), 286 | * the interrupt handler will store the current thread state in the zero address, wiping 287 | * vital data, like the interrupt trampoline at 0x80. 288 | * 289 | * We do not need to reenable the interrupts because SetConf already does it for us. 290 | */ 291 | debug_write("Configuring kernel"); 292 | EnterCriticalSection(); 293 | SetConf(event, tcb, stacktop); 294 | 295 | debug_write("Clearing RAM"); 296 | uint8_t * user_start = (uint8_t *) 0x80010000; 297 | bzero(user_start, &__RO_START__ - user_start); 298 | 299 | debug_write("Reading executable header"); 300 | int32_t exe_fd = FileOpen(bootfile, FILE_READ); 301 | if (exe_fd < 0) { 302 | debug_write("Open error %d", GetLastError()); 303 | return; 304 | } 305 | file_control_block_t * exe_fcb = *BIOS_FCBS + exe_fd; 306 | 307 | read = FileRead(exe_fd, data_buffer, 2048); 308 | if (read < 0) { 309 | debug_write("Failed to read. Error %d.", exe_fcb->last_error); 310 | FileClose(exe_fd); 311 | return; 312 | } 313 | 314 | /* 315 | * Patch executable header like stock does. Fixes issue #153 with King's Field (J) (SLPS-00017). 316 | * https://github.com/grumpycoders/pcsx-redux/blob/a072e38d78c12a4ce1dadf951d9cdfd7ea59220b/src/mips/openbios/main/main.c#L380-L381 317 | */ 318 | exe_header_t * exe_header = (exe_header_t *) data_buffer; 319 | exe_header->offsets.initial_sp_base = stacktop; 320 | exe_header->offsets.initial_sp_offset = 0; 321 | 322 | /* 323 | * Patch executable load size, capping it to the file size. 324 | * 325 | * According to https://github.com/socram8888/tonyhax/issues/161, 326 | * Kileak, The Blood (J) (SLPS-00027) specifies in its header a an invalid load size, larger 327 | * than the actual executable. 328 | * 329 | * While the BIOS does not validate this, we do to ensure the file could be read in its 330 | * entirety and detect possible CD read errors. 331 | */ 332 | uint32_t actual_exe_size = exe_fcb->size - 2048; 333 | if (actual_exe_size < exe_header->offsets.load_size) { 334 | exe_header->offsets.load_size = actual_exe_size; 335 | } 336 | 337 | if (!exe_file_okay(exe_header)) { 338 | FileClose(exe_fd); 339 | return; 340 | } 341 | 342 | // If the file overlaps tonyhax, we will use the unstable LoadAndExecute function 343 | // since that's all we can do. 344 | if ((uint8_t *) exe_header->offsets.load_addr + exe_header->offsets.load_size >= &__RO_START__) { 345 | debug_write("Executable won't fit. Using buggy BIOS call."); 346 | 347 | if (game_is_pal != gpu_is_pal()) { 348 | debug_write("Switching video mode"); 349 | debug_switch_standard(game_is_pal); 350 | } 351 | 352 | // Restore original error handler 353 | bios_restore_disc_error(); 354 | 355 | LoadAndExecute( 356 | bootfile, 357 | exe_header->offsets.initial_sp_base, 358 | exe_header->offsets.initial_sp_offset 359 | ); 360 | return; 361 | } 362 | 363 | debug_write( 364 | "Loading executable (%d bytes @ %x)", 365 | exe_header->offsets.load_size, 366 | exe_header->offsets.load_addr 367 | ); 368 | 369 | read = FileRead(exe_fd, exe_header->offsets.load_addr, exe_header->offsets.load_size); 370 | if (read < 0) { 371 | debug_write("Failed to read. Error %d.", exe_fcb->last_error); 372 | return; 373 | } 374 | 375 | FileClose(exe_fd); 376 | 377 | patcher_apply(bootfile); 378 | 379 | if (game_is_pal != gpu_is_pal()) { 380 | debug_write("Switching video mode"); 381 | debug_switch_standard(game_is_pal); 382 | } 383 | 384 | debug_write("Starting"); 385 | 386 | // Restore original error handler 387 | bios_restore_disc_error(); 388 | 389 | // Games from WarmBoot start with interrupts disabled 390 | EnterCriticalSection(); 391 | 392 | // FlushCache needs to be called with interrupts disabled 393 | FlushCache(); 394 | 395 | DoExecute(&exe_header->offsets, 0, 0); 396 | } 397 | 398 | void main() { 399 | // Undo all possible fuckeries during exploiting 400 | bios_reinitialize(); 401 | 402 | // Mute the audio 403 | audio_halt(); 404 | 405 | // Initialize debug screen 406 | debug_init(); 407 | 408 | debug_write("Integrity check %sed", integrity_ok ? "pass" : "fail"); 409 | if (!integrity_ok) { 410 | return; 411 | } 412 | 413 | bios_inject_disc_error(); 414 | log_bios_version(); 415 | 416 | debug_write("Resetting drive"); 417 | if (!cd_drive_init()) { 418 | debug_write("Reset failed"); 419 | return; 420 | } 421 | 422 | debug_write("Unlocking drive"); 423 | if (!unlock_drive()) { 424 | return; 425 | } 426 | 427 | while (1) { 428 | #if SOFTUART_PATCH 429 | patcher_apply_softuart(); 430 | std_out_puts("SoftUART ready\n"); 431 | #endif 432 | 433 | try_boot_cd(); 434 | 435 | debug_write("Reinitializing kernel"); 436 | bios_reinitialize(); 437 | bios_inject_disc_error(); 438 | } 439 | } 440 | 441 | void __attribute__((section(".start"))) start() { 442 | // Clear BSS 443 | bzero(&__BSS_START__, &__BSS_END__ - &__BSS_START__); 444 | 445 | // Execute integrity test 446 | integrity_test(); 447 | 448 | main(); 449 | 450 | while(1); 451 | } 452 | -------------------------------------------------------------------------------- /loader/secondary.ld: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ram(wrx) :ORIGIN = 0x801FA100, LENGTH = 0x3F00 3 | } 4 | SECTIONS { 5 | . = 0x801FA100; 6 | PROVIDE(__RO_START__ = .); 7 | .text : 8 | { 9 | *(.start) 10 | *(.text .text.*) 11 | } >ram 12 | .rodata : 13 | { 14 | *(.rodata .rodata.*) 15 | } >ram 16 | .data : 17 | { 18 | *(.data .data.*) 19 | } >ram 20 | PROVIDE(__CRC_START__ = .); 21 | .crc : 22 | { 23 | *(.crc .crc.*) 24 | } >ram 25 | PROVIDE(__BSS_START__ = .); 26 | .bss : 27 | { 28 | *(.bss .bss.*) 29 | } >ram 30 | PROVIDE(__BSS_END__ = .); 31 | /DISCARD/ : 32 | { 33 | *(*) 34 | } 35 | } 36 | ENTRY (start) 37 | -------------------------------------------------------------------------------- /loader/str.c: -------------------------------------------------------------------------------- 1 | 2 | #include "str.h" 3 | #include 4 | 5 | int isspace(int c) { 6 | switch (c) { 7 | case ' ': 8 | case '\r': 9 | case '\n': 10 | case '\t': 11 | case '\v': 12 | case '\f': 13 | return 1; 14 | 15 | default: 16 | return 0; 17 | } 18 | } 19 | 20 | int mini_sprintf(char * str, const char * format, ...) { 21 | int retcode = 0; 22 | va_list args; 23 | va_start(args, format); 24 | retcode = mini_vsprintf(str, format, args); 25 | va_end(args); 26 | return retcode; 27 | } 28 | 29 | int mini_vsprintf(char * str, const char * format, va_list args) { 30 | char * pos = str; 31 | 32 | while (*format != 0) { 33 | switch (*format) { 34 | case '\\': 35 | format++; 36 | 37 | switch (*format) { 38 | case 'n': 39 | *pos = '\n'; 40 | pos++; 41 | break; 42 | 43 | case 'r': 44 | *pos = '\r'; 45 | pos++; 46 | break; 47 | 48 | case '\0': 49 | *pos = '\\'; 50 | pos++; 51 | goto end; 52 | 53 | default: 54 | pos[0] = '\\'; 55 | pos[1] = *format; 56 | pos += 2; 57 | break; 58 | } 59 | 60 | break; 61 | 62 | case '%': 63 | format++; 64 | 65 | switch (*format) { 66 | case 'c': { 67 | int c = va_arg(args, int); 68 | *pos = c; 69 | pos++; 70 | break; 71 | } 72 | 73 | case 's': { 74 | const char * subtext = va_arg(args, const char *); 75 | while (*subtext != '\0') { 76 | *pos = *subtext; 77 | pos++; 78 | subtext++; 79 | } 80 | break; 81 | } 82 | 83 | case 'd': { 84 | int val = va_arg(args, int); 85 | 86 | if (val == 0) { 87 | *pos = '0'; 88 | pos++; 89 | } else { 90 | if (val < 0) { 91 | *pos = '-'; 92 | pos++; 93 | } 94 | 95 | // Calculate digits backwards 96 | char digits[10]; 97 | int digpos = 0; 98 | do { 99 | digits[digpos] = '0' + val % 10; 100 | val = val / 10; 101 | digpos++; 102 | } while (val != 0); 103 | 104 | // And copy them now also backwards 105 | while (digpos != 0) { 106 | digpos--; 107 | *pos = digits[digpos]; 108 | pos++; 109 | } 110 | } 111 | break; 112 | } 113 | 114 | case 'x': { 115 | char c; 116 | unsigned int val = va_arg(args, unsigned int); 117 | for (int i = 28; i >= 0; i -= 4) { 118 | c = (val >> i) & 0xF; 119 | if (c < 10) { 120 | c += '0'; 121 | } else { 122 | c += 'A' - 0xA; 123 | } 124 | *pos = c; 125 | pos++; 126 | } 127 | break; 128 | } 129 | 130 | case '\0': 131 | *pos = '\\'; 132 | pos++; 133 | goto end; 134 | 135 | default: 136 | pos[0] = '%'; 137 | pos[1] = *format; 138 | pos += 2; 139 | break; 140 | } 141 | break; 142 | 143 | default: 144 | *pos = *format; 145 | pos++; 146 | break; 147 | } 148 | 149 | format++; 150 | } 151 | 152 | end: 153 | *pos = '\0'; 154 | 155 | return pos - str; 156 | } 157 | 158 | // The BIOS has already this function but for clearing the console's RAM it's unbearably slow. 159 | void bzero(void * start, size_t len) { 160 | uint8_t * bytes = (uint8_t *) start; 161 | while (len) { 162 | *bytes = 0; 163 | bytes++; 164 | len--; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /loader/str.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | 6 | void bzero(void * start, size_t len); 7 | 8 | void memcpy(void * dest, const void * src, size_t len); 9 | 10 | int isspace(int c); 11 | 12 | int mini_sprintf(char * str, const char * format, ...); 13 | 14 | int mini_vsprintf(char * str, const char * format, va_list args); 15 | 16 | int strlen(const char * str); 17 | 18 | char * strchr(const char * str, int c); 19 | 20 | char * strcpy(char * dest, const char * src); 21 | 22 | int strcmp(const char * a, const char * b); 23 | 24 | int strncmp (const char * a, const char * b, size_t len); 25 | -------------------------------------------------------------------------------- /loader/tonyhax-tpl.mcs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socram8888/tonyhax/ace42f56d5094782fc69e2cc7a56d12d59a75189/loader/tonyhax-tpl.mcs -------------------------------------------------------------------------------- /loader/util.c: -------------------------------------------------------------------------------- 1 | 2 | #include "gpu.h" 3 | #include "util.h" 4 | 5 | void delay_ds(uint32_t deciseconds) { 6 | uint32_t frames = deciseconds * (gpu_is_pal() ? 5 : 6); 7 | while (frames) { 8 | gpu_wait_vblank(); 9 | frames--; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /loader/util.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | /** 6 | * Executes a delay of roughly the amount of specified deciseconds (1/10th of a second) 7 | * 8 | * @param deciseconds delay duration 9 | */ 10 | void delay_ds(uint32_t deciseconds); 11 | -------------------------------------------------------------------------------- /util/bin2h.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if [ $# -ne 3 ]; then 6 | echo "Usage: $0 constant_name binary_file header_file" 7 | exit 1 8 | fi 9 | 10 | constant_name="${1}" 11 | binary_file="${2}" 12 | header_file="${3}" 13 | 14 | cat <"${header_file}" 15 | /* 16 | * Automatically generated from ${binary_file}. 17 | * Do not edit 18 | */ 19 | 20 | #include 21 | #pragma once 22 | 23 | static const uint8_t ${constant_name}[] = { 24 | $(od -v -tx1 -An <${binary_file} | sed -r "s/\b(..)\b/0x\1,/g") 25 | }; 26 | EOF 27 | -------------------------------------------------------------------------------- /util/mcs2raw.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | for mcsfile in "$@" 6 | do 7 | rawname=$(dd status=none if="$mcsfile" bs=1 skip=10 count=20 | cut -d '' -f 1) 8 | echo "Converting $mcsfile to $rawname" 9 | dd status=none bs=128 skip=1 if="$mcsfile" of="$rawname" 10 | done 11 | -------------------------------------------------------------------------------- /util/print-ps2-romdir.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import struct 4 | import sys 5 | 6 | assert(len(sys.argv) == 2) 7 | 8 | def find_romdir(f): 9 | while True: 10 | line = f.read(16) 11 | if len(line) != 16: 12 | return False 13 | 14 | if line[0:10] == b'RESET\0\0\0\0\0': 15 | # Unread reset entry 16 | f.seek(-16, 1) 17 | return True 18 | 19 | def print_romdir(f): 20 | address = 0xBFC00000 21 | 22 | while True: 23 | line = f.read(16) 24 | if len(line) != 16: 25 | return 26 | 27 | entry = struct.unpack('10sHI', line) 28 | 29 | entry_name = entry[0].rstrip(b'\0').decode('ascii') 30 | if len(entry_name) == 0: 31 | return 32 | print('%10s: %08X' % (entry_name, address)) 33 | 34 | address = (address + entry[2] + 0xF) & 0xFFFFFFF0 35 | 36 | with open(sys.argv[1], 'rb') as f: 37 | if find_romdir(f): 38 | print('ROMDIR found at %08X' % f.tell()) 39 | print_romdir(f) 40 | else: 41 | print('ROMDIR not found', file=sys.stderr) 42 | -------------------------------------------------------------------------------- /util/psx-exe-crc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | crc32() { 6 | # Super hacky way of calculating a CRC32 7 | # From https://stackoverflow.com/a/49446525/4454028 8 | gzip -c1 | tail -c 8 | od -N4 -An -tx4 | tr -dc [a-f0-9] | tr [a-f] [A-F] 9 | } 10 | 11 | if [ $# -ne 1 ]; then 12 | echo "Usage: $0 psx-exe" 13 | exit 1 14 | fi 15 | psx_exe="$1" 16 | 17 | # Small sanity check 18 | magic=$(head -c 8 "$psx_exe") 19 | if [ "$magic" != "PS-X EXE" ]; then 20 | echo "Invalid file" 21 | exit 1 22 | fi 23 | 24 | # Load size from header. 25 | # Tokimeki Memorial 2 has data that does not get loaded by LoadExe, so we can't just read until EOF 26 | read -r loadaddr loadsize < <(dd status=none if="$psx_exe" bs=1 skip=24 count=8 | od -An -tu4) 27 | printf "Uses: %08X-%08X\n" $loadaddr $(( $loadaddr + $loadsize - 1 )) 28 | 29 | # Calculate in 2048-byte sectors 30 | sectors=$(( $loadsize / 2048 )) 31 | 32 | # Calculate the CRC32 33 | crc=$(dd status=none if="$psx_exe" bs=2048 skip=1 count=$sectors | crc32) 34 | 35 | # Log it 36 | echo "CRC32: 0x$crc" 37 | -------------------------------------------------------------------------------- /variables.mk: -------------------------------------------------------------------------------- 1 | 2 | .DELETE_ON_ERROR: 3 | 4 | .PHONY: clean 5 | 6 | SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 7 | 8 | # Common variables 9 | 10 | TONYHAX_VERSION=v1.4.6b 11 | SOFTUART_PATCH=0 12 | 13 | CC=mips-linux-gnu-gcc 14 | CFLAGS=-O1 -Wall -Wextra -Wno-main -EL -march=r3000 -mabi=32 -mfp32 -mno-abicalls -fno-pic -fdata-sections -ffunction-sections -fno-builtin -nostdlib -DTONYHAX_VERSION=$(TONYHAX_VERSION) -DSOFTUART_PATCH=$(SOFTUART_PATCH) 15 | 16 | LD=mips-linux-gnu-ld 17 | LDFLAGS=-EL --gc-sections 18 | 19 | OBJCOPY=mips-linux-gnu-objcopy 20 | OBJCOPYFLAGS=-O binary 21 | 22 | # Entry point variables 23 | 24 | ENTRY_MCS := $(patsubst $(SELF_DIR)/entrypoints/%-tpl.mcs, %.mcs, $(wildcard $(SELF_DIR)/entrypoints/*-tpl.mcs)) 25 | ENTRY_RAW := \ 26 | BASCUS-9415400047975 \ 27 | BASCUS-9424400000000 \ 28 | BASCUS-9455916 \ 29 | BASLUS-00571 \ 30 | BASLUS-00856 \ 31 | BASLUS-00882CHSv1 \ 32 | BASLUS-01066TNHXG01 \ 33 | BASLUS-01384DRACULA \ 34 | BASLUS-01419TNHXG01 \ 35 | BASLUS-01485TNHXG01 \ 36 | BASLUS-01506XSMOTOv1 \ 37 | BESCES-0096700765150 \ 38 | BESCES-0142000000000 \ 39 | BESCES-0228316 \ 40 | BESLES_01182CHSv1 \ 41 | BESLES-01376 \ 42 | BESLES-02618 \ 43 | BESLES-02908TNHXG01 \ 44 | BESLES-02909TNHXG01 \ 45 | BESLES-02910TNHXG01 \ 46 | BESLES-02942CHSVTRv1 \ 47 | BESLES-03057SSBv1 \ 48 | BESLES-03645TNHXG01 \ 49 | BESLES-03646TNHXG01 \ 50 | BESLES-03647TNHXG01 \ 51 | BESLES-03827SSII \ 52 | BESLES-03954TNHXG01 \ 53 | BESLES-03955TNHXG01 \ 54 | BESLES-03956TNHXG01 \ 55 | BESLES-04095XSMOTO 56 | 57 | ENTRY_FILES := $(ENTRY_MCS) $(ENTRY_RAW) 58 | 59 | # Program loader variables 60 | 61 | LOADER_FILES := tonyhax.mcs BESLEM-99999TONYHAX tonyhax.exe 62 | 63 | # FreePSXBoot images 64 | 65 | FREEPSXBOOT_IMAGES := \ 66 | tonyhax_scph-1001_v2.0_slot1.mcd \ 67 | tonyhax_scph-1001_v2.0_slot2.mcd \ 68 | tonyhax_scph-1001_v2.1_slot1.mcd \ 69 | tonyhax_scph-1001_v2.1_slot2.mcd \ 70 | tonyhax_scph-1001_v2.2_slot1.mcd \ 71 | tonyhax_scph-1001_v2.2_slot2.mcd \ 72 | tonyhax_scph-1002_v2.0_slot1.mcd \ 73 | tonyhax_scph-1002_v2.0_slot2.mcd \ 74 | tonyhax_scph-1002_v2.1_slot1.mcd \ 75 | tonyhax_scph-1002_v2.1_slot2.mcd \ 76 | tonyhax_scph-1002_v2.2_slot1.mcd \ 77 | tonyhax_scph-1002_v2.2_slot2.mcd \ 78 | tonyhax_scph-5001_slot1.mcd \ 79 | tonyhax_scph-5001_slot2.mcd \ 80 | tonyhax_scph-5501_slot1.mcd \ 81 | tonyhax_scph-5501_slot2.mcd \ 82 | tonyhax_scph-5502_slot1.mcd \ 83 | tonyhax_scph-5502_slot2.mcd \ 84 | tonyhax_scph-5552_slot1.mcd \ 85 | tonyhax_scph-5552_slot2.mcd \ 86 | tonyhax_scph-7001_slot1.mcd \ 87 | tonyhax_scph-7001_slot2.mcd \ 88 | tonyhax_scph-7002_slot1.mcd \ 89 | tonyhax_scph-7002_slot2.mcd \ 90 | tonyhax_scph-7501_slot1.mcd \ 91 | tonyhax_scph-7501_slot2.mcd \ 92 | tonyhax_scph-7502_slot1.mcd \ 93 | tonyhax_scph-7502_slot2.mcd \ 94 | tonyhax_scph-9001_slot1.mcd \ 95 | tonyhax_scph-9001_slot2.mcd \ 96 | tonyhax_scph-9002_slot1.mcd \ 97 | tonyhax_scph-9002_slot2.mcd \ 98 | tonyhax_scph-101_v4.4_slot1.mcd \ 99 | tonyhax_scph-101_v4.4_slot2.mcd \ 100 | tonyhax_scph-101_v4.5_slot1.mcd \ 101 | tonyhax_scph-101_v4.5_slot2.mcd \ 102 | tonyhax_scph-102_v4.4_slot1.mcd \ 103 | tonyhax_scph-102_v4.4_slot2.mcd \ 104 | tonyhax_scph-102_v4.5_slot1.mcd \ 105 | tonyhax_scph-102_v4.5_slot2.mcd 106 | 107 | # Boot CD files 108 | 109 | BOOT_CD_FILES := tonyhax-boot-cd.bin tonyhax-boot-cd.cue 110 | --------------------------------------------------------------------------------