3DS reverse engineering

Discussion of development of software for any "obsolete" computer or video game system. See the WSdev wiki and ObscureDev wiki for more information on certain platforms.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: 3DS reverse engineering

Post by tepples »

I suspect the "ghosting" might have something to do with frame blending. Some games rely on the GBA's LCD being slower than a CRT or modern TN LCD for transparency effects or to improve perceived fill rate in software texture mapping.
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

Yes, might be something like that. Or scanline interleave, or RGB color bleeding. I don't know. There seems to be a whole hacking scene for gba footers and ghosting, they do probably know how ghosting looks like. but I don't know if they are able to describe the effect. If anyone can do: Please say something!!! Help is needed!!!

For the GBA footer, the basic outline seems to be:

Code: Select all

  3dbrew suggests ".CAA" in first 16 bytes of 360h-byte footer
  yml suggests ".CAA" in last 12 bytes of 35Ch-byte footer
Well, that's probably both wrong.

But, when merging the yml & 3dbrew data, and doing lots of bold guesses - without ever having seen any of those footers - I think that the footer is 360h bytes, appended at the end of the .code file (after the ROM-image), and that it might be meant to contain these values:

Code: Select all

Config Data:
  000h 4    Unknown (guess: maybe contains a 32bit value...?)
  004h 4    ROM Size (probably same as Romsize)
  008h 4    Cartridge Type (Port 10018100h, ARM7_SAVE_TYPE)
  00Ch 4    Unknown (reportedly 0000FFFFh) (wild guess: savedata fillvalue?)
  010h 4    Unknown (Port 10018120h, reportedly 1561662 or 2607238) ;\maybe
  014h 4    Unknown (Port 10018124h, reportedly 156166  or 577077 ) ; write
  018h 4    Unknown (Port 10018128h, reportedly 134     or 388    ) ; erase
  01Ch 4    Unknown (Port 1001812Ch, reportedly 187667  or 201072 ) ;/timings?
  020h 4    LCD Ghosting (01h..FFh) (uh, what is that?)
  024h 300h LCD Video LUT (guess: maybe for Port 10400484h/10400584h or so?)
  324h 0Ch  Unknown (guess: Maybe ALL bytes just Padding, probably zero)
1st Descriptor:
  330h 4    Unknown (guess: Type 00h=ROM-Image)
  334h 4    Unknown (guess: ROM-image Offset, probably usually 0)
  338h 4    Unknown (guess: ROM-image Size, probably usually Romsize)
  33Ch 4    Unknown (guess: Padding, probably zero)
2nd Descriptor:
  340h 4    Unknown (guess: Type 01h=Config Data)
  344h 4    Unknown (guess: Config Offset, probably usually Romsize+0)
  348h 4    Unknown (guess: Config Size, probably usually 324h or 330h or so)
  34Ch 4    Unknown (guess: Padding, probably zero)
Footer Entrypoint (guess: probably in last 10h-byte of .code file):
  350h 4    GBA Footer ID          (".CAA")
  354h 4    Maybe Version          (must be 1)
  358h 4    Descriptor List Offset (probably usually Romsize+330h)
  35Ch 4    Descriptor List Size   (probably usually 20h) (N*10h)
Problem: 3dbrew says that the first descriptor must have type=01h... or more specifically, it allows type=00h as 1st descriptor... but only if next descriptor does ALSO have type=00h... which would mean that none could have type<>00h ???

Would be cool if somebody could help on finding one of those footers!
Even if it's a homebrew footer, there seems to be a homebrew tool for creating footers.
Last edited by nocash on Wed Mar 25, 2020 2:16 pm, edited 1 time in total.
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

Here are some findings about the "ARM7" registers.

Code: Select all

  10018000h 1   ARM7_CNT
  10018080h 20h ARM7_BOOTCODE
  10018100h 2   ARM7_SAVE_TYPE
  10018104h 2   ARM7_SAVE_CNT
  10018108h 2   ARM7_RTC_CNT
  10018110h 4   ARM7_RTC_BCD_DATE
  10018114h 4   ARM7_RTC_BCD_TIME
  10018118h 4   ARM7_RTC_HEX_TIME
  1001811Ch 4   ARM7_RTC_HEX_DATE
  10018120h 4   ARM7_SAVE_CFG_?
  10018124h 4   ARM7_SAVE_CFG_?
  10018128h 4   ARM7_SAVE_CFG_?
  1001812Ch 4   ARM7_SAVE_CFG_?
They are actually ARM9 registers that control GBA mode, so it might be best to rename them from ARM7_xxx to GBA_xxx or so. The first some registers do also affect NDS mode though.

Code: Select all

10018000h - ARM7_CNT (R/W)
  0-1   Console Mode (0=3DS, 1=NDS/DSi, 2=GBA, 3=Auto-replaced by 0)      (R/W)
  2-31  Unused (0)
To apply the mode value, write [10141100h].bit15=1 (CFG11_TWLMODE_0).
GBA/NDS/DSi mode will start ARM7, executing the ARM7_BOOTCODE. NDS/DSi mode
changes the ARM9 memory map (ARM9 should execute ITCM code during the mode
change). GBA mode does keep ARM9 in 3DS mode. ARM11 is always kept running in
3DS mode.

10018080h..1001809Fh - ARM7_BOOTCODE (32 bytes) (R/W)
These 32 bytes do overlay the exception vectors at 00000000h..0000001Fh in GBA
BIOS ROM (and maybe also NDS7 BIOS ROM?). The opcode(s) are executed after
setting ARM7_CNT, and then setting CFG11_TWLMODE_0.bit15.
Unknown if there is a way to DISABLE the overlay... writing the original BIOS
values might work... if the memory is still write-able once when ARM7 starts
running?... or otherwise one could change only the reset vector (eg. to MOV
PC,3000000h, and set the other vectors to original BIOS values).
Then there is something for the GBA cartridge type (ROM size versus savedata and RTC present flag).

Code: Select all

10018100h - ARM7_SAVE_TYPE (R/W)
  0-3   GBA Cartridge Type (00h-0Fh, see below)                           (R/W)
  4-15  Unused (0)
Type values (same as used in the footer of the GBA ROM-image):
  00h = ROM 16.0Mbyte, EEPROM 0.5Kbyte (in upper 16Mbyte of ROM area)
  01h = ROM 31.9Mbyte, EEPROM 0.5Kbyte (in upper 100h byte of ROM area)
  02h = ROM 16.0Mbyte, EEPROM 8Kbyte   (in upper 16Mbyte of ROM area)
  03h = ROM 31.9Mbyte, EEPROM 8Kbyte   (in upper 100h byte of ROM area)
  04h = ROM 32Mbyte, FLASH 64Kbyte, RTC   ;\(FLASH ID=3D1Fh, Atmel)
  05h = ROM 32Mbyte, FLASH 64Kbyte        ;/
  06h = ROM 32Mbyte, FLASH 64Kbyte, RTC   ;\(FLASH ID=D4BFh, SST)
  07h = ROM 32Mbyte, FLASH 64Kbyte        ;/
  08h = ROM 32Mbyte, FLASH 64Kbyte, RTC   ;\(FLASH ID=1B32h, Panasonic)
  09h = ROM 32Mbyte, FLASH 64Kbyte        ;/
  0Ah = ROM 32Mbyte, FLASH 128Kbyte, RTC  ;\(FLASH ID=09C2h, Macronix)
  0Bh = ROM 32Mbyte, FLASH 128Kbyte       ;/
  0Ch = ROM 32Mbyte, FLASH 128Kbyte, RTC  ;\(FLASH ID=1362h, Sanyo)
  0Dh = ROM 32Mbyte, FLASH 128Kbyte       ;/
  0Eh = ROM 32Mbyte, SRAM 32Kbyte         ;-SRAM
  0Fh = ROM 32Mbyte                       ;-Raw ROM

10018104h - ARM7_SAVE_CNT (R/W)
  0     Savedata mapping (0=GBA:0E000000h, 1=3DS:08080000h)               (R/W)
  1-15  Unused (0)

10018120h - ARM7_SAVE_CFG_? - R/W mask 00FFFFFFh (reset=007FD000h=8376320)
10018124h - ARM7_SAVE_CFG_? - R/W mask 00FFFFFFh (reset=007FD000h=8376320)
10018128h - ARM7_SAVE_CFG_? - R/W mask 000FFFFFh (reset=000000E0h=224)
1001812Ch - ARM7_SAVE_CFG_? - R/W mask 000FFFFFh (reset=00051000h=331776)
Maybe write/erase timings, or so? See GBA footer description in previous post for some sample values.
The RTC is the craziest that I've seen so far. One should think that RTCs are simple pieces of hardware. But this one seems to have built-in dividers for converting "Days since 1st Jan 2000" to "BCD day, month, year" (and perhaps vice-versa, too). There's also some weird support for negative HH:MM:SS values. It's just crazy ; )

Code: Select all

10018108h - ARM7_RTC_CNT (W and R)
  0     Write   (0=No change, 1=Apply RTC_HEX)                              (W)
  1     Read    (0=No change, 1=Latch RTC_BCD and RTC_HEX)                  (W)
  2-13  Unused (0)
  14    Write Error Flag      (0=Okay, 1=Error, invalid data)               (R)
  15    Write/Read Busy Flag  (0=Ready, 1=Busy)                             (R)
