├── LICENSE ├── README.md └── UltraCIC.asm /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UltraCIC 2 | ======== 3 | 4 | Nintendo 64 CIC Clone 5 | --------------------- 6 | 7 | UltraCIC is a clone of the Nintendo 64 copy protection chip, the CIC. 8 | This code is meant to run on a PIC16F1613 microcontroller. 9 | 10 | Note that the code omits the seed and checksum data which is necessary 11 | to boot this code on a console. Adding this data is left as an exercise 12 | to the reader. 13 | 14 | History 15 | ------- 16 | 17 | This code was written by Mike Ryan as a part of the effort to reverse 18 | engineer the Nintendo 64 CIC. The author would like to thank: 19 | 20 | - John McMaster (die shots) 21 | - Marshall H (ROM extraction) 22 | - Segher (too smart) 23 | - Zoinkity 24 | - Markus and Markus 25 | -------------------------------------------------------------------------------- /UltraCIC.asm: -------------------------------------------------------------------------------- 1 | processor pic16f1613 2 | 3 | ; UltraCIC - Nintendo 64 CIC clone by Mike Ryan 4 | ; This code is released in the public domain 5 | 6 | ; Pins 7 | ; Num Port Function 8 | ; 9 C.1 PIF_DATA (bidir) 9 | ; 10 C.0 PIF_DCLK 10 | 11 | include p16f1613.inc 12 | 13 | ;;;;;;;;;;;;;; 14 | ; DEFINES 15 | ;;;;;;;;;;;;;; 16 | variable region = 0 ; 0 is NTSC, 1 is PAL 17 | 18 | 19 | ; CONFIG1 20 | __CONFIG _CONFIG1, _FOSC_ECM & _PWRTE_OFF & _MCLRE_ON & _CP_OFF & _BOREN_OFF & _CLKOUTEN_ON 21 | ; CONFIG2 22 | __CONFIG _CONFIG2, _WRT_OFF & _ZCD_ON & _PLLEN_ON & _STVREN_ON & _BORV_LO & _LPBOR_OFF & _LVP_ON 23 | ; CONFIG3 24 | __CONFIG _CONFIG3, _WDTCPS_WDTCPS1F & _WDTE_OFF & _WDTCWS_WDTCWSSW & _WDTCCS_SWC 25 | 26 | 27 | ;;;;;;;;;;;; 28 | ; UTILITY 29 | ;;;;;;;;;;; 30 | 31 | ; increment FSR0 32 | ; uses 70 (common RAM) as a temp register 33 | ; incf FSR0,F does not set DC on 4-bit overflow 34 | incfsr MACRO 35 | movwf 70 36 | movlw 1 37 | addwf FSR0,F 38 | movf 70,W 39 | ENDM 40 | 41 | ; adc emulation 42 | ; uses 70 as carry in 43 | adc_nocarry_out MACRO 44 | bcf STATUS, DC 45 | btfsc 70, 0 ; if carry, add 1 46 | addlw 1 47 | bcf 70, 0 ; clear fake C, will set it again below if necessary 48 | btfsc STATUS, DC 49 | bsf 70, 0 ; set fake C if there was DC 50 | ; after carry-in is accounted for, add mem 51 | addwf INDF0, W 52 | ENDM 53 | 54 | 55 | ;;;;;;;;;;; 56 | ;; ENTRY 57 | ;;;;;;;;;; 58 | ; initialize ports 59 | call init_ports 60 | 61 | movlw region 62 | movwf 72 63 | 64 | ; boot sequence 65 | movlw 0 66 | call write_bit 67 | movf 72, W ; region: 0 for NTSC, 1 for PAL 68 | call write_bit 69 | movlw 0 70 | call write_bit 71 | movlw 1 72 | call write_bit 73 | 74 | ; load seed and write that 75 | call seed_write 76 | 77 | ; load checksum and write that 78 | call checksum_write 79 | 80 | ;;;;;;;;;;;;;;;;;;;; 81 | ; MAIN LOOP 82 | ;;;;;;;;;;;;;;;;;; 83 | 84 | main_runtime: 85 | movlw 0xe 86 | movwf 20 87 | movlw 0xb 88 | movwf 31 89 | 90 | call load_pat 91 | 92 | movlw 21 93 | movwf FSR0 94 | call get_four_bits 95 | 96 | movlw 31 97 | movwf FSR0 98 | call get_four_bits 99 | 100 | main_loop: 101 | call get_bit 102 | btfss STATUS, C 103 | goto main_zero 104 | 105 | ; received a one 106 | call get_bit 107 | btfss STATUS, C 108 | ; 1 then 0, x105 mode 109 | goto x105_main 110 | 111 | ; 1 then 1 112 | bcf STATUS, C 113 | goto console_reset 114 | 115 | ; received a zero 116 | main_zero: 117 | call get_bit 118 | btfss STATUS, C 119 | goto main_zero_zero 120 | 121 | ; 0 then 1, kill yourself 122 | goto dead 123 | 124 | ; 0 then 0 125 | main_zero_zero: 126 | movlw 0x20 127 | movwf FSR0 128 | call main_algorithm 129 | call main_algorithm 130 | call main_algorithm 131 | 132 | movlw 0x30 133 | movwf FSR0 134 | call main_algorithm 135 | call main_algorithm 136 | call main_algorithm 137 | 138 | ; W = RAM[17] 139 | ; if W < F 140 | ; ++W 141 | ; [trust me on this] 142 | movf 37, W 143 | addlw 0xF 144 | btfss STATUS, DC 145 | movlw 0 146 | addlw 1 147 | 148 | ; BL = A 149 | ; such a hack 150 | bcf FSR0, 0 151 | bcf FSR0, 1 152 | bcf FSR0, 2 153 | bcf FSR0, 3 154 | andlw 0xF 155 | iorwf FSR0, F 156 | 157 | getabit: 158 | clrf 70 ; storing carry here 159 | 160 | call get_bit 161 | btfsc STATUS, C 162 | bsf 70, 0 ; save the bit 163 | 164 | ;-------- 165 | ; write lowest bit of RAM 166 | ; address is BM = 1, BL starts at RAM[17] 167 | 168 | ; BM = 1 169 | movlw 30 170 | iorwf FSR0, F 171 | 172 | ; W = lowest bit of current RAM 173 | movlw 1 174 | btfss INDF0, 0 175 | movlw 0 176 | 177 | call write_bit 178 | 179 | ; /writing lowest bit of ram 180 | ;-------- 181 | 182 | movlw 2F 183 | andwf FSR0, F 184 | 185 | ; if bit from PIF was 0, if RAM is 1 then kill yourself 186 | btfss 70, 0 187 | goto pif_said_zero 188 | 189 | ; if bit from PIF was 1, if RAM is 0 then kill yourself 190 | btfss INDF0, 0 191 | goto dead 192 | 193 | goto more_bits 194 | 195 | pif_said_zero: 196 | ; if PIF said 0 and we have 1, kill self (see above) 197 | btfss INDF0, 0 198 | goto more_bits 199 | 200 | goto dead 201 | 202 | more_bits: 203 | movlw 1 204 | 205 | ; jump down to PAL if region is 1 206 | btfsc 72, 0 207 | goto mb_pal 208 | 209 | ; NTSC 210 | addwf FSR0, F 211 | 212 | btfss STATUS, DC 213 | goto getabit 214 | 215 | goto main_loop 216 | 217 | mb_pal: 218 | ; PAL 219 | subwf FSR0, F 220 | movlw 0xF 221 | andwf FSR0, W 222 | btfss STATUS, Z 223 | goto getabit 224 | 225 | goto main_loop 226 | 227 | ; end of function 228 | 229 | 230 | ;;;;;;;;;;;;;;;;; 231 | ;; x105 algorithm 232 | ;;;;;;;;;;;;;;;;; 233 | x105_main: 234 | ; write A A 235 | movlw 0xa 236 | movwf 40 237 | 238 | movlw 40 239 | movwf FSR0 240 | call write_nibble 241 | call write_nibble 242 | 243 | ; load 40 .. 5D 244 | keep_loading: 245 | call get_four_bits 246 | incf FSR0,F 247 | call get_four_bits 248 | incf FSR0,F 249 | 250 | movlw 5E 251 | subwf FSR0,W 252 | btfss STATUS, Z 253 | goto keep_loading 254 | 255 | ; magic happens here 256 | call x105_algo 257 | 258 | movlw 0 259 | call write_bit 260 | 261 | ; write out RAM 262 | movlw 40 263 | movwf FSR0 264 | x105_write_nibbles: 265 | call write_nibble 266 | incf FSR0, F 267 | movlw 5e 268 | subwf FSR0, W 269 | btfss STATUS, Z 270 | goto x105_write_nibbles 271 | 272 | goto main_loop 273 | 274 | exc MACRO 275 | xorwf INDF0, W 276 | xorwf INDF0, F 277 | xorwf INDF0, W 278 | ENDM 279 | 280 | ; x105 core algorithm 281 | x105_algo: 282 | movlw 0x40 283 | movwf FSR0 284 | 285 | ; using lowest bit of 70 as C stand-in 286 | clrf 70 287 | bsf 70, 0 288 | 289 | movlw 0x5 290 | 291 | x105_loop: 292 | btfss INDF0, 0 293 | addlw 8 294 | exc 295 | 296 | btfss INDF0, 1 297 | addlw 4 298 | 299 | addwf INDF0, W 300 | movwf INDF0 301 | 302 | btfss 70, 0 303 | addlw 7 304 | 305 | addwf INDF0, W 306 | 307 | ; adc emulation is gross 308 | adc_no_carry_out 309 | btfsc STATUS, DC 310 | bsf 70, 0 ; set fake C if there was DC 311 | 312 | movwf INDF0 313 | comf INDF0, F 314 | 315 | movlw 5D 316 | subwf FSR0, W 317 | btfsc STATUS, Z 318 | goto done 319 | 320 | movf INDF0, W 321 | incf FSR0, F 322 | goto x105_loop 323 | 324 | done: 325 | return 326 | 327 | 328 | ; 0 1 mode 329 | console_reset: 330 | ; delay a bit 331 | movlw 0 332 | movwf 70 333 | dloop: 334 | clrwdt 335 | incf 70 336 | btfss STATUS, Z 337 | goto dloop 338 | addlw 1 339 | btfss STATUS, Z 340 | goto dloop 341 | 342 | ; let the PIF know we're done delaying 343 | movlw 0 344 | call write_bit 345 | 346 | goto main_loop 347 | 348 | ;;;;;;;;;;;;;;;;;; 349 | ;; main algorithm 350 | ;;;;;;;;;;;;;;;;;; 351 | cic_round: 352 | clrwdt 353 | 354 | ; 70 is fake carry, 71 is temporary storage 355 | clrf 70 356 | 357 | movlw 0xf 358 | iorwf FSR0, F 359 | movf INDF0, W 360 | 361 | outer_loop: 362 | movwf 71 ; 71 is "X" 363 | 364 | bsf 70, 0 365 | 366 | ; jump back to RAM[1] 367 | movlw 0xf1 368 | andwf FSR0, F 369 | 370 | ; get back saved W 371 | movf 71, W 372 | 373 | ; adc emulation 374 | adc_nocarry_out 375 | bsf 70, 0 ; set carry 376 | 377 | movwf INDF0 378 | incf FSR0, F 379 | 380 | ; adc emulation again 381 | adc_nocarry_out 382 | bsf 70, 0 ; set carry 383 | 384 | exc 385 | comf INDF0, F 386 | incf FSR0, F 387 | 388 | adc_nocarry_out 389 | btfsc STATUS, DC 390 | bsf 70, 0 ; set fake C if there was DC 391 | 392 | btfsc 70, 0 ; need to skip if there was carry 393 | goto nostore 394 | 395 | exc 396 | incf FSR0, F 397 | 398 | nostore: 399 | addwf INDF0, W 400 | movwf INDF0 401 | 402 | incf FSR0, F 403 | addwf INDF0, W 404 | exc 405 | 406 | incf FSR0, F 407 | addlw 8 408 | 409 | btfss STATUS, DC 410 | addwf INDF0, W 411 | 412 | exc 413 | incf FSR0, F 414 | 415 | inner_loop: 416 | addlw 1 417 | addwf INDF0, F 418 | 419 | ; test for overflow 420 | movlw 1 421 | addwf FSR0, W 422 | btfsc STATUS, DC 423 | goto break_loop 424 | 425 | movf INDF0, W 426 | incf FSR0, F 427 | 428 | goto inner_loop 429 | 430 | break_loop: 431 | movf 71, W 432 | addlw 0xF 433 | 434 | btfsc STATUS, DC ; inverted logic 435 | goto outer_loop 436 | 437 | return 438 | 439 | ;;;;;;;;;;;;;;;;;;;;; 440 | ;; seed and checksum 441 | ;;;;;;;;;;;;;;;;;;;;; 442 | seed_write: 443 | call load_seed 444 | ; preload b5 then mishmash twice 445 | movlw 0xb 446 | movwf 2a 447 | movlw 5 448 | movwf 2b 449 | call mashup 450 | 451 | movlw 2a 452 | movwf FSR0 453 | 454 | _loop_nibbles: 455 | call write_nibble 456 | 457 | incfsr 458 | btfss STATUS, DC 459 | 460 | goto _loop_nibbles 461 | 462 | return 463 | 464 | load_seed: 465 | movlw 0xFF ; FIXME - use a real seed 466 | movwf 2c 467 | 468 | movf 2c, W 469 | movwf 2d 470 | movwf 2e 471 | movwf 2f 472 | 473 | lsrf 2c, f 474 | lsrf 2c, f 475 | lsrf 2c, f 476 | lsrf 2c, f 477 | lsrf 2e, f 478 | lsrf 2e, f 479 | lsrf 2e, f 480 | lsrf 2e, f 481 | return 482 | 483 | checksum_write: 484 | call load_checksum 485 | 486 | _checksum_wait_low: 487 | btfsc PORTC, 0 488 | goto _checksum_wait_low 489 | 490 | movlw 0xd 491 | movwf 21 492 | 493 | ; encode the checksum by running mishmash four times 494 | movlw 20 495 | movwf FSR0 496 | call mishmash 497 | movlw 20 498 | movwf FSR0 499 | call mishmash 500 | movlw 20 501 | movwf FSR0 502 | call mishmash 503 | movlw 20 504 | movwf FSR0 505 | call mishmash 506 | 507 | ; lower CIC_OUT to indicate we're done 508 | movlw 0 509 | call write_bit 510 | 511 | movlw 20 512 | movwf FSR0 513 | 514 | _cw_dump_ram 515 | call write_nibble 516 | incfsr 517 | btfss STATUS, DC 518 | goto _cw_dump_ram 519 | 520 | return 521 | 522 | ; checksum byte 523 | cb MACRO byte 524 | movlw byte 525 | movwf INDF0 526 | incf FSR0, 1 527 | ENDM 528 | 529 | ; checksum 530 | checksum MACRO va, vb, vc, vd, ve, vf, vg, vh, vi, vj, vk, vl 531 | kb va 532 | kb vb 533 | kb vc 534 | kb vd 535 | kb ve 536 | kb vf 537 | kb vg 538 | kb vh 539 | kb vi 540 | kb vj 541 | kb vk 542 | kb vl 543 | ENDM 544 | 545 | load_checksum: 546 | movlw 24 547 | movwf FSR0 548 | 549 | ; FIXME - replace this with a real checksum 550 | checksum 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb 551 | return 552 | 553 | ;;;;;;;;;;;;; 554 | ; ENCRAPTION 555 | ;;;;;;;;;;;;; 556 | mashup: 557 | ; load 0xa into indirect reg pointer 558 | movlw 2a 559 | movwf FSR0 560 | call mishmash 561 | movlw 2a 562 | movwf FSR0 563 | call mishmash 564 | return 565 | 566 | mishmash: 567 | ; operates on indf until it hits 0x20 or 0x30 568 | movf INDF0, 0 569 | incf FSR0, 1 570 | 571 | mishmash_loop: 572 | addlw 1 573 | addwf INDF0, 0 ; A += M 574 | movwf INDF0 575 | 576 | ; increment FSR0 577 | ; DC is set if its lower 4 bits overflow 578 | incfsr 579 | 580 | ; skip if bottom four bits overflow 581 | btfss STATUS, DC 582 | goto mishmash_loop 583 | return 584 | 585 | 586 | ;;;;;;;;;;;; 587 | ;; Load RAM 588 | ;;;;;;;;;;;; 589 | 590 | pat MACRO addr, nibble 591 | movlw nibble 592 | movwf addr 593 | ENDM 594 | 595 | pat_nibbles MACRO a32, a22, a33, a23, a34, a24, a35, a25, a36, a26, a37, a27, a38, a28, a39, a29, a3A, a2A, a3B, a2B, a3C, a2C, a3D, a2D, a3E, a2E, a3F, a2F 596 | pat 0x22, a22 597 | pat 0x32, a32 598 | pat 0x23, a23 599 | pat 0x33, a33 600 | pat 0x24, a24 601 | pat 0x34, a34 602 | pat 0x25, a25 603 | pat 0x35, a35 604 | pat 0x26, a26 605 | pat 0x36, a36 606 | pat 0x27, a27 607 | pat 0x37, a37 608 | pat 0x28, a28 609 | pat 0x38, a38 610 | pat 0x29, a29 611 | pat 0x39, a39 612 | pat 0x2A, a2A 613 | pat 0x3A, a3A 614 | pat 0x2B, a2B 615 | pat 0x3B, a3B 616 | pat 0x2C, a2C 617 | pat 0x3C, a3C 618 | pat 0x2D, a2D 619 | pat 0x3D, a3D 620 | pat 0x2E, a2E 621 | pat 0x3E, a3E 622 | pat 0x2F, a2F 623 | pat 0x3F, a3F 624 | ENDM 625 | 626 | load_pat: 627 | ; jump down to PAL if region is 1 628 | btfsc 72, 0 629 | goto lp_pal 630 | 631 | ; NTSC 632 | pat_nibbles 0x1, 0x9, 0x4, 0xA, 0xF, 0x1, 0x8, 0x8, 0xB, 0x5, 0x5, 0xA, 0x7, 0x1, 0xC, 0x3, 0xD, 0xE, 0x6, 0x1, 0x1, 0x0, 0xE, 0xD, 0x9, 0xE, 0x8, 0xC 633 | return 634 | 635 | lp_pal: 636 | ; PAL 637 | pat_nibbles 0x1, 0x4, 0x2, 0xF, 0x3, 0x5, 0xF, 0x1, 0x8, 0x2, 0x2, 0x1, 0x7, 0x7, 0x1, 0x1, 0x9, 0x9, 0x8, 0x8, 0x1, 0x5, 0x1, 0x7, 0x5, 0x5, 0xC, 0xA 638 | return 639 | 640 | 641 | ; when CIC is instructed to halt: infinite loop 642 | dead: 643 | nop 644 | goto dead 645 | 646 | ;;;;;;;;;;;;;;;;;;;;; 647 | ; I/O 648 | ;;;;;;;;;;;;;;;;;;;;; 649 | 650 | ; wait for PIF to go low write a bit, then wait for it to go high 651 | write_bit: 652 | banksel PORTC 653 | 654 | _wait_low: 655 | clrwdt 656 | btfsc PORTC, 0 657 | goto _wait_low 658 | 659 | ; write out W to port 1 660 | iorlw 0 ; test if W is 0 661 | btfss STATUS, Z 662 | goto _wait_high 663 | 664 | ; zero: drive zero 665 | _zero: 666 | bcf PORTC, 1 667 | banksel TRISC 668 | bcf TRISC, 1 ; set port 1 to output 669 | banksel PORTC 670 | 671 | _wait_high: 672 | clrwdt 673 | btfss PORTC, 0 674 | goto _wait_high 675 | nop 676 | nop 677 | nop 678 | nop 679 | 680 | banksel TRISC 681 | bsf TRISC, 1 682 | banksel PORTC 683 | 684 | return 685 | 686 | 687 | ; wait for PIF_DCLK (C.0) to go low 688 | ; gets one bit of input on PIF_DATA 689 | ; wait for PIF_DCLK to go high again 690 | get_bit: 691 | bsf STATUS, C 692 | 693 | ; ensure port 1 is an input 694 | banksel TRISC 695 | bsf TRISC, 1 696 | bsf TRISC, 0 697 | banksel PORTC 698 | 699 | ; test bit 0 700 | gb_wait_low: 701 | clrwdt 702 | btfsc PORTC, 0 703 | goto gb_wait_low 704 | 705 | ; get input 706 | btfss PORTC, 1 707 | bcf STATUS, C 708 | 709 | gb_wait_high: 710 | clrwdt 711 | btfss PORTC, 0 712 | goto gb_wait_high 713 | 714 | return 715 | 716 | 717 | ; get four bits of input from the PIF 718 | ; clear bits of RAM depending on if these bits are set 719 | get_four_bits: 720 | movlw 0xF 721 | movwf INDF0 722 | 723 | call get_bit 724 | btfss STATUS, C 725 | bcf INDF0, 3 726 | call get_bit 727 | btfss STATUS, C 728 | bcf INDF0, 2 729 | call get_bit 730 | btfss STATUS, C 731 | bcf INDF0, 1 732 | call get_bit 733 | btfss STATUS, C 734 | bcf INDF0, 0 735 | return 736 | 737 | 738 | ; write a nibble pointed to by INDF0 739 | write_nibble: 740 | movlw 1 741 | btfss INDF0, 3 742 | movlw 0 743 | call write_bit 744 | movlw 1 745 | btfss INDF0, 2 746 | movlw 0 747 | call write_bit 748 | movlw 1 749 | btfss INDF0, 1 750 | movlw 0 751 | call write_bit 752 | movlw 1 753 | btfss INDF0, 0 754 | movlw 0 755 | call write_bit 756 | return 757 | 758 | ; time how long it takes the input to go low 759 | ; store the result in RAM 0 1 2 760 | ; 761 | ; This is normally used as a "random seed", for the mishmash function. The PIF 762 | ; doesn't seem to care if the seed value is the same every time, so punt. 763 | wait_low_time: 764 | btfsc PORTC, 0 765 | goto wait_low_time 766 | return 767 | 768 | 769 | init_ports: 770 | ; always input: 771 | ; - A.* 772 | ; - C.0 773 | ; 774 | ; open drain: 775 | ; - C.1 776 | 777 | ; why the FUCK do these default as analog? 778 | banksel ANSELA 779 | clrf ANSELA 780 | clrf ANSELC 781 | 782 | banksel TRISA 783 | movlw b'1111' 784 | movwf TRISA 785 | movwf TRISC 786 | 787 | banksel PORTA 788 | clrf PORTA 789 | clrf PORTC 790 | 791 | return 792 | 793 | end 794 | --------------------------------------------------------------------------------