Palimpsest's dumping ground

Becoming GOD on the TI-84

Some TI hardware primer

Feel free to skip this section

First off, there are many TI-84 varieties, but for this article we are going to be covering the monochrome TI83 and Ti84, those ones are a lot more fun to play with. In these system, they run of a z80 cpu clock at 8Mhz with the ability to go up to 15Mhz, and surrounded in a much of hardware. We can communicate with this hardware with ports, like this:

    ; Read from the status port
    ld a, ($2)


    ; Write to the LCD data port.
    ld a, $FF
    out ($11), a

This lets us directly talk with hardware, and we can do a lot of things with it, but nothing permanent.

Just like a regular computer, the calculator has both permanent storage (hereafter referred to as flash), and ram for temporary storage. However, any writes to flash get ignored!

    ; Try to write 0x69 to the bootloaded, does nothing!
    ld a, $69
    ld (0000h), a

When you look in the official System Routine document, you find a grand total of one routine that can write to flash, Arc_Unarc, a painfully slow routine who requires you to create a variable to archive.

But its not just the flash

A lot of people don't know this, but TI went through an awfully lot of work securing their calculators back in the day, so much so they went on a rampage when people cracked the OS signing keys, DMCAin' anybody who dared to host them. But beyond that, half the IO ports are block off!

These are known as the protected ports, any write to them without a very special procedure will be ignored. These are some fun ones we are loosing out on:

$22/$23: Flash Execution Limits Controls what flash sectors are executable.
$25/$26: Ram Execution Limits Controls what ram addresses are executable, by default this is at only $83FF (not actually sure what this means since ram programs are at $9D95 and run fine)
$21: Flash Size / RAM Size Controls what flash sectors are privledged

Luckily for us, this protect port unlock sequence has been known for 20 years!

    nop
    nop
    im 1
    di
    out (...),a

But unfortunately for us, it can only be executed from certain ROM pages, none of which we can (easily) get any code onto:

Model OS pages Possible pages
TI-83+ 1Ch, 1Dh, 1Fh 1Eh
TI-84+ 3Ch, 3Dh, 3Fh 2Ch-2Eh, 6Ch-6Eh, 3Eh
TI-83+ SE 7Ch, 7Dh, 7Fh 7Eh
TI-84+ SE 6Fh, 7Ch, 7Dh, 7Fh 7Eh

From the WikiTi page

Exploiting the operating system

But we know the operating system does unlock the flash, after all that Arc_Unarc system routine does it somehow.

Ordinarily, this is the point where I go on a heroic story on how I alone found one, possibly with a massive fuzzing campaign. I did, in fact, spends several days fuzzing every unlock sequence in the OS, but then I remembered something. I'm an idiot, somebody probably did this at some point before!




Yup, second page of google, quite convenient!

Steal all the ram!

This is what most people are here for. You see, in the old days calculators had a 8 ram pages, 5 of which were unused by the operating system, a whopping 80KB of unused space, but, then TI attacked. Starting in ASIC version $55 [1] [2], all the unused ones were locked away, leaving us with:

Ram page
$80 The default C000h ram page, no code execution allowed. Used by the OS as user variable overflow from 8000h, and where ram programs live
$81 The default 8000h ram page, code execution allowed, but mostly used up by the OS
$83 Almost entirly unused (apart from the equation history), and we can execute code!

This leaves us with only a single page for us to have our way with! (Without corrupting user data)

But, what if we didn't have to corrupt user data? The answer is simple, flush all that ram to flash! The Ti-84 flash chip is advertised as Minimum 1,000,000 program/erase cycles per sector guaranteed [3] , so as long as we don't put it in a loop we should be safe.

But the question is, where do we stash it? There are a few options:
1. Store as a variable: Annoying, lots of system calls
2. Store in a known unused sector: Closer, but could brick a calc if our assumptions are wrong
3. Use the OS designated temp sector: ...

Yeah, the OS has swap sector built in for temporary usage, and we can ask the OS for it specifically with FindSwapSector. But what are the cons, well this routine is kind of dangerous (up to TIs typical quality standards). These are some of the ways is can ruin your day:

  1. In a worse case scenario, it defaults to page 8 EVEN IF SOMETHING IS THERE, causing possible variable corruption
  2. If you fail to mark the sector as swap (by writing $FE to the first byte of the sector), it has a habit of overwriting your app pages. As such, uninstalling random apps for the fun of it

So I will leave you with one warning: For the love of GOD, set the first byte $FE

But lets get into the meat and potatos! You want 32KB of ram right???? The answer is simple, use page $83, and then back up all the users variables from $81 (aka the typical 8000h ram page).

This method only works for flash apps, but simply swap out the regular 4000h with ram page $83, and then back up $80 (the $C000) page, and you get all the benefits (note: remember to update the code bellow)