To get the current time:
  Set Read flag, wait until busy=0, then read RTC_HEX or RTC_BCD registers
To set the current time:
  Write RTC_HEX registers, then set Write flag, then wait until busy=0
  (this does also set BCD registers, the hardware auto-converts HEX to BCD)
The initial time on power-up in 01 Jan 2000, 00:00:00. The actual battery
backed time can be obtained from MCU[30h..36h].
The GBA software can access the RTC via port 080000C4h, 080000C6h, 080000C8h.

10018110h - ARM7_RTC_BCD_DATE (R)
  0-7   Year  BCD   (00h..99h)
  8-15  Month BCD   (01h..12h)
  16-23 Day   BCD   (01h..31h)
  24-31 Day of Week (00h..06h) (WHAT=Monday?)

10018114h - ARM7_RTC_BCD_TIME (R)
  0-7   Hour    BCD (00h..23h) (always 24-hours, even in AM/PM mode)
  8-15  Minute  BCD (00h..59h)
  16-23 Second  BCD (00h..59h)
  24-31 Zero        (00h)

10018118h - ARM7_RTC_HEX_TIME (R=Latched Read value, W=Written value)
  0-6   Second      (signed -40h..+3Fh, usually 00h..3Bh)
  7     Force Reset (0=Normal, 1=Force 1st Jan 2000, 00:00:00)
  8-14  Minute      (signed -40h..+3Fh, usually 00h..3Bh)
  15    12/24-hour  (0=12 hour, 1=24 hour) (for GBA side, both with AM/PM flag)
  16-21 Hour        (signed -20h..+1Fh, usually 00h..17h)
  22-23 Unused      (0)
  24-27 Day of Week (signed -08h..+07h, usually 00h..06h) (WHAT=Monday?)
  28-30 Unknown     (can be 0..7)
  31    Error       (0=Normal, 1=Triggers error)

1001811Ch - ARM7_RTC_HEX_DATE (R=Latched Read value, W=Written value)
  0-15  Days since 1st Jan 2000 (0000h..8EACh=100 years, Bigger=Triggers error)
  16-31 Unknown                 (can be 0000h..FFFFh)
And, the memory map for 3DS memory that gets remapped to GBA side:

Code: Select all

Memory map
  3DS       --> GBA
  08080000h --> 0E000000h, GBA Cart FLASH/SRAM/EEPROM (max 128Kbyte)
  080A0000h --> 06000000h, GBA 2D-Engine VRAM (64K+32Kbyte)
  080B8000h --> 03000000h, GBA Fast WRAM (32Kbyte)
  080C0000h --> 02000000h, GBA Slow WRAM (256Kbyte)
  20000000h --> 08000000h, GBA Cart ROM (with address shuffle!) (32Mbyte max?)
When in GBA mode, ARM9 seems to be no longer able to access the remapped ARM9
memory blocks (ARM9 only sees 00h's)? And, ARM9/ARM11 seem to HANG (without any
exception) when trying to access Main RAM at 20000000h?
At first glance, the ROM address shuffle seems works as

Code: Select all

addr = (addr AND FFFF800h) + ((addr+20h) AND 7FFh)
or maybe there's some more shuffling going on, I have used only code in WRAM so far.
Not sure what that shuffle is about, as copy protection... it would be a bit lame.
I am wondering if the .code files (those with the footer) are containing the ROM-image in shuffled form, too.

Btw. where are those GBA titles stored? And 3DS shop titles in general?
Dsiware titles are stored on the internal eMMC partition.
But GBA and 3DS titles... there seem to be no internal eMMC folders for them... or are they always stored on external SD card?
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
coto
Posts: 102
Joined: Wed Mar 06, 2019 6:00 pm
Location: Chile

Re: 3DS reverse engineering

Post by coto »

nocash wrote: Wed Mar 25, 2020 6:41 am The RTC is the craziest that I've seen so far. One should think that RTCs are simple pieces of hardware. But this one seems to have built-in dividers for converting "Days since 1st Jan 2000" to "BCD day, month, year" (and perhaps vice-versa, too). There's also some weird support for negative HH:MM:SS values. It's just crazy ; )

Code: Select all

10018108h - ARM7_RTC_CNT (W and R)
  0     Write   (0=No change, 1=Apply RTC_HEX)                              (W)
  1     Read    (0=No change, 1=Latch RTC_BCD and RTC_HEX)                  (W)
  2-13  Unused (0)
  14    Write Error Flag      (0=Okay, 1=Error, invalid data)               (R)
  15    Write/Read Busy Flag  (0=Ready, 1=Busy)                             (R)
To get the current time:
  Set Read flag, wait until busy=0, then read RTC_HEX or RTC_BCD registers
To set the current time:
  Write RTC_HEX registers, then set Write flag, then wait until busy=0
  (this does also set BCD registers, the hardware auto-converts HEX to BCD)
The initial time on power-up in 01 Jan 2000, 00:00:00. The actual battery
backed time can be obtained from MCU[30h..36h].
The GBA software can access the RTC via port 080000C4h, 080000C6h, 080000C8h.

10018110h - ARM7_RTC_BCD_DATE (R)
  0-7   Year  BCD   (00h..99h)
  8-15  Month BCD   (01h..12h)
  16-23 Day   BCD   (01h..31h)
  24-31 Day of Week (00h..06h) (WHAT=Monday?)

10018114h - ARM7_RTC_BCD_TIME (R)
  0-7   Hour    BCD (00h..23h) (always 24-hours, even in AM/PM mode)
  8-15  Minute  BCD (00h..59h)
  16-23 Second  BCD (00h..59h)
  24-31 Zero        (00h)

10018118h - ARM7_RTC_HEX_TIME (R=Latched Read value, W=Written value)
  0-6   Second      (signed -40h..+3Fh, usually 00h..3Bh)
  7     Force Reset (0=Normal, 1=Force 1st Jan 2000, 00:00:00)
  8-14  Minute      (signed -40h..+3Fh, usually 00h..3Bh)
  15    12/24-hour  (0=12 hour, 1=24 hour) (for GBA side, both with AM/PM flag)
  16-21 Hour        (signed -20h..+1Fh, usually 00h..17h)
  22-23 Unused      (0)
  24-27 Day of Week (signed -08h..+07h, usually 00h..06h) (WHAT=Monday?)
  28-30 Unknown     (can be 0..7)
  31    Error       (0=Normal, 1=Triggers error)

1001811Ch - ARM7_RTC_HEX_DATE (R=Latched Read value, W=Written value)
  0-15  Days since 1st Jan 2000 (0000h..8EACh=100 years, Bigger=Triggers error)
  16-31 Unknown                 (can be 0000h..FFFFh)
To me these seem to be hardware fixes for some games that rely and break on hardware bugs (Such as the Pokémon Berry Glitch). And since GBA SDK RTC methods target the RTC IO map, these allow to run "pure 3DS mode" or "GBA mode", but it's just a wild guess as always. ;-)
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

Like this...
"Replaced battery When the supply of power from the battery is interrupted, the RTC is reset to January 1, 2000. As with the Berry glitch , this reset causes all scheduled calendar-based events to be frozen until the RTC reaches the expected value, which may take many years."?
Well, maybe, but I think 1st January 2000 is just the begin of the century, not a game-specific hardware fix.