; Save page $81 to the swap page
    call flashunlock
        ld a, $FE     ; Mark page as swap (prevent flash leaks)
        ld ($8000), a

        bcall _FindSwapSector

        push af ; Store the page for later
        push af
            bcall _EraseFlashPage ; Remove the last contents
        pop af

        ld hl, $8000 ; Src  (page $81, assumed to already be at $8000)
        ld de, $4000 ; Dst (always based at $4000)
        ld bc, $4000 ; Size (You could optimize this)
        bcall _WriteFlash   ; Note: This is quite slow (a few secs)
    call flashlock


    pop af ; Get back the flash page


    ... ; You probably want to do this in your program cleanup code 
        ; Just remember to save that flash page in 'a' somewhere
        ; and retrieve it before here!



    out ($7), a  ; This maps $8000 to the restore page. 
                 ; Please note: This is written for apps! Since asm programs
                 ; live in the $8000 this page, none of this will work!

    ld a, 1      ; Set the $C000 page to the ram page $81, this means the stack
    out ($5), a  ; is not going to work until it is restored!

    ld hl, $8000 ; Src
    ld de, $C000 ; Dst
    ld bc, $4000 ; Size
    ldir


    ld a, $81   ; Restore regular $8000 page
    out (7), a 
    xor a      ; Restore regular $C000 page
    out (5), a

Overwriting the operating system

👹👹👹👹 WARNING 👹👹👹👹
Never do this on a real calculator! Play in an emulator, delete the 8xk/8xp, and let that be it. This section describes many a way to brick you calculator! You have been warned.
HERE BE DRAGONS

Lets get out of the realm of useful, and do some magic! After the OS is installed, TI assumes it is impossible to modify, as such they never reverify the OS.

There are a few bytes that signal to the OS that is has already been verified, so just make sure not to override $0055–$0056, these are set to $A55A after verification [3]. (I suppose you could use this to prank your friends, just requires them to reinstall the os)

TODO: FINISH

Appendix: Flash unlock routines

Sourced from: snacks.zip by the_mad_joob

;#####

;flashunlock - fast variant

;DESCRIPTION
;Unlocks the flash chip.
;Inspired by thepenguin77's code.

;WARNINGS
;UNLOCKING FLASH OPENS A DOOR TO SOME DANGEROUS THINGS.
;DON'T IF YOU'RE A BEGINNER.
;IT'S HIGHLY RECOMMENDED TO CALL flashlock WHEN YOU'RE DONE.

;IN
;interrupts : disabled
;bank 2 : RAM page $01 (system default)
;code location : anywhere (see NOTES)
;stack location : bank 3
;free stack space : 40+ bytes (call included)

;OUT
;interrupt mode : 1
;b = $40
;hl = $0007
;sp = unchanged
;all other registers = ?

;NOTES
;The following addresses are written to, don't have your code there :
;   $8100>$817B
;   $81D4>$81FE
;   $82A2
;   $83E8>$83E9
;   $83EB
;   $83EE
;   $84DB>$84DC
;   $9834
;   $9836>$9837
;   $983A
;If you want their content preserved, use the non-destructive variant instead.
PUBLIC flashunlock
flashunlock:

    ld a,$14
    ld bc,flashunlock_ram_end-flashunlock_ram_start
    ld de,flashunlock_ram_start-flashunlock_return+$81E3
    ld hl,$8167
    ld iy,$0031 ; must be $0031
    ld ($83EE),a ; must be $08>$15
    ld ($84DB),hl ; must be $8167
    ld ($9834),a ; must be $03>$FF
    add a,l
    ld ($983A),a ; must be close enough to but under $80
    ld hl,flashunlock_ram_start
    ldir

    in a,($06)
    push af

    in a,($02)
    rra
    or %10111111
    ld d,a
    and $7B

    jp flashunlock_ram_start-flashunlock_return+$81E3

flashunlock_ram_start:

    out ($06),a

    ld hl,($5092)

    ld a,d
    and $7C
    out ($06),a

    ld a,$10
    cpir

    jp (hl)

flashunlock_return:

    ld hl,24
    add hl,sp
    ld sp,hl
    ld hl,$0007
    ld (hl),$FF
    ld b,%01000000

    pop af
    out ($06),a

flashunlock_wait:

    ld a,(hl)

    rla
    ret c

    and b
    jp z,flashunlock_wait-flashunlock_return+$81E3

    ld a,(hl)

    rla
    ret c

    ld (hl),$F0

    ret

flashunlock_ram_end:



;flashlock - app variant

;DESCRIPTION
;Locks the flash chip.

;IN
;interrupts : disabled
;code location : bank 1|3
;stack location : bank 1|3
;free stack space : 6 bytes (call included)

;OUT
;interrupt mode : 1
;a = page in bank 2
;f = %???????0
;b = a
;c = ?
;hl = ?

PUBLIC flashlock
flashlock:

    in a,($07)
    ld b,a

    in a,($02)
    rra
    or %10111111
    ld c,a
    and $7B
    out ($07),a

    ld hl,($8F3C)
    ld a,h
    xor %11000000
    ld h,a

    ld a,c
    and $7C
    out ($07),a

    call flashlock_jump

    ld a,b
    out ($07),a

    ret

flashlock_jump:
    jp (hl)