The thing that looks crazy to me is that the RTC seems to contain a built-in microprocessor with division functions for converting "days since 1/1/2000" into three BCD counter values; they could have as well used raw hardware counters, and handled the initialization maths on ARM11 side.
The support for negative seconds, minutes, and hours is also a bit weird, even if it's related to some kind of inner workings, it's odd to see that feature in the user write-able registers.
And the extra craziness is that the RTC's for GBA and NDS are almost identical, but the 3DS seems to use entirely different implementations for emulating them. I guess they have two different people for doing almost the same thing.
The other RTC with the bitswapped bytes (for NDS) is almost even weirder (but at least it's offloading the bitswapping to ARM11 side, without needing another bult-in microprocessor for that).
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

I have asked Sono at gbatemp for help, and got some useful info: The FIFOs are at 10310000h/10311000h, but they can be read via CDMA only.
I didn't wanted to believe the CDMA part because all other FIFOs work with CPU reads... but it's true, with CDMA it did work on first try (after countless failed attempts with CPU reads).

And I also got some more info on the Scaling registers from Sono: The 8x6 word arrays are for scaling up to 8 output pixels, each from 6 input pixels. And, one must use value 400h for full brightness (I had tried 7FFh, which apparently caused some weird multiply overflow, causing white to get converted to dark gray).

I couldn't grasp why the registers where named MTX_xxx and KRN_xxx, so I have renamed everything back to the LGYFB_xxx (Legacy Framebuffer). MTX and KRN did only remind me about 3D/maths and OS/kernels, but hardly about bitmap scaling. And "screen width" versus "array/pattern width" did also confuse me, so I have changed the latter from "width" to "length". Anyways, the new names are...

Code: Select all

3DS Video LGY Registers (Legacy GBA/NDS Video to Framebuffer)
-------------------------------------------------------------

The LGYFB units are for forwarding GBA/NDS/DSi video to 3DS screens with
optional scaling. The input comes directly from the GBA/NDS video controller,
the output must be DMAed to memory.
That is, ARM11 must handle that memory transfers in background while running
GBA/NDS/DSi software on ARM7/ARM9 side.

10110000h - LGYFB0 (Legacy Framebuffer 0) (NDS bottom screen) (and GBA)
10111000h - LGYFB1 (Legacy Framebuffer 1) (NDS top screen) (and GBA)
  10110000h/10111000h 4     LGYFB_CNT        R/W   mask:00019f37h  ;\
  10110004h/10111004h 4     LGYFB_SIZE       R/W   mask:01ff01ffh  ; Control
  10110008h/10111008h 4     LGYFB_IRQ_STAT   R/ack mask:01f80007h  ; Status
  1011000Ch/1011100Ch 4     LGYFB_IRQ_ENABLE R/W   mask:0007h      ;/
  10110020h/10111020h 4     LGYFB_ALPHA      R/W   mask:000000ffh  ;-Alpha
  101100F0h/101110F0h 4     LGYFB_UNKNOWN    R/W   mask:0000000fh  ;-Unknown?
  10110100h/10111100h 4     LGYFB_DITHER0    R/W   mask:0000cccch  ;\
  10110108h/10111108h 4     LGYFB_DITHER1    R/W   mask:0000cccch  ; Dither
  10110110h/10111110h 4     LGYFB_DITHER2    R/W   mask:0000cccch  ;
  10110118h/10111118h 4     LGYFB_DITHER3    R/W   mask:0000cccch  ;/
  10110200h/10111200h 4     LGYFB_V_LEN      R/W   mask:00000007h  ;\Vertical
  10110204h/10111204h 4     LGYFB_V_PATTERN  R/W   mask:000000ffh  ; Scaling
  10110240h/10111240h 4x30h LGYFB_V_ARRAY    R/W   mask:0000fff0h  ;/
  10110300h/10111300h 4     LGYFB_H_LEN      R/W   mask:00000007h  ;\Horizontal
  10110304h/10111304h 4     LGYFB_H_PATTERN  R/W   mask:000000ffh  ; Scaling
  10110340h/10111340h 4x30h LGYFB_H_ARRAY    R/W   mask:0000fff0h  ;/
  10310000h/10311000h 1000h LGYFB_FIFO       R     CDMA only       ;-Output
The registers are almost fully known now (except one bit in LGYFB_CNT.bit16, and four bits in LGYFB_UNKNOWN).

Code: Select all

10110000h/10111000h - LGYFB_CNT (R/W)
  0     Start/Enable               (0=Stop, 1=Start)                      (R/W)
  1     Enable Vertical Scaling    (0=Disable, 1=Enable)                  (R/W)
  2     Enable Horizontal Scaling  (0=Disable, 1=Enable)                  (R/W)
  3     Unused (0)
  4     Brightness Dither Enable   (0=No, 1=Use Y2R_DITHER0-3)            (R/W)
  5     Brightness Dither, too?    (as above, no Y2R-style Pulsation)     (R/W)
  6-7   Unused (0)
  8-9   Output Format RGBA      (0=8888, 1=8880, 2=5551, 3=5650)          (R/W)
  10-11 Output Clockwise Rotate (0=None, 1=90', 2=180', 3=270')           (R/W)
  12    Output Swizzle          (0=LinearFramebuf, 1=MortonSwizzleTexture)(R/W)
  13-14 Unused (0)
  15    Enable DMA              (0=Off, 1=Enable CDMA 0Dh/0Eh)            (R/W)
  16    Unknown... seems to have no effect on GBA (maybe for NDS?)        (R/W)
  17-31 Unused (0)
Once when started, the transfer does auto-repeat each frame (although, that may
hang with some/wrong settings; in that case it can help to toggle CNT.bit0
after DMAing the last block of each frame).

10110004h/10111004h - LGYFB_SIZE (R/W)
  0-8   Output Width (after scaling), minus 1  (0..1FFh = 1..512 pixels)  (R/W)
  9-15  Unused (0)
  16-24 Output Height (after scaling), minus 1 (0..1FFh = 1..512 pixels)  (R/W)
  25-31 Unused (0)
Caution: Must be written via 32bit STR (trying to use 16bit STRH will set BOTH
halfwords to the same value).

10110008h/10111008h - LGYFB_IRQ_STAT (R/ack)
  0     First 8-Line Output Block (0=No, 1=Yes/IRQ) (write 1 to clear)  (R/ack)
  1     Next 8-Line Output Block  (0=No, 1=Yes/IRQ) (write 1 to clear)  (R/ack)
  2     Last Input? Line          (0=No, 1=Yes/IRQ) (write 1 to clear)  (R/ack)
  3-15  Unused (0)
  16-24 Output Block Line Number for IRQ bit0/1 (step 8)                    (R)
  25-31 Unused (0)
The Output Block Line Number can be used to compute destination address for IRQ
bit0/1. The initial line number upon reset is random/garbage (but gets valid
after setting LGYFB_CNT.bit0).
Overrun can occur when not reading the output FIFO fast enough. After overrun,
bit1 triggers only on each 2nd block, and bit2 won't trigger at all.

1011000Ch/1011100Ch - LGYFB_IRQ_ENABLE - ? (R/W)
  0     First 8-Line Output Block (0=Off, 1=Enable IRQ 4Ch/4Dh)           (R/W)
  1     8-Line Output Blocks      (0=Off, 1=Enable IRQ 4Ch/4Dh)           (R/W)
  2     Last Input? Line          (0=Off, 1=Enable IRQ 4Ch/4Dh?)          (R/W)
  3-31  Unused (0)
IRQ enable does also require LGYFB_CNT.bit0=1 and CFG11_TWLMODE_0.bit15=1.
The end of frame irq occurs only if the blocks were actually transferred (via
CDMA).

10110020h/10111020h - LGYFB_ALPHA - (R/W)
  0-7   Alpha value for all pixels    (00h..FFh = Transparent..Solid)
  8-31  Unused (0)
Used as alpha for output format 8888 and 5551 (the latter uses only bit7 of the
8bit value).

101100F0h/101110F0h - LGYFB_UNKNOWN (R/W)
  0-3   Unknown (initially 0Fh on reset)
  4-31  Unused (0)
Unknown. IRQ and DMA requests won't occur when using too small values. Without
scaling values 01h..0Fh are working, with 2x vertical scaling only values
06h..0Fh are working.

10110100h/10111100h - LGYFB_DITHER0 (R/W)
10110108h/10111108h - LGYFB_DITHER1 (R/W)
10110110h/10111110h - LGYFB_DITHER2 (R/W)
10110118h/10111118h - LGYFB_DITHER3 (R/W)
  0-31  Dither alike Y2R, R/W-mask 0000CCCCh

10310000h/10311000h - LGYFB_FIFO (R)
Caution: This FIFO works via CDMA only (unlike most or all other FIFOs, it does
trigger data abort when trying to read via CPU LDR opcodes; even if there is
data in the FIFO).
  0-31  Output FIFO (contains 8 output lines per DMA request)
Use DMAWFP opcode (Wait for Peripheral) before reading an 8-line block. Or,
wait for LGYFB_IRQ_STAT bit0/1, and then manually start the DMA for one 8-line
block (the latter can be useful for transfers with clockwise rotate; where one
may need to patch the destination address for each block).
And, the scaling registers...

Code: Select all

10110200h/10111200h - LGYFB_V_LEN - Vertical scaling (R/W)
10110300h/10111300h - LGYFB_H_LEN - Horizontal scaling (R/W)
  0-2   Batch size-1  (0..7 = 1..8 dst pixels) (using (1..8)*6 array entries)
  3-31  Unused (0)
Selects the number of pattern bits and array entries to be used (before
repeating the scaling pattern).

10110204h/10111204h - LGYFB_V_PATTERN - Vertical scaling (R/W)
10110304h/10111304h - LGYFB_H_PATTERN - Horizontal scaling (R/W)
  0-7   Read a new src pixel before computing 1st..8th dst pixel (0=No, 1=Yes)
  8-31  Unused (0)
"The amount of set bits determine how many pixels are read each batch."
"Any bit indexes past LGYFB_x_LEN are ignored."
"This value is 8 bits, but it has to be written with a 32bit write."
Example values:
  Len Pattern    Effect
  1   xxxxxxx1b  No scaling    (1 input pixels --> 1 output pixels)
  8   11111111b  No scaling    (8 input pixels --> 8 output pixels)
  5   xxx01111b  Scale by 1.25 (4 input pixels --> 5 output pixels) ;NDS/DSi
  4   xxxx0111b  Scale by 1.33 (3 input pixels --> 4 output pixels)
  3   xxxxx011b  Scale by 1.5  (2 input pixels --> 3 output pixels)
  6   xx011011b  Scale by 1.5  (4 input pixels --> 6 output pixels) ;GBA
  3   xxxxx001b  Scale by 3    (1 input pixels --> 3 output pixels)
  2   xxxxxx01b  Scale by 2    (1 input pixels --> 2 output pixels)
  8   01010101b  Scale by 2    (4 input pixels --> 8 output pixels)
 GBA (240x160) scale by 1.5  = 3DS top screen (360x240)
 GBA (240x160) scale by 1.33 = 3DS bottom screen (320x213)
 NDS (256x192) scale by 1.25 = 3DS either screen (320x240)

10110240h/10111240h - LGYFB_V_ARRAY - Vertical scaling, 6x8 words (R/W)
10110340h/10111340h - LGYFB_H_ARRAY - Horizontal scaling, 6x8 words (R/W)
This array contains 6x8 words, used to compute up to 8 output pixels, with
brightness multipliers for 6 input pixels each.
  0-3   Unused (0) (Nintendo writes 16bit to bit0-15, but bit0-3 are ignored)
  4-15  Brightness per source pixel (signed, -800h..+7FFh; 400h=full/max)
  16-31 Unused (0)
The sum of six input values should be 400h (the hardware does automatically
clip results to min/max brightness; clipping can happen when mixing positive
and negative values; with some of them getting multiplied with dark input
pixels).
Note: Multipliers bigger than 400h are glitchy (value 7FFh somehow converts
white pixels to dark gray).
And, the default values for scaling by 1.5 and 1.25 (as used by Nintendo):

Code: Select all

Default Array for GBA screen (scale by 1.5) (240x160 to 360x240)
This is using Pattern=011011b and Length=6 (minus 1). The array entries are
straight ahead, using pixels with full brightness, and merged pixels with half
brightness:
  0000h,0000h,0000h,0000h,0000h,0000h, N/A , N/A  <-- for 1st input pixel
  0000h,0000h,0000h,0000h,0000h,0000h, N/A , N/A  <-- for 2nd input pixel
  0000h,2000h,4000h,0000h,2000h,4000h, N/A , N/A  <-- for 3rd input pixel
  4000h,2000h,0000h,4000h,2000h,0000h, N/A , N/A  <-- for 4th input pixel
  0000h,0000h,0000h,0000h,0000h,0000h, N/A , N/A  <-- for 5th input pixel
  0000h,0000h,0000h,0000h,0000h,0000h, N/A , N/A  <-- for 6th input pixel
    |     |     |     |     |     |
    |                             '-----------------> to 6th output pixel
    '-----------------------------------------------> to 1st output pixel
For whatever reason, this is scaling by 6:4 with 6 output pixels (instead of
3:2 with 3 output pixels). Unknown if it's faster that way, or if there's some
other advantage.

Default Array for NDS/DSi screens (scale by 1.25) (256x192 to 320x240)
This is using Pattern=01111b and Length=5 (minus 1). The array entries contain
positive and negative values, which might raise contrast between bright/dark
pixels:
  0000h,004Eh,011Dh,01E3h,01C1h, N/A , N/A , N/A  <-- for 1st input pixel
  0000h,FCA5h,F8D0h,F69Dh,F873h, N/A , N/A , N/A  <-- for 2nd input pixel
  0000h,0D47h,1E35h,2F08h,3B6Fh, N/A , N/A , N/A  <-- for 3rd input pixel
  4000h,3B6Fh,2F08h,1E35h,0D47h, N/A , N/A , N/A  <-- for 4th input pixel
  0000h,F873h,F69Dh,F8D0h,FCA5h, N/A , N/A , N/A  <-- for 5th input pixel
  0000h,01C1h,01E3h,011Dh,004Eh, N/A , N/A , N/A  <-- for 6th input pixel
    |     |     |     |     |
    |                       '-----------------------> to 5th output pixel
    '-----------------------------------------------> to 1st output pixel
Weirdly, the values for 2nd-5th output pixel values sum up to 3FDDh/3FAAh
(actually less, because the lower 4bit are ignored), making them a bit darker
than 1st output pixel.
LCD ghosting remains a mystery for now.
But well, the name implies that it must be something that couldn't be explained.
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

GBA Mode is mostly working. I can run code in RAM without problems, and also run the GBA BIOS intro, display GBA video via above LGYFB registers, and managed to enable GBA sound. The sound requires two settings in CODEC_SNDEXCNT (Port 10145000h), bit6-11 is GBA volume, and bit15 enables GBA sound (maybe these bits are for NDS/DSi volume, too).

But GBA Cartridge ROM is unstable.
In lack of a real GBA cartridge slot, the ROM is emulated by storing the ROM-image in 3DS FCRAM, and that memory gets mapped to the ROM space once when in GBA mode (with the odd 20h-byte shuffle mentioned a few days ago).
I am using Magic Floor for testing. Sometimes it's working, sometimes it does crash, and sometimes it won't even show an intact Nintendo logo.

To see what is wrong, I have mapped two copies of the 5.5Kbyte game, one in ROM space, and one in RAM space, and then used a memory compare function on ARM7. That shows about 1-5 words with mismatching data. Repeating the memory compare (without rewriting the memory) does again show around 1-5 errors, but at different addresses.

So, the reading is unreliable, either some timing issue, or maybe collisions with some kind of external memory refresh? Is there anything known about configuring FCRAM?

3dbrew mentions a MB81EDS516545 datasheet for (parts of) the memory chip, which shows some configuration registers, but they can be accessed only by manipulating /RAS and /CAS and some other signals. The datasheet does say that they must be initialized after power-up, but I don't know if or the 3DS could do that... it might do so automatically, or have some write-config feature, or some enter-config-mode feature (then allowing to write config data to FCRAM memory space)?

Apart from the FCRAM's built-in registers, there might be more config stuff in the 3DS itself, I've tried to toggle various unknown bits (and, no, I don't have data cache's enabled). The NDS does set a Async memory mode bit in EXMENCNT before entering GBA mode, maybe something similar must be done on 3DS, too. Or, maybe one must actually first switch to NDS mode and initialize that EXMENCNT register on NDS side - instead of directly switching from 3DS to GBA mode.

I am running out of ideas what to try next.
Would be very interesting if anybody ever came across something that looked like FCRAM configuration!
Or, of course, any GBA-mode specific config details.
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

This doesn't help on the unstable FCRAM issue, but I've had a look at the "AXI Registers" registers.

Here's the overall Register Map.

Code: Select all

PrimeCell High-Performance AXI Bus Matrix (HPM) (PL301) Revision: r1p2
This is something for configuring interactions between Master Interfaces (MIs)
and Slave Interfaces (SIs). The datasheet contains only meaningless blurb in a
too-big-too-fail language. Guessing between the lines, Master does probably
refer to CPUs and DMA controllers, and Slave might refer to Memory Chips.
  1020F000h-3FCh   Unused (0)                                               (-)
  1020F400h+MI*20h QoS Tidemark for Master MI=00h                         (R/W)
  1020F404h+MI*20h QoS Access Control for Master MI=00h                   (R/W)
  1020F408h+MI*20h AR Channel Arbitration value for MI=00h..NumMI-1       (R/W)
  1020F40Ch+MI*20h AW Channel Arbitration value for MI=00h..NumMI-1       (R/W)
  1020F800h-FBCh   Reserved (0)                                             (-)
  1020FFC0h        PrimeCell Configuration Register 0 NumSI's (07h/0Ah)     (R)
  1020FFC4h        PrimeCell Configuration Register 1 NumMI's (11h/16h)     (R)
  1020FFC8h        PrimeCell Configuration Register 2 Zero    (00h)         (R)
  1020FFCCh        PrimeCell Configuration Register 3 Zero    (00h)         (R)
  1020FFD0h-FDCh   Reserved (0)                                             (-)
  1020FFE0h-FECh   PrimeCell Peripheral Register 0,1,2,3 (01h,13h,x4h,00h)  (R)
  1020FFF0h-FFCh   PrimeCell ID Register 0,1,2,3         (0Dh,F0h,05h,B1h)  (R)
These registers allow to read some fixed settings, and to change a few variable
settings (there must be a lot more fixed internal settings assigned at
manufacturing time, but one cannot read or change them).
The 3DS seems to support 1020F400h/1020F404h for Master 0 only.
Whilst 1020F408h/1020F40Ch are supported for Master 0 and up.
There are registers for "Quality of Service" (whatever that is):

Code: Select all

1020F400h+MI*20h - QoS Tidemark for Master MI=00h (R/W)
  0-6   Max number of outstanding transactions before activating QoS (0..7Fh)
  7-31  Unused (0)
A value of 00h does completely disable QoS (instead of instantly triggering it
upon 0 outstanding transactions). QoS is short for Quality of Service (whatever
than means).

1020F404h+MI*20h - QoS Access Control for Master MI=00h (R/W)
  0-6   Permit Slave 0-6 to use reserved slots (1=Yes) ;\Old3DS mode
  7-31  Unused (0)                                     ;/
  0-9   Permit Slave 0-9 to use reserved slots (1=Yes) ;\New3DS mode
  10-31 Unused (0)                                     ;/
And registers for Arbitration (whatever that is):

Code: Select all

1020F408h+MI*20h AR Channel Arbitration value for MI=00h..NumMI-1 (R/W)
1020F40Ch+MI*20h AW Channel Arbitration value for MI=00h..NumMI-1 (R/W)
Arbitration for AXI read (AR) and AXI write (AW) address channel signals.
The meaning of these register depends on the chip-configuration. There are
three possible modes for each MI (of which, the 3DS uses LRG for MI=00h, and
Fixed RR for MI=01h and up).
 Programmable Least Recently Granted (LRG) arbitration (3DS: used for MI=00h)
  Write ii00pp00h   ;set priority for interface                         ;-write
  Write FF0000iih   ;select interface for reading                       ;\read
  Read  0000ppiih   ;read priority for previously selected interface    ;/
 Fixed Round-robin (RR) arbitration scheme (3DS: used for MI=01h..NumMI-1)
  Write xxxxxxxxh   ;ignored (values are fixed)                         ;-write
  Write FF0000ssh   ;select slot for reading                            ;\read
  Read  000000iih   ;read interface for previously selected slot        ;/
 Programmable Round-robin (RR) arbitration scheme (3DS: not used)
  Write ss0000iih   ;set interface for slot                             ;-write
  Write FF0000ssh   ;select slot for reading                            ;\read
  Read  000000iih   ;read interface for previously selected slot        ;/
Whereas, the parameter bits are:
  pp = Priority               (00h..FFh; 00h=Highest, FFh=Lowest)  ;for LRG
  ss = Slot number            (00h..unknown max value)             ;for RR
  ii = Slave interface number (00h..NumSI-1)
  FF = Fixed code for reading (FFh)
  00 = Unused/reserved        (00h)
And some read-only ID registers:

Code: Select all

1020FFC0h PrimeCell Configuration Register 0 Num SIs (R)
1020FFC4h PrimeCell Configuration Register 1 Num MIs (R)
  0-7   Number of Master/Slave Interfaces (MIs/SIs) (01h..20h)
  8-31  -
The values here change depending on whether running in Old3DS or New3DS mode.
  Old3DS Mode: NumMI=11h, NumSI=07h
  New3DS Mode: NumMI=16h, NumSI=0Ah

1020FFC8h PrimeCell Configuration Register 2 Zero (R)
1020FFCCh PrimeCell Configuration Register 3 Zero (R)
  0-31  Zero

1020FFE0h-FECh - PrimeCell Peripheral Register 0-3 (R)
This region contains four 32bit registers (01h,13h,x4h,00h). One is supposed to
extract the lower 8bit of these 32bit values, and then to merge them into a
"conceptual-32bit-value" (00x41301h). And then interprete it as so:
  0-11  Part number            (301h=HPM)
  12-19 Designer               (41h=ARM)
  20-23 Revision               (1=r1p0, 2=r1p1, 3=r1p2)
  24-31 Reserved (undef)
This value is fixed=00341301h on New3DS (ie. r1p2, no matter if running in
Old3DS or New3DS mode).
The value on actual Old3DS is unknown (although, some consoles are reportedly
using r1p0?).

1020FFF0h-FFCh - PrimeCell ID Register 0-3 (R)
This region contains four 32bit registers (0Dh,F0h,05h,B1h). One is supposed to
extract the lower 8bit of these 32bit values, and then to merge them into a
"conceptual-32bit-value" (B105F00Dh). And then interprete it as so:
  0-31  Component ID (B105F00Dh) (same ID as for Corelink DMA controller)
I have dumped the Arbitration registers on New3DS (in both Old3DS and New3DS mode):

Code: Select all

The following settings exist on New3DS (the fixed values are same for AR+AW):
  Master  Old3DS Mode                        New3DS Mode
  MI=00h  pp pp pp pp pp pp pp -- -- -- --   pp pp pp pp pp pp pp pp pp pp --
  MI=01h  -- 01 02 03 04 05 06 -- -- -- --   -- 01 02 03 04 05 06 07 08 09 --
  MI=02h  01 02 05 06 -- -- -- -- -- -- --   01 02 05 06 08 09 -- -- -- -- --
  MI=03h  01 02 04 05 06 -- -- -- -- -- --   01 02 04 05 06 08 09 -- -- -- --
  MI=04h  01 02 04 -- -- -- -- -- -- -- --   01 02 04 08 -- -- -- -- -- -- --
  MI=05h  01 02 04 -- -- -- -- -- -- -- --   01 02 04 08 -- -- -- -- -- -- --
  MI=06h  01 02 04 -- -- -- -- -- -- -- --   01 02 04 08 -- -- -- -- -- -- --
  MI=07h  01 02 -- -- -- -- -- -- -- -- --   01 02 08 -- -- -- -- -- -- -- --
  MI=08h  01 02 -- -- -- -- -- -- -- -- --   01 02 08 -- -- -- -- -- -- -- --
  MI=09h  01 02 -- -- -- -- -- -- -- -- --   01 02 08 -- -- -- -- -- -- -- --
  MI=0Ah  01 02 05 -- -- -- -- -- -- -- --   01 02 05 08 -- -- -- -- -- -- --
  MI=0Bh  -- -- -- -- -- -- -- -- -- -- --   02 08 -- -- -- -- -- -- -- -- --
  MI=0Ch  -- -- -- -- -- -- -- -- -- -- --   08 02 -- -- -- -- -- -- -- -- --
  MI=0Dh  -- -- -- -- -- -- -- -- -- -- --   01 08 -- -- -- -- -- -- -- -- --
  MI=0Eh  -- -- -- -- -- -- -- -- -- -- --   -- -- -- -- -- -- -- -- -- -- --
  MI=0Fh  -- -- -- -- -- -- -- -- -- -- --   -- -- -- -- -- -- -- -- -- -- --
  MI=10h  -- -- -- -- -- -- -- -- -- -- --   -- -- -- -- -- -- -- -- -- -- --
  MI=11h  -- -- -- -- -- -- -- -- -- -- --   01 08 -- -- -- -- -- -- -- -- --
  MI=12h  -- -- -- -- -- -- -- -- -- -- --   01 08 -- -- -- -- -- -- -- -- --
  MI=13h  -- -- -- -- -- -- -- -- -- -- --   01 08 -- -- -- -- -- -- -- -- --
  MI=14h  -- -- -- -- -- -- -- -- -- -- --   -- 01 02 03 04 05 06 07 08 09 --
  MI=15h  -- -- -- -- -- -- -- -- -- -- --   -- 01 02 03 04 05 06 07 08 09 --
Entries with value "01..09" are fixed (and have same values for AR and AW).
Entries marked "--" are reading as zero (but there seems to be no way to
distinguish between SI=00h and SI=None).
The priority values (pp) for MI=00h are ininitally 00h, but can be changed,
New3DS has 34 priority values in total (7+7 for AR+AW in Old3DS mode, and
another 10+10 for AR+AW in New3DS mode).
If Master really means CPU/DMA, and Slave means Memory... here are some guesses what they might be (in no specific order):

Code: Select all

Guesses on possible Master Interfaces (MIs)
Old3DS = 11h MI's
  1xARM11 (with 2 CPU cores)
  1xCDMA  (with 8 channels)
  1xCSND  (with 32+2 sound+capture channels)
  1xGPU   (internal rendering, external to 2 LCD's, and memcopy/memfill)
  1xDSP
  1xARM9                                ;\
  1xXDMA  (with 4 channels)             ; ARM9
  1xNDMA  (with 8 channels)             ;
  1xDMA   (with 4 channels)             ;/
  1xARM7                                ;\
  1xNDMA  (with 4 channels)             ; ARM7
  1xDMA   (with 4 channels)             ; (can't really share ARM11 bus though)
  1xNDS/GBA GPU (2x 2D and 1x 3D)       ;
  1xNDS/GBA Sound (with 15+2 channels)  ;/
New3DS = 16h MIs (five more than Old3DS)
  0xNewARM11 (but with 2 more CPU cores)
  1xNewCDMA  (with 8 channels)
  1xMVD
  1xLevel 2 Cache Controller

Guesses on possible Slave Interfaces (SIs)
Old3DS = 07h SI's
  FCRAM
  VRAM
  ARM9 RAM
  DSP RAM
  AXI RAM
  BIOS ROM(s)
  I/O Area(s)
New3DS = 0Ah SIs (three more than Old3DS)
  Extended VRAM? aka QTM?
  Extended ARM9 RAM
  Extended FCRAM
I have used the "DDI0422D_hpm_pl301_r1p2_ts.pdf" datasheet as reference, which does match the ID values found on New3DS.
For whatever reason, 3dbrew suggests using an older r1p0 datasheet, which might hint on r1p0 being used in Old3DS(?)

And a summary: Most of the registers are read-only. The only things that can be changed are:
- One can change the 2 QoS registers for MI=0.
- One can change the 34 priority settings for MI=0.
And, switching between Old3DS and New3DS mode does somewhat indirectly change some fixed settings.

I don't know if the firmware is changing the power up default settings.
(or if there are any positive/negative effects at all when changing any of those settings)
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
PSI
Posts: 17
Joined: Mon May 13, 2019 5:32 pm

Re: 3DS reverse engineering

Post by PSI »

Have you tried first switching to NDS/DSi mode before switching to GBA mode? It sounds as though you're switching from 3DS to GBA mode directly, and I don't know if the 3DS is designed to do that. It might help to look at what AGB_FIRM does for initializing the various magic registers as well.
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

Yes, my mode switch is done directly from 3DS mode to GBA mode. Can you already emulate GBA FIRM well enough to see what it is writing to the ARM7_CNT register? Or can somebody see how it is done in the GBA FIRM disassembly? It should be either...

Code: Select all

[10018000h]=1   ;ARM7_CNT switched to DSi mode instead of directly switching to GBA mode
    or
[10018000h]=2   ;ARM7_CNT switched to GBA mode directly
The latter seems to be 99% working (the only issue is the unstable FCRAM), so I would almost bet that the FCRAM issue could be fixed on ARM11 side, without needing to go through NDS/DSi mode. But I may be wrong there.

I am currently working on running code in NDS/DSi mode, once when that's working I can try to switch from DSi to GBA mode. Or first switch from DSi to NDS mode before switching to GBA mode. Or maybe it will turn out that DSi mode is suffering the same issues with FCRAM.
Going by first test results, one must enable Main RAM in NDS/DSi EXMEMCNT register before seeing FCRAM at 02000000h, and then... it seems that DSi mode does only use the lower 16bit of the 64bit FCRAM databus... and there might be some more address shuffling going on.

For FCRAM init, there is that Port 10141210h CFG11_GPU_FCRAM_CNT register. I don't know if that's related to GPU and/or FCRAM, and if some settings in that register could switch FCRAM to a GBA-compatible mode.

Is there any possibly FCRAM init in the offical FIRM's? The NATIVE FIRM should theoretically initialize FCRAM before using FCRAM. And NDS/GBA FIRM's might reinitialize FCRAM before switching to GBA/NDS mode. If there is any such code, it should probably look like this...

Code: Select all

[io_port]    = fcram_config_setting
    or
[io_port]    = fcram_config_enable
[fcram_area] = fcram_config_setting
[io_port]    = fcram_config_disable
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
coto
Posts: 102
Joined: Wed Mar 06, 2019 6:00 pm
Location: Chile

Re: 3DS reverse engineering

Post by coto »

nocash wrote: Fri Apr 03, 2020 4:34 am Yes, my mode switch is done directly from 3DS mode to GBA mode. Can you already emulate GBA FIRM well enough to see what it is writing to the ARM7_CNT register? Or can somebody see how it is done in the GBA FIRM disassembly? It should be either...

Code: Select all

[10018000h]=1   ;ARM7_CNT switched to DSi mode instead of directly switching to GBA mode
    or
[10018000h]=2   ;ARM7_CNT switched to GBA mode directly
The latter seems to be 99% working (the only issue is the unstable FCRAM), so I would almost bet that the FCRAM issue could be fixed on ARM11 side, without needing to go through NDS/DSi mode. But I may be wrong there.

I am currently working on running code in NDS/DSi mode, once when that's working I can try to switch from DSi to GBA mode. Or first switch from DSi to NDS mode before switching to GBA mode. Or maybe it will turn out that DSi mode is suffering the same issues with FCRAM.
Going by first test results, one must enable Main RAM in NDS/DSi EXMEMCNT register before seeing FCRAM at 02000000h, and then... it seems that DSi mode does only use the lower 16bit of the 64bit FCRAM databus... and there might be some more address shuffling going on.

For FCRAM init, there is that Port 10141210h CFG11_GPU_FCRAM_CNT register. I don't know if that's related to GPU and/or FCRAM, and if some settings in that register could switch FCRAM to a GBA-compatible mode.

Is there any possibly FCRAM init in the offical FIRM's? The NATIVE FIRM should theoretically initialize FCRAM before using FCRAM. And NDS/GBA FIRM's might reinitialize FCRAM before switching to GBA/NDS mode. If there is any such code, it should probably look like this...

Code: Select all

[io_port]    = fcram_config_setting
    or
[io_port]    = fcram_config_enable
[fcram_area] = fcram_config_setting
[io_port]    = fcram_config_disable

While researching MMU emulation a year ago I found it allocates and deallocate pages randomly out a given physical memory source.
This is called page translation in ARM nomenclature: http://infocenter.arm.com/help/index.js ... bhigi.html
Also, normally known as page is called Page Table Entry in ARM nomenclature. (for the page itself and its contents)

MMU emulation behaves pretty much like a malloc / free operation (in POSIX C environment), where it allocates and deallocate pages
(input: physical memory which may/will be shuffled -- output coherent/linear virtual memory),
and will re-arrange pages as desired (example: fragmentation occurs, a fragmented chunk of pages has enough memory for a giant page request, thus
all fragmented chunk of pages is re-arranged to a new area (updating the internal physical address), and freeing up the older chunk of pages, of which is assigned to
the new incoming giant page.

MPU emulation does not need to track the internal physical address except by a simple Co-op register because the protected ranges are linear and the co processor raises exceptions directly, also it lacks the page table registers


Somebody else documented the ARM MMU while extending its use to Linux:
https://elinux.org/Tims_Notes_on_ARM_memory_allocation


Also IIRC ARM uses AHB/AXI bus protocol commands to handle master / slave AHB/AXI controllers (because that's what they are, and controllers are wired between them, thus, the registers exposed are meant to setup the master interface, like you guessed)
Controllers have data packet transfer commands such as HBURST, which hardcodes how many words may a Master send (or Slave can receive) when the packet size is known, which can be useful sometimes (when using DMA, which will abuse sequential (ARM Nomenclature: SEQ) rather than initial (ARM Nomenclature: NONSEQ) memory accesses)
Other commands: HMASTLOCK: used by slaves with multiple ports, and HPROT: Protected Access

It is important to understand these older AHB commands because the AXI commands are an improvement of these. Think of the base for AXI protocol.


Since I have no experience with AXI, here's a useful post by Neil Parris:
https://community.arm.com/developer/ip- ... ith-axi-id

Huh, there seems to be an ARM feature called "Outstanding Request", which runs off the QoS.

Hope it helps!
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

I've got code running in NDS/DSi mode. And dumped the Memory map for NDS/DSi Mode.

Code: Select all

  3DS       --> NDS/DSi
  08000000h --> 06800000h, NDS VRAM A (128Kbyte)
  08020000h --> 06820000h, NDS VRAM B (128Kbyte)
  08040000h --> 06840000h, NDS VRAM C (128Kbyte)
  08060000h --> 03xx0000h, NDS ARM7 WRAM (64Kbyte) (ARM7 only)
  08070000h --> 06898000h, NDS VRAM H (32Kbyte)
  08078000h --> 068A0000h, NDS VRAM I (16Kbyte)
  08080000h --> 06860000h, NDS VRAM D (128Kbyte)
  080A0000h --> 06880000h, NDS VRAM E (64Kbyte)
  080B0000h --> 06890000h, NDS VRAM F (16Kbyte)
  080B4000h --> 06894000h, NDS VRAM G (16Kbyte)
  080B8000h --> 03000000h, NDS Shared RAM (32Kbyte) (initially mapped to ARM9)
  080C0000h --> 03xxxxxxh, DSi New Shared WRAM A (256Kbyte) (Misc)
  1FF00000h --> 03xxxxxxh, DSi New Shared WRAM B (256Kbyte) (DSP Code)
  1FF40000h --> 03xxxxxxh, DSi New Shared WRAM C (256Kbyte) (DSP Data)
  20000000h --> 02000000h, NDS Main RAM (max 16MByte)     ;\only each 4th
  20000000h --> 0C000000h, DSi Main RAM (max 32Mbyte)     ;/halfword used
  ITCM/DTCM --> ITCM/DTCM, NDS ITCM/DTCM (32K+16K, same mapping as in 3DS mode)
  FF-filled --> 08000000h, GBA Cart ROM/SRAM (32MB+64K) (empty, FFh-filled)
Most of that memory is initially disabled. Main RAM needs enable in EXMEMCNT, Main RAM uses only the lower 16bit of the 64bit FCRAM data bus (ie. only each 4th halfword is used). OAM/Palette need enable in POWCNT1. VRAM needs enable in VRAMCNT. New Shared WRAM needs enable in MBK.

The ARM9 KEYCNT register doesn't seem to be forwarded to ARM11 side, so I had to output ARM9 text messages from ARM9 to ARM7 via IPCFIFO, and then via KEYCNT from ARM7 to ARM11.

Setting ARM7 Port 4700000h.bit0=1 does disable the patched ARM7 exception vectors, and enables the original GBA/NDS/DSi bootrom vectors. The register is write-only, and can be only changed from 0-to-1, but not back to 0.

FCRAM is working stable in NDS/DSi mode. I haven't yet tried switching from NDS to GBA mode.

The NDS bootrom contains this code for main ram config:

Code: Select all

  STRH 2000h,[4000204h]    ;EXMEMCNT, enable RAM, async mode
  LDRH R0,[27FFFFEh]
  STRH R0,[27FFFFEh]
  STRH R0,[27FFFFEh]
  STRH FFDFh,[27FFFFEh]
  STRH E732h,[27FFFFEh]
  LDRH R0,[27E57FEh]
  STRH 6000h,[4000204h]    ;EXMEMCNT, enable RAM, normal mode
The DSi stage 2 bootcode contains something similar (there using port 2FFFFFEh instead of 27FFFFEh).

The 3DS should theoretically have something similar, too. The RAM enable does reportedly occur when disabling the upper 32K of the bootroms - which is done at the very end of the bootrom code. So the RAM configuration should be done at the begin of the native firm code... giving it a quick glance, I can't see anything like that there though.
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

Here comes the Level 2 Cache controller, that's probably the last major register block that wasn't yet documented in homebrew 3DS specs. So, well, I don't think that more than 0 people will ever read that documention... but, anyways, now it is documented. Alongsides, there's one more solved unknown interrupt source: Interrupt 76h is for the Level 2 Cache.

Code: Select all

L2C-310 r3p3, Level 2 Cache Controller (New3DS only)
 Cache ID and Cache Type (NS and S)
  17E10000h     L2C_CACHE_ID             R  410000C9h ;\
  17E10004h     L2C_CACHE_TYPE           R  9E440440h ;/
 Control (Write S, Read NS and S)
  17E10100h     L2C_CONTROL              RW 00000000h ;\
  17E10104h     L2C_AUX_CONTROL          RW 02090000h ;
  17E10108h     L2C_TAG_RAM_CONTROL      RW 00000111h ;
  17E1010Ch     L2C_DATA_RAM_CONTROL     RW 00000221h ;/
 Interrupt and Counter Control (NS and S)
  17E10200h     L2C_EV_COUNTER_CTRL      RW 00000000h ;\
  17E10204h     L2C_EV_COUNTER1_CFG      RW 00000000h ;
  17E10208h     L2C_EV_COUNTER0_CFG      RW 00000000h ;
  17E1020Ch     L2C_EV_COUNTER1          RW 00000000h ;
  17E10210h     L2C_EV_COUNTER0          RW 00000000h ;
  17E10214h     L2C_INT_MASK             RW 00000000h ;  ;\
  17E10218h     L2C_INT_STATUS_MASKED    R  00000000h ;  ; Interrupts
  17E1021Ch     L2C_INT_STATUS_RAW       R  00000000h ;  ;
  17E10220h     L2C_INT_CLEAR            W  00000000h ;/ ;/
 Cache Maintenance Operations (Secure bit of access affects operation)
  17E10730h     L2C_CACHE_SYNC           RW 00000000h ;\
  17E10770h     L2C_INV_PA               RW 00000000h ;
  17E1077Ch     L2C_INV_WAY              RW 00000000h ;
  17E107B0h     L2C_CLEAN_PA             RW 00000000h ;
  17E107B8h     L2C_CLEAN_INDEX          RW 00000000h ;
  17E107BCh     L2C_CLEAN_WAY            RW 00000000h ;
  17E107F0h     L2C_CLEAN_INV_PA         RW 00000000h ;
  17E107F8h     L2C_CLEAN_INV_INDEX      RW 00000000h ;
  17E107FCh     L2C_CLEAN_INV_WAY        RW 00000000h ;/
 Cache Lockdown (Secure bit of access affects operation)
  17E10900h+N*8 L2C_D_LOCKDOWN_0..7      RW 00000000h ;\
  17E10904h+N*8 L2C_I_LOCKDOWN_0..7      RW 00000000h ;
  17E10950h     L2C_LOCK_LINE_EN         RW 00000000h ;
  17E10954h     L2C_UNLOCK_WAY           RW 00000000h ;/
 Address Filtering (Write S, Read NS and S)
  17E10C00h     L2C_ADDR_FILTERING_START RW 00000000h ;\
  17E10C04h     L2C_ADDR_FILTERING_END   RW 00000000h ;/
 Debug, Prefetch and Power (Write S, Read NS and S)
  17E10F40h     L2C_DEBUG_CTRL           RW 00000004h ;\
  17E10F60h     L2C_PREFETCH_CTRL        RW 04000000h ;
  17E10F80h     L2C_POWER_CTRL           RW 00000000h ;/
Caution: L2C registers can be read via LDR only (LDRB/LDRH cause data abort).
Official specs: DDI0246H_l2c310_r3p3_trm.pdf

 _____________________ Cache ID and Cache Type (NS and S) _____________________

17E10000h - L2C_CACHE_ID - Cache ID Register (410000C9h) (R)
  0-5   RTL release               (9=r3p3)
  6-9   Part number               (3=L2C-310)
  10-15 CACHEID pins              (reads as 0 on New3DS)
  16-23 Reserved (0)
  24-31 Implementer               (41h=ARM)

17E10004h - L2C_CACHE_TYPE - Cache Type Register (9E440440h) (R)
  0-1   L2 cache line length      (0=32 bytes)                    ;\
  2-5   Reserved (0)                                              ;
  6     L2 associativity          (from L2C_AUX_CONTROL.bit16)    ; instruction
  7     Reserved (0)                                              ;
  8-10  Isize L2 cache way size   (from L2C_AUX_CONTROL.bit19-17) ;
  11    Reserved (0)                                              ;/
  12-13 L2 cache line length      (0=32 bytes)                    ;\
  14-17 Reserved (0)                                              ;
  18    L2 associativity          (from L2C_AUX_CONTROL.bit16)    ; data
  19    Reserved (0)                                              ;
  20-22 Dsize L2 cache way size   (from L2C_AUX_CONTROL.bit19-17) ;
  23    Reserved (0)                                              ;/
  24    Harvard                   (0=Unified, 1=Harvard)          ;-harvard
  25    Lockdown by Line option   (0=Off, 1=On)                   ;\
  26    Lockdown by Master option (0=Off, 1=On)                   ; ctype
  27-28 Fixed (always 3)                                          ;/
  29-30 Reserved (0)
  31    Data banking              (0=Not implemented, 1=Implemented)

 ______________________ Control (Write S, Read NS and S) ______________________

17E10100h - L2C_CONTROL - Control Register (R/W)
  0     L2 Cache enable           (0=Disable, 1=Enable)
  1-31  Reserved (0)
Caution: The cache seems to contain garbage on power-up. Set L2C_INV_WAY=FFFFh,
then wait for L2C_INV_WAY=0 before enabling L2C_CONTROL.

17E10104h - L2C_AUX_CONTROL - Auxiliary Control Register (02090000h) (R/W)
  0     Full Line of Write Zero Enable (0=Disable, 1=Enable)
  1-9   Reserved (0)
  10    Priority for Strongly Ordered and Device Reads Enable (0=Low, 1=High)
  11    Limit  (0=Device writes can use all slots, 1=Ensure one Memory slot)
  12    Exclusive cache configuration      (0=Disable, 1=Enable)
  13    Shared Attribute Invalidate Enable (0=Disable, 1=Enable if no override)
  14-15 Reserved (0)
  16    Associativity               (0=8-way, 1=16-way)
  17-19 Way-size (1=16K, 2=32K, 3=64K, 4=128K, 5=256K, 6=512K, 0/7=Same as 1/6)
  20    Event monitor bus enable    (0=Disable, 1=Enable)
  21    Parity enable               (0=Disable, 1=Enable)
  22    Shared attribute override   (0=No, 1=Ignore Shared Attrubute)
  23-24 Force write allocate (0=Use WA, 1=ForceWA=0, 2=ForceWA=1, 3=Same as 0?)
  25    Cache Replacement Policy    (0=Pseudo-random/LFSR, 1=Round-robin)
  26    Lockdown Register Writes    (0=Secure only, 1=Allow non-secure)
  27    Interrupt MASK/CLEAR Access (0=Secure only, 1=Allow non-secure)
  28    Data Prefetch Enable        (0=Disable, 1=Enable)
  29    Instruction Prefetch Enable (0=Disable, 1=Enable)
  30    Early BRESP Enable          (0=Disable, 1=Enable, Early write response)
  31    Reserved (0)
Note: R/W mask is FFFFFF7Fh (ie. most of the "Reserved" bits are write-able).
Cache size is reportedly "2MB" on New3DS, which is probably meant to be
2Mbyte, ie. 16 ways of 128Kbyte each, and that shared for both code and data
caching?

17E10108h - L2C_TAG_RAM_CONTROL - Tag RAM Latency Control (00000111h) (R/W)
17E1010Ch - L2C_DATA_RAM_CONTROL - Data RAM Latency Control (00000221h) (R/W)
  0-2   RAM setup latency           (0-7 = 1..8 cycles of latency)
  3     Reserved (0)
  4-6   RAM read access latency     (0-7 = 1..8 cycles of latency)
  7     Reserved (0)
  8-10  RAM write access latency    (0-7 = 1..8 cycles of latency)
  11-31 Reserved (0)
Uh, is that the latency of the Cache Memory (ie. not the external "cached"
memory)?

 __________________ Interrupt and Counter Control (NS and S) __________________

17E10200h - L2C_EV_COUNTER_CTRL - Event Counter Control (R/W)
  0     Event Counting Enable       (0=Disable, 1=Enable)    (R/W)
  1     Event Counter 0 Reset       (0=No change, 1=Reset)   (W)
  2     Event Counter 1 Reset       (0=No change, 1=Reset)   (W)
  3-31  Reserved (0)

17E10204h - L2C_EV_COUNTER1_CFG - Event Counter 1 Configuration (R/W)
17E10208h - L2C_EV_COUNTER0_CFG - Event Counter 0 Configuration (R/W)
  0-1   Event counter interrupt generation  (00h-03h, see below)
  2-5   Counter event source                (00h-0Fh, see below)
  6-31  Reserved (0)
Event counter interrupt generation:
  00h Disabled                          ;count, without irq
  01h Enabled: Increment condition      ;count, with irq on any increment
  02h Enabled: Overflow condition       ;count, with irq on overflow
  03h Interrupt generation is disabled  ;count, without irq (same as 0?)
Counter event source:
  00h  -        Counter Disabled
  01h  CO       Eviction, CastOUT, of a line from the L2 cache
  02h  DRHIT    Data read hit in the L2 cache
  03h  DRREQ    Data read lookup to the L2 cache
  04h  DWHIT    Data write hit in the L2 cache
  05h  DWREQ    Data write lookup to the L2 cache
  06h  DWTREQ   Data write lookup to the L2 cache with Write-Through attribute
  07h  IRHIT    Instruction read hit in the L2 cache
  08h  IRREQ    Instruction read lookup to the L2 cache
  09h  WA       Allocation into the L2 cache caused by a write, with
                  Write-Allocate attribute, miss
  0Ah  IPFALLOC Allocation of a prefetch generated by L2C-310 into the L2 cache
  0Bh  EPFHIT   Prefetch hint hits in the L2 cache
  0Ch  EPFALLOC Prefetch hint allocated into the L2 cache
  0Dh  SRRCVD   Speculative read received by slave port(s)
  0Eh  SRCONF   Speculative read confirmed in slave port(s)
  0Fh  EPFRCVD  Prefetch hint received by slave port(s)
  Note: All REQ lookups will subsequently result in a hit or miss.

17E1020Ch - L2C_EV_COUNTER1 - Event counter 1 value (R/W)
17E10210h - L2C_EV_COUNTER0 - Event counter 0 value (R/W)
  0-31  Counter value, incremented on selected event
If a counter reaches its maximum value, it saturates at that value until it is
reset.

17E10214h - L2C_INT_MASK - Interrupt Mask (0=Disable, 1=Enable) (R/W)
17E10218h - L2C_INT_STATUS_MASKED - Masked Interrupt Status Register (R)
17E1021Ch - L2C_INT_STATUS_RAW - Raw Interrupt Status (1=IRQ) (R)
17E10220h - L2C_INT_CLEAR - Interrupt Clear (0=No change, 1=Clear) (W)
  0     ECNTR: Event Counter 0 and 1 Overflow/Increment
  1     PARRT: Parity Error on L2 tag RAM, Read
  2     PARRD: Parity Error on L2 data RAM, Read
  3     ERRWT: Error on L2 tag RAM, Write
  4     ERRWD: Error on L2 data RAM, Write
  5     ERRRT: Error on L2 tag RAM, Read
  6     ERRRD: Error on L2 data RAM, Read
  7     SLVERR: SLVERR from L3
  8     DECERR: DECERR from L3
  9-31  Reserved (0)
Note: STATUS_MASKED is same as STATUS_RAW, but ANDed with MASK.
The IRQ triggers interrupt 76h.

 ________________________ Cache Maintenance Operations ________________________

17E10730h - L2C_CACHE_SYNC - Cache Maintenance Operations (R and W)
  0     C, When writing: Must be 0 (trigger cache sync...?)
  0     C, When reading: Background/Way operation is in progress (0=No, 1=Yes)
  1-31  Reserved (0)

17E10770h - L2C_INV_PA - Invalidate by Physical Address (R/W)
17E107B0h - L2C_CLEAN_PA - Clean by Physical Address (R/W)
17E107F0h - L2C_CLEAN_INV_PA - Clean+Invalidate by Physical Address (R/W)
  0     C (uh, is that same meaning as in L2C_CACHE_SYNC.bit0?)   (R?)
  1-4   Reserved (0)
  5-xx  Index                                                     (W?)
  xx-31 Tag                                                       (W?)

17E1077Ch - L2C_INV_WAY - Invalidate by Way (R/W)
17E107BCh - L2C_CLEAN_WAY - Clean by Way (R/W)
17E107FCh - L2C_CLEAN_INV_WAY - Clean+Invalidate by Way (R/W)
  0-15  Way bits (for way 0..15) (1=Trigger/busy?)                (R/W)
  16-31 Reserved (0)

17E107B8h - L2C_CLEAN_INDEX - Clean by Index (R/W)
17E107F8h - L2C_CLEAN_INV_INDEX - Clean+Invalidate by Index (R/W)
  0     C (uh, is that same meaning as in L2C_CACHE_SYNC.bit0?)   (R?)
  1-4   Reserved (0)
  5-xx  Index                                                     (W?)
  xx-xx Reserved (0)
  28-31 Way number (0..15)                                        (W?)

 _______________________________ Cache Lockdown _______________________________

17E10900h+N*8 - L2C_D_LOCKDOWN_0..7 - Data Cache lockdown 0-7 (R/W)
17E10904h+N*8 - L2C_I_LOCKDOWN_0..7 - Instruction Cache lockdown 0-7 (R/W)
  0-15  DATALOCK/INSTRLOCK 000..111  (use when AyUSERSx[7:5]=000b..111b) ;way?
  16-31 Reserved (0)

17E10950h - L2C_LOCK_LINE_EN - Lockdown by Line Enable (R/W)
  0     Lockdown by Line Enable (0=Disable, 1=Enable)
  1-31  Reserved (0)

17E10954h - L2C_UNLOCK_WAY - Unlock all Lines by Way (R/W)
  0-15  Unlock all Lines by Way operation (0=No/off, 1=Unlock/busy?) ;way0-15
  16-31 Reserved (0)

 _________________ Address Filtering (Write S, Read NS and S) _________________

17E10C00h - L2C_ADDR_FILTERING_START - Address filtering Start (R/W)
17E10C04h - L2C_ADDR_FILTERING_END - Address filtering End (R/W)
Not implemented, always zero in New3DS. Allows to redirect a whole address
range to master 1 (when two masters are implemented).
  0     Address Filtering Enable (0=Disable, 1=Enable)  ;<-- in Start register
  0     Reserved (0)                                    ;<-- in End register
  1-19  Reserved (0)
  20-31 Address Filtering Start/End Address bit31-20

 _____________ Debug, Prefetch and Power (Write S, Read NS and S) _____________

17E10F40h - L2C_DEBUG_CTRL - Debug Register (00000004h) (R/W)
  0     Disable cache linefill (0=Enable, 1=Disable cache linefills)     (R/W)
  1     Disable write-back     (0=Write-back, 1=Force Write-through)     (R/W)
  2     Secure Privileged Non-Invasive Debug Enable SPNIDEN option (1=on?) (R)
  3-31  Reserved (0)
This is write-able only if code cache is disabled in cp15?

17E10F60h - L2C_PREFETCH_CTRL - Prefetch Control Register (04000000h) (R/W)
  0-4   Prefetch Offset (must be 0-7, 15, 23, or 31) (other=Unsupported)
  5-20  Reserved (0)
  21    Not same AXI ID on exclusive sequence enable   (0=Same ID, 1=Not same)
  22    Reserved (0)
  23    Incr Double Linefill enable (allow 8x64bit)    (0=Disable, 1=Allow)
  24    Prefetch drop, Discard prefetch reads to L3    (0=Disable, 1=Enable)
  25    Reserved (0)
  26    Speculative Read Synthesis Option (read-only)  (0=On, 1=Off) (R)
  27    Double linefill on WRAP read disable           (0=Enable, 1=Disable)
  28    Data prefetch enable                           (0=Disable, 1=Enable)
  29    Instruction prefetch enable                    (0=Disable, 1=Enable)
  30    Double Linefill, Read bursts to L3 on L2 miss  (0=4x64bit, 1=8x64bit)
  31    Reserved (0)
This is write-able only if code cache is disabled in cp15?
Writing value FFE0001Fh is possible (ie. with reserved bit22,25,31 set)?

17E10F80h - L2C_POWER_CTRL - Power Control Register (R/W)
  0     Standby mode enable         (0=Disable, 1=Enable)
  1     Dynamic clock gating enable (0=Disable, 1=Enable)
  2-31  Reserved (0)
This is write-able only if code cache is disabled in cp15?
Writing value 00000007h is possible (ie. with reserved bit2 set)?
There are restrictions on accessing the above registers by Secure and Non-secure accesses (S and NS).
That is probably very important...
But what the fuck is a "Secure access"???
ARM CPUs do have a "privileged mode", which is probably not the same as secure access. As far as I know, secure accesses don't exist at all (or only for later ARMv7 models). The ARM11mpcore specs and ARMv6 reference don't seem to mention any "secure" feature. Am I missing something?

The Corelink DMA stuff does also have something called Secure state (where Secure seems to result in "restricted permission", ie. somewhat the opposite of privileged). For the Level 2 Cache debug registers, they are writeable only by Secure accesses (which would imply the opposite meaning).
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: 3DS reverse engineering

Post by tepples »

It appears to have something to do with "TrustZone", introduced sometime during the ARM11 cycle. See ARM1176JZF-S Technical Reference Manual. (ARM1176 is the version of ARM11 just before mpcore was introduced.)
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: 3DS reverse engineering

Post by nocash »

Yeah, looks so, thanks!
If I get it right, that TrustZone thing is the ARMv6Z security extension of ARMv6.
And presence of the security extension would be indicated in "Processor Feature Register 1" bit4-7 (which is zero on 3DS, so apparently it doesn't exist there).
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
Post Reply